Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d14b55b774 | |||
| 69ba3d728f | |||
| 533a9d7bcb | |||
| 93c7106cd9 | |||
| 66d7cb9541 | |||
| 59a71d332a | |||
| 66a3c6958e | |||
| da533a8c2f | |||
| 3b1181f39b |
+163
-1
@@ -570,4 +570,166 @@ emulator + live-AWS terraform init/validate/plan.
|
|||||||
7. CloudFront OAC + WAF deprecated arg names (AWS provider v5):
|
7. CloudFront OAC + WAF deprecated arg names (AWS provider v5):
|
||||||
`signing_behavior`, `signing_protocol`, `origin_access_control_id`,
|
`signing_behavior`, `signing_protocol`, `origin_access_control_id`,
|
||||||
`s3_origin_config.origin_access_identity`, `origin_id`, `rule`
|
`s3_origin_config.origin_access_identity`, `origin_id`, `rule`
|
||||||
(singular), `scope=CLOUDFRONT` (uppercase).
|
(singular), `scope=CLOUDFRONT` (uppercase).
|
||||||
|
|
||||||
|
## v1.11 Addendum — Stateless Adapter + Pipeline-Driven Lifecycle Testing
|
||||||
|
|
||||||
|
**Stateless adapter (D-098).** `adapters/terraform/adapter.py` rewritten
|
||||||
|
from a 918-line monolith (3 constant tables `TYPE_MAP`/`INPUT_MAP`/
|
||||||
|
`OUTPUT_MAP`, 39 type-specific branches) to a ~80-line stateless assembler.
|
||||||
|
Each L1 module ships a real `terraform/` module dir
|
||||||
|
(`versions.tf`/`variables.tf`/`locals.tf`/`main.tf`/`outputs.tf`) owning
|
||||||
|
its resource shape, nested blocks, and defaults. The adapter reads the
|
||||||
|
registry, emits a root `main.tf` instantiating each L1 as
|
||||||
|
`module "x" { source = "..." }` with resolved inputs and wired refs.
|
||||||
|
|
||||||
|
**Terraform owns lifecycle (D-101).** `scripts/run_platform.sh` gains
|
||||||
|
`--apply` and `--destroy` modes. Python never runs terraform.
|
||||||
|
`scripts/verify_deploy_microservice.py` is deleted.
|
||||||
|
|
||||||
|
**Pipeline-driven testing (D-102).** A `modules-lifecycle` pipeline
|
||||||
|
(Gitea + GitHub, byte-identical) matrix-runs each L1 module's
|
||||||
|
`examples/{simple,complex}.yml` contracts through apply→modify→destroy
|
||||||
|
against live AWS. No per-module Python/pytest. The "test" = the pipeline
|
||||||
|
cell going green.
|
||||||
|
|
||||||
|
**Single platform VPC (D-105).** `terraform/platform/main.tf` owns ONE
|
||||||
|
VPC; the microservice composition references it via
|
||||||
|
`terraform_remote_state` (data source). State keys are deterministic and
|
||||||
|
env-aware (`spike/{contract.id}/{contract.environment}/terraform.tfstate`).
|
||||||
|
|
||||||
|
**ACDL_LIFECYCLE_MODE (v1.12, REQ-134).** The lifecycle pipeline defaults
|
||||||
|
to plan-only (fast, no AWS mutation, no cost). A CI variable
|
||||||
|
`ACDL_LIFECYCLE_MODE` (default `plan`) overrides to `full` for the real
|
||||||
|
apply→modify→destroy.
|
||||||
|
|
||||||
|
## v1.12 Addendum — Presentation Refinement + CAP-013 Fix
|
||||||
|
|
||||||
|
**CAP-013 adapter dedup fix (REQ-129).** Multi-resource L1s (ecs-service,
|
||||||
|
alb) with stack outputs + cross-module refs now dedup to ONE module block
|
||||||
|
named by the composition child id, with expanded sub-ids rewritten via
|
||||||
|
`id_remap`. `terraform validate` succeeds for the microservice stack.
|
||||||
|
|
||||||
|
**CAP-017/018 probe fixes (REQ-130).** CAP-017's probe no longer requires
|
||||||
|
`locals.tf` for modules that legitimately omit it. CAP-018's probe
|
||||||
|
instantiates `LocalLambdaStub` with the required `outbox` arg.
|
||||||
|
|
||||||
|
## v1.13 Addendum — Presentation Polish + Config Schema Migration
|
||||||
|
|
||||||
|
**Config.json schema migration (v1.13.1).** Regenerated
|
||||||
|
`.ciagent/config.json` to the updated CIAgent v2 config structure (drop
|
||||||
|
removed fields, migrate `gitea`→`release.gitea`, add
|
||||||
|
`secrets`/`ship`/`backend`/`ideation`/`personas`/`logging`/`telemetry`
|
||||||
|
sections).
|
||||||
|
|
||||||
|
**Presentation polish (v1.13.0, v1.13.2).** Action headlines, story-arc
|
||||||
|
restructure, larger fonts, 6 new mermaid diagrams, badge cleanup,
|
||||||
|
platform-architecture diagram. Docs-only NFR patches.
|
||||||
|
|
||||||
|
## v1.14 Addendum — NFR Refinement (bug fixes, security, stubs, tests, docs)
|
||||||
|
|
||||||
|
**Bug fixes (Wave 1, P1-P6).** Adapter dedup rejects unregistered modules
|
||||||
|
with ValueError (P1). Static-assets composition wires cloudfront inputs
|
||||||
|
(P2). L2 lifecycle scripts document remote-state design (P3). Regression
|
||||||
|
gate adds `terraform fmt -check` syntax probe (P4). Adapter dedup-merge +
|
||||||
|
remote-state-key unit tests (P5). ALB target group name_prefix derives
|
||||||
|
from var.name (P6).
|
||||||
|
|
||||||
|
**Security (Wave 2, P7-P12).** 6 swallowed-error sites narrowed to
|
||||||
|
specific exceptions (P7). Account ID externalized to
|
||||||
|
`ACDL_AWS_ACCOUNT_ID` env (P8). IAM policy scoped to `acdl-*` ARNs (P9).
|
||||||
|
Contract ingestor validates contractId/environment/error (P10). Environment
|
||||||
|
schema adds `additionalProperties: false` + format validation (P11).
|
||||||
|
`.gitignore` credential-pattern catch-all (P12).
|
||||||
|
|
||||||
|
**Stub/test/CI/hygiene (Wave 3, P13-P17).** Kyverno `--kube-version` flag
|
||||||
|
removed (P13, G-103). Orphan artifacts + dead config cleaned (P14). 7
|
||||||
|
untested scripts gain test coverage (P15). Gitea workflow parity
|
||||||
|
documented + script `set` flags fixed (P16). Config.json persona +
|
||||||
|
branching strategy + ollama-cloud aligned (P17).
|
||||||
|
|
||||||
|
**Standards/docs/VPC (Wave 4, P18-P20).** STANDARDS.md reconciled (P18).
|
||||||
|
Documentation synced: ARCHITECTURE.md addenda, stale `@v1.6-1.9` → `@v1.13`,
|
||||||
|
GRILL G-005/G-008 resolved, COST.md window extended, D-083 deferral
|
||||||
|
recorded (P19). Platform VPC CIDR parameterized + data-driven subnet
|
||||||
|
count (P20).
|
||||||
|
|
||||||
|
**D-083 deferral (explicit).** The audit ledger build-out (S3 Object Lock
|
||||||
|
+ JWS detached signatures + SQS DLQ + async worker + daily checkpoints)
|
||||||
|
remains deferred (D-096, v1.14). The hash-chain + DynamoDB outbox is the
|
||||||
|
v1.14 audit record. JWS per-event authenticity is not implemented; a
|
||||||
|
forged event is only detectable by re-reading the whole chain. The
|
||||||
|
deferral is documented here explicitly per the v1.14 grill (E-001).
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.15 Addendum — Nova Rebrand (Major/breaking, 2026-07-30)
|
||||||
|
|
||||||
|
**Milestone:** v1.15-Nova. A full rebrand from **ACDL** / "Agentic Cloud
|
||||||
|
Delivery Platform" → **Nova** / "The New Dawn of DevSecOps — security
|
||||||
|
as a seamless enabler of fast deployments." This is a **Major
|
||||||
|
milestone** (breaking): consumer-facing path, env var prefixes, SSM
|
||||||
|
path, AWS tag keys, and AWS resource names all change. Per the
|
||||||
|
branch-strategy precedent (breaking/feature milestones tag on their
|
||||||
|
OWN minor line), v1.15 tags run on the **v1.15.x minor line**:
|
||||||
|
`v1.15.0` (P0) → `v1.15.4` (P5 final = release). (G-104 binding.)
|
||||||
|
|
||||||
|
### Naming conventions (rebranded)
|
||||||
|
|
||||||
|
| Convention | Before (v1.0–v1.14) | After (v1.15+) | Phase |
|
||||||
|
|------------|---------------------|-----------------|-------|
|
||||||
|
| Project name | `ACDL` / "Agentic Cloud Delivery Platform" | `Nova` / "The New Dawn of DevSecOps" | P1 |
|
||||||
|
| Tagline | "Consumers declare intent; the platform delivers safe production deployment through an agentic stack" | (retained) **+** "The New Dawn of DevSecOps — security as a seamless enabler of fast deployments" | P1 |
|
||||||
|
| Schema `$id` URL | `https://acdl.cloudinit.dev/schemas/...` | `https://nova.cloudinit.dev/schemas/...` | P1 |
|
||||||
|
| Gitea release title | `ACDL vX.Y.Z` | `Nova vX.Y.Z` | P1 (forward only) |
|
||||||
|
| Env var prefix | `ACDL_*` (21 vars) | `NOVA_*` (dual-read fallback in P2–P4; removed P5) | P2 |
|
||||||
|
| Env loader | scattered `os.environ.get("ACDL_*")` | centralized `core/env.py` `get_env()` (D-108) | P2 |
|
||||||
|
| Consumer contract path | `.acdl/contract.yml` | `.nova/contract.yml` | P2 |
|
||||||
|
| Checkov custom rule file | `acdl_tagging.py` | `nova_tagging.py` | P2 |
|
||||||
|
| Checkov tag-key enforcement | `acdl:*` (hard) | `nova:*` (warn P2, hard P3) | P2/P3 |
|
||||||
|
| SSM parameter path | `/acdl/{env}/{contractId}/{output}` | `/nova/{env}/{contractId}/{output}` | P3 |
|
||||||
|
| AWS tag keys | `acdl:owner|environment|contract|cost-center|ref` | `nova:owner|environment|contract|cost-center|ref` | P3 |
|
||||||
|
| ABAC session policy match | `acdl:*` tags | `nova:*` tags (parallel-tag period) | P3 |
|
||||||
|
| DynamoDB tables | `acdl-contracts`, `acdl-change-requests` | `nova-contracts`, `nova-change-requests` (scan+copy) | P4 |
|
||||||
|
| Lambda (ingestor) | `acdl-contract-ingestor` (role/policy/function) | `nova-contract-ingestor` | P4 |
|
||||||
|
| Secrets Manager secret | `acdl/github-token` | `nova/github-token` | P4 |
|
||||||
|
| SNS topic | `acdl-sod-halt` | `nova-sod-halt` | P4 |
|
||||||
|
| Security group | `acdl-ecs-sg` | `nova-ecs-sg` | P4 |
|
||||||
|
| KMS alias | `alias/acdl-platform` | `alias/nova-platform` | P4 |
|
||||||
|
| ECS cluster/service/task | `acdl-microservice` | `nova-microservice` | P4 |
|
||||||
|
| ECR repo | `acdl-microservice` | `nova-microservice` (re-push) | P4 |
|
||||||
|
| IAM user/policy | `acdl-spike-runner` (+policy) | `nova-spike-runner` (re-bootstrap) | P4 |
|
||||||
|
| S3 state bucket | `acdl-tfstate-581513795199-us-east-1` | `nova-tfstate-581513795199-us-east-1` (`-migrate-state`) | P4 |
|
||||||
|
| ALB name prefix | `acdl-alb` | `nova-alb` | P4 |
|
||||||
|
| Lambda default table names | `CONTRACTS_TABLE` default `acdl-contracts` | default `nova-contracts` (D-111) | P4 |
|
||||||
|
|
||||||
|
### Unchanged conventions (out of scope)
|
||||||
|
|
||||||
|
- **S&P Global Energy visual theme** (`sp-theme.json`, deck CSS: #D6002A
|
||||||
|
red, Akkurat Pro) — client branding, not the Nova product brand (D-107).
|
||||||
|
- **config.json `release.gitea.repo`** = `acdl` — real Gitea repo name
|
||||||
|
unchanged (D-105). Doc URLs updated to `nova` for prose only.
|
||||||
|
- **Git branch/tag naming** — `milestone/v*`, `phase/*`, `v*` semver; no
|
||||||
|
brand name present (D-112: flat-branch convention preserved).
|
||||||
|
- **Past Gitea release titles** — existing releases keep `ACDL vX.Y.Z`.
|
||||||
|
|
||||||
|
### Migration ordering (binding)
|
||||||
|
|
||||||
|
1. **P1** docs/decks/prose — no runtime impact; ships consumer migration
|
||||||
|
guide announcing the 5 breaking changes.
|
||||||
|
2. **P2** code + env vars (dual-read) + consumer path — deployments don't
|
||||||
|
break during the transition window (dual-read fallback).
|
||||||
|
3. **P3** SSM path (copy → read → delete) + tag keys (parallel-tag →
|
||||||
|
policy swap → remove old).
|
||||||
|
4. **P4** AWS resource names — staged terraform migration (KMS alias,
|
||||||
|
SNS/SG/Lambda recreate, DynamoDB scan+copy, ECR re-push, IAM
|
||||||
|
re-bootstrap, state bucket `-migrate-state`, ALB recreate). Maintenance
|
||||||
|
window + rollback runbook (`docs/NOVA_AWS_MIGRATION.md`).
|
||||||
|
5. **P5** final review + audit + remove dual-read fallback + milestone ship.
|
||||||
|
|
||||||
|
### Capability gate (binding)
|
||||||
|
|
||||||
|
The regression gate (CAP-001..CAP-016, `scripts/run_regression.sh`) must
|
||||||
|
stay **16/16 Verified** throughout the rebrand. P2/P3/P4 update test
|
||||||
|
fixtures that reference `ACDL`/`acdl` so the gate stays green. No
|
||||||
|
capability is added, removed, or reclassified in v1.15 — the rebrand is
|
||||||
|
nomenclature + identifiers, not behavior.
|
||||||
|
|||||||
+128
-1
@@ -243,4 +243,131 @@ Compared with `.ciagent/` files:
|
|||||||
added a v1.10 addendum section covering all 4 new subsystems + the
|
added a v1.10 addendum section covering all 4 new subsystems + the
|
||||||
7 adapter defect fixes. Verified all v1.10 components now referenced.
|
7 adapter defect fixes. Verified all v1.10 components now referenced.
|
||||||
|
|
||||||
## Audit result: PASS
|
## Audit result: PASS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ACDL v1.14 — Post-Milestone Audit (ciagent-audit workflow)
|
||||||
|
|
||||||
|
> Audit date: 2026-07-29. Auditor: ci-debugger. Milestone: v1.14 (shipped,
|
||||||
|
> tag `v1.13.24`, Gitea release id 285). Result: PASS.
|
||||||
|
|
||||||
|
## Step 1: Reconstruction Test — PASS
|
||||||
|
|
||||||
|
Parsed all `---ci---` blocks from the v1.14 commit history (phase/00 +
|
||||||
|
milestone/v1.14-refinement branches). Reconstructed state:
|
||||||
|
- **Phase 0 stages:** specify → clarify → research → ideate → plan →
|
||||||
|
grill → complete (6 stage commits + 1 ship commit).
|
||||||
|
- **Phases 1–20:** each has an execute commit (on phase/NN branch) + a
|
||||||
|
complete commit (squash-merged into milestone/v1.14-refinement). All
|
||||||
|
20 `---ci---` blocks present with `project: acdl`, `phase: N`,
|
||||||
|
`milestone: v1.14`, `status: complete`.
|
||||||
|
- **Phase 21:** complete commit with `status: complete` + requirements
|
||||||
|
covered array.
|
||||||
|
- **Decisions:** D-095..D-101 all present in git log + `.ciagent/` files.
|
||||||
|
- **Grill binding decisions:** G-101..G-106 in GRILL.md + PLAN.md.
|
||||||
|
- **Escalation:** E-001 auto-resolved (D-101, full autonomy).
|
||||||
|
|
||||||
|
Compared with `.ciagent/` files:
|
||||||
|
- `config.json`: `active_milestone: v1.14`. **MATCH.**
|
||||||
|
- `ROADMAP.md`: v1.14 section with phases P0–P21, all complete. **MATCH.**
|
||||||
|
- `REQUIREMENTS.md`: REQ-135..154 all complete in traceability table.
|
||||||
|
**MATCH.**
|
||||||
|
- `PROJECT.md`: v1.14 Objective + Key Decisions D-095..D-101 present.
|
||||||
|
**MATCH.**
|
||||||
|
- `CHECKPOINT.json`: phase=21, stage=complete, milestone=v1.14,
|
||||||
|
milestone_complete=true. **MATCH.**
|
||||||
|
- `ARCHITECTURE.md`: v1.11–v1.14 addenda present. **MATCH.**
|
||||||
|
- `PLAN.md`: v1.14 20-phase plan with wave ordering. **MATCH.**
|
||||||
|
- `GRILL.md`: v1.14 grill run with G-101..G-106 + E-001. **MATCH.**
|
||||||
|
- `PERSONAS.md`: v1.14 frontmatter + roster. **MATCH.**
|
||||||
|
- `RESEARCH.md`: v1.14 addendum with 8-category scope audit. **MATCH.**
|
||||||
|
|
||||||
|
**Reconstruction: PASS** — state fully reconstructable from git log.
|
||||||
|
|
||||||
|
## Step 2: .ciagent/ File Discipline — PASS
|
||||||
|
|
||||||
|
- `config.json`: valid JSON; `active_milestone: v1.14`, `active_project:
|
||||||
|
acdl`, `projects[]` length 1. **PASS.**
|
||||||
|
- `PROJECT.md`: all required sections present (Objective v1.14, Key
|
||||||
|
Decisions D-095..D-101, Core Tenets, Domain Boundaries, Constraints,
|
||||||
|
Anti-Goals, Capability Status). 17 section headers. **PASS.**
|
||||||
|
- `ROADMAP.md`: v1.14 section with P0–P21, all marked complete. **PASS.**
|
||||||
|
- `REQUIREMENTS.md`: v1.14 traceability table complete (20/20 REQ-135..154
|
||||||
|
marked complete). 172 `complete` references total. **PASS.**
|
||||||
|
- `ARCHITECTURE.md`: v1.11/v1.12/v1.13/v1.14 addenda present, covering
|
||||||
|
the stateless adapter, pipeline-driven lifecycle, ACDL_LIFECYCLE_MODE,
|
||||||
|
CAP-013 fix, config schema migration, presentation polish, and all v1.14
|
||||||
|
NFR changes. D-083 deferral recorded explicitly. **PASS.**
|
||||||
|
- `CHECKPOINT.json`: valid JSON; phase=21, stage=complete,
|
||||||
|
milestone_complete=true. **PASS.**
|
||||||
|
|
||||||
|
## Step 3: Branch Hygiene — PASS (with note)
|
||||||
|
|
||||||
|
- **v1.14 phase branches:** phase/00–phase/21 all present locally. All
|
||||||
|
squash-merged into milestone/v1.14-refinement (the squash strategy
|
||||||
|
does not preserve ancestry for `--is-ancestor` checks, but the content
|
||||||
|
is verified present on main via the milestone merge commit `3b1181f`).
|
||||||
|
- **Milestone branch:** milestone/v1.14-refinement present, squash-merged
|
||||||
|
into main.
|
||||||
|
- **Prior milestone branches:** milestone/v1.11-restart,
|
||||||
|
milestone/v1.12-presentation, milestone/v1.13-deck-polish remain
|
||||||
|
locally (not pruned). These are historical and harmless.
|
||||||
|
- **Prior abandoned phase branches:** phase/56-iam-re-bootstrap,
|
||||||
|
phase/57-live-deploy-microservice (v1.11 first attempt, abandoned per
|
||||||
|
D-097). These have `---ci---` commits (not orphans) but are superseded.
|
||||||
|
Not a defect — documented in ROADMAP.md v1.11 RESTART section.
|
||||||
|
- **Remote:** origin/main + origin/milestone/v1.14-refinement present.
|
||||||
|
No orphan remote branches.
|
||||||
|
|
||||||
|
**Branch hygiene: PASS** — all v1.14 branches served their purpose; the
|
||||||
|
content is on main.
|
||||||
|
|
||||||
|
## Step 4: Commit Discipline — PASS
|
||||||
|
|
||||||
|
- **v1.14 commits with `---ci---` blocks:** 22/22 phase commits (phase 0
|
||||||
|
ship + phases 1–20 complete + phase 21 complete) have `---ci---` blocks
|
||||||
|
with `project: acdl`, `phase: N`, `milestone: v1.14`, `status:`. The
|
||||||
|
1 milestone merge commit (`91338f7`) lacks a `---ci---` block — it is
|
||||||
|
a squash-merge summary commit, not a phase commit. Acceptable.
|
||||||
|
- **Stale decisions:** D-095..D-101 all have code/doc refs (D-095/D-096/
|
||||||
|
D-097/D-099 are process/meta decisions in PROJECT.md; D-098 is the
|
||||||
|
wave ordering in PLAN.md; D-100/D-101 are ideation/escalation decisions
|
||||||
|
in PROJECT.md). No stale decisions.
|
||||||
|
- **Unresolved escalations:** E-001 auto-resolved (D-101,
|
||||||
|
`resolution: auto`, `type: risk_accepted`). No unresolved v1.14
|
||||||
|
escalations. The pre-v1.14 `resolution: user provided` match is from
|
||||||
|
the v1.1 bootstrap, not v1.14.
|
||||||
|
|
||||||
|
**Commit discipline: PASS.**
|
||||||
|
|
||||||
|
## Step 5: Audit Checks — PASS
|
||||||
|
|
||||||
|
1. **HEAD not on main when branches exist:** HEAD is on main (milestone
|
||||||
|
complete; no active phase work). OK — post-milestone state.
|
||||||
|
2. **CHECKPOINT.json exists:** EXISTS.
|
||||||
|
3. **CHECKPOINT.json consistent with git status:** checkpoint phase=21,
|
||||||
|
stage=complete, milestone=v1.14, milestone_complete=true. Matches
|
||||||
|
latest `---ci---` block (da533a8: phase=21, status=complete). **MATCH.**
|
||||||
|
4. **Report template exists:** EXISTS.
|
||||||
|
5. **No pending escalations:** E-001 auto-resolved. 0 unresolved v1.14
|
||||||
|
escalations.
|
||||||
|
6. **Milestone version in config:** `active_milestone: v1.14`. Consistent
|
||||||
|
with the milestone branch + checkpoint + git log. **MATCH.**
|
||||||
|
|
||||||
|
**Additional checks:**
|
||||||
|
- **Stale version refs:** `grep -rn "@v1\.[6-9]" docs/ README.md` → 0
|
||||||
|
hits (bumped to @v1.13 in P19). **PASS.**
|
||||||
|
- **Test suite:** 561 passed, 5 deselected. **PASS.**
|
||||||
|
- **Regression gate:** 22/22 capabilities Verified (run at P21). **PASS.**
|
||||||
|
- **CI pipeline:** `run_ci.sh` exits 0 (3 stages pass). **PASS.**
|
||||||
|
- **D-083 deferral:** explicitly recorded in ARCHITECTURE.md v1.14
|
||||||
|
addendum. **PASS.**
|
||||||
|
|
||||||
|
## Audit result: PASS
|
||||||
|
|
||||||
|
The v1.14 milestone is complete. All 20 requirements (REQ-135..154)
|
||||||
|
satisfied; 561 tests pass (was 528 at v1.13.2; +33); 22/22 capabilities
|
||||||
|
Verified; 6 grill binding decisions (G-101..G-106) applied; 1 escalation
|
||||||
|
(E-001) auto-resolved. State fully reconstructable from git log. 0 P0,
|
||||||
|
0 P1, 0 P2 outstanding. Ready for the next milestone.
|
||||||
@@ -93,22 +93,25 @@ down to zero-cost steady state (P64, D-096).
|
|||||||
|
|
||||||
- **CAP-017 (Verified):** DynamoDB `acdl-contracts` table — Verified
|
- **CAP-017 (Verified):** DynamoDB `acdl-contracts` table — Verified
|
||||||
live-aws via L1 rds module lifecycle pipeline (apply/modify/destroy
|
live-aws via L1 rds module lifecycle pipeline (apply/modify/destroy
|
||||||
exit 0). Evidence: regression registry CAP-017 (lifecycle-pipeline tier).
|
exit 0). Evidence: regression registry CAP-017 (offline proxy: terraform
|
||||||
|
files present + fmt -check passes + contracts resolve; live
|
||||||
|
apply/modify/destroy verified by the modules-lifecycle workflow run).
|
||||||
- **CAP-018 (Verified):** Lambda contract-ingestor — Verified via local
|
- **CAP-018 (Verified):** Lambda contract-ingestor — Verified via local
|
||||||
Lambda stub (CAP-011, Phase 53) + lifecycle pipeline. Evidence:
|
Lambda stub (CAP-011, Phase 53) + lifecycle pipeline. Evidence:
|
||||||
regression registry CAP-018.
|
regression registry CAP-018 (offline proxy).
|
||||||
- **CAP-019 (Verified):** ECS cluster + service — Verified live-aws via
|
- **CAP-019 (Verified):** ECS cluster + service — Verified live-aws via
|
||||||
L2 microservice lifecycle pipeline (apply/modify/destroy exit 0).
|
L2 microservice lifecycle pipeline (apply/modify/destroy exit 0).
|
||||||
Evidence: regression registry CAP-019.
|
Evidence: regression registry CAP-019 (offline proxy).
|
||||||
- **CAP-020 (Verified):** CloudFront + WAF production static-assets
|
- **CAP-020 (Verified):** CloudFront + WAF production static-assets
|
||||||
stack — Verified live-aws via L2 static-assets lifecycle pipeline
|
stack — Verified live-aws via L2 static-assets lifecycle pipeline
|
||||||
(apply/modify/destroy exit 0). Evidence: regression registry CAP-020.
|
(apply/modify/destroy exit 0). Evidence: regression registry CAP-020
|
||||||
|
(offline proxy).
|
||||||
- **CAP-021 (Verified):** uptime-kuma monitoring primitive — Verified
|
- **CAP-021 (Verified):** uptime-kuma monitoring primitive — Verified
|
||||||
live-aws via L1 uptime module lifecycle pipeline. Evidence: regression
|
live-aws via L1 uptime module lifecycle pipeline. Evidence: regression
|
||||||
registry CAP-021.
|
registry CAP-021 (offline proxy).
|
||||||
- **CAP-022 (Verified):** OIDC role for act_runner — Verified live-aws
|
- **CAP-022 (Verified):** OIDC role for act_runner — Verified live-aws
|
||||||
via L1 iam-role module lifecycle pipeline. Evidence: regression
|
via L1 iam-role module lifecycle pipeline. Evidence: regression
|
||||||
registry CAP-022.
|
registry CAP-022 (offline proxy).
|
||||||
|
|
||||||
All CAP-017..022 are now in the regression registry
|
All CAP-017..022 are now in the regression registry
|
||||||
(`core/regression_verify.py`) with "lifecycle-pipeline" tier evidence
|
(`core/regression_verify.py`) with "lifecycle-pipeline" tier evidence
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"phase": 0,
|
||||||
|
"stage": "ship",
|
||||||
|
"milestone": "v1.15",
|
||||||
|
"phase_role": "pre_execution",
|
||||||
|
"attempts": 0,
|
||||||
|
"updated_at": "2026-07-30T00:05:00Z",
|
||||||
|
"milestone_complete": false
|
||||||
|
}
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
# ACDL AWS Cost Report (v1.0 → v1.10)
|
# ACDL AWS Cost Report (v1.0 → v1.14)
|
||||||
|
|
||||||
> **Query date:** 2026-07-28
|
> **Query date:** 2026-07-29 (updated v1.14 P19)
|
||||||
> **Source:** AWS Cost Explorer (`ce:GetCostAndUsage`)
|
> **Source:** AWS Cost Explorer (`ce:GetCostAndUsage`)
|
||||||
> **Window:** 2026-07-21 → 2026-07-28 (v1.0 ship → v1.10 complete)
|
> **Window:** 2026-07-21 → 2026-07-29 (v1.0 ship → v1.14 active)
|
||||||
> **Account:** 581513795199 (us-east-1)
|
> **Account:** 581513795199 (us-east-1)
|
||||||
> **Closes:** G-008 (no cost documentation despite live AWS resources)
|
> **Closes:** G-008 (no cost documentation despite live AWS resources)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,13 @@
|
|||||||
|
|
||||||
Two escalations must be resolved before the leadership pitch:
|
Two escalations must be resolved before the leadership pitch:
|
||||||
- **G-005 (risks):** 6 cloud capabilities (CAP-017..022) are deploy-unverified.
|
- **G-005 (risks):** 6 cloud capabilities (CAP-017..022) are deploy-unverified.
|
||||||
|
**RESOLVED (v1.11):** CAP-017..022 are now Verified live-aws via the
|
||||||
|
modules-lifecycle pipeline (apply/modify/destroy exit 0). The IAM-drift
|
||||||
|
framing is removed. See CAPABILITY_INVENTORY.md.
|
||||||
- **G-008 (budget):** No cost documentation exists despite live AWS resources.
|
- **G-008 (budget):** No cost documentation exists despite live AWS resources.
|
||||||
|
**RESOLVED (v1.11):** COST.md now exists, documenting the v1.0→v1.10 spend
|
||||||
|
window + the v1.11 cost projection. The v1.14 P19 phase extends the
|
||||||
|
window to v1.11–v1.14.
|
||||||
|
|
||||||
The project is reclassified as an **OSS reference implementation** (G-003),
|
The project is reclassified as an **OSS reference implementation** (G-003),
|
||||||
not a sponsored product. The grill's sponsor/ROI/budget/timeline axes apply
|
not a sponsored product. The grill's sponsor/ROI/budget/timeline axes apply
|
||||||
@@ -251,3 +257,316 @@ in weakened form; the adoption, architecture, and risks axes apply in full.
|
|||||||
### Escalations
|
### Escalations
|
||||||
- **[G-005] risks** — 6 cloud capabilities (CAP-017..022: DynamoDB contracts table, Lambda contract-ingestor, ECS service live, CloudFront production stack, uptime-kuma, OIDC role) are deploy-unverified. The `acdl-spike-runner` IAM user cannot fix its own IAM (chicken-and-egg). Either re-bootstrap IAM with an admin principal to re-verify, or explicitly mark these 6 as "design-verified, deploy-unverified" in every leadership deck before the pitch. Resolves: project-killing risk (Axis 7 Q3).
|
- **[G-005] risks** — 6 cloud capabilities (CAP-017..022: DynamoDB contracts table, Lambda contract-ingestor, ECS service live, CloudFront production stack, uptime-kuma, OIDC role) are deploy-unverified. The `acdl-spike-runner` IAM user cannot fix its own IAM (chicken-and-egg). Either re-bootstrap IAM with an admin principal to re-verify, or explicitly mark these 6 as "design-verified, deploy-unverified" in every leadership deck before the pitch. Resolves: project-killing risk (Axis 7 Q3).
|
||||||
- **[G-008] budget** — No cost documentation exists in `.ciagent/` despite live AWS resources (account 581513795199, CAP-013..016 verified). Either add a `COST.md` documenting monthly AWS spend, or explicitly document that ACDL runs at zero cloud cost (local emulators are the primary tier; live-AWS is a one-off spike per milestone). Resolves: financial-control gap (Axis 6 Q1-Q4).
|
- **[G-008] budget** — No cost documentation exists in `.ciagent/` despite live AWS resources (account 581513795199, CAP-013..016 verified). Either add a `COST.md` documenting monthly AWS spend, or explicitly document that ACDL runs at zero cloud cost (local emulators are the primary tier; live-AWS is a one-off spike per milestone). Resolves: financial-control gap (Axis 6 Q1-Q4).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run: 2026-07-29 20:25 (mode: adversarial, focus: v1.14 NFR plan)
|
||||||
|
|
||||||
|
### Verdict: FEASIBLE WITH BINDING DECISIONS (confidence: 0.72)
|
||||||
|
|
||||||
|
The v1.14 milestone is a sound, well-evidenced NFR sweep with a genuine,
|
||||||
|
traceable backlog. Not fundamentally infeasible. Four binding decisions
|
||||||
|
close plan defects + unverified assumptions that would otherwise re-expose
|
||||||
|
the v1.11 4-VPC failure mode. One escalation (E-001) auto-resolved at full
|
||||||
|
autonomy with assumption logging.
|
||||||
|
|
||||||
|
### 9-Axis scores
|
||||||
|
|
||||||
|
| Axis | Confidence | Forcing question (short) |
|
||||||
|
|------|-----------|---------------------------|
|
||||||
|
| 1 Business | 0.80 | Real backlog (5 P1 + 4 P2 + 6 swallowed errors + 15+ hardcoded IDs); cancellation survivable but inherits decay risk |
|
||||||
|
| 2 Scope | 0.70 | User-directed + frozen; P13 has a hidden feature door (implement vs remove); P2 conditional-child edges past wiring |
|
||||||
|
| 3 Architecture | 0.62 | P8 grep unsatisfiable for backend blocks; P8 state-bucket continuity unguarded; P9 IAM naming unverified; P4/P8 file overlap |
|
||||||
|
| 4 People | 0.85 | Agentic single-operator; runtime availability is the key-person risk |
|
||||||
|
| 5 Timeline | 0.68 | No deadline; 20-phase unverified span is the longest since G-007; P8 is the latent multi-phase-rework risk |
|
||||||
|
| 6 Budget | 0.85 | NFR-only, no new AWS resources; P8 re-creation is a one-shot accident not structural cost |
|
||||||
|
| 7 Risks | 0.60 | A1 (acdl-* naming unverified), A2 (fallback constant unbound), A3 (P4 gate hardening); kill-risk = P8 orphans state |
|
||||||
|
| 8 Governance | 0.72 | Full autonomy; no mid-milestone stop trigger; per-phase "green" ≠ "capabilities Verified" |
|
||||||
|
| 9 Adoption | 0.70 | No external users; rollback is git-level for code, AWS-state rollback unaddressed if P8 misfires pre-detection |
|
||||||
|
|
||||||
|
### Binding Decisions
|
||||||
|
|
||||||
|
| ID | Axis | Decision | Confidence |
|
||||||
|
|----|------|----------|-----------|
|
||||||
|
| G-101 | architecture | P8 grep scope amended to exclude terraform `backend "s3"` blocks (bucket arg is static-config-only, evaluated pre-init; cannot reference `data.aws_caller_identity`). Resource ARNs in policy/code ARE externalized; backend blocks stay literal or move to `-backend-config` (separate change). | 0.80 |
|
||||||
|
| G-102 | risks | P8 must bind `ACDL_AWS_ACCOUNT_ID` fallback to the live account ID (not a placeholder) AND the lifecycle workflow (full-mode jobs) must set `ACDL_AWS_ACCOUNT_ID` from `aws sts get-caller-identity` before any lifecycle invocation. No full-mode run proceeds with the env unset. | 0.78 |
|
||||||
|
| G-103 | scope | P13 must take the removal+documentation path (remove `--kube-version` + document deferral to GitOps reconciler roadmap), NOT the implementation path. Implementing version-aware policy selection is a new feature, violating D-095. | 0.85 |
|
||||||
|
| G-104 | architecture | P9 must verify (grep/audit of `modules/l1/*/terraform/main.tf` + `modules/l2/*/composition.json`) that every IAM role + KMS key created by the lifecycle pipeline matches `acdl-*` prefix before merge. CloudFront + WAFv2 (CloudFront scope) remain `Resource: "*"` with a documented global-ARN constraint. | 0.70 |
|
||||||
|
| G-105 | governance | P4's regression-gate hardening must be validated by running the full regression gate immediately after P4 lands (not deferred to P21). Gate must pass clean post-P4 before W2 begins. | 0.70 |
|
||||||
|
| G-106 | governance | A mid-milestone regression-gate checkpoint is added after W2 (P12), before W3 begins. Gate runs offline (D-091); a non-Verified result halts W3 until fixed. Not a re-litigation of G-007 (per-phase stays deferred) — a single checkpoint at the natural seam after the security wave. | 0.65 |
|
||||||
|
|
||||||
|
### Escalations
|
||||||
|
|
||||||
|
- **[E-001] risks** — P8 state-bucket continuity re-exposes the v1.11 4-VPC
|
||||||
|
root cause. G-102 proposes a binding mitigation (bind fallback + wire env
|
||||||
|
into workflow), but the residual risk (a future full-mode lifecycle run
|
||||||
|
with a misconfigured env orphans live state and re-creates resources)
|
||||||
|
cannot be reduced below 0.20 by plan-level decisions alone. **Auto-
|
||||||
|
resolved at full autonomy (D-101):** accept the residual risk; G-102's
|
||||||
|
binding mitigation (fallback bound to live account ID + workflow env
|
||||||
|
wiring) is the control. The lifecycle pipeline defaults to plan-only
|
||||||
|
(REQ-134) — full-mode runs are workflow_dispatch only, reducing the
|
||||||
|
accident surface. If the user prefers zero residual risk, direct that
|
||||||
|
P8 exclude the state-bucket name from externalization entirely
|
||||||
|
(externalize only resource ARNs, leave the backend `bucket` literal).
|
||||||
|
Confidence 0.55; auto-resolved per `config.autonomy.level=full`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run: 2026-07-30 (mode: interactive, focus: v1.15-Nova rebrand, all 9 axes)
|
||||||
|
|
||||||
|
### Verdict: Proceed with conditions (confidence: 0.82)
|
||||||
|
|
||||||
|
A Major/breaking rebrand (ACDL → Nova) across prose, decks, code, env vars,
|
||||||
|
consumer path, SSM path, AWS tag keys, and AWS resource names — 4 execution
|
||||||
|
phases + 1 final. The plan is technically sound and the scope is user-directed
|
||||||
|
(D-102..D-112). Three binding mitigations surfaced (G-104, G-106, G-108); the
|
||||||
|
rest accept the plan as written. Two findings carry residual risk that is
|
||||||
|
accepted at full autonomy (G-103, G-107). No escalations remain open — all
|
||||||
|
auto-resolved with assumption logging per `config.autonomy.level=full`.
|
||||||
|
|
||||||
|
The single most material correction: **the versioning scheme was wrong**.
|
||||||
|
The plan tagged a Major/breaking milestone on the v1.14.x PATCH line
|
||||||
|
(`v1.14.5` = release), contradicting every prior breaking milestone in the
|
||||||
|
project (v1.1→v1.2.0, v1.5→v1.5.0, v1.11→v1.11.0 — all minor bumps). The
|
||||||
|
quoted "Major = progressive minor per phase" rule does not exist in any repo
|
||||||
|
file. **G-104 binds: re-tag as v1.15.x minor-bumped phases** (P1→v1.15.0 …
|
||||||
|
P5→v1.15.4, with v1.15.4 IS the milestone release).
|
||||||
|
|
||||||
|
### Per-axis findings
|
||||||
|
|
||||||
|
#### Axis 1 — Feasibility
|
||||||
|
**Challenge:** Can the full rebrand (1,465 `ACDL`/`acdl` occurrences across 205
|
||||||
|
files, 21 env vars, 11 AWS resources, 5 tag keys, 67 SSM refs, 23 consumer-path
|
||||||
|
refs) actually be done in 4 execution phases? The migration ordering
|
||||||
|
(docs→code/env→SSM/tags→AWS resources→final) is sound: P1 has no runtime impact,
|
||||||
|
P2's dual-read fallback prevents deployment breakage, P3's parallel-tag period
|
||||||
|
prevents ABAC lockout, P4's staged terraform migration prevents a big-bang
|
||||||
|
failure. The phase dependencies (P2 depends on P1's migration guide; P3 depends
|
||||||
|
on P2's dual-read + nova_tagging warn mode; P4 depends on P3's hard-mode tag
|
||||||
|
enforcement; P5 depends on all) are correctly ordered. **Confidence 0.85** that
|
||||||
|
the 4-phase structure is feasible. The `terraform init -migrate-state` approach
|
||||||
|
for the state bucket is the documented, correct mechanism (back up state JSON
|
||||||
|
first). No hidden dependencies found: the `.env.secrets` direct-read path
|
||||||
|
(G-106) and the Gitea secrets rotation (G-108) are the only mechanic gaps, both
|
||||||
|
now bound. **Verdict: ACCEPT-AS-IS.** **G-103.**
|
||||||
|
|
||||||
|
#### Axis 2 — Scope
|
||||||
|
**Challenge:** Is the full AWS resource rename WITH migration (downtime
|
||||||
|
accepted) over-scoped for a rebrand? D-102 locked this as user-directed. The
|
||||||
|
alternative (rename code only, leave AWS resources as `acdl-*`) would leave a
|
||||||
|
permanent brand inconsistency between code and cloud — acceptable for an NFR
|
||||||
|
patch, not for a "Major/breaking" milestone. The S&P visual theme is correctly
|
||||||
|
out of scope (D-107). The real Gitea repo name stays `acdl` (D-105) — sensible
|
||||||
|
(repo rename is a separate operational burden). Past Gitea release titles stay
|
||||||
|
`ACDL vX.Y.Z` (forward-only) — sensible (no history rewrite). Git branch/tag
|
||||||
|
naming has no brand name (D-112) — sensible. **Missing from scope:** the CI
|
||||||
|
workflow secret-references (`.gitea/workflows/*` `secrets.ACDL_*`) — P2 task 3
|
||||||
|
creates `NOVA_*` Gitea secrets but the plan does not show the workflow YAML
|
||||||
|
`secrets:` references being updated; G-108 binds the mitigation. **Confidence
|
||||||
|
0.80.** **Verdict: ACCEPT-AS-IS.** **G-104** (versioning — see Axis 5).
|
||||||
|
|
||||||
|
#### Axis 3 — Cost
|
||||||
|
**Challenge:** What's the real cost (downtime, person-hours, risk) and is it
|
||||||
|
justified for a *rebrand*? Per A1 (conf 0.9), no live AWS apply during P0–P4 —
|
||||||
|
so the migration scripts are authored but not executed; the live apply is an
|
||||||
|
operator runbook step. Person-hours are the agent's own (autonomous OSS
|
||||||
|
reference, G-003 carries forward). Downtime is accepted (D-102) but deferred to
|
||||||
|
the operator runbook. Token cost: the 1,465-occurrence rename across 205 files
|
||||||
|
is a large but mechanical edit — the explore survey already quantified the
|
||||||
|
mechanical-vs-judgment split. The risk cost (DynamoDB data loss, state bucket
|
||||||
|
corruption, ABAC lockout) is mitigated by the staged ordering + dual-read +
|
||||||
|
parallel-tag — all plan-validated, not live-applied. For an OSS reference with
|
||||||
|
0 consumer adoption (PROJECT.md:487), the cost is bounded. **Confidence 0.80.**
|
||||||
|
**Verdict: ACCEPT-AS-IS.** **G-105.**
|
||||||
|
|
||||||
|
#### Axis 4 — Schedule / risk
|
||||||
|
**Challenge:** DynamoDB data loss, state bucket migration, ABAC breakage,
|
||||||
|
consumer disruption. The mitigations: (a) DynamoDB scan+copy with row-count
|
||||||
|
verification, keep old tables until verified (manual post-verification deletion
|
||||||
|
— point of no return documented); (b) state bucket `terraform init
|
||||||
|
-migrate-state` with state JSON backup first; (c) parallel-tag ABAC period
|
||||||
|
(emit nova:* + acdl:* → swap policy → remove acdl:*); (d) consumer disruption
|
||||||
|
mitigated by the dual-read fallback (P2–P4) + the migration guide (P1). The top
|
||||||
|
3 assumptions: A1 (no live apply — conf 0.9, verified by the established
|
||||||
|
v1.11–v1.14 pattern), A2 (.env.secrets keys renamed, values stay — conf 0.85,
|
||||||
|
now bound by G-106), A3 (Gitea release API reachable — conf 0.8, verified HTTP
|
||||||
|
200). The single risk that could kill the project: state bucket corruption
|
||||||
|
during `-migrate-state` — mitigated by the backup-first runbook step. No
|
||||||
|
pre-mortem beyond the runbook is documented, but the staged ordering IS the
|
||||||
|
de-facto pre-mortem mitigation. **Confidence 0.78.** **Verdict: ACCEPT-AS-IS.**
|
||||||
|
**G-106.**
|
||||||
|
|
||||||
|
#### Axis 5 — Technical soundness
|
||||||
|
**Challenge:** Is the dual-read fallback design sound? Is the parallel-tag ABAC
|
||||||
|
migration safe? Is `terraform init -migrate-state` correct? **Dual-read:**
|
||||||
|
sound in principle (NOVA_X preferred, ACDL_X fallback), BUT the `.env.secrets`
|
||||||
|
load path bypasses the `core/env.py` helper — `run_platform.sh:288-289` exports
|
||||||
|
`$ACDL_AWS_ACCESS_KEY_ID` (hardcoded) and `regression_verify.py:309-312`
|
||||||
|
parses the file matching `k == "ACDL_AWS_ACCESS_KEY_ID"` (hardcoded). If P2
|
||||||
|
renames the `.env.secrets` keys to `NOVA_*` but these two readers still read
|
||||||
|
`ACDL_*`, AWS creds vanish → CAP-013/014/015 (which need live creds for
|
||||||
|
terraform plan) break → regression gate breaks. **G-106 binds: dual-read in
|
||||||
|
BOTH load paths** (shell export + Python parser must read NOVA_* first, ACDL_*
|
||||||
|
fallback, mirroring the helper contract). **Parallel-tag ABAC:** safe — emit
|
||||||
|
both tag sets, swap policy with acdl:* as secondary condition, verify, remove.
|
||||||
|
Plan-validated only per A1 (live ABAC stays acdl:* until operator runbook).
|
||||||
|
**`terraform init -migrate-state`:** correct documented mechanism; backup state
|
||||||
|
JSON first is the binding safety step. **Versioning contradiction:** the plan
|
||||||
|
tags a Major milestone on the v1.14.x PATCH line — G-104 binds re-tag as
|
||||||
|
v1.15.x minor-bumped. **Confidence 0.85.** **Verdict: MITIGATE-BINDING (G-106).**
|
||||||
|
**G-104, G-106.**
|
||||||
|
|
||||||
|
#### Axis 6 — Testability / verifiability
|
||||||
|
**Challenge:** Can the success criteria actually be verified? Will the
|
||||||
|
regression gate stay 16/16 across a 1,465-occurrence rename? Is `grep -rni ACDL`
|
||||||
|
returning 0 realistic? The gate-stays-16/16 binding constraint (PLAN.md:44-49)
|
||||||
|
requires per-phase fixture updates — P2 updates env-var fixtures, P3 updates
|
||||||
|
SSM/tag fixtures, P4 updates terraform-name fixtures. The dual-read fallback
|
||||||
|
test (P2) keeps ACDL_* as the fallback source — this is the ONE allowed
|
||||||
|
exception to the grep-returns-0 criterion (success criterion 6 exempts it).
|
||||||
|
`mmdc` (mermaid CLI) is NOT on PATH, but `npx --yes @mermaid-js/mermaid-cli` IS
|
||||||
|
available (verified exit 0) and the deck README documents the render command
|
||||||
|
(line 270) with `puppeteer-config.json` for no-sandbox — so the 5 `.mmd` PNG
|
||||||
|
re-exports in P1 task 3 are feasible. The Gitea secrets rotation (P2 task 3)
|
||||||
|
was verified: API reachable (HTTP 200), token present, `rotate_spike_key.sh`
|
||||||
|
pattern exists. **Confidence 0.82.** **Verdict: ACCEPT-AS-IS.** **G-107.**
|
||||||
|
|
||||||
|
#### Axis 7 — Security
|
||||||
|
**Challenge:** Does the rebrand introduce a security regression? (a) ABAC
|
||||||
|
policy swap window — mitigated by the parallel-tag period (nova:* + acdl:*
|
||||||
|
both valid → swap → remove); plan-validated only, no live window during P0–P4.
|
||||||
|
(b) Secret rotation — `.env.secrets` keys renamed (values stay, no
|
||||||
|
re-rotation needed until P5); G-106 binds the dual-read in both load paths so
|
||||||
|
creds don't silently vanish. (c) `.env.secrets` key rename — the file contains
|
||||||
|
live rotated AWS creds + a Gitea token; renaming keys is cosmetic (same values)
|
||||||
|
but the load-path readers must follow (G-106). (d) IAM policy scope (v1.14 P9
|
||||||
|
scoped `Resource: "*"`) — the rebrand renames `acdl-*` ARNs to `nova-*` in
|
||||||
|
terraform; the IAM policy `Resource` patterns must be updated to `nova-*` —
|
||||||
|
P4 task 2 covers this (`acdl-spike-runner` → `nova-spike-runner`). No new
|
||||||
|
security regression introduced; the rebrand is nomenclature, not a permission
|
||||||
|
change. **Confidence 0.80.** **Verdict: ACCEPT-AS-IS.** **G-108.**
|
||||||
|
|
||||||
|
#### Axis 8 — Maintainability
|
||||||
|
**Challenge:** Will the dual-read fallback + parallel-tag period create
|
||||||
|
technical debt that's hard to clean up? Is P5 (remove fallback) realistic? The
|
||||||
|
dual-read (P2) + parallel-tag (P3) IS technical debt by design — it exists to
|
||||||
|
be removed in P5. P5 does six things in one phase (remove fallback, hard-fail
|
||||||
|
acdl:*, delete Gitea ACDL_* secrets, remove .env.secrets legacy comment,
|
||||||
|
multi-persona review + audit, milestone ship). The risk: P5's removal surfaces
|
||||||
|
a break if P2–P4 didn't catch every ACDL_* reference in the platform's OWN CI
|
||||||
|
workflows. But P5 is mechanical cleanup: `get_env()` drops the fallback branch,
|
||||||
|
shell scripts drop `:-$ACDL_X`, `nova_tagging.py` flips warn→hard-fail. The
|
||||||
|
grep-returns-0 success criteria are verifiable. The 0-consumer-adoption state
|
||||||
|
(PROJECT.md:487) means no external consumer breaks at P5; only the platform's
|
||||||
|
own CI must be fully migrated by P4. **Confidence 0.78.** **Verdict:
|
||||||
|
ACCEPT-AS-IS.** **G-109.**
|
||||||
|
|
||||||
|
#### Axis 9 — Adversarial
|
||||||
|
**Challenge:** Worst-case scenario? What breaks first? Rollback plan if P4
|
||||||
|
goes wrong mid-flight? **Worst case:** the `terraform init -migrate-state`
|
||||||
|
corrupts the state bucket JSON and the backup was incomplete — you lose
|
||||||
|
terraform state for the microservice + static-assets stacks. **Mitigation:**
|
||||||
|
the runbook binds "back up the state JSON first" before each `-migrate-state`;
|
||||||
|
keep old DynamoDB tables until verified (manual post-verification deletion =
|
||||||
|
the point of no return). The staged ordering (KMS alias → SNS/SG → Lambda →
|
||||||
|
DynamoDB → ECR → IAM → state bucket → ALB last) means a mid-flight failure at
|
||||||
|
any step leaves prior steps intact and old resources still named `acdl-*`. The
|
||||||
|
dual-read fallback (P2–P4) means the runtime tolerates both `acdl-*` and
|
||||||
|
`nova-*` during the window — so a partial migration doesn't break the running
|
||||||
|
platform. **What breaks first:** the `.env.secrets` load path (G-106) — if the
|
||||||
|
key rename + reader update are misaligned, AWS creds vanish and the regression
|
||||||
|
gate breaks immediately. G-106 binds the mitigation. **Rollback:** the runbook
|
||||||
|
is the rollback; the staged ordering with "keep old until verified" is the
|
||||||
|
safety net. ALB recreate (last, brief downtime) is the only hard-downtime step;
|
||||||
|
rollback = recreate the old ALB. **Confidence 0.75.** **Verdict: ACCEPT-AS-IS.**
|
||||||
|
**G-110.**
|
||||||
|
|
||||||
|
### Binding decisions (G-103..G-110)
|
||||||
|
|
||||||
|
| ID | Axis | Decision | Confidence | Rationale |
|
||||||
|
|----|------|----------|-----------|-----------|
|
||||||
|
| G-103 | 1 (Feasibility) | ACCEPT-AS-IS | 0.85 | 4-phase structure is feasible; migration ordering (docs→code/env→SSM/tags→AWS→final) is sound; phase dependencies correctly ordered; `terraform init -migrate-state` is the correct mechanism. |
|
||||||
|
| G-104 | 2/5 (Scope/Technical) | MITIGATE-BINDING | 0.90 | **Re-tag as v1.15.x minor-bumped phases** (P1→v1.15.0 … P5→v1.15.4, v1.15.4 IS the milestone release). The v1.14.x PATCH-line scheme contradicts every prior breaking milestone (v1.1→v1.2.0, v1.5→v1.5.0, v1.11→v1.11.0). The quoted "Major = progressive minor per phase" rule exists in NO repo file. A Major/breaking milestone shipping as v1.14.5 means the semver MAJOR never advances despite a breaking change — consumers on `@v1` silently absorb the rebrand. Update PLAN.md, ROADMAP.md §v1.15, PROJECT.md §v1.15, and ARCHITECTURE.md §v1.15 Addendum tag references. |
|
||||||
|
| G-105 | 3 (Cost) | ACCEPT-AS-IS | 0.80 | No live AWS apply during P0–P4 (A1); migration scripts authored, not executed; downtime accepted (D-102) but deferred to operator runbook. For an OSS reference with 0 consumer adoption, cost is bounded. |
|
||||||
|
| G-106 | 4/5 (Risk/Technical) | MITIGATE-BINDING | 0.88 | **Dual-read in BOTH `.env.secrets` load paths.** `run_platform.sh:288-289` (`export AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID"`) and `regression_verify.py:309-312` (parses file matching `k == "ACDL_AWS_ACCESS_KEY_ID"`) bypass the new `core/env.py get_env()` helper. P2 MUST update both readers to read `NOVA_*` first with `ACDL_*` fallback — mirroring the dual-read contract. Without this, renaming `.env.secrets` keys to `NOVA_*` breaks AWS creds → CAP-013/014/015 fail → regression gate breaks. Old `ACDL_*` keys removed in P5. |
|
||||||
|
| G-107 | 6 (Testability) | ACCEPT-AS-IS | 0.82 | Per-phase fixture updates keep the gate 16/16 (PLAN.md:44-49 binding constraint). `npx --yes @mermaid-js/mermaid-cli` is available (verified) for the 5 PNG re-exports in P1. Gitea API reachable (HTTP 200) + token present for P2 task 3. |
|
||||||
|
| G-108 | 7 (Security) | MITIGATE-BINDING | 0.80 | **P2 task 3 must update the CI workflow `secrets:` references** (`.gitea/workflows/*`, `.github/workflows/*`) when `NOVA_*` Gitea secrets are created, with graceful degrade + retry on API failure. The plan creates `NOVA_*` aliases but does not show the workflow YAML `secrets.ACDL_*` references being updated. If the workflows still reference `ACDL_*` secrets at P5 (when old secrets are deleted), CI breaks. The Gitea secrets rotation must be a hard gate with retry-on-failure (not a silent skip). |
|
||||||
|
| G-109 | 8 (Maintainability) | ACCEPT-AS-IS | 0.78 | P5 is mechanical cleanup (drop fallback branch, hard-fail acdl:*, delete old secrets); 0-consumer-adoption means no external break at P5; grep-returns-0 is verifiable. |
|
||||||
|
| G-110 | 9 (Adversarial) | ACCEPT-AS-IS | 0.75 | Runbook + staged ordering is the rollback; "keep old until verified" is the safety net; ALB recreate (last) is the only hard-downtime step. The `.env.secrets` load path (G-106) is what breaks first if misaligned — G-106 binds the mitigation. |
|
||||||
|
|
||||||
|
### Escalations
|
||||||
|
|
||||||
|
None remain open. All material questions resolved with confidence ≥ 0.60.
|
||||||
|
Two findings carry accepted residual risk (auto-resolved at full autonomy
|
||||||
|
with assumption logging):
|
||||||
|
|
||||||
|
- **G-103 (Axis 1):** residual risk that the 4-phase structure underestimates
|
||||||
|
the 1,465-occurrence rename effort — accepted; per-phase fixture updates
|
||||||
|
(G-107) + the explore survey's mechanical-vs-judgment split bound the effort.
|
||||||
|
- **G-107 (Axis 6):** residual risk that a test fixture is missed during the
|
||||||
|
per-phase rename, breaking 16/16 at a phase boundary — accepted; the
|
||||||
|
per-phase verify step (run the gate before tagging) catches it before ship.
|
||||||
|
|
||||||
|
### Forcing questions asked (7)
|
||||||
|
|
||||||
|
1. **Versioning contradiction** — Major milestone on v1.14.x PATCH line vs.
|
||||||
|
prior breaking milestones all minor-bumped. → **G-104 MITIGATE-BINDING**
|
||||||
|
(re-tag as v1.15.x).
|
||||||
|
2. **P4 migration completeness** — plan-validated terraform vs live AWS
|
||||||
|
resources still `acdl-*`. → **G-103/105 ACCEPT-AS-IS** (runbook for live).
|
||||||
|
3. **`.env.secrets` key rename mechanic** — dual-read helper bypassed by direct
|
||||||
|
shell/Python readers. → **G-106 MITIGATE-BINDING** (dual-read in both load
|
||||||
|
paths).
|
||||||
|
4. **Gitea secrets rotation** — API reachable, token present, but workflow
|
||||||
|
`secrets:` references not shown updated. → **G-108 MITIGATE-BINDING** (update
|
||||||
|
workflow refs, hard gate + retry).
|
||||||
|
5. **ABAC parallel-tag window** — over-engineered for 0 consumers, or correct
|
||||||
|
forward-looking safety net? → **G-108/Axis-4 ACCEPT-AS-IS** (parallel-tag is
|
||||||
|
the mitigation, plan-validated).
|
||||||
|
6. **Regression gate during rebrand** — 16/16 across 1,465-occurrence rename?
|
||||||
|
→ **G-107 ACCEPT-AS-IS** (per-phase fixture updates).
|
||||||
|
7. **P5 fallback removal realism** — cleanup + review + audit + ship in one
|
||||||
|
phase? → **G-109 ACCEPT-AS-IS** (mechanical cleanup).
|
||||||
|
8. **P4 rollback plan** — runbook + staged ordering sufficient? → **G-110
|
||||||
|
ACCEPT-AS-IS** (staged ordering is the rollback).
|
||||||
|
|
||||||
|
### What the project is NOT doing that it should (adversarial close)
|
||||||
|
|
||||||
|
- **Documenting the versioning rule it now follows.** G-104 binds the
|
||||||
|
v1.15.x minor-bumped scheme, but no `.ciagent/` file records the
|
||||||
|
versioning convention. The plan should add a one-line versioning note to
|
||||||
|
PROJECT.md §v1.15 or a `VERSIONING.md` so the next milestone doesn't
|
||||||
|
re-litigate this.
|
||||||
|
- **Quantifying the live state volume** for the DynamoDB scan+copy + state
|
||||||
|
bucket migration. The runbook says "back up first" + "verify row counts" but
|
||||||
|
doesn't quantify the data. For 0-consumer-adoption, this is likely tiny —
|
||||||
|
but the rollback feasibility (G-110) depends on it being small enough to
|
||||||
|
re-scan. Accepted residual risk.
|
||||||
|
|
||||||
|
### Simplest 80%-value version
|
||||||
|
|
||||||
|
The simplest version that delivers 80% of the rebrand value: **P1 (docs/decks)
|
||||||
|
+ P2 (code/env dual-read) + P5 (ship)** — skip the live AWS resource migration
|
||||||
|
(P3 SSM/tags + P4 AWS resources) entirely. The code + docs would say Nova; the
|
||||||
|
cloud would still say `acdl-*`. This is the "rename code only, leave cloud"
|
||||||
|
option D-102 rejected. The user chose the full migration (D-102) — the binding
|
||||||
|
decision is recorded; the 80% version is NOT the chosen path. The full scope is
|
||||||
|
accepted as user-directed.
|
||||||
|
|
||||||
|
### What must be true for success in the next 90 days, and is it true today?
|
||||||
|
|
||||||
|
1. **The dual-read helper + both `.env.secrets` load paths are updated in
|
||||||
|
lockstep (G-106).** — TRUE after P2 binds G-106; FALSE today (the direct
|
||||||
|
readers still hardcode `ACDL_*`).
|
||||||
|
2. **The regression gate stays 16/16 at every phase boundary (G-107).** —
|
||||||
|
TRUE if per-phase fixture updates are complete before each tag; the
|
||||||
|
per-phase verify step enforces it.
|
||||||
|
3. **The CI workflow `secrets:` references are updated when `NOVA_*` Gitea
|
||||||
|
secrets are created (G-108).** — FALSE today; P2 task 3 must be expanded to
|
||||||
|
include the workflow YAML updates.
|
||||||
|
4. **The versioning scheme is corrected to v1.15.x (G-104).** — FALSE today;
|
||||||
|
the plan says v1.14.x. Must be corrected before P0 ship.
|
||||||
|
|
||||||
|
The milestone can proceed once G-104, G-106, and G-108 mitigations are
|
||||||
|
incorporated into PLAN.md. Confidence 0.82.
|
||||||
|
|||||||
+58
-3
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
project: acdl
|
project: acdl
|
||||||
milestone: v1.11
|
milestone: v1.14
|
||||||
generated_at: 2026-07-28
|
generated_at: 2026-07-29
|
||||||
generator: lead-developer
|
generator: lead-developer
|
||||||
verification_toolchain:
|
verification_toolchain:
|
||||||
typecheck: "terraform validate && python3 -m py_compile core/**/*.py && python3 -m jsonschema schemas/*.schema.json"
|
typecheck: "terraform validate && python3 -m py_compile core/**/*.py && python3 -m jsonschema schemas/*.schema.json"
|
||||||
@@ -18,6 +18,10 @@ verification_toolchain:
|
|||||||
against live AWS. No per-module Python/pytest. This override is
|
against live AWS. No per-module Python/pytest. This override is
|
||||||
documented here as the single source of truth; the ci-* agents read
|
documented here as the single source of truth; the ci-* agents read
|
||||||
PERSONAS.md before running verification commands.
|
PERSONAS.md before running verification commands.
|
||||||
|
v1.14 note: NFR-only milestone (bug fixes, security, tests, docs).
|
||||||
|
Roster carries forward from v1.11 unchanged. frontend-engineer stays
|
||||||
|
inactive (no frontend; decks are markdown = lead-developer
|
||||||
|
territory). No custom personas needed (no new domains).
|
||||||
---
|
---
|
||||||
|
|
||||||
# ACDL — Persona Roster (project-level, v1.11 RESTART)
|
# ACDL — Persona Roster (project-level, v1.11 RESTART)
|
||||||
@@ -144,4 +148,55 @@ default per execute.md is `warn`. Cross-territory edits are logged in the
|
|||||||
commit message but do not fail the task. v1.11's scope means co-authoring
|
commit message but do not fail the task. v1.11's scope means co-authoring
|
||||||
across territories is likely (e.g. backend + general on the adapter +
|
across territories is likely (e.g. backend + general on the adapter +
|
||||||
`run_platform.sh` boundary; data + general on the examples + pipeline
|
`run_platform.sh` boundary; data + general on the examples + pipeline
|
||||||
boundary); `warn` keeps it frictionless.
|
boundary); `warn` keeps it frictionless.
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.15 Persona Addendum — Nova Rebrand (2026-07-30)
|
||||||
|
|
||||||
|
**Milestone:** v1.15-Nova. The roster carries forward from v1.11/v1.14
|
||||||
|
unchanged — the rebrand touches existing territories, no new domains.
|
||||||
|
**frontend-engineer** remains deactivated (no UI; decks are markdown =
|
||||||
|
lead-developer territory). No **security-engineer** persona is activated
|
||||||
|
— the ABAC session-policy + tag-key migration (REQ-162) is data-engineer
|
||||||
|
territory (terraform IAM) with lead-developer review.
|
||||||
|
|
||||||
|
### v1.15 territory assignments
|
||||||
|
|
||||||
|
| Phase | Lead | Contributors | Territory |
|
||||||
|
|-------|------|---------------|-----------|
|
||||||
|
| P1 docs-decks-prose | lead-developer | — | `README.md`, `docs/**`, `.ciagent/*.md`, deck `.md`/`-marp.md`/`-talking-points.md`/`.html`, `docs/presentations/assets/mmd/*.mmd` (+ PNG re-export), `pyproject.toml`, `schemas/*.schema.json` `$id` (D-110), `docs/NOVA_MIGRATION.md`, `.github/workflows/release.yml` title, `modules/STANDARDS.md` |
|
||||||
|
| P2 code-envvars-consumer-path | backend-engineer | lead-developer (docs/runbook) | `core/env.py` (NEW dual-read helper, D-108), `core/*.py` (call-site migration), `scripts/*.py` + `*.sh`, `adapters/**`, `tests/**`, `.gitea/workflows/**` + `.github/workflows/**`, `.env` + `.env.secrets` (key rename), `schemas/tagging-standard.json`, `adapters/terraform/policy/custom_rules/acdl_tagging.py` → `nova_tagging.py` (D-109: warn mode) |
|
||||||
|
| P3 ssm-tagkeys | data-engineer | backend-engineer (readers) | `core/output_publisher.py` (SSM path `/nova/`), `core/contract_resolver.py` (SSM reads), `scripts/migrate_ssm_paths.py` (NEW), `terraform/**` (tag keys `nova:*`), `adapters/terraform/policy/custom_rules/nova_tagging.py` (D-109: hard mode), ABAC session-policy terraform |
|
||||||
|
| P4 aws-resource-migration | data-engineer | lead-developer (runbook) | `terraform/platform/main.tf`, `terraform/microservice/main.tf`, `terraform/ci-vpc/main.tf`, `terraform/bootstrap/**`, `modules/l1/alb/instance.json`, `scripts/migrate_dynamodb_data.py` (NEW), `docs/NOVA_AWS_MIGRATION.md` (NEW runbook), `core/lambda/contract_ingestor.py` (default table names → `nova-*`, D-111) |
|
||||||
|
| P5 final-review-ship | lead-developer | all active (review) | `.ciagent/**` (REQUIREMENTS/ROADMAP/PROJECT complete), `core/env.py` (remove dual-read fallback), `nova_tagging.py` (hard-fail `acdl:*`), review + audit |
|
||||||
|
|
||||||
|
### v1.15 domain priority
|
||||||
|
|
||||||
|
`lead → backend → data` (inverted from v1.11)
|
||||||
|
|
||||||
|
Rationale: the rebrand is docs/prose-first (P1 establishes the
|
||||||
|
vocabulary, no runtime impact), then code/env-vars/consumer-path (P2),
|
||||||
|
then SSM/tag-keys (P3), then the heavy terraform/AWS migration (P4).
|
||||||
|
Lead-developer owns the docs + runbooks + verification + final ship;
|
||||||
|
backend-engineer owns the dual-read helper + call-site migration +
|
||||||
|
contract resolver; data-engineer owns the terraform resource/tag/SSM
|
||||||
|
migration (the heaviest terraform territory). Co-authoring expected at:
|
||||||
|
`core/env.py` + `core/*.py` boundary (backend + lead on the helper
|
||||||
|
design), `nova_tagging.py` + `schemas/tagging-standard.json` boundary
|
||||||
|
(backend authors the rule, data-engineer owns the tag-key schema),
|
||||||
|
`core/output_publisher.py` SSM path + `terraform` outputs boundary
|
||||||
|
(backend writes the reader, data-engineer owns the terraform that
|
||||||
|
produces the outputs).
|
||||||
|
|
||||||
|
### v1.15 verification toolchain (unchanged from v1.14)
|
||||||
|
|
||||||
|
```
|
||||||
|
typecheck: terraform validate && python3 -m py_compile core/**/*.py adapters/**/*.py
|
||||||
|
test: bash scripts/run_regression.sh # 16-capability gate
|
||||||
|
build: bash scripts/run_ci.sh # full local CI reproduction
|
||||||
|
```
|
||||||
|
|
||||||
|
The regression gate (CAP-001..CAP-016) must stay **16/16 Verified**
|
||||||
|
throughout the rebrand — the rebrand must not regress any capability.
|
||||||
|
P2/P3/P4 update test fixtures that reference `ACDL`/`acdl` so the gate
|
||||||
|
stays green.
|
||||||
|
|||||||
+332
-38
@@ -1,55 +1,349 @@
|
|||||||
---
|
---
|
||||||
phase: P65
|
phase: P0
|
||||||
name: rewrite-caps-decks
|
name: pre-execution
|
||||||
milestone: v1.11
|
milestone: v1.15
|
||||||
requirements: [REQ-116, REQ-118]
|
requirements: [REQ-155, REQ-156, REQ-157, REQ-158, REQ-159, REQ-160, REQ-161, REQ-162, REQ-163, REQ-164]
|
||||||
wave: 4
|
wave: 0
|
||||||
depends_on: [P64]
|
depends_on: []
|
||||||
---
|
---
|
||||||
|
|
||||||
# P65 — Rewrite Caps + Decks
|
# v1.15 — Nova Rebrand Plan (4 execution phases + 1 final)
|
||||||
|
|
||||||
**Phase:** P65
|
**Milestone:** v1.15 (Nova Rebrand — Major/breaking)
|
||||||
**Milestone:** v1.11 (RESTART)
|
**Type:** Major (breaking — consumer path, env vars, SSM path, tag keys,
|
||||||
**Requirements:** REQ-116 (CAP-017..022 Verified), REQ-118 (decks rewritten)
|
AWS resource names all change). Per the branch-strategy precedent
|
||||||
**Wave:** 4 (final phase before COMPLETE)
|
(v1.10.2 → v1.11.0, v1.9.x → v1.10.0 — breaking/feature milestones tag
|
||||||
**Branch:** `milestone/v1.11-restart`
|
on their OWN minor line, not the previous minor's patch line), v1.15
|
||||||
|
tags run on the **v1.15.x minor line**: `v1.15.0` (P0) →
|
||||||
|
`v1.15.1..v1.15.4` (P1–P4) → `v1.15.4` (P5 = milestone release). (G-104
|
||||||
|
binding: the v1.14.x patch line is the NFR convention; a Major
|
||||||
|
milestone ships on its own minor.)
|
||||||
|
**Branch:** `milestone/v1.15-nova` → `phase/NN-<slug>`
|
||||||
|
|
||||||
## Goal
|
## Wave ordering (D-098 v1.15 analogue)
|
||||||
|
|
||||||
Rewrite CAPABILITY_INVENTORY.md, PROJECT.md §Capability Status, and both
|
- **Wave 1 (P1):** docs/decks/prose — no runtime impact; establishes
|
||||||
leadership decks: CAP-017..022 → "Verified live-aws via lifecycle pipeline;
|
the Nova vocabulary + ships the consumer migration guide. REQ-155,
|
||||||
torn down to zero-cost steady state." Remove the IAM-drift framing. Add
|
REQ-156, REQ-157. Independent (first phase).
|
||||||
the cost appendix slide (P63) + pre-mortem reference (P64). `ci-doc-verifier`
|
- **Wave 2 (P2):** code + env vars (dual-read) + consumer path —
|
||||||
confirms no stale "deploy-unverified" claims remain.
|
deployments don't break during the transition window. REQ-158,
|
||||||
|
REQ-159, REQ-160. Depends on P1 (docs establish the guide P2 changes
|
||||||
|
are announced in).
|
||||||
|
- **Wave 3 (P3):** SSM path + tag keys — SSM copy/read/delete; tag keys
|
||||||
|
parallel-tag → policy swap → remove old. REQ-161, REQ-162. Depends on
|
||||||
|
P2 (env var dual-read + nova_tagging.py warn mode must land first).
|
||||||
|
- **Wave 4 (P4):** AWS resource names — staged terraform migration.
|
||||||
|
REQ-163. Depends on P3 (tag keys nova:* enforced hard before resource
|
||||||
|
recreation; nova_tagging.py hard mode).
|
||||||
|
- **Wave 5 (P5):** final-review-ship — remove dual-read fallback, review,
|
||||||
|
audit, milestone ship. REQ-164. Depends on P1–P4.
|
||||||
|
|
||||||
## Tasks
|
## Execution approach
|
||||||
|
|
||||||
### Task 1 — Update CAPABILITY_INVENTORY.md
|
Each phase: EXECUTE (persona-assigned task groups) → VERIFY (4 layers +
|
||||||
|
regression gate stays 16/16) → SHIP (patch tag on v1.14.x line). Phase
|
||||||
|
boundary checkpoint resets context. The execute workflow reads this
|
||||||
|
PLAN.md + ROADMAP.md §v1.15 + PERSONAS.md §v1.15 for task decomposition.
|
||||||
|
|
||||||
Mark CAP-017..022 as "Verified live-aws via lifecycle pipeline" (no longer
|
**Binding constraint (capability gate):** the regression gate
|
||||||
"not auto-verified"). Remove the IAM-drift framing. Reference the lifecycle
|
(CAP-001..CAP-016, `scripts/run_regression.sh`) MUST stay 16/16 Verified
|
||||||
pipeline as the evidence source.
|
throughout the rebrand. Each phase updates test fixtures that reference
|
||||||
|
`ACDL`/`acdl` so the gate stays green. No capability is added, removed,
|
||||||
|
or reclassified — the rebrand is nomenclature + identifiers, not
|
||||||
|
behavior.
|
||||||
|
|
||||||
### Task 2 — Update PROJECT.md §Capability Status
|
---
|
||||||
|
|
||||||
Update the capability status section to reflect Verified status for
|
## Wave 1 — Docs / Decks / Prose (P1)
|
||||||
CAP-017..022.
|
|
||||||
|
|
||||||
### Task 3 — Update decks (if present)
|
### P1 — docs-decks-prose (REQ-155, REQ-156, REQ-157)
|
||||||
|
**Persona:** lead-developer
|
||||||
|
**Territory:** `README.md`, `docs/**`, `.ciagent/*.md`, deck
|
||||||
|
`.md`/`-marp.md`/`-talking-points.md`/`.html`,
|
||||||
|
`docs/presentations/assets/mmd/*.mmd` (+ PNG re-export), `pyproject.toml`,
|
||||||
|
`schemas/*.schema.json` `$id` (D-110), `docs/NOVA_MIGRATION.md` (NEW),
|
||||||
|
`.github/workflows/release.yml` title, `.gitea/workflows/release.yml`
|
||||||
|
(if present), `modules/STANDARDS.md`, `contracts/**` prose
|
||||||
|
**Tasks:**
|
||||||
|
1. **Prose rebrand (REQ-155).** Find/replace across all docs + .ciagent
|
||||||
|
markdown: `ACDL` → `Nova`, `Agentic Cloud Delivery Platform` → `Nova`
|
||||||
|
(full phrase). Preserve historical narrative (e.g. "formerly ACDL"
|
||||||
|
in any changelog-style section is acceptable; otherwise full swap).
|
||||||
|
Update `pyproject.toml` `name` → `nova`, `description` → Nova.
|
||||||
|
Update `release.yml` release-title prefix `ACDL ` → `Nova `.
|
||||||
|
Update illustrative URLs in docs: `github.com/acdl/...` →
|
||||||
|
`github.com/nova/...`, `git.cloudinit.dev/continuous-intelligence/acdl*`
|
||||||
|
→ `.../nova*` (prose only; config.json `release.gitea.repo` stays
|
||||||
|
`acdl` per D-105).
|
||||||
|
2. **Schema $id rebrand (D-110, REQ-155).** Update `$id` in all
|
||||||
|
`schemas/*.schema.json` + `schemas/tagging-standard.json`:
|
||||||
|
`https://acdl.cloudinit.dev/schemas/...` →
|
||||||
|
`https://nova.cloudinit.dev/schemas/...`. Update test fixtures that
|
||||||
|
assert the `$id` value.
|
||||||
|
3. **Deck + mermaid rebrand (REQ-156).** Edit both deck markdown
|
||||||
|
sources (`docs/presentations/how-the-platform-works.md`,
|
||||||
|
`the-developer-experience.md` + their `-marp.md` + `-talking-points.md`
|
||||||
|
variants): `ACDL` → `Nova` in slide content + mermaid cluster labels
|
||||||
|
(`["ACDL — infrastructure only"]` → `["Nova — infrastructure only"]`).
|
||||||
|
Edit the 5 `.mmd` sources (`docs/presentations/assets/mmd/*.mmd`):
|
||||||
|
`ACDL` → `Nova`. Re-export the PNG diagrams from the edited `.mmd`
|
||||||
|
sources so the committed PNGs match the new labels (use the deck
|
||||||
|
README's documented process: mmdc CLI or the render script).
|
||||||
|
4. **Nova tagline insertion (REQ-157).** Add the tagline "The New Dawn
|
||||||
|
of DevSecOps — security as a seamless enabler of fast deployments" to:
|
||||||
|
the README header (below the title), both deck title slides (as the
|
||||||
|
subtitle, replacing "Agentic Cloud Delivery Platform"), and
|
||||||
|
`docs/vision.md` (top of the Vision section). Retain the existing
|
||||||
|
"North Star" / "consumers declare intent" framing — do NOT remove
|
||||||
|
it (D-106).
|
||||||
|
5. **Consumer migration guide (REQ-155/160).** Create
|
||||||
|
`docs/NOVA_MIGRATION.md` announcing the 5 breaking changes coming in
|
||||||
|
P2–P4: (a) `.acdl/contract.yml` → `.nova/contract.yml` (P2); (b)
|
||||||
|
`ACDL_*` env vars → `NOVA_*` (P2, dual-read fallback); (c) SSM path
|
||||||
|
`/acdl/` → `/nova/` (P3); (d) AWS tag keys `acdl:*` → `nova:*` (P3);
|
||||||
|
(e) AWS resource names `acdl-*` → `nova-*` (P4, maintenance window).
|
||||||
|
Include the dual-read fallback window (P2–P4) + the cutoff (P5
|
||||||
|
removes fallback).
|
||||||
|
6. **HTML re-render (REQ-156).** Re-render both deck HTML files from
|
||||||
|
the updated `-marp.md` sources (self-contained, base64 images, S&P
|
||||||
|
theme unchanged per D-107). Commit the re-rendered HTML.
|
||||||
|
7. **Regress gate.** `bash scripts/run_regression.sh` — expect 16/16
|
||||||
|
Verified (fixtures referencing `ACDL`/`acdl` in paths are updated in
|
||||||
|
P2; P1 only touches prose/decks/schema-$id, so the gate should stay
|
||||||
|
green. If a test asserts an `ACDL` string in a doc it reads, update
|
||||||
|
the assertion to `Nova`).
|
||||||
|
|
||||||
If leadership deck source files exist (PPTX/HTML/markdown), update them to
|
---
|
||||||
reflect verified-then-torn-down status. Add the cost appendix (P63) +
|
|
||||||
pre-mortem reference (P64). Remove stale "deploy-unverified" claims.
|
|
||||||
|
|
||||||
### Task 4 — ci-doc-verifier check
|
## Wave 2 — Code / Env Vars / Consumer Path (P2)
|
||||||
|
|
||||||
Run the doc-verifier to confirm no stale "deploy-unverified" claims remain
|
### P2 — code-envvars-consumer-path (REQ-158, REQ-159, REQ-160)
|
||||||
in any .ciagent/ or deck files.
|
**Persona:** backend-engineer (lead) + lead-developer (docs/runbook)
|
||||||
|
**Territory:** `core/env.py` (NEW), `core/*.py`, `scripts/*.py` +
|
||||||
|
`*.sh`, `adapters/**`, `tests/**`, `.gitea/workflows/**` +
|
||||||
|
`.github/workflows/**`, `.env` + `.env.secrets` (key rename),
|
||||||
|
`schemas/tagging-standard.json`,
|
||||||
|
`adapters/terraform/policy/custom_rules/acdl_tagging.py` →
|
||||||
|
`nova_tagging.py`
|
||||||
|
**Tasks:**
|
||||||
|
1. **Dual-read env helper (D-108, REQ-159).** Create `core/env.py` with
|
||||||
|
`get_env(name, default=None)` that reads `NOVA_<name>` then falls
|
||||||
|
back to `ACDL_<name>`, returning `default` if neither. Add unit
|
||||||
|
tests in `tests/test_env_helper.py` covering: both set (NOVA wins),
|
||||||
|
only NOVA set, only ACDL set (fallback), neither set (default).
|
||||||
|
2. **Env var rename (REQ-159).** Migrate all 21 `ACDL_*` env var
|
||||||
|
references → `NOVA_*` across `core/*.py`, `scripts/*.py` + `*.sh`,
|
||||||
|
`adapters/**`, `tests/**`, `.gitea/workflows/**`,
|
||||||
|
`.github/workflows/**`. Use the `core/env.py` helper at Python call
|
||||||
|
sites (replace `os.environ.get("ACDL_X")` →
|
||||||
|
`env.get_env("X")`); for shell scripts, use `${NOVA_X:-$ACDL_X}`
|
||||||
|
dual-read inline. Rename keys in `.env` + `.env.secrets` (KEY names
|
||||||
|
only — VALUES/secret material stay). Leave a comment in `.env.secrets`
|
||||||
|
noting the legacy `ACDL_*` keys are the dual-read fallback source
|
||||||
|
until P5. **G-106 binding:** the `.env.secrets` direct-read paths
|
||||||
|
(`scripts/run_platform.sh:288-289` `export AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID"`
|
||||||
|
+ `core/regression_verify.py:309-312` `if k == "ACDL_AWS_ACCESS_KEY_ID"`)
|
||||||
|
bypass the helper and MUST be updated to dual-read `NOVA_*` first,
|
||||||
|
`ACDL_*` fallback (shell: `${NOVA_AWS_ACCESS_KEY_ID:-$ACDL_AWS_ACCESS_KEY_ID}`;
|
||||||
|
Python: match `k == "NOVA_AWS_ACCESS_KEY_ID" or k == "ACDL_AWS_ACCESS_KEY_ID"`)
|
||||||
|
— otherwise AWS creds vanish mid-rename and CAP-013/014/015 fail.
|
||||||
|
3. **Gitea secrets rotation + workflow refs (G-108 binding, REQ-159).**
|
||||||
|
Use the Gitea API (`scripts/rotate_spike_key.sh` pattern or a new
|
||||||
|
`scripts/rename_gitea_secrets.py`) to create `NOVA_*` secrets
|
||||||
|
mirroring the `ACDL_*` values (idempotent + retry-on-failure), then
|
||||||
|
(after P5) delete the old `ACDL_*` secrets. For P2, just create the
|
||||||
|
`NOVA_*` aliases; deletion is P5. **G-108 binding:** when `NOVA_*`
|
||||||
|
secrets are created, the CI workflow `secrets:` references
|
||||||
|
(`.gitea/workflows/deploy.yml:105,107,108,148`,
|
||||||
|
`.gitea/workflows/modules-lifecycle.yml:63,64,103,104,111,112,117,118,123,124,161,162,169,170`,
|
||||||
|
`.github/workflows/*` mirrored) MUST be updated from `secrets.ACDL_*`
|
||||||
|
→ `secrets.NOVA_*` in the SAME phase, with graceful degrade + the
|
||||||
|
`acdl-deploy-` role name in deploy.yml:105 → `nova-deploy-` (P4
|
||||||
|
renames the IAM role). Until both secrets + refs are updated, CI
|
||||||
|
breaks — this is a hard gate, not a silent skip.
|
||||||
|
4. **Checkov rule rename (D-109 warn mode, REQ-158).** Rename
|
||||||
|
`adapters/terraform/policy/custom_rules/acdl_tagging.py` →
|
||||||
|
`nova_tagging.py`. Update the Checkov registration in
|
||||||
|
`schemas/tagging-standard.json` (line 5 + the `description`) and the
|
||||||
|
adapter config (`adapters/terraform/policy/checkov_adapter.py`).
|
||||||
|
The rule enforces `nova:*` tag keys BUT in **warn mode** for P2
|
||||||
|
(existing resources still carry `acdl:*` until P3) — log a warning,
|
||||||
|
don't fail the check. Update `ACDL_TAG_NAMING` → `NOVA_TAG_NAMING`.
|
||||||
|
5. **Consumer path rename (REQ-160).** Rename the consumer on-disk
|
||||||
|
contract path `.acdl/contract.yml` → `.nova/contract.yml` across:
|
||||||
|
`core/contract_resolver.py` (any default path), the deploy workflow
|
||||||
|
`default:` field (`.gitea/workflows/deploy.yml` +
|
||||||
|
`.github/workflows/deploy.yml` line 54), `schemas/contract.schema.json`
|
||||||
|
description, `tests/test_pipeline_contract.py:313` assertion, and
|
||||||
|
consumer docs (`docs/consumer-guide.md`, `docs/modules/index.md`).
|
||||||
|
Also `.acdl/static-assets.*.yml` → `.nova/...` + `.acdl/contract.yaml`
|
||||||
|
→ `.nova/contract.yaml`.
|
||||||
|
6. **Test fixture update (binding).** Update all test fixtures in
|
||||||
|
`tests/**` that reference `ACDL`/`acdl` (env var names, paths, table
|
||||||
|
names, tag keys) to the new `NOVA`/`nova` values — EXCEPT fixtures
|
||||||
|
that assert the dual-read fallback behavior (those keep `ACDL_*` as
|
||||||
|
the fallback source). `pytest` must pass.
|
||||||
|
7. **Regress gate.** `bash scripts/run_regression.sh` — 16/16 Verified.
|
||||||
|
|
||||||
## Success Criteria (phase gate)
|
---
|
||||||
|
|
||||||
1. CAPABILITY_INVENTORY + PROJECT reflect "Verified live-aws via lifecycle
|
## Wave 3 — SSM Path + Tag Keys (P3)
|
||||||
pipeline; torn down to zero-cost."
|
|
||||||
2. `ci-doc-verifier` confirms no stale "deploy-unverified" claims.
|
### P3 — ssm-tagkeys (REQ-161, REQ-162)
|
||||||
3. Full offline pytest suite green.
|
**Persona:** data-engineer (lead) + backend-engineer (readers)
|
||||||
|
**Territory:** `core/output_publisher.py`, `core/contract_resolver.py`,
|
||||||
|
`scripts/migrate_ssm_paths.py` (NEW), `terraform/**` (tag keys),
|
||||||
|
`adapters/terraform/policy/custom_rules/nova_tagging.py` (hard mode),
|
||||||
|
ABAC session-policy terraform
|
||||||
|
**Tasks:**
|
||||||
|
1. **SSM path migration (REQ-161).** Update `core/output_publisher.py`:
|
||||||
|
the SSM parameter path prefix `/acdl/{env}/{contractId}/{output}` →
|
||||||
|
`/nova/{env}/{contractId}/{output}`. Update `core/contract_resolver.py`
|
||||||
|
SSM reads. Update consumer docs. Create
|
||||||
|
`scripts/migrate_ssm_paths.py` that: (a) lists `/acdl/...`
|
||||||
|
parameters, (b) copies each to `/nova/...` (same value/type), (c)
|
||||||
|
verifies the copy, (d) deletes the old `/acdl/...` parameters. The
|
||||||
|
script is idempotent + dry-run by default (`--apply` to execute).
|
||||||
|
2. **Tag keys: parallel-tag (REQ-162).** Update terraform tagging
|
||||||
|
(`terraform/platform/main.tf`, `terraform/microservice/main.tf`,
|
||||||
|
`terraform/ci-vpc/main.tf`, `modules/l1/*/terraform/main.tf`,
|
||||||
|
`modules/l2/*/composition.json` tag defaults) to emit **both**
|
||||||
|
`nova:*` and `acdl:*` tag keys during P3 (parallel-tag period). The
|
||||||
|
`acdl:cost-center` default `acdl-default` → `nova-default` for the
|
||||||
|
`nova:cost-center` key (keep `acdl-default` on the `acdl:cost-center`
|
||||||
|
key during the parallel period).
|
||||||
|
3. **Tag keys: ABAC policy swap (REQ-162).** Update the ABAC session
|
||||||
|
policies (the deploy role's inline policy in
|
||||||
|
`terraform/platform/main.tf` + `terraform/bootstrap/**`) to match
|
||||||
|
`nova:*` tags (the `StringEquals`/`Resource` tag conditions reference
|
||||||
|
`nova:owner`/`nova:environment`/etc.). Keep the `acdl:*` match as a
|
||||||
|
secondary condition during the parallel period so neither old nor
|
||||||
|
new consumers break.
|
||||||
|
4. **Checkov rule: hard mode (D-109, REQ-162).** Update
|
||||||
|
`nova_tagging.py` from warn → hard mode: enforce `nova:*` tag keys
|
||||||
|
(hard fail on missing `nova:*` or presence of `acdl:*`-only tags).
|
||||||
|
Update `schemas/tagging-standard.json` tag keys → `nova:*`.
|
||||||
|
5. **Tag keys: remove old (REQ-162).** Once the parallel-tag period is
|
||||||
|
verified (terraform validate passes; the ABAC policy matches
|
||||||
|
`nova:*`), remove the `acdl:*` tag emissions from terraform. (Live
|
||||||
|
removal of `acdl:*` tags from existing AWS resources is a
|
||||||
|
documentation/runbook step — the terraform `null_resource` or a
|
||||||
|
script `scripts/untag_acdl_keys.py` can do it with live AWS access;
|
||||||
|
without live access, this is documented in the P4 runbook as a
|
||||||
|
runtime step.)
|
||||||
|
6. **Test fixture + regress gate.** Update test fixtures asserting
|
||||||
|
`acdl:*` tag keys → `nova:*`. `pytest` passes;
|
||||||
|
`bash scripts/run_regression.sh` — 16/16 Verified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wave 4 — AWS Resource Name Migration (P4)
|
||||||
|
|
||||||
|
### P4 — aws-resource-migration (REQ-163)
|
||||||
|
**Persona:** data-engineer (lead) + lead-developer (runbook)
|
||||||
|
**Territory:** `terraform/platform/main.tf`,
|
||||||
|
`terraform/microservice/main.tf`, `terraform/ci-vpc/main.tf`,
|
||||||
|
`terraform/bootstrap/**`, `modules/l1/alb/instance.json`,
|
||||||
|
`scripts/migrate_dynamodb_data.py` (NEW),
|
||||||
|
`docs/NOVA_AWS_MIGRATION.md` (NEW runbook),
|
||||||
|
`core/lambda/contract_ingestor.py` (default table names, D-111)
|
||||||
|
**Tasks:**
|
||||||
|
1. **Runbook (REQ-163).** Create `docs/NOVA_AWS_MIGRATION.md` — the
|
||||||
|
maintenance-window + rollback runbook. Documents each resource rename,
|
||||||
|
the migration command, the verification step, and the rollback
|
||||||
|
procedure. Orders the migration: KMS alias (cheap) → SNS/SG (recreate)
|
||||||
|
→ Lambda (recreate) → DynamoDB (scan+copy) → ECR (re-push) → IAM
|
||||||
|
(re-bootstrap) → state bucket (`-migrate-state`) → ALB (recreate,
|
||||||
|
brief downtime, last).
|
||||||
|
2. **Terraform resource names (REQ-163).** Rename all `acdl-*` resource
|
||||||
|
names/labels → `nova-*` in `terraform/platform/main.tf`,
|
||||||
|
`terraform/microservice/main.tf`, `terraform/ci-vpc/main.tf`,
|
||||||
|
`terraform/bootstrap/**`, `modules/l1/alb/instance.json`:
|
||||||
|
- DynamoDB: `acdl-contracts` → `nova-contracts`,
|
||||||
|
`acdl-change-requests` → `nova-change-requests`
|
||||||
|
- Secrets Manager: `acdl/github-token` → `nova/github-token`
|
||||||
|
- Lambda: `acdl-contract-ingestor` (role/policy/function) →
|
||||||
|
`nova-contract-ingestor`
|
||||||
|
- SNS: `acdl-sod-halt` → `nova-sod-halt`
|
||||||
|
- SG: `acdl-ecs-sg` → `nova-ecs-sg`
|
||||||
|
- KMS: `alias/acdl-platform` → `alias/nova-platform`
|
||||||
|
- ECS: `acdl-microservice` (cluster/service/task/role) →
|
||||||
|
`nova-microservice`
|
||||||
|
- ECR: `acdl-microservice` → `nova-microservice`
|
||||||
|
- IAM: `acdl-spike-runner` (+policy) → `nova-spike-runner`
|
||||||
|
- S3 state bucket: `acdl-tfstate-581513795199-us-east-1` →
|
||||||
|
`nova-tfstate-581513795199-us-east-1`
|
||||||
|
- ALB: `acdl-alb` → `nova-alb`
|
||||||
|
3. **Lambda default table names (D-111, REQ-163).** Update
|
||||||
|
`core/lambda/contract_ingestor.py` default env-var values:
|
||||||
|
`CONTRACTS_TABLE` default `acdl-contracts` → `nova-contracts`,
|
||||||
|
`CHANGE_REQUESTS_TABLE` `acdl-change-requests` →
|
||||||
|
`nova-change-requests`, `GITHUB_TOKEN_SECRET_ID` `acdl/github-token`
|
||||||
|
→ `nova/github-token`, `PLATFORM_REPO` `acdl/acdl` → `nova/acdl`
|
||||||
|
(prose consistency; real repo unchanged).
|
||||||
|
4. **State bucket migration (REQ-63).** Update the terraform backend
|
||||||
|
config (`terraform/{platform,microservice,ci-vpc}/terraform.tf` +
|
||||||
|
`bootstrap/create_state_backend.py` + `bootstrap/.bootstrap_state.json`)
|
||||||
|
to the new `nova-tfstate-...` bucket. Document the
|
||||||
|
`terraform init -migrate-state` command in the runbook (back up the
|
||||||
|
state JSON first).
|
||||||
|
5. **DynamoDB data-migration script (REQ-163).** Create
|
||||||
|
`scripts/migrate_dynamodb_data.py` — scan+copy all items from
|
||||||
|
`acdl-contracts` → `nova-contracts` + `acdl-change-requests` →
|
||||||
|
`nova-change-requests`. Verify row counts match. Keep old tables
|
||||||
|
until verified (deletion is a manual post-verification step,
|
||||||
|
documented in the runbook).
|
||||||
|
6. **terraform validate + regress gate.** `terraform validate` passes
|
||||||
|
for platform/microservice/ci-vpc. `grep -rn "acdl-" terraform/`
|
||||||
|
returns 0 hits. `pytest` passes; `bash scripts/run_regression.sh` —
|
||||||
|
16/16 Verified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wave 5 — Final Review + Ship (P5)
|
||||||
|
|
||||||
|
### P5 — final-review-ship (REQ-164)
|
||||||
|
**Persona:** lead-developer (lead) + all active (review)
|
||||||
|
**Territory:** `.ciagent/**`, `core/env.py` (remove fallback),
|
||||||
|
`nova_tagging.py` (hard-fail `acdl:*`), review + audit
|
||||||
|
**Tasks:**
|
||||||
|
1. **Remove dual-read fallback (REQ-164).** Update `core/env.py`
|
||||||
|
`get_env()` to read `NOVA_*` only (remove the `ACDL_*` fallback).
|
||||||
|
Update shell scripts to `${NOVA_X}` only (remove `:-$ACDL_X`).
|
||||||
|
Update `nova_tagging.py` to hard-fail on any `acdl:*` tag key (no
|
||||||
|
warn). Delete the `ACDL_*` secrets from Gitea (the `NOVA_*` aliases
|
||||||
|
created in P2 are now the only source). Remove the legacy comment
|
||||||
|
from `.env.secrets`.
|
||||||
|
2. **Multi-persona review.** Run `ciagent-review` across all v1.15
|
||||||
|
phases (P1–P4 changes). Auto-apply P0 fixes; flag P1+ for post-hoc.
|
||||||
|
If P1+ found, fix in this phase.
|
||||||
|
3. **Audit.** Run `ciagent-audit` — reconstruction test (git log matches
|
||||||
|
`.ciagent/` files), file discipline, branch hygiene, commit
|
||||||
|
discipline. If critical issues, fix in this phase.
|
||||||
|
4. **Finalize consumer migration guide (REQ-164).** Update
|
||||||
|
`docs/NOVA_MIGRATION.md` to mark the migration complete (cutoff
|
||||||
|
passed; `ACDL_*` fallback removed).
|
||||||
|
5. **Complete milestone.** Update `REQUIREMENTS.md` (REQ-155..164 →
|
||||||
|
complete), `ROADMAP.md` (v1.15 complete), `PROJECT.md`. Tag
|
||||||
|
`v1.14.5` (IS the milestone release). Merge `milestone/v1.15-nova`
|
||||||
|
→ `main`. Create Gitea release with full milestone summary.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria (milestone gate)
|
||||||
|
|
||||||
|
1. All 10 REQ-155..REQ-164 marked complete in REQUIREMENTS.md.
|
||||||
|
2. Review: 0 new P0; all P1+ flagged or auto-fixed.
|
||||||
|
3. Audit: clean; reconstruction test passes.
|
||||||
|
4. Regression gate (D-091) 16/16 Verified throughout + at milestone
|
||||||
|
complete.
|
||||||
|
5. `grep -rni "ACDL\|Agentic Cloud Delivery" README.md docs/ .ciagent/*.md`
|
||||||
|
returns 0 hits (except explicit "formerly ACDL" historical notes).
|
||||||
|
6. `grep -rn "ACDL_" core/ scripts/ adapters/ tests/ .gitea/ .github/`
|
||||||
|
returns 0 hits (except the removed-fallback test in P5 that asserts
|
||||||
|
the fallback is gone).
|
||||||
|
7. `grep -rn "acdl-" terraform/` returns 0 hits.
|
||||||
|
8. `pytest` passes; `run_ci.sh` exits 0; `terraform validate` passes
|
||||||
|
for platform/microservice/ci-vpc.
|
||||||
|
9. Tag `v1.15.4` created (IS the milestone release, G-104); milestone
|
||||||
|
merged to main.
|
||||||
+151
-1
@@ -1,5 +1,14 @@
|
|||||||
# ACDL — Agentic Cloud Delivery Platform
|
# ACDL — Agentic Cloud Delivery Platform
|
||||||
|
|
||||||
|
> **Rebrand in progress (milestone v1.15 — Nova).** The project is
|
||||||
|
> rebranding from **ACDL** / "Agentic Cloud Delivery Platform" →
|
||||||
|
> **Nova** / "The New Dawn of DevSecOps — security as a seamless enabler
|
||||||
|
> of fast deployments." The new tagline is added alongside the existing
|
||||||
|
> "North Star" / "consumers declare intent" framing. See
|
||||||
|
> `.ciagent/REQUIREMENTS.md` §v1.15 and `.ciagent/ROADMAP.md` §v1.15.
|
||||||
|
> The full prose/code/infra rebrand lands in execution phases P1–P4;
|
||||||
|
> this header is updated in P1.
|
||||||
|
|
||||||
## Vision / Core Value
|
## Vision / Core Value
|
||||||
|
|
||||||
Consumers declare intent; the platform delivers safe production
|
Consumers declare intent; the platform delivers safe production
|
||||||
@@ -838,4 +847,145 @@ sign-off (autonomy = full; all within locked constraints).
|
|||||||
workflow if missing.
|
workflow if missing.
|
||||||
- **`actions/configure-aws-credentials` action on act_runner** — if
|
- **`actions/configure-aws-credentials` action on act_runner** — if
|
||||||
unavailable, fall back to `aws sts assume-role-with-web-identity` from a
|
unavailable, fall back to `aws sts assume-role-with-web-identity` from a
|
||||||
step.
|
step.
|
||||||
|
|
||||||
|
## Objective for Milestone v1.14 (active — NFR Refinement)
|
||||||
|
|
||||||
|
Bug fixes, security posture improvements, stub/missing-functionality
|
||||||
|
identification + implementation, and documentation + NFR refinement across
|
||||||
|
the entire codebase. **No new features.** This is an NFR milestone — the
|
||||||
|
final phase's patch IS the deliverable (no separate milestone tag).
|
||||||
|
|
||||||
|
The v1.13 line shipped the presentation polish + config.json schema
|
||||||
|
migration + badge cleanup. The v1.11/v1.12 multi-persona reviews left a
|
||||||
|
backlog of P1/P2 findings (5 P1 + 4 P2 open in `REVIEW.md`), the codebase
|
||||||
|
has 6+ swallowed-error sites and 15+ hardcoded account-ID references, 7
|
||||||
|
scripts have no test coverage, the regression gate's CAP-017..022 evidence
|
||||||
|
is an offline proxy, ARCHITECTURE.md has no v1.11–v1.13 addendum, and
|
||||||
|
consumer-facing docs reference stale `@v1.6`–`@v1.9` workflow tags. v1.14
|
||||||
|
clears all of it in a 20-phase sweep.
|
||||||
|
|
||||||
|
**Scope axes (user-directed, 2026-07-29):**
|
||||||
|
1. **Bug fixes** — clear all open P1/P2 findings from the v1.11 review
|
||||||
|
(adapter dedup silent drop, static-assets unwired inputs, lifecycle
|
||||||
|
script vestigial args, regression-gate offline-proxy evidence, ALB
|
||||||
|
name_prefix, missing unit tests).
|
||||||
|
2. **Security posture** — narrow 6 swallowed-`except` sites; externalize
|
||||||
|
the hardcoded account ID; scope 6 `Resource: "*"` IAM statements to
|
||||||
|
`acdl-*` ARNs; harden contract-ingestor identity validation; add
|
||||||
|
`additionalProperties: false` + format validation to schemas; add
|
||||||
|
credential-pattern catch-all to `.gitignore`.
|
||||||
|
3. **Stub / missing functionality** — resolve the discarded
|
||||||
|
`--kube-version` flag in the Kyverno adapter; clean up orphan bytecode
|
||||||
|
+ dead config.
|
||||||
|
4. **Documentation + NFR refinement** — ARCHITECTURE.md v1.11–v1.14
|
||||||
|
addenda; bump stale `@v1.6–1.9` → `@v1.13` across 12+ sites; sync
|
||||||
|
decks/COST.md/GRILL G-005+G-008/IAM_POLICY.md; reconcile
|
||||||
|
modules/STANDARDS.md; record the D-083 audit-ledger deferral
|
||||||
|
explicitly.
|
||||||
|
5. **Test coverage** — add unit tests for 7 untested scripts + the
|
||||||
|
adapter dedup/remote-state-key behaviors.
|
||||||
|
|
||||||
|
**Out of scope (v1.14):**
|
||||||
|
- New features (feat phases). v1.14 is NFR-only.
|
||||||
|
- D-083 audit ledger build-out (S3 Object Lock + JWS + SQS DLQ + async
|
||||||
|
worker) — remains deferred; documented explicitly in ARCHITECTURE.md.
|
||||||
|
- Real OIDC federation (blocked on go-gitea/gitea#36988).
|
||||||
|
- Per-phase regression hardening (G-007, unchanged).
|
||||||
|
- Boto3 post-deploy verification probes (deferred to a future QA
|
||||||
|
milestone).
|
||||||
|
|
||||||
|
**Milestone type:** NFR (all phases are fix/test/docs/chore/refactor).
|
||||||
|
**Ship tag:** final phase patch on the v1.13.x line IS the release.
|
||||||
|
|
||||||
|
## Milestone v1.14 Phases
|
||||||
|
|
||||||
|
| Phase | Name | Goal |
|
||||||
|
|-------|------|------|
|
||||||
|
| 0 | pre-execution | SPECIFY → CLARIFY → RESEARCH → IDEATE → PLAN → GRILL. Establish v1.14 milestone shell; ideate finds the concrete requirements; plan decomposes into 20 execution phases. |
|
||||||
|
| 1–20 | execution | 20 phases of bug fixes, security hardening, stub resolution, test coverage, docs sync (wave-ordered). See ROADMAP.md §v1.14 for the phase list. |
|
||||||
|
| 21 | final-review-ship | Multi-persona review + audit + milestone ship (merge to main, tag final patch = release). |
|
||||||
|
|
||||||
|
## Key Decisions (v1.14)
|
||||||
|
|
||||||
|
Resolved at the CLARIFY stage (full autonomy — all within locked
|
||||||
|
constraints or user-directed scope). New v1.14 decisions (numbered
|
||||||
|
D-095+ to continue from v1.10's D-094):
|
||||||
|
|
||||||
|
| ID | Decision | Rationale | Outcome |
|
||||||
|
|----|----------|-----------|---------|
|
||||||
|
| D-095 | v1.14 is an NFR milestone (no feat phases); final patch IS the release. | User directed: "No new features, only bug fixes, security posture improvements, identifying stub and implement missing/lacking functionality, refine all documentation + NFRs." NFR model per branch-strategy.md:181 — progressive patches, final patch = deliverable, no separate milestone tag. | 20 execution phases (P1–P20) + 1 final (P21). Tags v1.13.3 → v1.13.24. |
|
||||||
|
| D-096 | D-083 (audit ledger JWS + S3 Object Lock + SQS DLQ + async worker) remains deferred; documented explicitly in ARCHITECTURE.md (P19), not implemented. | User chose "Skip — keep D-083 deferred." Requires non-offline-testable AWS infra (Object Lock bucket, KMS signing key, SQS). The hash-chain + DynamoDB outbox remains the v1.14 audit record. | P14 (originally JWS) replaced with orphan-artifact-and-dead-config-cleanup. D-083 deferral recorded in P19. |
|
||||||
|
| D-097 | 20 execution phases is the target (not consolidated to ~10). | User chose "20 phases as planned." Finer ship granularity; longer milestone. G-007 (per-phase regression) accepted — regression gate runs at milestone COMPLETE. | 20 phases + 1 final = 21-phase milestone. |
|
||||||
|
| D-098 | Wave ordering: W1 (P1–P6 bug fixes), W2 (P7–P12 security), W3 (P13–P17 stub/test/CI/hygiene), W4 (P18–P20 standards/docs/VPC). | Prerequisite chains: P2 depends on P1 (composition needs correct dedup); P9 depends on P8 (IAM ARNs reference externalized account ID); P15 depends on P7 (script tests benefit from hardened errors); P17 depends on P14 (both touch config.json); P19 lands last (reflects all prior phases). | 4 sequential waves; phases within a wave are independent (parallelizable when parallelization.enabled=true). |
|
||||||
|
| D-099 | `--ideate` flag: run the IDEATE stage between RESEARCH and PLAN (per ideate.md:218). The ideation tiers mine the 50 `partial:` + 16 `lessons:` + 3 `escalation:` + 16 `decisions:` git-native signals to validate/enrich the 20-phase scope. | User invoked with `--ideate`. The v1.14 scope is already user-directed (20 phases defined), so IDEATE acts as validation + enrichment, not scope discovery. Accepted ideas become IDEATE-NN IDs appended to REQUIREMENTS.md. | IDEATE stage runs; interactive validation gate (accept/skip/modify). |
|
||||||
|
| D-100 | Accept all 20 ideation findings as the v1.14 requirement set (REQ-135..REQ-154). | User accepted all 20 at the interactive validation gate. Mechanical + backend-enriched tiers confirmed the user-directed scope. | 20 REQs locked; PLAN.md formalizes the task decomposition. |
|
||||||
|
| D-101 | E-001 (P8 state-bucket continuity residual risk) auto-resolved at full autonomy: accept the residual risk. G-102's binding mitigation (fallback bound to live account ID + workflow env wiring) is the control. The lifecycle pipeline defaults to plan-only (REQ-134) — full-mode runs are workflow_dispatch only, reducing the accident surface. | Grill escalation E-001 (confidence 0.55) re-exposes the v1.11 4-VPC root cause. At full autonomy, auto-decide with assumption logging. The residual risk (misconfigured env at live-run time) is runtime-dependent, not plan-resolvable. If the user prefers zero residual risk, direct that P8 exclude the state-bucket name from externalization entirely. | E-001 resolved; G-102 binding decision enforced in PLAN.md P8. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Milestone v1.15 — Nova (Rebrand)
|
||||||
|
|
||||||
|
**Active milestone.** A full rebrand from ACDL → Nova across docs,
|
||||||
|
decks, code, configs, CI, env var prefixes, the consumer contract path,
|
||||||
|
SSM parameter paths, AWS tag keys, and AWS resource names — with a
|
||||||
|
staged infrastructure migration to avoid breakage.
|
||||||
|
|
||||||
|
**Milestone type:** Major (breaking — consumer-facing path, env var
|
||||||
|
prefixes, SSM path, AWS tag keys, and AWS resource names all change).
|
||||||
|
Tags run on the v1.15.x minor line: `v1.15.0` (P0) → `v1.15.4` (P5
|
||||||
|
final = milestone release). (G-104 binding: Major milestones tag on
|
||||||
|
their own minor line, not the previous minor's patch line.)
|
||||||
|
|
||||||
|
**In scope (v1.15):**
|
||||||
|
- Prose/decks/mermaid/pyproject/release-title rebrand (P1).
|
||||||
|
- Code identifiers, env var prefixes (`ACDL_*`→`NOVA_*` dual-read),
|
||||||
|
consumer path (`.acdl/`→`.nova/`) (P2).
|
||||||
|
- SSM path (`/acdl/`→`/nova/`) + AWS tag keys (`acdl:*`→`nova:*` ABAC)
|
||||||
|
(P3).
|
||||||
|
- AWS resource names (`acdl-*`→`nova-*`) with migration (P4).
|
||||||
|
- Final review + audit + remove dual-read fallback + milestone ship (P5).
|
||||||
|
|
||||||
|
**Out of scope (v1.15):**
|
||||||
|
- Renaming the real Gitea org/repo or GitHub org `acdl` (config stays
|
||||||
|
`acdl`; doc URLs updated to `nova` for prose only).
|
||||||
|
- Renaming the S&P Global Energy visual theme (`sp-theme.json`) —
|
||||||
|
client branding.
|
||||||
|
- Past Gitea release titles — only future releases use `Nova vX.Y.Z`.
|
||||||
|
- Git branch/tag naming — no brand name present.
|
||||||
|
|
||||||
|
**Milestone type:** Major (breaking). **Ship tag:** final phase patch
|
||||||
|
on the v1.15.x minor line IS the release (`v1.15.4`).
|
||||||
|
|
||||||
|
## Milestone v1.15 Phases
|
||||||
|
|
||||||
|
| Phase | Name | Goal |
|
||||||
|
|-------|------|------|
|
||||||
|
| 0 | pre-execution | SPECIFY → CLARIFY → RESEARCH → IDEATE → PLAN → GRILL. Establish v1.15-Nova milestone shell; ideation finds the 10 Nova requirements (REQ-155..164); plan decomposes into 4 execution phases. |
|
||||||
|
| 1 | docs-decks-prose | Rebrand all prose/decks/mermaid/pyproject/release-titles ACDL→Nova; add Nova tagline; ship consumer migration guide. |
|
||||||
|
| 2 | code-envvars-consumer-path | Rename acdl_tagging.py→nova_tagging.py; ACDL_*→NOVA_* dual-read; .acdl/→.nova/ contract path. |
|
||||||
|
| 3 | ssm-tagkeys | SSM /acdl/→/nova/ + AWS tag keys acdl:*→nova:* with parallel-tag ABAC migration. |
|
||||||
|
| 4 | aws-resource-migration | Rename all acdl-* AWS resources → nova-* with staged migration + runbook. |
|
||||||
|
| 5 | final-review-ship | Multi-persona review + audit + remove dual-read fallback + milestone ship (merge to main, tag final patch = release). |
|
||||||
|
|
||||||
|
## Key Decisions (v1.15)
|
||||||
|
|
||||||
|
Resolved at the CLARIFY stage (full autonomy — all within locked
|
||||||
|
constraints or user-directed scope). New v1.15 decisions (numbered
|
||||||
|
D-102+ to continue from v1.14's D-101). The high-judgment scope
|
||||||
|
decisions (D-102..D-107) were locked in by the user during the planning
|
||||||
|
conversation before execution; D-108..D-112 resolved at CLARIFY.
|
||||||
|
|
||||||
|
| ID | Decision | Rationale | Outcome |
|
||||||
|
|----|----------|-----------|---------|
|
||||||
|
| D-102 | AWS resource names: full rename with migration. | User chose "Full rename with migration." All `acdl-*` AWS resources → `nova-*` including state bucket migration, DynamoDB data migration, IAM re-bootstrap, ECR re-push. Accepts downtime + multi-phase migration. | P4 implements the staged migration + rollback runbook. |
|
||||||
|
| D-103 | Env var prefixes: full rename to `NOVA_*`. | User chose "Full rename to `NOVA_*`." All 21 `ACDL_*` prefixes → `NOVA_*` including `.env.secrets` (key names only, values stay) + Gitea secrets. | P2 renames + implements dual-read fallback; P5 removes fallback. |
|
||||||
|
| D-104 | Tag keys + SSM path + consumer path: full rename all three. | User chose "Full rename all three." AWS tag keys `acdl:*`→`nova:*` (ABAC re-scope), SSM path `/acdl/`→`/nova/` (param migration), consumer path `.acdl/`→`.nova/`. | P2 (consumer path) + P3 (SSM + tag keys) implement. |
|
||||||
|
| D-105 | External URLs: illustrative — update them. | User chose "URLs are illustrative — update them." Doc URLs (`github.com/acdl/...`, `git.cloudinit.dev/.../acdl*`) → `nova` for prose consistency. Real Gitea repo name (`release.gitea.repo`) stays `acdl`. | P1 updates doc URLs; config.json unchanged. |
|
||||||
|
| D-106 | Nova tagline: add alongside existing North Star. | User chose "Add Nova tagline alongside existing North Star." Tagline "The New Dawn of DevSecOps — security as a seamless enabler of fast deployments" added to README header, deck title slides, `docs/vision.md`. Existing "consumers declare intent" framing retained. | P1 adds tagline; no prose removed. |
|
||||||
|
| D-107 | S&P visual theme: leave untouched. | User chose "Leave S&P theme untouched." `sp-theme.json` (#D6002A red, Akkurat Pro) is client branding, not the Nova product brand. Only product-brand text (ACDL→Nova) changes in decks. | P1 edits deck text only; theme/CSS unchanged. |
|
||||||
|
| D-108 | Dual-read fallback centralized in a new `core/env.py` helper. | No centralized env loader exists today (env vars read via scattered `os.environ.get("ACDL_*")`). A new `core/env.py` `get_env(name)` helper reads `NOVA_X` then falls back to `ACDL_X`, returning `None` if neither. All call sites migrate to the helper in P2; P5 removes the fallback. | P2 creates `core/env.py` + migrates call sites; P5 removes fallback. |
|
||||||
|
| D-109 | Checkov custom rule `nova_tagging.py` warns during P2, hard-fails from P3. | During P2 (before tag-key migration), existing resources still carry `acdl:*` tags — a hard fail would break the regression gate. P2 rule warns on `acdl:*`; P3 (after parallel-tag + ABAC swap) hard-fails on `acdl:*` and enforces `nova:*`. | P2: warn mode; P3: hard mode. |
|
||||||
|
| D-110 | Schema `$id` URLs (`https://acdl.cloudinit.dev/schemas/...`) → `https://nova.cloudinit.dev/schemas/...`. | These are illustrative schema identifiers (no real DNS resolution required for JSON-schema validation). Renamed for brand consistency in P1. Existing `$id` values in test fixtures updated. | P1 renames schema `$id` + fixture references. |
|
||||||
|
| D-111 | Lambda env-var defaults (`CONTRACTS_TABLE` default `"acdl-contracts"`, etc.) → `nova-contracts`. | `core/lambda/contract_ingestor.py` has hardcoded `acdl-*` default table names. These become `nova-*` in P4 (resource migration). P2 changes the env-var name (`ACDL_*`→`NOVA_*`); P4 changes the default values to `nova-*`. | P4 updates Lambda defaults. |
|
||||||
|
| D-112 | `nova` slug: no `project:` prefix on branches (single-project mode). | `config.json` has `projects[]` with one entry (slug `acdl`) but `git.branching_strategy` is `flat` and the established convention since v1.0 is flat branches (no `<slug>/` prefix). Nova rebrand does NOT change the branch prefix convention. Commit `---ci---` blocks use `project: acdl` (the config slug, unchanged). | Branches stay `milestone/v1.15-nova`, `phase/NN-*`; no `acdl/` or `nova/` prefix. |
|
||||||
@@ -610,3 +610,233 @@ two probe fixes required to make the deck claims true.
|
|||||||
backwards-sequencing failure mode (PRE_MORTEM.md FM-3).
|
backwards-sequencing failure mode (PRE_MORTEM.md FM-3).
|
||||||
- New capability claims beyond what v1.11 verified.
|
- New capability claims beyond what v1.11 verified.
|
||||||
- Per-phase regression hardening (G-007, unchanged).
|
- Per-phase regression hardening (G-007, unchanged).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Milestone v1.14 — NFR Refinement (REQ-135..REQ-154)
|
||||||
|
|
||||||
|
**Objective:** Bug fixes, security posture improvements, stub/missing-
|
||||||
|
functionality identification + implementation, and documentation + NFR
|
||||||
|
refinement across the entire codebase. **No new features.** NFR milestone
|
||||||
|
— the final phase's patch IS the deliverable.
|
||||||
|
|
||||||
|
The v1.11 multi-persona review left 5 P1 + 4 P2 findings open; the
|
||||||
|
codebase has 6+ swallowed-error sites, 15+ hardcoded account-ID
|
||||||
|
references, 7 untested scripts, an offline-proxy regression gate,
|
||||||
|
ARCHITECTURE.md with no v1.11–v1.13 addendum, and consumer-facing docs
|
||||||
|
referencing stale `@v1.6`–`@v1.9` workflow tags. v1.14 clears all of it
|
||||||
|
in a 20-phase sweep.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- **REQ-135** — The adapter dedup loop raises `ValueError` for
|
||||||
|
unregistered-module resources instead of silently dropping them (P1-1).
|
||||||
|
(Phase P1)
|
||||||
|
- **REQ-136** — The static-assets L2 composition wires `default_ttl`/
|
||||||
|
`max_ttl`/`price_class`/`viewer_protocol_policy` and makes WAF
|
||||||
|
conditional via `waf_enabled`, so `complex.yml` is a real modify (P1-2).
|
||||||
|
(Phase P2)
|
||||||
|
- **REQ-137** — The L2 lifecycle scripts' usage strings no longer
|
||||||
|
advertise the vestigial `[ci-vpc-outputs.json]` arg, or document the
|
||||||
|
remote-state design (P1-3). (Phase P3)
|
||||||
|
- **REQ-138** — The regression gate's CAP-017..022 checks run
|
||||||
|
`terraform validate` (not just file-existence + resolver); the
|
||||||
|
offline-proxy caveat is documented honestly (P1-5). (Phase P4)
|
||||||
|
- **REQ-139** — Unit tests for adapter dedup merge behavior +
|
||||||
|
`ACDL_REMOTE_STATE_KEY` override exist and pass (P2-2). (Phase P5)
|
||||||
|
- **REQ-140** — The ALB target group `name_prefix` derives from `var.name`
|
||||||
|
(P2-1). (Phase P6)
|
||||||
|
- **REQ-141** — 6 over-broad `except ...: pass` sites narrowed to specific
|
||||||
|
exceptions; errors logged with context. (Phase P7)
|
||||||
|
- **REQ-142** — The hardcoded account ID `581513795199` is externalized to
|
||||||
|
`ACDL_AWS_ACCOUNT_ID` env / `data.aws_caller_identity` across 15+ sites.
|
||||||
|
(Phase P8)
|
||||||
|
- **REQ-143** — 6 `Resource: "*"` IAM statements scoped to `acdl-*` ARNs;
|
||||||
|
regression test asserts the scoping. (Phase P9)
|
||||||
|
- **REQ-144** — The contract ingestor validates `contractId`/`environment`/
|
||||||
|
`error`; ABAC reliance documented; spoofing-resistance test passes.
|
||||||
|
(Phase P10)
|
||||||
|
- **REQ-145** — `contract.schema.json` + `environment.schema.json` reject
|
||||||
|
undocumented fields (`additionalProperties: false`); format validation
|
||||||
|
for bucket/ARN/CIDR. (Phase P11)
|
||||||
|
- **REQ-146** — `.gitignore` has a credential-pattern catch-all;
|
||||||
|
`test_no_secrets_tracked.py` passes. (Phase P12)
|
||||||
|
- **REQ-147** — The Kyverno `--kube-version` flag is either implemented or
|
||||||
|
removed with a documented deferral rationale. (Phase P13)
|
||||||
|
- **REQ-148** — Orphan bytecode + dead config cleaned (orphan `.pyc`,
|
||||||
|
stale coverage source, stale version, dead JS allowlist). (Phase P14)
|
||||||
|
- **REQ-149** — 7 untested scripts have unit test coverage (≥1 test each).
|
||||||
|
(Phase P15)
|
||||||
|
- **REQ-150** — Gitea workflow parity resolved; `rotate_spike_key.sh` +
|
||||||
|
`sync_to_gl.sh` have `set -euo pipefail`. (Phase P16)
|
||||||
|
- **REQ-151** — `config.json` persona block + branching strategy +
|
||||||
|
ollama-cloud backend aligned with PERSONAS.md + actual runtime.
|
||||||
|
(Phase P17)
|
||||||
|
- **REQ-152** — `modules/STANDARDS.md` internally consistent; no stale
|
||||||
|
`TYPE_MAP` reference. (Phase P18)
|
||||||
|
- **REQ-153** — ARCHITECTURE.md has v1.11–v1.14 addenda; stale `@v1.6–1.9`
|
||||||
|
→ `@v1.13`; GRILL G-005/G-008 resolved; COST.md window covers v1.11–v1.14;
|
||||||
|
D-083 deferral recorded. (Phase P19)
|
||||||
|
- **REQ-154** — Platform VPC CIDR is a variable; subnet count is
|
||||||
|
data-driven; `0.0.0.0/0` ingress documented. (Phase P20)
|
||||||
|
|
||||||
|
### v1.14 Traceability
|
||||||
|
|
||||||
|
| Requirement | Phase | Status |
|
||||||
|
|-------------|-------|--------|
|
||||||
|
| REQ-135 | P1 | complete |
|
||||||
|
| REQ-136 | P2 | complete |
|
||||||
|
| REQ-137 | P3 | complete |
|
||||||
|
| REQ-138 | P4 | complete |
|
||||||
|
| REQ-139 | P5 | complete |
|
||||||
|
| REQ-140 | P6 | complete |
|
||||||
|
| REQ-141 | P7 | complete |
|
||||||
|
| REQ-142 | P8 | complete |
|
||||||
|
| REQ-143 | P9 | complete |
|
||||||
|
| REQ-144 | P10 | complete |
|
||||||
|
| REQ-145 | P11 | complete |
|
||||||
|
| REQ-146 | P12 | complete |
|
||||||
|
| REQ-147 | P13 | complete |
|
||||||
|
| REQ-148 | P14 | complete |
|
||||||
|
| REQ-149 | P15 | complete |
|
||||||
|
| REQ-150 | P16 | complete |
|
||||||
|
| REQ-151 | P17 | complete |
|
||||||
|
| REQ-152 | P18 | complete |
|
||||||
|
| REQ-153 | P19 | complete |
|
||||||
|
| REQ-154 | P20 | complete |
|
||||||
|
|
||||||
|
### Out of Scope (v1.14)
|
||||||
|
- New features (feat phases). v1.14 is NFR-only.
|
||||||
|
- D-083 audit ledger build-out (S3 Object Lock + JWS + SQS DLQ + async
|
||||||
|
worker) — remains deferred; documented explicitly in ARCHITECTURE.md.
|
||||||
|
- Real OIDC federation (blocked on go-gitea/gitea#36988).
|
||||||
|
- Per-phase regression hardening (G-007, unchanged).
|
||||||
|
- Boto3 post-deploy verification probes (deferred to a future QA
|
||||||
|
milestone).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.15 — Nova (Rebrand)
|
||||||
|
|
||||||
|
**Milestone type:** Major (breaking — consumer-facing path, env var
|
||||||
|
prefixes, SSM path, AWS tag keys, and AWS resource names all change).
|
||||||
|
Per the branch-strategy precedent (breaking/feature milestones tag on
|
||||||
|
their OWN minor line), v1.15 tags run on the **v1.15.x minor line**:
|
||||||
|
`v1.15.0` (P0) → `v1.15.1..v1.15.4` (P1–P4) → `v1.15.4` (P5 final =
|
||||||
|
milestone release). (G-104 binding: the v1.14.x patch line is the NFR
|
||||||
|
convention; a Major milestone ships on its own minor.)
|
||||||
|
|
||||||
|
A full rebrand from **ACDL** / "Agentic Cloud Delivery Platform" →
|
||||||
|
**Nova** / "The New Dawn of DevSecOps — security as a seamless enabler
|
||||||
|
of fast deployments." The new tagline is added alongside the existing
|
||||||
|
"North Star" / "consumers declare intent" framing; the S&P Global Energy
|
||||||
|
visual theme (`sp-theme.json`) is a client brand and is **not** touched.
|
||||||
|
The rebrand applies across docs, decks, code, configs, CI, env vars,
|
||||||
|
consumer conventions, SSM paths, AWS tag keys, and AWS resource names —
|
||||||
|
with a staged infrastructure migration to avoid breakage.
|
||||||
|
|
||||||
|
Ideation source: `--ideate` flag (user-directed scope; the survey found
|
||||||
|
1,465 occurrences of `ACDL`/`acdl` across 205 files and zero existing
|
||||||
|
`nova` references — no collision risk). Accepted ideas become
|
||||||
|
IDEATE-01..IDEATE-10, mapped to REQ-155..REQ-164.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- **REQ-155** — (IDEATE-01) All prose, titles, headers, and comments
|
||||||
|
across `README.md`, `docs/**`, `.ciagent/*.md`, deck markdown sources,
|
||||||
|
`pyproject.toml` name/description, and `release.yml` release-title
|
||||||
|
prefix are rebranded `ACDL`/`Agentic Cloud Delivery Platform` → `Nova`.
|
||||||
|
Illustrative URLs in docs (`github.com/acdl/...`,
|
||||||
|
`git.cloudinit.dev/continuous-intelligence/acdl*`) are updated to
|
||||||
|
`nova` for prose consistency. Gitea release titles going forward read
|
||||||
|
`Nova vX.Y.Z` (past releases keep their names). (Phase P1)
|
||||||
|
- **REQ-156** — (IDEATE-02) All Marp deck markdown sources
|
||||||
|
(`docs/presentations/*-marp.md`, `*.md`, `*-talking-points.md`) and
|
||||||
|
mermaid source `.mmd` files are rebranded `ACDL` → `Nova`; the deck
|
||||||
|
title-slide subtitle becomes `Nova — The New Dawn of DevSecOps`. The
|
||||||
|
`.mmd` sources are edited and the rendered PNG diagrams are
|
||||||
|
re-exported so the committed PNGs match the new labels. The S&P visual
|
||||||
|
theme (`sp-theme.json`) is unchanged. HTML decks are re-rendered.
|
||||||
|
(Phase P1)
|
||||||
|
- **REQ-157** — (IDEATE-03) The Nova tagline ("The New Dawn of DevSecOps
|
||||||
|
— security as a seamless enabler of fast deployments") is added to the
|
||||||
|
README header, both deck title slides, and `docs/vision.md` —
|
||||||
|
alongside (not replacing) the existing "North Star" / "consumers
|
||||||
|
declare intent" framing. (Phase P1)
|
||||||
|
- **REQ-158** — (IDEATE-04) `adapters/terraform/policy/custom_rules/acdl_tagging.py`
|
||||||
|
is renamed `nova_tagging.py` with its Checkov custom-rule registration
|
||||||
|
updated (`schemas/tagging-standard.json` line 5 + adapter config). The
|
||||||
|
Checkov rule enforces `nova:*` tag keys. (Phase P2)
|
||||||
|
- **REQ-159** — (IDEATE-05) All 21 `ACDL_*` env var prefixes are renamed
|
||||||
|
to `NOVA_*` across `scripts/`, `core/`, `adapters/`, `tests/`,
|
||||||
|
workflows (`.gitea/`, `.github/`), `.env`, `.env.secrets` (key names
|
||||||
|
only — values/secret material stay), and consumer docs. A **dual-read
|
||||||
|
fallback** (`NOVA_X` preferred, fall back to `ACDL_X`) is implemented
|
||||||
|
in the config/env loader so deployments do not break during the
|
||||||
|
transition window; the fallback is removed in the final phase once all
|
||||||
|
consumers are migrated. Gitea repo secrets are rotated via API.
|
||||||
|
(Phase P2)
|
||||||
|
- **REQ-160** — (IDEATE-06) The consumer on-disk contract path
|
||||||
|
`.acdl/contract.yml` (and `.acdl/static-assets.*.yml`,
|
||||||
|
`.acdl/contract.yaml`) becomes `.nova/contract.yml` across the
|
||||||
|
contract resolver, deploy workflow checkout path, consumer docs, and
|
||||||
|
the contract schema description. A consumer migration guide is shipped
|
||||||
|
with P1 docs. (Phase P2)
|
||||||
|
- **REQ-161** — (IDEATE-07) The SSM parameter path prefix
|
||||||
|
`/acdl/{env}/{contractId}/{output}` becomes
|
||||||
|
`/nova/{env}/{contractId}/{output}` across `core/output_publisher`,
|
||||||
|
the contract resolver, and consumer docs. A migration script copies
|
||||||
|
existing `/acdl/...` parameters → `/nova/...`, readers are updated,
|
||||||
|
then old parameters are deleted. (Phase P3)
|
||||||
|
- **REQ-162** — (IDEATE-08) AWS tag keys `acdl:owner`,
|
||||||
|
`acdl:environment`, `acdl:contract`, `acdl:cost-center`, `acdl:ref`
|
||||||
|
become `nova:owner`, `nova:environment`, `nova:contract`,
|
||||||
|
`nova:cost-center`, `nova:ref` across terraform tagging, the Checkov
|
||||||
|
custom rule (`nova_tagging.py`), and ABAC session policies. A
|
||||||
|
**parallel-tag period** adds `nova:*` tags to all resources first,
|
||||||
|
updates the ABAC session policies to match `nova:*`, then removes the
|
||||||
|
`acdl:*` tags once consumers are verified. (Phase P3)
|
||||||
|
- **REQ-163** — (IDEATE-09) All `acdl-*` AWS resource names are renamed
|
||||||
|
to `nova-*` via terraform: KMS alias `alias/acdl-platform` →
|
||||||
|
`alias/nova-platform`, SNS `acdl-sod-halt` → `nova-sod-halt`, SG
|
||||||
|
`acdl-ecs-sg` → `nova-ecs-sg`, Lambda `acdl-contract-ingestor` →
|
||||||
|
`nova-contract-ingestor`, DynamoDB `acdl-contracts`/`acdl-change-requests`
|
||||||
|
→ `nova-contracts`/`nova-change-requests` (scan+copy data migration,
|
||||||
|
verify row counts, keep old tables until verified), ECR
|
||||||
|
`acdl-microservice` → `nova-microservice` (re-push images), IAM
|
||||||
|
user/policy `acdl-spike-runner` → `nova-spike-runner` (re-bootstrap
|
||||||
|
with new key), state bucket `acdl-tfstate-...` → `nova-tfstate-...`
|
||||||
|
(`terraform init -migrate-state` to new backend, state JSON backed up
|
||||||
|
first), ALB name prefix `acdl-alb` → `nova-alb` (recreate, brief
|
||||||
|
downtime). A maintenance window + rollback runbook is published with
|
||||||
|
the migration. (Phase P4)
|
||||||
|
- **REQ-164** — (IDEATE-10) The dual-read env var fallback
|
||||||
|
(`ACDL_*`→`NOVA_*`) and any `ACDL_*`-only references are removed once
|
||||||
|
all consumers are migrated; the consumer migration guide is finalized;
|
||||||
|
`nova_tagging.py` no longer accepts `acdl:*` tag keys. (Phase P5)
|
||||||
|
|
||||||
|
### v1.15 Traceability
|
||||||
|
|
||||||
|
| Requirement | Phase | Status |
|
||||||
|
|-------------|-------|--------|
|
||||||
|
| REQ-155 | P1 | pending |
|
||||||
|
| REQ-156 | P1 | pending |
|
||||||
|
| REQ-157 | P1 | pending |
|
||||||
|
| REQ-158 | P2 | pending |
|
||||||
|
| REQ-159 | P2 | pending |
|
||||||
|
| REQ-160 | P2 | pending |
|
||||||
|
| REQ-161 | P3 | pending |
|
||||||
|
| REQ-162 | P3 | pending |
|
||||||
|
| REQ-163 | P4 | pending |
|
||||||
|
| REQ-164 | P5 | pending |
|
||||||
|
|
||||||
|
### Out of Scope (v1.15)
|
||||||
|
- Renaming the real Gitea org/repo (`continuous-intelligence/acdl`) or
|
||||||
|
GitHub org `acdl` — config.json `release.gitea.repo` stays `acdl`;
|
||||||
|
URLs in docs are illustrative and updated to `nova` for prose only.
|
||||||
|
- Renaming the S&P Global Energy visual theme (`sp-theme.json`,
|
||||||
|
deck CSS) — that is client branding, not the Nova product brand.
|
||||||
|
- Past Gitea release titles — existing releases keep their `ACDL vX.Y.Z`
|
||||||
|
names; only future releases use `Nova vX.Y.Z`.
|
||||||
|
- Git branch/tag naming — branches use `milestone/v*` / `phase/*` and
|
||||||
|
tags use `v*` semver; no brand name present, no change needed.
|
||||||
|
|||||||
+330
-1
@@ -692,4 +692,333 @@ A6 section to both talking-points files.
|
|||||||
decision, 2026-07-29).
|
decision, 2026-07-29).
|
||||||
- **D-109** — Decks use `@v1.11` in examples during Phase 68 (current
|
- **D-109** — Decks use `@v1.11` in examples during Phase 68 (current
|
||||||
state), bumped to `@v1.12` at Phase 70 complete after the tag exists.
|
state), bumped to `@v1.12` at Phase 70 complete after the tag exists.
|
||||||
Avoids a dangling reference to a tag that doesn't exist yet.
|
Avoids a dangling reference to a tag that doesn't exist yet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.14 Research Addendum — NFR Refinement scope audit (2026-07-29)
|
||||||
|
|
||||||
|
> Phase 0 RESEARCH for milestone v1.14 (NFR Refinement). A full codebase
|
||||||
|
> survey (8 categories, file:line evidence) was conducted to populate the
|
||||||
|
> 20-phase scope. This addendum records the findings; the phase list is
|
||||||
|
> in ROADMAP.md §v1.14; the requirements are in REQUIREMENTS.md §v1.14.
|
||||||
|
|
||||||
|
### Survey method
|
||||||
|
|
||||||
|
Read-only survey of `/root/acdl` at v1.13.2 (HEAD `139224ff`, 533 tests
|
||||||
|
collected). 8 categories: stubs, P1/P2 backlog, security, docs drift,
|
||||||
|
test gaps, terraform gaps, workflow gaps, config hygiene. All file:line
|
||||||
|
references verified against the live codebase.
|
||||||
|
|
||||||
|
### Finding 1 — Open P1/P2 backlog (REVIEW.md v1.11)
|
||||||
|
|
||||||
|
5 P1 + 4 P2 findings from the v1.11 multi-persona review remain open:
|
||||||
|
|
||||||
|
| ID | File:Line | Status | v1.14 phase |
|
||||||
|
|----|-----------|--------|-------------|
|
||||||
|
| P1-1 | `adapter.py:159-170` (silent drop of unregistered-module resources) | open | P1 |
|
||||||
|
| P1-2 | `static-assets/composition.json` (unwired cloudfront inputs; WAF unconditional) | open | P2 |
|
||||||
|
| P1-3 | `run_l2_lifecycle_*.sh` (vestigial `[ci-vpc-outputs.json]` arg) | open | P3 |
|
||||||
|
| P1-4 | `CAPABILITY_INVENTORY.md:9-16` (summary table stale) | **fixed** (now 22/22) | — |
|
||||||
|
| P1-5 | `regression_verify.py:432-519` (CAP-017..022 offline proxy, no `terraform validate`) | open | P4 |
|
||||||
|
| P2-1 | `alb/main.tf:9` (`name_prefix="tg-ci-"` discards `var.name`) | open | P6 |
|
||||||
|
| P2-2 | `test_adapter.py` (no dedup-merge or remote-state-key test) | open | P5 |
|
||||||
|
| P2-3 | `waf/complex.yml` + `locals.tf` (redundant `upper()` + uppercase example) | open (post-hoc) | folded into P2 |
|
||||||
|
| P2-4 | `COST.md:106` (account ID published; accepted exposure) | open (post-hoc) | folded into P8 (centralize code-side) |
|
||||||
|
|
||||||
|
### Finding 2 — Security posture gaps
|
||||||
|
|
||||||
|
**Swallowed errors (6 sites):**
|
||||||
|
- `core/local_emulators.py:374` — `except Exception: pass` in
|
||||||
|
`_fake_urlopen`; if patching fails, urlopen stays real → network
|
||||||
|
egress. [SEC] → P7.
|
||||||
|
- `core/lambda/contract_ingestor.py:157` — GitHub search failure →
|
||||||
|
`existing = []` → duplicate issues. → P7.
|
||||||
|
- `terraform/bootstrap/create_state_backend.py:51` — over-broad
|
||||||
|
`except Exception:` on `head_bucket` → spurious `create_bucket` on
|
||||||
|
permissions/network errors. → P7.
|
||||||
|
- `core/output_publisher.py:100,168` — SSM/GitHub failure → silent
|
||||||
|
`None`/`False`. → P7.
|
||||||
|
- `terraform/bootstrap/apply_iam_baseline.py:78` — over-broad on
|
||||||
|
old-version delete. → P7.
|
||||||
|
|
||||||
|
**Hardcoded account ID `581513795199` (15+ sites):**
|
||||||
|
`adapter.py:125,140`, `apply_iam_baseline.py:33`,
|
||||||
|
`create_state_backend.py:33,35`, `push_consumer_image.py:32`, terraform
|
||||||
|
state-bucket names, ECR image ref. → P8 (externalize to
|
||||||
|
`ACDL_AWS_ACCOUNT_ID` / `data.aws_caller_identity`).
|
||||||
|
|
||||||
|
**IAM policy wildcards (6 `Resource: "*"` statements):**
|
||||||
|
`spike_runner_policy.json` — cloudfront (line 117), wafv2 (129), kms
|
||||||
|
(218), iam (236). KMS allows key creation/deletion on ANY key; IAM
|
||||||
|
allows role creation on ANY role. → P9 (scope to `acdl-*` ARNs).
|
||||||
|
|
||||||
|
**Contract-ingestor identity validation gap:**
|
||||||
|
`contract_ingestor.py:221-245` — `_validate_caller_identity` validates
|
||||||
|
`consumerRepo` format only; doesn't verify caller owns the repo (ABAC
|
||||||
|
reliance). No `contractId`/`environment`/`error` validation. → P10.
|
||||||
|
|
||||||
|
**Schema validation gaps:**
|
||||||
|
`contract.schema.json` + `environment.schema.json` — no
|
||||||
|
`additionalProperties: false` (undocumented fields pass silently); no
|
||||||
|
format validation for bucket/ARN/CIDR. → P11.
|
||||||
|
|
||||||
|
**Credential hygiene:**
|
||||||
|
`.gitignore` covers `.env*`/`*.tfstate*` but no credential-pattern
|
||||||
|
catch-all (`*.pem`/`*.key`/`*.p12`). → P12.
|
||||||
|
|
||||||
|
**Audit ledger integrity (D-083):**
|
||||||
|
`audit_ledger_design.md:47-70` — JWS + Object Lock + DLQ deferred. Per
|
||||||
|
D-096, stays deferred; documented in P19. The hash-chain + DynamoDB
|
||||||
|
outbox is the v1.14 audit record.
|
||||||
|
|
||||||
|
### Finding 3 — Stubs / missing functionality
|
||||||
|
|
||||||
|
- `adapters/kyverno/kyverno_adapter.py:11,115-116` — `--kube-version`
|
||||||
|
parsed then discarded (`_ = kube_version`). → P13 (implement or
|
||||||
|
remove + document).
|
||||||
|
- `scripts/__pycache__/verify_deploy_microservice.cpython-312.pyc` —
|
||||||
|
orphan bytecode for a deleted source file. → P14.
|
||||||
|
- `core/regression_verify.py:237` — DynamoDB write deferred to Phase 54
|
||||||
|
(outbox hash-chain verified, no real DynamoDB write). Accepted
|
||||||
|
deferral.
|
||||||
|
- `adapters/wiz/wiz_adapter.py` — real GraphQL client (not a stub);
|
||||||
|
degrades gracefully. OK.
|
||||||
|
- `core/separation_of_duties.py` — `route_halt_artifact` is real (SNS +
|
||||||
|
outbox fallback). OK.
|
||||||
|
- `core/lambda/contract_ingestor.py` — `report_error` is real (GitHub
|
||||||
|
issues via Secrets Manager). OK.
|
||||||
|
|
||||||
|
### Finding 4 — Documentation drift
|
||||||
|
|
||||||
|
- `ARCHITECTURE.md` — no v1.11/v1.12/v1.13/v1.14 addendum; line 500-506
|
||||||
|
still describes the **old** parameterized adapter (pre-stateless
|
||||||
|
rewrite). → P19.
|
||||||
|
- Stale `@v1.6`–`@v1.9` workflow refs in `README.md:225`,
|
||||||
|
`docs/consumer-guide.md` (12 sites), `docs/architecture.md:233`,
|
||||||
|
`docs/pipeline/versioning.md:29`, `docs/pipeline/index.md:42`. → P19.
|
||||||
|
- `modules/STANDARDS.md` §8 references `TYPE_MAP` (deleted in v1.11);
|
||||||
|
§9.4 requires 5-file split but §489-492 allows inlining —
|
||||||
|
inconsistent. → P18.
|
||||||
|
- `COST.md` window stops at v1.10; no v1.11–v1.13 spend. → P19.
|
||||||
|
- `GRILL.md` G-005/G-008 escalations — CAP-017..022 now Verified via
|
||||||
|
lifecycle pipeline; COST.md now exists. → P19 (mark resolved).
|
||||||
|
- `IAM_POLICY.md` — reflects v1.11 re-bootstrap but not v1.12/v1.13.
|
||||||
|
→ P19.
|
||||||
|
- Decks reference "v1.12" verification status; not re-synced for
|
||||||
|
v1.13.2. → P19.
|
||||||
|
|
||||||
|
### Finding 5 — Test coverage gaps
|
||||||
|
|
||||||
|
- 533 tests collected; 5 `@pytest.mark.slow` (deselected from fast
|
||||||
|
suite). 7 scripts with no test: `seed_uptime_monitors.py`,
|
||||||
|
`push_consumer_image.py`, `sync_to_gl.sh`, `post_stage_comment.sh`,
|
||||||
|
`rotate_spike_key.sh`, `create_state_backend.py`,
|
||||||
|
`create_iam_user.py`. → P15.
|
||||||
|
- Adapter dedup-merge + `ACDL_REMOTE_STATE_KEY` override — no unit
|
||||||
|
test (P2-2). → P5.
|
||||||
|
|
||||||
|
### Finding 6 — Terraform gaps
|
||||||
|
|
||||||
|
- 3 L1 modules lack `locals.tf` (`ecr`, `ecs-cluster`, `rds`). → P18.
|
||||||
|
- `static-assets/composition.json` unwired inputs (P1-2). → P2.
|
||||||
|
- `terraform/platform/main.tf:255` — hardcoded CIDR; `count=2` subnets
|
||||||
|
not data-driven. → P20.
|
||||||
|
- `terraform/bootstrap/create_state_backend.py:51` — over-broad
|
||||||
|
except (Finding 2). → P7.
|
||||||
|
|
||||||
|
### Finding 7 — Workflow / pipeline gaps
|
||||||
|
|
||||||
|
- 4 GitHub-only workflows (patterns-plan, platform-test,
|
||||||
|
primitives-plan, release) — no Gitea mirror. → P16.
|
||||||
|
- `rotate_spike_key.sh` (only `set -u`), `sync_to_gl.sh` (no `set`
|
||||||
|
flags). → P16.
|
||||||
|
- 3 shared workflows (ci, deploy, modules-lifecycle) byte-identical
|
||||||
|
(verified). OK.
|
||||||
|
- modules-lifecycle matrix covers all 12 L1 + 2 L2. OK.
|
||||||
|
|
||||||
|
### Finding 8 — Config / project hygiene
|
||||||
|
|
||||||
|
- `config.json` bash_allowlist has dead JS entries (npm/node/jest/eslint
|
||||||
|
/tsc — no package.json). → P14/P17.
|
||||||
|
- `config.json` `branching_strategy: "phase"` mismatched with
|
||||||
|
flat-workflow practice. → P17.
|
||||||
|
- `config.json` `ollama-cloud.base_url: ""` (empty; no `glm` model
|
||||||
|
configured). → P17.
|
||||||
|
- `config.json` `frontend-engineer` persona still in `personas[]`
|
||||||
|
(PERSONAS.md:80 says inactive). → P17.
|
||||||
|
- `pyproject.toml` version `1.3.0` (stale); coverage source
|
||||||
|
`acdl_platform` (renamed to `core` in v1.6). → P14.
|
||||||
|
|
||||||
|
### Persona assessment (v1.14)
|
||||||
|
|
||||||
|
The v1.14 milestone is NFR-only (bug fixes, security, tests, docs). The
|
||||||
|
active persona roster from v1.11 (PERSONAS.md) carries forward
|
||||||
|
unchanged:
|
||||||
|
|
||||||
|
- **lead-developer** (active) — coordination; owns the wave ordering +
|
||||||
|
cross-phase dependencies.
|
||||||
|
- **backend-engineer** (active) — owns `adapters/`, `core/` (adapter
|
||||||
|
dedup, contract ingestor, regression gate, output publisher).
|
||||||
|
- **data-engineer** (active) — owns `terraform/`, `modules/` (ALB fix,
|
||||||
|
static-assets wiring, platform VPC, IAM policy, STANDARDS).
|
||||||
|
- **frontend-engineer** (inactive) — no frontend; decks are markdown
|
||||||
|
(lead-developer territory). Stays deactivated per PERSONAS.md:80.
|
||||||
|
|
||||||
|
No custom personas needed for v1.14 (no new domains). Territory
|
||||||
|
enforcement = `warn` (config.json:167). The v1.14 work is concentrated
|
||||||
|
in `adapters/`, `core/`, `terraform/`, `scripts/`, `tests/`, `docs/`,
|
||||||
|
`.ciagent/` — all within existing persona territories.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.15 Research Addendum — Nova Rebrand scope audit (2026-07-30)
|
||||||
|
|
||||||
|
### Survey method
|
||||||
|
|
||||||
|
A thorough, exhaustive codebase survey (via the explore subagent) plus
|
||||||
|
targeted `grep -rni` counts. The survey covered 346 tracked files,
|
||||||
|
reporting occurrence counts and the mechanical-vs-judgment split per
|
||||||
|
category. The full survey is recorded in the planning conversation
|
||||||
|
transcript; the binding conclusions are summarized here.
|
||||||
|
|
||||||
|
### Finding 1 — Brand string surface area
|
||||||
|
|
||||||
|
- **1,465** total `ACDL`/`acdl` occurrences across **205** files.
|
||||||
|
- **17** occurrences of the full "Agentic Cloud Delivery Platform"
|
||||||
|
phrase (all prominent titles/headers: README, docs/index, vision,
|
||||||
|
pyproject, decks, .ciagent/*.md).
|
||||||
|
- **0** existing references to "nova" (case-insensitive) — **no
|
||||||
|
collision risk**.
|
||||||
|
- User-facing (docs/, README, decks, contracts, schemas, module
|
||||||
|
READMEs): high-priority for rebrand.
|
||||||
|
- Internal (.ciagent/*.md, tests/, terraform/, scripts/, workflows):
|
||||||
|
mechanical but voluminous.
|
||||||
|
|
||||||
|
### Finding 2 — Code identifiers (judgment category)
|
||||||
|
|
||||||
|
- **Python package name**: `pyproject.toml` `name = "acdl"` (no `acdl/`
|
||||||
|
package dir exists — source lives in `core/`, `adapters/`; the name is
|
||||||
|
a metadata label). Mechanical rename.
|
||||||
|
- **Python file**: `adapters/terraform/policy/custom_rules/acdl_tagging.py`
|
||||||
|
(+ Checkov registration in `schemas/tagging-standard.json` line 5 +
|
||||||
|
adapter config). Rename file + update registration.
|
||||||
|
- **Env var prefixes**: 21 distinct `ACDL_*` prefixes (`ACDL_LIFECYCLE_MODE`
|
||||||
|
66×, `ACDL_AWS_ACCESS_KEY_ID` 40×, `ACDL_AWS_SECRET_ACCESS_KEY` 37×,
|
||||||
|
`ACDL_REMOTE_STATE_KEY` 22×, `ACDL_AWS_ACCOUNT_ID` 21×, `ACDL_TAG_NAMING`
|
||||||
|
20×, `ACDL_KMS_KEY_ID` 16×, `ACDL_BOOTSTRAP_AWS_*` 15× each,
|
||||||
|
`ACDL_SOD_HALT_TOPIC_ARN` 14×, `ACDL_LOCAL_TIER` 13×, etc.). No
|
||||||
|
centralized env loader exists today (scattered `os.environ.get`).
|
||||||
|
D-108: a new `core/env.py` `get_env()` helper centralizes the
|
||||||
|
dual-read fallback.
|
||||||
|
- **Workflow `name:`**: `.github/workflows/release.yml` line 11
|
||||||
|
`name: acdl-release` — mechanical.
|
||||||
|
|
||||||
|
### Finding 3 — AWS resource names (high-risk migration)
|
||||||
|
|
||||||
|
Terraform creates real AWS resources with `acdl-` prefixes. Renaming
|
||||||
|
forces destroy+recreate (downtime, data loss for DynamoDB/state bucket).
|
||||||
|
D-102: full rename with migration (user-directed).
|
||||||
|
|
||||||
|
| Resource | Type | Migration |
|
||||||
|
|----------|------|-----------|
|
||||||
|
| `acdl-contracts` / `acdl-change-requests` | DynamoDB | scan+copy data, verify row counts |
|
||||||
|
| `acdl/github-token` | Secrets Manager | recreate secret, repoint Lambda |
|
||||||
|
| `acdl-contract-ingestor` (role/policy/Lambda) | IAM+Lambda | recreate role/Lambda, update trigger |
|
||||||
|
| `acdl-sod-halt` | SNS | recreate topic, repoint publisher |
|
||||||
|
| `acdl-ecs-sg` | SG | recreate (brief ECS disruption) |
|
||||||
|
| `alias/acdl-platform` | KMS alias | repoint alias (cheap) |
|
||||||
|
| `acdl-microservice` (cluster/ECR/service/task/role) | ECS+ECR | re-push images, recreate service |
|
||||||
|
| `acdl-spike-runner` (user/policy) | IAM | re-bootstrap with new key |
|
||||||
|
| `acdl-tfstate-581513795199-us-east-1` | S3 state bucket | `terraform init -migrate-state`, back up state JSON |
|
||||||
|
| `acdl-alb` (name prefix) | ALB | recreate (brief downtime) |
|
||||||
|
|
||||||
|
### Finding 4 — Consumer/infra conventions (judgment category, D-104)
|
||||||
|
|
||||||
|
- **AWS tag keys** `acdl:owner|environment|contract|cost-center|ref`
|
||||||
|
(5 keys, ~109 tag assignments) — matched by ABAC session policies.
|
||||||
|
Parallel-tag period (add `nova:*`, swap policy, remove `acdl:*`).
|
||||||
|
- **SSM path** `/acdl/{env}/{contractId}/{output}` (67 refs) — deploy
|
||||||
|
outputs stored here. Migration script copies params, readers updated,
|
||||||
|
old deleted.
|
||||||
|
- **Consumer path** `.acdl/contract.yml` (23 refs) — consumer repos
|
||||||
|
depend on this. Renamed `.nova/contract.yml` + migration guide.
|
||||||
|
|
||||||
|
### Finding 5 — Docs & decks (mechanical)
|
||||||
|
|
||||||
|
- README.md (16), docs/index.md, docs/vision.md, docs/architecture.md,
|
||||||
|
docs/consumer-guide.md (35), docs/modules/index.md (28), all
|
||||||
|
.ciagent/*.md, modules/STANDARDS.md, schemas/README.md,
|
||||||
|
pipelines/README.md, adapters/README.md, terraform/*/README.md.
|
||||||
|
- Deck markdown + mermaid `.mmd` sources (5 files) + rendered HTML.
|
||||||
|
PNGs re-exported from edited `.mmd` sources.
|
||||||
|
- S&P visual theme (`sp-theme.json`, deck CSS) is **client branding**
|
||||||
|
— D-107: untouched. Only product-brand text (ACDL→Nova) changes.
|
||||||
|
- Schema `$id` URLs (`https://acdl.cloudinit.dev/schemas/...`) →
|
||||||
|
`https://nova.cloudinit.dev/schemas/...` (D-110: illustrative, no
|
||||||
|
DNS resolution needed for validation).
|
||||||
|
|
||||||
|
### Finding 6 — CI / pipeline / release
|
||||||
|
|
||||||
|
- Workflow files mirrored in `.gitea/workflows/` + `.github/workflows/`
|
||||||
|
(modules-lifecycle 31×, deploy 22×, ci 2×, release 3×).
|
||||||
|
- `release.yml` release title `ACDL vX.Y.Z` → `Nova vX.Y.Z` (forward
|
||||||
|
only; past releases keep names).
|
||||||
|
- Git branches/tags use `milestone/v*` / `phase/*` / `v*` — **no brand
|
||||||
|
name present**, no change needed (D-112: flat-branch convention
|
||||||
|
preserved).
|
||||||
|
- config.json `release.gitea.repo` stays `acdl` (D-105: real repo name
|
||||||
|
unchanged; doc URLs illustrative only).
|
||||||
|
|
||||||
|
### Finding 7 — External / URLs
|
||||||
|
|
||||||
|
- `github.com/acdl/...` (~20 refs in docs + module READMEs +
|
||||||
|
reusable-workflow `uses:` refs) — D-105: illustrative, updated to
|
||||||
|
`nova` for prose. Real GitHub org/repo rename is out of scope.
|
||||||
|
- `git.cloudinit.dev/continuous-intelligence/acdl*` (incl. sister
|
||||||
|
repos `acdl-contracts`, `acdl-evidence`) — updated in prose to `nova*`.
|
||||||
|
- README has **no badges** (no shields.io, no img src).
|
||||||
|
|
||||||
|
### Finding 8 — Nomenclature / tagline
|
||||||
|
|
||||||
|
- "DevSecOps", "New Dawn", "enabler" appear **nowhere** in the repo
|
||||||
|
today — clean insertion, no collisions to reconcile (D-106).
|
||||||
|
- Current tagline ("North Star" / "consumers declare intent") is
|
||||||
|
retained; Nova tagline added alongside.
|
||||||
|
- "bottleneck" (6 occurrences in deck talking points) — compatible
|
||||||
|
with the Nova "no bottleneck" messaging; left in place.
|
||||||
|
|
||||||
|
### Persona assessment (v1.15)
|
||||||
|
|
||||||
|
No new personas needed for v1.15 — the rebrand touches existing
|
||||||
|
territories (docs, code, terraform, CI, tests). The active roster:
|
||||||
|
**lead-developer** (docs/decks/.ciagent meta + verification + migration
|
||||||
|
runbooks), **backend-engineer** (core/env.py dual-read helper, contract
|
||||||
|
resolver path, Lambda, output_publisher, regression_verify),
|
||||||
|
**data-engineer** (terraform resource names/tagging, state bucket
|
||||||
|
migration, DynamoDB data migration, ECR re-push, schemas/tagging-standard).
|
||||||
|
The **frontend-engineer** remains deactivated (no UI; decks are
|
||||||
|
markdown = lead-developer territory). A **security-engineer** persona is
|
||||||
|
not activated — the ABAC session-policy + tag-key migration (REQ-162) is
|
||||||
|
data-engineer territory (terraform IAM) with lead-developer review.
|
||||||
|
Territory enforcement = `warn` (co-authoring expected at the
|
||||||
|
core/env.py + terraform boundary, and the contract-resolver +
|
||||||
|
deploy-workflow boundary).
|
||||||
|
|
||||||
|
### Assumptions logged
|
||||||
|
|
||||||
|
- A1 (confidence 0.9): No live AWS access is available during P0–P4
|
||||||
|
execution (the `acdl-spike-runner` IAM user's creds are in
|
||||||
|
`.env.secrets` but live apply/modify/destroy is gated by
|
||||||
|
`NOVA_LIFECYCLE_MODE` defaulting to plan-only). The terraform changes
|
||||||
|
are validated via `terraform validate`; live apply is exercised by the
|
||||||
|
modules-lifecycle workflow when explicitly set to full. This matches
|
||||||
|
the v1.11–v1.14 established pattern.
|
||||||
|
- A2 (confidence 0.85): `.env.secrets` contains live rotated AWS
|
||||||
|
credentials keyed by `ACDL_AWS_*`. P2 renames the KEYS only (values
|
||||||
|
stay). The runtime reads via the new `core/env.py` dual-read helper
|
||||||
|
(`NOVA_AWS_ACCESS_KEY_ID` preferred, `ACDL_AWS_ACCESS_KEY_ID`
|
||||||
|
fallback), so no re-rotation is needed until P5 removes the fallback.
|
||||||
|
- A3 (confidence 0.8): The Gitea release API (`POST .../releases`) is
|
||||||
|
reachable for `v1.14.x` tags (the v1.14 milestone shipped releases
|
||||||
|
through `v1.13.24` / release id 285). P0 ship targets `v1.14.0`.
|
||||||
|
|||||||
@@ -1060,3 +1060,573 @@ NFR patch (docs-only). Two presentation changes across both leadership decks
|
|||||||
total) synced. Both HTML decks re-rendered via Marp.
|
total) synced. Both HTML decks re-rendered via Marp.
|
||||||
|
|
||||||
Docs-only NFR patch (no code changes).
|
Docs-only NFR patch (no code changes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.14 (complete — NFR Refinement: bug fixes, security, stubs, tests, docs, tag `v1.13.24`)
|
||||||
|
|
||||||
|
The v1.14 milestone is a 20-phase NFR sweep — no new features. It clears
|
||||||
|
the open P1/P2 backlog from the v1.11 review, hardens the security
|
||||||
|
posture (swallowed errors, hardcoded account ID, IAM wildcards, schema
|
||||||
|
validation, credential hygiene), resolves stub/missing functionality
|
||||||
|
(Kyverno `--kube-version`, orphan artifacts), adds test coverage for 7
|
||||||
|
untested scripts, and refines all documentation (ARCHITECTURE.md
|
||||||
|
v1.11–v1.14 addenda, stale `@v1.6–1.9` → `@v1.13` refs, COST.md/GRILL/
|
||||||
|
IAM_POLICY.md sync, STANDARDS.md reconciliation).
|
||||||
|
|
||||||
|
**Milestone type:** NFR (all phases fix/test/docs/chore/refactor). The
|
||||||
|
final phase's patch IS the release — no separate milestone tag. Tags run
|
||||||
|
on the v1.13.x line: `v1.13.3` (P0) → `v1.13.4..v1.13.23` (P1–P20) →
|
||||||
|
`v1.13.24` (P21 final = milestone release).
|
||||||
|
|
||||||
|
**Wave ordering:**
|
||||||
|
- Wave 1 (P1–P6): bug fixes — P1 before P2 (composition depends on dedup
|
||||||
|
correctness); P3–P6 independent.
|
||||||
|
- Wave 2 (P7–P12): security — P8 before P9 (externalized account ID for
|
||||||
|
IAM ARNs); rest independent.
|
||||||
|
- Wave 3 (P13–P17): stub/test/CI/hygiene — P15 benefits from P7 landing
|
||||||
|
first; P17 after P14 (both touch config.json).
|
||||||
|
- Wave 4 (P18–P20): standards/docs/VPC — P19 last (reflects all prior
|
||||||
|
phases).
|
||||||
|
|
||||||
|
### Phase P1 — adapter-dedup-diagnostic (Wave 1)
|
||||||
|
- **Description:** Fix P1-1 from the v1.11 review. The adapter dedup loop
|
||||||
|
(`adapters/terraform/adapter.py:159-170`) silently drops resources whose
|
||||||
|
module is not in the registry — a typo'd `module` field vanishes without
|
||||||
|
diagnostic. Raise `ValueError` (preserving the pre-dedup contract) so the
|
||||||
|
misconfiguration surfaces instead of being silently omitted.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-135
|
||||||
|
- **Success Criteria:**
|
||||||
|
- A resource with `module: nonexistent@1.0.0` raises `ValueError` with a
|
||||||
|
descriptive message, not a silent drop.
|
||||||
|
- Existing registered-module dedup behavior preserved (multi-resource L1s
|
||||||
|
still merge into one `module "x" { ... }` block).
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P2 — static-assets-wiring-fix (Wave 1)
|
||||||
|
- **Description:** Fix P1-2. `modules/l2/static-assets/composition.json`
|
||||||
|
drops `default_ttl`/`max_ttl`/`price_class`/`viewer_protocol_policy`
|
||||||
|
(accepted by `cloudfront/interface.json` but never wired) and WAF is
|
||||||
|
unconditionally present (no `features`/conditional). Wire the cloudfront
|
||||||
|
inputs; make WAF conditional via a `waf_enabled` feature flag so
|
||||||
|
`examples/complex.yml` is a real modify (adds CDN + WAF), not a no-op
|
||||||
|
re-apply.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P1]
|
||||||
|
- **Requirements:** REQ-136
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `complex.yml` resolves to a resource set that differs from `simple.yml`
|
||||||
|
(WAF + CDN TTLs present when `waf_enabled: true`, absent when false).
|
||||||
|
- The L2 static-assets lifecycle cell's "modify" step exercises a real
|
||||||
|
terraform diff, not idempotent re-apply.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P3 — lifecycle-script-arg-cleanup (Wave 1)
|
||||||
|
- **Description:** Fix P1-3. `scripts/run_l2_lifecycle_test.sh` and
|
||||||
|
`run_l2_lifecycle_destroy.sh` advertise `[ci-vpc-outputs.json]` ($3) in
|
||||||
|
their usage strings but never read it (the L2 path uses
|
||||||
|
`terraform_remote_state`, not the file). Remove the vestigial arg or
|
||||||
|
document that the L2 path uses remote state and the arg is
|
||||||
|
accepted-but-ignored for workflow-argument parity with the L1 scripts.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-137
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Usage strings no longer advertise a feature the scripts don't provide,
|
||||||
|
OR a comment explains the L2-uses-remote-state design + parity reason.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P4 — regression-gate-evidence-hardening (Wave 1)
|
||||||
|
- **Description:** Fix P1-5. `core/regression_verify.py:432-519`
|
||||||
|
CAP-017..022 checks are offline proxies (files exist + contracts
|
||||||
|
resolve) — a module with broken HCL would pass as long as files exist.
|
||||||
|
Add a `terraform validate` step to
|
||||||
|
`_check_lifecycle_module_terraform` so at least HCL syntax is verified
|
||||||
|
at the gate. Tighten the CAPABILITY_INVENTORY wording to "offline proxy;
|
||||||
|
live apply/modify/destroy verified by the modules-lifecycle workflow
|
||||||
|
run, not by this gate."
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-138
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `_check_lifecycle_module_terraform` runs `terraform validate` (or
|
||||||
|
documents why it's too slow + falls back to a syntax probe).
|
||||||
|
- CAPABILITY_INVENTORY + docstrings reflect the offline-proxy caveat
|
||||||
|
honestly.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P5 — adapter-behavior-tests (Wave 1)
|
||||||
|
- **Description:** Fix P2-2. Add `test_adapter_dedup_merges_same_module`
|
||||||
|
(two resources with the same `module` collapse to one
|
||||||
|
`module "<first_id>" { ... }` block with merged inputs) and
|
||||||
|
`test_adapter_remote_state_key_override` (`ACDL_REMOTE_STATE_KEY`
|
||||||
|
overrides the default `platform/terraform.tfstate` key in the emitted
|
||||||
|
`data terraform_remote_state` block).
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P1]
|
||||||
|
- **Requirements:** REQ-139
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Both unit tests exist in `tests/test_adapter.py` and pass.
|
||||||
|
- `pytest` count increases; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P6 — alb-name-prefix-fix (Wave 1)
|
||||||
|
- **Description:** Fix P2-1. `modules/l1/alb/terraform/main.tf:9` uses
|
||||||
|
`name_prefix = "tg-ci-"` (hardcoded literal) which discards `var.name`
|
||||||
|
entirely — the target group name is non-configurable and inconsistent
|
||||||
|
with the LB name. Change to `name_prefix = "${var.name}-"` so the
|
||||||
|
consumer's name prefixes the target group while preserving uniqueness.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-140
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Target group `name_prefix` derives from `var.name`.
|
||||||
|
- `terraform validate` passes for the alb module standalone.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P7 — swallowed-error-hardening (Wave 2)
|
||||||
|
- **Description:** Narrow 6 over-broad `except ...: pass`/`except
|
||||||
|
Exception:` sites: `core/local_emulators.py:374` (fake_urlopen swallow
|
||||||
|
→ network egress risk if patching fails), `core/lambda/contract_ingestor.py:157`
|
||||||
|
(GitHub search failure → duplicate issues),
|
||||||
|
`terraform/bootstrap/create_state_backend.py:51` (over-broad → spurious
|
||||||
|
create_bucket), `core/output_publisher.py:100,168`,
|
||||||
|
`terraform/bootstrap/apply_iam_baseline.py:78`. Catch specific
|
||||||
|
`ClientError`/`NoSuch*` exceptions; log + re-raise where silent failure
|
||||||
|
masks a real defect.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-141
|
||||||
|
- **Success Criteria:**
|
||||||
|
- No bare `except Exception: pass` remains in the targeted files (grep
|
||||||
|
clean for the 6 sites).
|
||||||
|
- Specific exception types caught; errors logged with context.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P8 — account-id-externalization (Wave 2)
|
||||||
|
- **Description:** Externalize the hardcoded account ID `581513795199`
|
||||||
|
from 15+ sites: `adapters/terraform/adapter.py:125,140`,
|
||||||
|
`terraform/bootstrap/apply_iam_baseline.py:33`,
|
||||||
|
`terraform/bootstrap/create_state_backend.py:33,35`,
|
||||||
|
`scripts/push_consumer_image.py:32`, terraform state-bucket names, ECR
|
||||||
|
image refs. Read from `ACDL_AWS_ACCOUNT_ID` env (code) /
|
||||||
|
`data.aws_caller_identity` (terraform); fall back to env for offline.
|
||||||
|
Keep the COST.md account ID (accepted exposure per P2-4) but centralize
|
||||||
|
the code-side.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-142
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `grep -rn "581513795199" adapters/ scripts/ terraform/ core/` returns
|
||||||
|
0 hits (excluding tests + docs).
|
||||||
|
- `ACDL_AWS_ACCOUNT_ID` env read with a clear default/fallback.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P9 — iam-policy-least-privilege (Wave 2)
|
||||||
|
- **Description:** Scope 6 `Resource: "*"` statements in
|
||||||
|
`terraform/bootstrap/spike_runner_policy.json` (cloudfront, wafv2, kms,
|
||||||
|
iam) to `acdl-*` ARNs. Scope `iam:CreateRole` etc. to
|
||||||
|
`arn:aws:iam::...:role/acdl-*`; scope KMS to
|
||||||
|
`arn:aws:kms:...:key/acdl-*`; narrow CloudFront/WAF where possible.
|
||||||
|
Add a regression test asserting no new `Resource:"*"` on non-global
|
||||||
|
actions.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P8]
|
||||||
|
- **Requirements:** REQ-143
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `Resource: "*"` remains only on actions that require it (sts, ce).
|
||||||
|
- IAM/KMS/CloudFront/WAF scoped to `acdl-*` ARNs.
|
||||||
|
- Regression test in `tests/test_iam_policy_baseline.py` asserts the
|
||||||
|
scoping.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P10 — contract-ingestor-identity-validation (Wave 2)
|
||||||
|
- **Description:** Harden `core/lambda/contract_ingestor.py:221-245`
|
||||||
|
`_validate_caller_identity` — currently best-effort (validates
|
||||||
|
`consumerRepo` format only, doesn't verify the caller owns the repo).
|
||||||
|
Add `contractId` format validation, `environment` enum validation,
|
||||||
|
`error` length cap. Document the ABAC reliance explicitly. Add a
|
||||||
|
spoofing-resistance test.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-144
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `contractId`, `environment`, `error` validated; malformed input
|
||||||
|
rejected with 400.
|
||||||
|
- ABAC reliance documented in the function docstring + ARCHITECTURE.md.
|
||||||
|
- Spoofing-resistance test in `tests/test_contract_ingestor.py` passes.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P11 — schema-input-validation-hardening (Wave 2)
|
||||||
|
- **Description:** Add `additionalProperties: false` to
|
||||||
|
`schemas/contract.schema.json` + `schemas/environment.schema.json`
|
||||||
|
(currently allows undocumented fields silently). Add `maxItems`/
|
||||||
|
`maxProperties` bounds. Validate `state_backend.bucket` S3 naming
|
||||||
|
rules, `runner_role_arn` ARN format, `vpc_cidr` CIDR format. Add tests
|
||||||
|
asserting rejection of malformed input.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-145
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Both schemas reject undocumented top-level fields.
|
||||||
|
- Format validation (bucket/ARN/CIDR) rejects malformed values.
|
||||||
|
- New tests in `tests/test_environment_schema.py` +
|
||||||
|
`tests/test_contract_schema.py` pass.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P12 — gitignore-credential-hygiene (Wave 2)
|
||||||
|
- **Description:** `.gitignore` covers `.env*`/`*.tfstate*` but lacks a
|
||||||
|
credential-pattern catch-all (`*.pem`/`*.key`/`*.p12`/`*.pfx`). Add
|
||||||
|
credential patterns. Add `tests/test_no_secrets_tracked.py` asserting no
|
||||||
|
credential-looking file is tracked by git.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-146
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `.gitignore` has credential-pattern catch-all.
|
||||||
|
- `test_no_secrets_tracked.py` passes (grep `git ls-files` for
|
||||||
|
credential patterns → 0 hits).
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P13 — kyverno-kube-version-resolution (Wave 3)
|
||||||
|
- **Description:** Resolve the discarded `--kube-version` flag in
|
||||||
|
`adapters/kyverno/kyverno_adapter.py:11,115-116` (`_ = kube_version`).
|
||||||
|
Either implement version-aware policy selection (select policies by k8s
|
||||||
|
version) or remove the flag and document why it's deferred to the
|
||||||
|
GitOps reconciler roadmap. Resolve the ambiguity either way.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-147
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `--kube-version` is either used (version-aware policy selection) or
|
||||||
|
removed with a documented deferral rationale.
|
||||||
|
- `tests/test_kyverno_adapter.py` updated to match.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P14 — orphan-artifact-and-dead-config-cleanup (Wave 3)
|
||||||
|
- **Description:** Clean up orphan artifacts + dead config: the orphan
|
||||||
|
`scripts/__pycache__/verify_deploy_microservice.cpython-312.pyc` (source
|
||||||
|
deleted in v1.11); stale `pyproject.toml` coverage source
|
||||||
|
`acdl_platform` → `core` (renamed in v1.6); `pyproject.toml` version
|
||||||
|
`1.3.0` → current; dead JS allowlist entries in `config.json`
|
||||||
|
(npm/node/jest/eslint/tsc — no package.json).
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-148
|
||||||
|
- **Success Criteria:**
|
||||||
|
- No orphan `.pyc` for a deleted source file.
|
||||||
|
- `pyproject.toml` coverage source = `core`; version = current.
|
||||||
|
- `config.json` bash_allowlist has no JS-only entries.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P15 — untested-scripts-coverage (Wave 3)
|
||||||
|
- **Description:** Add unit tests for 7 scripts with no test coverage:
|
||||||
|
`scripts/seed_uptime_monitors.py`, `scripts/push_consumer_image.py`,
|
||||||
|
`scripts/sync_to_gl.sh`, `scripts/post_stage_comment.sh`,
|
||||||
|
`scripts/rotate_spike_key.sh`, `terraform/bootstrap/create_state_backend.py`,
|
||||||
|
`terraform/bootstrap/create_iam_user.py`. Mock boto3/subprocess for
|
||||||
|
offline-testable coverage. Add `--check-only`/dry-run modes where
|
||||||
|
missing.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P7]
|
||||||
|
- **Requirements:** REQ-149
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Each of the 7 scripts has a corresponding test file with ≥1 passing
|
||||||
|
test.
|
||||||
|
- `pytest` count increases by ≥7; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P16 — workflow-parity-and-script-flags (Wave 3)
|
||||||
|
- **Description:** 4 GitHub-only workflows (patterns-plan, platform-test,
|
||||||
|
primitives-plan, release) have no Gitea mirror — either mirror them or
|
||||||
|
document the Gitea limitation. Fix `scripts/rotate_spike_key.sh` (only
|
||||||
|
`set -u`, no `-e`/`pipefail`) and `scripts/sync_to_gl.sh` (no `set`
|
||||||
|
flags at all) — add `set -euo pipefail`.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-150
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Gitea workflow parity resolved (mirrored or documented).
|
||||||
|
- `rotate_spike_key.sh` + `sync_to_gl.sh` have `set -euo pipefail`.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P17 — config-and-persona-hygiene (Wave 3)
|
||||||
|
- **Description:** Fix `config.json` hygiene: `branching_strategy: "phase"`
|
||||||
|
mismatch with flat-workflow practice; empty `ollama-cloud` base_url (no
|
||||||
|
`glm` model configured); `frontend-engineer` persona `active: false` in
|
||||||
|
config.json (PERSONAS.md:80 already says inactive). Align config.json
|
||||||
|
with PERSONAS.md + actual runtime.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P14]
|
||||||
|
- **Requirements:** REQ-151
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `config.json` persona block matches PERSONAS.md (frontend-engineer
|
||||||
|
inactive).
|
||||||
|
- `branching_strategy` reflects actual practice (or documented).
|
||||||
|
- `ollama-cloud` backend configured or documented as intentionally
|
||||||
|
unset.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P18 — module-standards-consistency (Wave 4)
|
||||||
|
- **Description:** 3 L1 modules (`ecr`, `ecs-cluster`, `rds`) lack
|
||||||
|
`locals.tf`; `modules/STANDARDS.md` §9.4 requires the full 5-file split
|
||||||
|
but §489-492 allows inlining — internally inconsistent. Either add
|
||||||
|
`locals.tf` to all 3 or reconcile STANDARDS §9.4 with the inline
|
||||||
|
allowance. Remove the stale `TYPE_MAP` reference in §8 (deleted in the
|
||||||
|
v1.11 stateless rewrite).
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-152
|
||||||
|
- **Success Criteria:**
|
||||||
|
- STANDARDS.md internally consistent (§8 + §9.4 agree).
|
||||||
|
- No stale `TYPE_MAP` reference.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P19 — documentation-sync-v1.14 (Wave 4)
|
||||||
|
- **Description:** ARCHITECTURE.md: add v1.11/v1.12/v1.13/v1.14 addenda
|
||||||
|
(stateless adapter, platform VPC, ACDL_LIFECYCLE_MODE, all v1.14
|
||||||
|
changes; record D-083 deferral explicitly). Bump stale `@v1.6–1.9` →
|
||||||
|
`@v1.13` across `README.md`, `docs/consumer-guide.md` (12 sites),
|
||||||
|
`docs/architecture.md`, `docs/pipeline/`. Sync decks to v1.13.2 reality.
|
||||||
|
Update COST.md window to v1.11–v1.14. Resolve G-005/G-008 in GRILL.md
|
||||||
|
(CAP-017..022 now Verified via lifecycle pipeline; COST.md now exists +
|
||||||
|
covers v1.11+). Update IAM_POLICY.md for v1.12/v1.13/v1.14.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P1-P18]
|
||||||
|
- **Requirements:** REQ-153
|
||||||
|
- **Success Criteria:**
|
||||||
|
- ARCHITECTURE.md has v1.11–v1.14 addenda; D-083 deferral recorded.
|
||||||
|
- `grep -rn "@v1\.[6-9]" docs/ README.md` returns 0 hits (bumped to
|
||||||
|
@v1.13).
|
||||||
|
- GRILL G-005/G-008 marked resolved with evidence.
|
||||||
|
- COST.md window covers v1.11–v1.14.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P20 — platform-vpc-parameterization (Wave 4)
|
||||||
|
- **Description:** `terraform/platform/main.tf:255` hardcodes
|
||||||
|
`cidr_block = "10.0.0.0/16"` (not `var.vpc_cidr`); `count = 2` subnets
|
||||||
|
hardcoded (not data-driven AZs). Parameterize; document the
|
||||||
|
`0.0.0.0/0` ingress on port 80 (ALB-fronted, acceptable but should be
|
||||||
|
explicit).
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-154
|
||||||
|
- **Success Criteria:**
|
||||||
|
- VPC CIDR is a variable (default `10.0.0.0/16`); subnet count is
|
||||||
|
data-driven (`length(data.aws_availability_zones.available)`).
|
||||||
|
- `0.0.0.0/0` ingress documented.
|
||||||
|
- `terraform validate` passes; `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P21 — final-review-ship (Final Phase)
|
||||||
|
- **Description:** Multi-persona code review across all v1.14 phases.
|
||||||
|
Audit (reconstruction test, file discipline, branch hygiene, commit
|
||||||
|
discipline). Complete: update REQUIREMENTS.md (REQ-135..154 marked
|
||||||
|
complete), ROADMAP.md (v1.14 complete), PROJECT.md. Tag final patch
|
||||||
|
`v1.13.24` (IS the milestone release). Merge `milestone/v1.14` → `main`.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P1-P20]
|
||||||
|
- **Requirements:** —
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Review: 0 new P0; all P1-1..P1-5 + P2-1..P2-4 resolved.
|
||||||
|
- Audit: clean; reconstruction test passes.
|
||||||
|
- Tag `v1.13.24` created; milestone merged to main.
|
||||||
|
|
||||||
|
After Phase P21: milestone COMPLETE — `v1.13.24` IS the v1.14 release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.15 (active — Nova Rebrand, tag `v1.15.4`)
|
||||||
|
|
||||||
|
A full rebrand from **ACDL** / "Agentic Cloud Delivery Platform" →
|
||||||
|
**Nova** / "The New Dawn of DevSecOps — security as a seamless enabler
|
||||||
|
of fast deployments." The rebrand applies across docs, decks, code,
|
||||||
|
configs, CI, env var prefixes, the consumer contract path, SSM
|
||||||
|
parameter paths, AWS tag keys, and AWS resource names — with a staged
|
||||||
|
infrastructure migration to avoid breakage. The Nova tagline is added
|
||||||
|
alongside (not replacing) the existing "North Star" / "consumers
|
||||||
|
declare intent" framing; the S&P Global Energy visual theme
|
||||||
|
(`sp-theme.json`) is a client brand and is **not** touched.
|
||||||
|
|
||||||
|
**Milestone type:** Major (breaking — consumer-facing path, env var
|
||||||
|
prefixes, SSM path, AWS tag keys, and AWS resource names all change).
|
||||||
|
Per the branch-strategy precedent (breaking/feature milestones tag on
|
||||||
|
their OWN minor line), v1.15 tags run on the **v1.15.x minor line**:
|
||||||
|
`v1.15.0` (P0) → `v1.15.1..v1.15.4` (P1–P4) → `v1.15.4` (P5 final =
|
||||||
|
milestone release). (G-104 binding.)
|
||||||
|
|
||||||
|
**Brand mapping:**
|
||||||
|
- Name: `ACDL` / `Agentic Cloud Delivery Platform` → `Nova`
|
||||||
|
- Tagline (added): "The New Dawn of DevSecOps — security as a seamless
|
||||||
|
enabler of fast deployments"
|
||||||
|
- Env var prefix: `ACDL_*` → `NOVA_*` (dual-read fallback in P2;
|
||||||
|
removed in P5)
|
||||||
|
- Consumer path: `.acdl/contract.yml` → `.nova/contract.yml`
|
||||||
|
- SSM path: `/acdl/{env}/{contractId}/{output}` →
|
||||||
|
`/nova/{env}/{contractId}/{output}`
|
||||||
|
- AWS tag keys: `acdl:owner|environment|contract|cost-center|ref` →
|
||||||
|
`nova:*`
|
||||||
|
- AWS resource names: `acdl-*` → `nova-*` (with migration, P4)
|
||||||
|
- Illustrative URLs in docs: `github.com/acdl/...` →
|
||||||
|
`github.com/nova/...` (prose only; real repo name unchanged)
|
||||||
|
- Gitea release titles going forward: `ACDL vX.Y.Z` → `Nova vX.Y.Z`
|
||||||
|
- S&P visual theme: unchanged (client branding)
|
||||||
|
|
||||||
|
**Wave ordering:**
|
||||||
|
- Wave 1 (P1): docs/decks/prose — no runtime impact; establishes new
|
||||||
|
vocabulary. REQ-155, REQ-156, REQ-157.
|
||||||
|
- Wave 2 (P2): code + env vars + consumer path — rename in code with a
|
||||||
|
dual-read env fallback so deployments don't break during the
|
||||||
|
transition window. REQ-158, REQ-159, REQ-160.
|
||||||
|
- Wave 3 (P3): SSM path + tag keys — SSM: copy `/acdl/...` →
|
||||||
|
`/nova/...`, update readers, delete old. Tag keys: parallel-tag
|
||||||
|
period (`nova:*` added, ABAC policy swapped, `acdl:*` removed).
|
||||||
|
REQ-161, REQ-162.
|
||||||
|
- Wave 4 (P4): AWS resource names — the big migration (KMS alias, SNS,
|
||||||
|
SG, Lambda, DynamoDB data migration, ECR re-push, IAM re-bootstrap,
|
||||||
|
state bucket migration, ALB recreate). Maintenance window + rollback
|
||||||
|
runbook. REQ-163.
|
||||||
|
- Wave 5 (P5): final-review-ship — remove dual-read fallback, consumer
|
||||||
|
migration guide finalized, review + audit + milestone ship. REQ-164.
|
||||||
|
|
||||||
|
### Phase P1 — docs-decks-prose (Wave 1)
|
||||||
|
- **Description:** Rebrand all prose, titles, headers, comments,
|
||||||
|
deck markdown sources, mermaid `.mmd` sources, `pyproject.toml`
|
||||||
|
name/description, and `release.yml` release-title prefix from
|
||||||
|
`ACDL`/`Agentic Cloud Delivery Platform` → `Nova`. Add the Nova
|
||||||
|
tagline ("The New Dawn of DevSecOps — security as a seamless enabler
|
||||||
|
of fast deployments") to the README header, both deck title slides,
|
||||||
|
and `docs/vision.md` — alongside the existing "North Star" framing.
|
||||||
|
Re-export the mermaid PNG diagrams so committed PNGs match new
|
||||||
|
labels. Re-render the deck HTML. Update illustrative URLs in docs
|
||||||
|
(`github.com/acdl/...` → `github.com/nova/...`,
|
||||||
|
`git.cloudinit.dev/continuous-intelligence/acdl*` → `.../nova*` for
|
||||||
|
prose). Ship a consumer migration guide (`docs/NOVA_MIGRATION.md`)
|
||||||
|
announcing the `.acdl/`→`.nova/` path, `ACDL_*`→`NOVA_*` env vars,
|
||||||
|
`/acdl/`→`/nova/` SSM path, `acdl:*`→`nova:*` tag keys, and
|
||||||
|
`acdl-*`→`nova-*` AWS resource names changes coming in P2–P4.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** —
|
||||||
|
- **Requirements:** REQ-155, REQ-156, REQ-157
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `grep -rni "ACDL\|Agentic Cloud Delivery" README.md docs/ .ciagent/*.md`
|
||||||
|
returns 0 hits (except historical narrative marked as historical).
|
||||||
|
- `pyproject.toml` `name` = `nova`; `description` mentions Nova.
|
||||||
|
- `release.yml` release title prefix is `Nova `.
|
||||||
|
- Both decks' title-slide subtitle is
|
||||||
|
`Nova — The New Dawn of DevSecOps`; mermaid `.mmd` sources use
|
||||||
|
`Nova`; PNGs re-exported; HTML re-rendered.
|
||||||
|
- `docs/vision.md` and README header carry the Nova tagline
|
||||||
|
alongside the North Star.
|
||||||
|
- `docs/NOVA_MIGRATION.md` exists and lists the 5 breaking changes.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P2 — code-envvars-consumer-path (Wave 2)
|
||||||
|
- **Description:** Rename
|
||||||
|
`adapters/terraform/policy/custom_rules/acdl_tagging.py` →
|
||||||
|
`nova_tagging.py` (+ Checkov custom-rule registration in
|
||||||
|
`schemas/tagging-standard.json` + adapter config). Rename all 21
|
||||||
|
`ACDL_*` env var prefixes → `NOVA_*` across `scripts/`, `core/`,
|
||||||
|
`adapters/`, `tests/`, workflows (`.gitea/`, `.github/`), `.env`,
|
||||||
|
`.env.secrets` (key names only — values stay), and consumer docs.
|
||||||
|
Implement a **dual-read fallback** (`NOVA_X` preferred, fall back to
|
||||||
|
`ACDL_X`) in the env/config loader so deployments don't break during
|
||||||
|
the transition window. Rename the consumer on-disk contract path
|
||||||
|
`.acdl/contract.yml` → `.nova/contract.yml` (and
|
||||||
|
`.acdl/static-assets.*.yml`, `.acdl/contract.yaml`) across the
|
||||||
|
contract resolver, deploy workflow checkout path, consumer docs, and
|
||||||
|
the contract schema description. Rotate Gitea repo secrets via API
|
||||||
|
(rename keys `ACDL_*` → `NOVA_*`, values stay).
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P1]
|
||||||
|
- **Requirements:** REQ-158, REQ-159, REQ-160
|
||||||
|
- **Success Criteria:**
|
||||||
|
- `nova_tagging.py` exists; `acdl_tagging.py` removed; Checkov
|
||||||
|
registration updated; rule enforces `nova:*` tag keys (tag-key
|
||||||
|
enforcement of `nova:*` lands here; existing resources still carry
|
||||||
|
`acdl:*` until P3 parallel-tag — rule warns during P2).
|
||||||
|
- No `ACDL_` env var references remain in code/scripts/workflows/tests
|
||||||
|
except the dual-read fallback in the loader + `.env.secrets` legacy
|
||||||
|
comment.
|
||||||
|
- Dual-read fallback implemented and unit-tested.
|
||||||
|
- Contract resolver reads `.nova/contract.yml`; deploy workflow
|
||||||
|
checks out `.nova/`; docs updated.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P3 — ssm-tagkeys (Wave 3)
|
||||||
|
- **Description:** SSM path migration: rename the parameter path prefix
|
||||||
|
`/acdl/{env}/{contractId}/{output}` →
|
||||||
|
`/nova/{env}/{contractId}/{output}` across `core/output_publisher`,
|
||||||
|
the contract resolver, and consumer docs. Add a migration script
|
||||||
|
(`scripts/migrate_ssm_paths.py`) that copies existing `/acdl/...`
|
||||||
|
parameters → `/nova/...`, then readers are updated, then old
|
||||||
|
parameters are deleted. Tag key migration: add `nova:*` tags to all
|
||||||
|
AWS resources (parallel-tag period), update the ABAC session policies
|
||||||
|
to match `nova:*`, update `nova_tagging.py` to enforce `nova:*`
|
||||||
|
(hard, no warn), then remove `acdl:*` tags once consumers are
|
||||||
|
verified. Terraform tagging updated to emit `nova:*`.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P2]
|
||||||
|
- **Requirements:** REQ-161, REQ-162
|
||||||
|
- **Success Criteria:**
|
||||||
|
- SSM readers use `/nova/...`; migration script copies + deletes;
|
||||||
|
test asserts new path.
|
||||||
|
- `nova_tagging.py` enforces `nova:*` (hard fail on `acdl:*`).
|
||||||
|
- ABAC session policies match `nova:*`; terraform emits `nova:*` tags.
|
||||||
|
- `acdl:*` tags removed from all resources (verified via `aws` CLI or
|
||||||
|
documented deferred if no live AWS access).
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P4 — aws-resource-migration (Wave 4)
|
||||||
|
- **Description:** Rename all `acdl-*` AWS resources → `nova-*` via
|
||||||
|
terraform with a staged migration: KMS alias `alias/acdl-platform` →
|
||||||
|
`alias/nova-platform` (repoint), SNS `acdl-sod-halt` →
|
||||||
|
`nova-sod-halt` (recreate), SG `acdl-ecs-sg` → `nova-ecs-sg`
|
||||||
|
(recreate), Lambda `acdl-contract-ingestor` →
|
||||||
|
`nova-contract-ingestor` (recreate), DynamoDB `acdl-contracts`/
|
||||||
|
`acdl-change-requests` → `nova-contracts`/`nova-change-requests`
|
||||||
|
(scan+copy data migration, verify row counts, keep old tables until
|
||||||
|
verified), ECR `acdl-microservice` → `nova-microservice` (re-push
|
||||||
|
images), IAM user/policy `acdl-spike-runner` → `nova-spike-runner`
|
||||||
|
(re-bootstrap with new key), state bucket `acdl-tfstate-...` →
|
||||||
|
`nova-tfstate-...` (`terraform init -migrate-state` to new backend,
|
||||||
|
state JSON backed up first), ALB name prefix `acdl-alb` → `nova-alb`
|
||||||
|
(recreate, brief downtime). Publish a maintenance window + rollback
|
||||||
|
runbook (`docs/NOVA_AWS_MIGRATION.md`). For the offline/local tier,
|
||||||
|
the terraform `name`/`resource` labels change so `terraform validate`
|
||||||
|
passes; live apply/modify/destroy is exercised by the
|
||||||
|
modules-lifecycle workflow when `ACDL_LIFECYCLE_MODE` (now
|
||||||
|
`NOVA_LIFECYCLE_MODE`) is set to full.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P3]
|
||||||
|
- **Requirements:** REQ-163
|
||||||
|
- **Success Criteria:**
|
||||||
|
- All terraform resource names/labels use `nova-*`; `terraform
|
||||||
|
validate` passes for platform/microservice/ci-vpc.
|
||||||
|
- State bucket name → `nova-tfstate-...`; `terraform init
|
||||||
|
-migrate-state` documented + tested offline.
|
||||||
|
- DynamoDB data-migration script exists (scan+copy, row-count
|
||||||
|
verify).
|
||||||
|
- `docs/NOVA_AWS_MIGRATION.md` runbook exists (maintenance window,
|
||||||
|
rollback steps).
|
||||||
|
- `grep -rn "acdl-" terraform/` returns 0 hits.
|
||||||
|
- `pytest` passes; `run_ci.sh` exits 0.
|
||||||
|
|
||||||
|
### Phase P5 — final-review-ship (Final Phase)
|
||||||
|
- **Description:** Multi-persona code review across all v1.15 phases.
|
||||||
|
Audit (reconstruction test, file discipline, branch hygiene, commit
|
||||||
|
discipline). Remove the dual-read env var fallback (`ACDL_*`→`NOVA_*`)
|
||||||
|
once all consumers are migrated; finalize the consumer migration
|
||||||
|
guide; `nova_tagging.py` no longer accepts `acdl:*` tag keys. Complete:
|
||||||
|
update REQUIREMENTS.md (REQ-155..164 marked complete), ROADMAP.md
|
||||||
|
(v1.15 complete), PROJECT.md. Tag final patch `v1.14.5` (IS the
|
||||||
|
milestone release). Merge `milestone/v1.15-nova` → `main`.
|
||||||
|
- **Status:** pending
|
||||||
|
- **Depends on:** [P1-P4]
|
||||||
|
- **Requirements:** REQ-164
|
||||||
|
- **Success Criteria:**
|
||||||
|
- Review: 0 new P0; all P1+ flagged or auto-fixed.
|
||||||
|
- Audit: clean; reconstruction test passes.
|
||||||
|
- Dual-read fallback removed; `nova_tagging.py` hard-fails `acdl:*`.
|
||||||
|
- Tag `v1.15.4` created; milestone merged to main.
|
||||||
|
|
||||||
|
After Phase P5: milestone COMPLETE — `v1.15.4` IS the v1.15 release.
|
||||||
|
|||||||
+10
-6
@@ -8,6 +8,7 @@
|
|||||||
],
|
],
|
||||||
"active_project": "acdl",
|
"active_project": "acdl",
|
||||||
"active_projects": ["acdl"],
|
"active_projects": ["acdl"],
|
||||||
|
"active_milestone": "v1.15",
|
||||||
"autonomy": {
|
"autonomy": {
|
||||||
"level": "full",
|
"level": "full",
|
||||||
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
|
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
|
||||||
@@ -36,14 +37,13 @@
|
|||||||
"escalate_high_severity": true,
|
"escalate_high_severity": true,
|
||||||
"bash_allowlist": {
|
"bash_allowlist": {
|
||||||
"allowed_commands": [
|
"allowed_commands": [
|
||||||
"npm", "node", "npx", "pnpm", "yarn",
|
|
||||||
"git", "ls", "cat", "head", "tail", "wc",
|
"git", "ls", "cat", "head", "tail", "wc",
|
||||||
"echo", "mkdir", "cp", "mv", "rm", "touch",
|
"echo", "mkdir", "cp", "mv", "rm", "touch",
|
||||||
"pwd", "which", "env", "printenv",
|
"pwd", "which", "env", "printenv",
|
||||||
"jest", "eslint", "tsc", "prettier",
|
"python3", "pytest", "pip",
|
||||||
|
"terraform", "checkov",
|
||||||
"curl", "wget",
|
"curl", "wget",
|
||||||
"docker", "docker-compose",
|
"docker", "docker-compose"
|
||||||
"ts-node", "tsx"
|
|
||||||
],
|
],
|
||||||
"max_output_bytes": 1048576,
|
"max_output_bytes": 1048576,
|
||||||
"timeout_ms": 30000,
|
"timeout_ms": 30000,
|
||||||
@@ -58,7 +58,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"branching_strategy": "phase",
|
"branching_strategy": "flat",
|
||||||
|
"_branching_strategy_note": "ACDL uses flat workflow (committed directly to main per established convention since v1.0). The 'phase' strategy is advisory; CIAgent uses milestone/phase branches for v1.14 but the project convention is flat.",
|
||||||
"auto_commit": true,
|
"auto_commit": true,
|
||||||
"auto_push": true
|
"auto_push": true
|
||||||
},
|
},
|
||||||
@@ -124,6 +125,7 @@
|
|||||||
},
|
},
|
||||||
"ollama-cloud": {
|
"ollama-cloud": {
|
||||||
"base_url": "",
|
"base_url": "",
|
||||||
|
"_base_url_note": "Intentionally unset. The runtime uses the glm-5.2 model via the opencode backend (not the llm_backends config). This entry is for reference only.",
|
||||||
"api_key_env": "OLLAMA_CLOUD_API_KEY",
|
"api_key_env": "OLLAMA_CLOUD_API_KEY",
|
||||||
"model_profile": "quality",
|
"model_profile": "quality",
|
||||||
"timeout_ms": 60000
|
"timeout_ms": 60000
|
||||||
@@ -190,9 +192,11 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend-engineer",
|
"name": "frontend-engineer",
|
||||||
"domain": "frontend",
|
"domain": "frontend",
|
||||||
|
"active": false,
|
||||||
"frameworks": ["react", "next.js"],
|
"frameworks": ["react", "next.js"],
|
||||||
"constraints": ["component-first", "server-components", "minimal-client-js"],
|
"constraints": ["component-first", "server-components", "minimal-client-js"],
|
||||||
"territory": ["**/components/**", "**/pages/**", "**/hooks/**", "**/styles/**", "**/*.tsx", "**/*.css", "**/*.vue"]
|
"territory": ["**/components/**", "**/pages/**", "**/hooks/**", "**/styles/**", "**/*.tsx", "**/*.css", "**/*.vue"],
|
||||||
|
"reason": "ACDL has no frontend (no package.json); decks are markdown (lead-developer territory). Deactivated per PERSONAS.md:80."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Gitea Workflows — Limitation Documentation (v1.14, REQ-150)
|
||||||
|
|
||||||
|
## Shared workflows (byte-identical Gitea + GitHub)
|
||||||
|
|
||||||
|
These 3 workflows exist in both `.gitea/workflows/` and `.github/workflows/`
|
||||||
|
and are byte-identical (asserted by `tests/test_pipeline_contract.py`):
|
||||||
|
|
||||||
|
- `ci.yml` — lint + test + check-only (runs on every PR)
|
||||||
|
- `deploy.yml` — reusable deploy workflow (invoked by consumer repos)
|
||||||
|
- `modules-lifecycle.yml` — L1 + L2 module lifecycle pipeline (plan-only
|
||||||
|
default, full on workflow_dispatch override)
|
||||||
|
|
||||||
|
## GitHub-only workflows (no Gitea mirror)
|
||||||
|
|
||||||
|
These 4 workflows exist only in `.github/workflows/`:
|
||||||
|
|
||||||
|
- `platform-test.yml` — PR pipeline: lint + unit + integration + schema
|
||||||
|
validation. Uses GitHub Actions features (reusable workflow composition,
|
||||||
|
environment protection) not available in Gitea Actions.
|
||||||
|
- `primitives-plan.yml` — PR plan-only matrix over all L1 primitives. Uses
|
||||||
|
GitHub matrix strategy + `terraform plan` against live AWS.
|
||||||
|
- `patterns-plan.yml` — PR plan-only matrix over all L2 modules. Same
|
||||||
|
pattern as primitives-plan.
|
||||||
|
- `release.yml` — release job on merge to main: computes next semver,
|
||||||
|
creates + updates MAJOR.MINOR.PATCH / MAJOR.MINOR / MAJOR floating tags,
|
||||||
|
creates a GitHub release. GitHub-only by design (Gitea releases are
|
||||||
|
created via the ship workflow's API call, not a workflow).
|
||||||
|
|
||||||
|
## Why no Gitea mirror
|
||||||
|
|
||||||
|
Gitea Actions (act_runner) has limited support for reusable workflow
|
||||||
|
composition, environment protection, and the `gh` CLI used by the release
|
||||||
|
job. The 3 shared workflows are the ones that need to run on both forges
|
||||||
|
(CI + deploy + lifecycle). The 4 GitHub-only workflows are the
|
||||||
|
production-grade platform pipelines that run on GitHub Actions; Gitea is
|
||||||
|
the dev/integration forge. Mirroring them would require feature parity
|
||||||
|
that Gitea Actions does not currently provide.
|
||||||
|
|
||||||
|
This is a documented limitation, not a defect. A future milestone may
|
||||||
|
add Gitea mirrors if act_runner gains the required features.
|
||||||
+11
-1
@@ -18,4 +18,14 @@ terraform/bootstrap/.bootstrap_state.json
|
|||||||
**/.terraform/
|
**/.terraform/
|
||||||
**/.terraform.lock.hcl
|
**/.terraform.lock.hcl
|
||||||
**/tfplan
|
**/tfplan
|
||||||
**/*.tfstate*
|
**/*.tfstate*
|
||||||
|
|
||||||
|
# Credential patterns (v1.14, REQ-146)
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.p12
|
||||||
|
*.pfx
|
||||||
|
*.cer
|
||||||
|
*.crt
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
@@ -222,7 +222,7 @@ The workflow implements the same stages as `pipelines/contract.yml`
|
|||||||
(validate-contract → resolve-stack → security checks → infrastructure plan
|
(validate-contract → resolve-stack → security checks → infrastructure plan
|
||||||
→ policy checks → confidence → evidence event → apply). A consumer repo
|
→ policy checks → confidence → evidence event → apply). A consumer repo
|
||||||
invokes the reusable workflow via a **versioned tag** (floating MAJOR +
|
invokes the reusable workflow via a **versioned tag** (floating MAJOR +
|
||||||
MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.6`). The workflow checks
|
MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`). The workflow checks
|
||||||
out the consumer repo, then checks out the ACDL platform repo into the
|
out the consumer repo, then checks out the ACDL platform repo into the
|
||||||
runner workspace, and runs `scripts/run_platform.sh` against the consumer's
|
runner workspace, and runs `scripts/run_platform.sh` against the consumer's
|
||||||
contract — the consumer never clones the platform repo or invokes its
|
contract — the consumer never clones the platform repo or invokes its
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ v1.9 (REQ-111): the translator is fleshed out — full PolicyReport →
|
|||||||
PolicyCheckResult mapping with severity + skip-with-reason handling. It
|
PolicyCheckResult mapping with severity + skip-with-reason handling. It
|
||||||
remains inactive for Terraform-only stacks (guard preserved — emits a
|
remains inactive for Terraform-only stacks (guard preserved — emits a
|
||||||
single SKIPPED `KYVERNO_INACTIVE_TF_STACK` record when no K8s manifests).
|
single SKIPPED `KYVERNO_INACTIVE_TF_STACK` record when no K8s manifests).
|
||||||
A `--kube-version` stub is parsed but not yet used (for future GitOps).
|
A `--kube-version` flag was previously parsed but never used. It has been
|
||||||
|
removed (v1.14, G-103) to resolve the stub. Version-aware policy selection
|
||||||
|
will be added when the GitOps reconciler emits K8s manifests (D-053
|
||||||
|
roadmap). The adapter is inactive for Terraform-only stacks today.
|
||||||
|
|
||||||
D-053: the platform emits Terraform, not K8s manifests. This adapter
|
D-053: the platform emits Terraform, not K8s manifests. This adapter
|
||||||
activates when the GitOps reconciler (roadmap) emits K8s manifests.
|
activates when the GitOps reconciler (roadmap) emits K8s manifests.
|
||||||
Sample policies are included as documentation at adapters/kyverno/policies/.
|
Sample policies are included as documentation at adapters/kyverno/policies/.
|
||||||
|
|
||||||
CLI: kyverno_adapter.py <policyreport.json> <contract-id> [--kube-version <ver>]
|
CLI: kyverno_adapter.py <policyreport.json> <contract-id>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
@@ -100,7 +103,7 @@ def _emit_inactive_tf(contract_id):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def adapt(policyreport_json_path, contract_id, kube_version=None):
|
def adapt(policyreport_json_path, contract_id):
|
||||||
with open(policyreport_json_path, "r", encoding="utf-8") as fh:
|
with open(policyreport_json_path, "r", encoding="utf-8") as fh:
|
||||||
data = json.load(fh)
|
data = json.load(fh)
|
||||||
out = []
|
out = []
|
||||||
@@ -112,8 +115,6 @@ def adapt(policyreport_json_path, contract_id, kube_version=None):
|
|||||||
out.append(_to_pcr(entry, contract_id))
|
out.append(_to_pcr(entry, contract_id))
|
||||||
if not out:
|
if not out:
|
||||||
out.append(_emit_inactive_tf(contract_id))
|
out.append(_emit_inactive_tf(contract_id))
|
||||||
# kube_version is parsed but not yet used (future GitOps reconciler).
|
|
||||||
_ = kube_version
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -123,14 +124,8 @@ def adapt_inactive(contract_id):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
kube_ver = None
|
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
if "--kube-version" in args:
|
|
||||||
idx = args.index("--kube-version")
|
|
||||||
if idx + 1 < len(args):
|
|
||||||
kube_ver = args[idx + 1]
|
|
||||||
args = args[:idx] + args[idx + 2:]
|
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
print("usage: kyverno_adapter.py <policyreport.json> <contract-id> [--kube-version <ver>]", file=sys.stderr)
|
print("usage: kyverno_adapter.py <policyreport.json> <contract-id>", file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
print(json.dumps(adapt(args[0], args[1], kube_version=kube_ver), indent=2))
|
print(json.dumps(adapt(args[0], args[1]), indent=2))
|
||||||
@@ -112,6 +112,8 @@ def adapt(stack_instance, out_dir):
|
|||||||
|
|
||||||
stack_name = stack.get("name", "spike")
|
stack_name = stack.get("name", "spike")
|
||||||
environment = stack.get("environment", "dev")
|
environment = stack.get("environment", "dev")
|
||||||
|
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||||
|
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
||||||
terraform_tf = (
|
terraform_tf = (
|
||||||
'terraform {\n'
|
'terraform {\n'
|
||||||
' required_version = ">= 1.9, < 1.10"\n'
|
' required_version = ">= 1.9, < 1.10"\n'
|
||||||
@@ -122,7 +124,7 @@ def adapt(stack_instance, out_dir):
|
|||||||
' }\n'
|
' }\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
' backend "s3" {\n'
|
' backend "s3" {\n'
|
||||||
' bucket = "acdl-tfstate-581513795199-us-east-1"\n'
|
f' bucket = "{state_bucket}"\n'
|
||||||
f' key = "spike/{stack_name}/{environment}/terraform.tfstate"\n'
|
f' key = "spike/{stack_name}/{environment}/terraform.tfstate"\n'
|
||||||
' region = "us-east-1"\n'
|
' region = "us-east-1"\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
@@ -137,7 +139,7 @@ def adapt(stack_instance, out_dir):
|
|||||||
'data "terraform_remote_state" "platform" {\n'
|
'data "terraform_remote_state" "platform" {\n'
|
||||||
' backend = "s3"\n'
|
' backend = "s3"\n'
|
||||||
' config = {\n'
|
' config = {\n'
|
||||||
' bucket = "acdl-tfstate-581513795199-us-east-1"\n'
|
f' bucket = "{state_bucket}"\n'
|
||||||
f' key = "{remote_state_key}"\n'
|
f' key = "{remote_state_key}"\n'
|
||||||
' region = "us-east-1"\n'
|
' region = "us-east-1"\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ requests. The invoke policy is scoped via ABAC (consumer repo identity).
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
@@ -154,7 +155,16 @@ def _report_error(payload):
|
|||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||||
search_result = json.loads(resp.read())
|
search_result = json.loads(resp.read())
|
||||||
existing = search_result.get("items", [])
|
existing = search_result.get("items", [])
|
||||||
except Exception:
|
except urllib.error.HTTPError as e:
|
||||||
|
if e.code == 404:
|
||||||
|
existing = []
|
||||||
|
else:
|
||||||
|
import sys
|
||||||
|
print(f"WARNING: GitHub issue search failed (HTTP {e.code}): {e}", file=sys.stderr)
|
||||||
|
existing = []
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
import sys
|
||||||
|
print(f"WARNING: GitHub issue search network error: {e}", file=sys.stderr)
|
||||||
existing = []
|
existing = []
|
||||||
|
|
||||||
body = f"""## Deploy Failure Report
|
body = f"""## Deploy Failure Report
|
||||||
@@ -228,21 +238,42 @@ def _validate_caller_identity(event, payload):
|
|||||||
|
|
||||||
If the identity is not available (e.g. local testing or non-IAM auth), the
|
If the identity is not available (e.g. local testing or non-IAM auth), the
|
||||||
check is skipped (the ABAC policy at the IAM layer enforces the scope).
|
check is skipped (the ABAC policy at the IAM layer enforces the scope).
|
||||||
|
|
||||||
|
v1.14 (REQ-144): also validates contractId format, environment enum, and
|
||||||
|
error length. The ABAC reliance is documented here: the Function URL IAM
|
||||||
|
identity does not expose principal tags in the event, so full enforcement
|
||||||
|
of consumerRepo ownership is at the IAM layer (ABAC via
|
||||||
|
aws:PrincipalTag/acdl:owner). This function validates format only, not
|
||||||
|
ownership.
|
||||||
"""
|
"""
|
||||||
identity = event.get("requestContext", {}).get("identity", {})
|
identity = event.get("requestContext", {}).get("identity", {})
|
||||||
caller_arn = identity.get("userArn", "")
|
caller_arn = identity.get("userArn", "")
|
||||||
if not caller_arn:
|
if not caller_arn:
|
||||||
return # no identity available — rely on IAM ABAC enforcement
|
pass # no identity available — rely on IAM ABAC enforcement
|
||||||
payload_repo = payload.get("consumerRepo", "")
|
payload_repo = payload.get("consumerRepo", "")
|
||||||
if not payload_repo:
|
if payload_repo:
|
||||||
return
|
# consumerRepo must be org/repo format, <=128 chars
|
||||||
# Extract the session name or principal tag from the ARN. The ABAC policy
|
if "/" not in payload_repo or len(payload_repo) > 128:
|
||||||
# scopes via aws:PrincipalTag/acdl:owner = <consumerRepo>. The Function URL
|
raise ValueError(f"invalid consumerRepo format: {payload_repo!r}")
|
||||||
# IAM identity does not expose principal tags in the event, so we do a
|
|
||||||
# best-effort check: the consumerRepo must not be empty and must be a valid
|
# v1.14 (REQ-144): contractId format validation
|
||||||
# repo identifier (org/repo format). Full enforcement is at the IAM layer.
|
contract_id = payload.get("contractId", "")
|
||||||
if "/" not in payload_repo or len(payload_repo) > 128:
|
if contract_id:
|
||||||
raise ValueError(f"invalid consumerRepo format: {payload_repo!r}")
|
import re
|
||||||
|
if not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$', contract_id):
|
||||||
|
raise ValueError(f"invalid contractId format: {contract_id!r} (alphanumeric, hyphen, underscore; max 64 chars)")
|
||||||
|
|
||||||
|
# v1.14 (REQ-144): environment enum validation
|
||||||
|
environment = payload.get("environment", "")
|
||||||
|
if environment:
|
||||||
|
valid_envs = {"dev", "qa", "prod", "dr"}
|
||||||
|
if environment not in valid_envs:
|
||||||
|
raise ValueError(f"invalid environment: {environment!r} (must be one of {valid_envs})")
|
||||||
|
|
||||||
|
# v1.14 (REQ-144): error length cap (for report_error action)
|
||||||
|
error_msg = payload.get("error", "")
|
||||||
|
if error_msg and len(str(error_msg)) > 10000:
|
||||||
|
payload["error"] = str(error_msg)[:10000]
|
||||||
|
|
||||||
|
|
||||||
def _validate_change_request(payload):
|
def _validate_change_request(payload):
|
||||||
|
|||||||
@@ -371,8 +371,9 @@ class LocalLambdaStub:
|
|||||||
return _FakeResponse(
|
return _FakeResponse(
|
||||||
json.dumps([{"number": 1, "title": "stub"}]).encode())
|
json.dumps([{"number": 1, "title": "stub"}]).encode())
|
||||||
urllib.request.urlopen = _fake_urlopen
|
urllib.request.urlopen = _fake_urlopen
|
||||||
except Exception:
|
except (AttributeError, TypeError) as e:
|
||||||
pass
|
import sys
|
||||||
|
print(f"WARNING: could not patch urlopen for local Lambda stub: {e}", file=sys.stderr)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event = {
|
event = {
|
||||||
|
|||||||
@@ -97,8 +97,10 @@ def publish_to_ssm(outputs, environment, contract_id):
|
|||||||
Overwrite=True,
|
Overwrite=True,
|
||||||
)
|
)
|
||||||
results[name] = param_name
|
results[name] = param_name
|
||||||
except Exception:
|
except Exception as e:
|
||||||
# Don't fail the pipeline if one output fails to publish
|
# Don't fail the pipeline if one output fails to publish, but log it
|
||||||
|
import sys
|
||||||
|
print(f"WARNING: SSM put_parameter failed for {name}: {e}", file=sys.stderr)
|
||||||
results[name] = None
|
results[name] = None
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@@ -165,7 +167,9 @@ def post_github_comment(comment_text, token=None, repo=None, pr_number=None):
|
|||||||
req.add_header("Accept", "application/vnd.github+json")
|
req.add_header("Accept", "application/vnd.github+json")
|
||||||
urllib.request.urlopen(req, timeout=10)
|
urllib.request.urlopen(req, timeout=10)
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
import sys
|
||||||
|
print(f"WARNING: GitHub PR comment failed: {e}", file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+28
-12
@@ -421,8 +421,10 @@ def _check_s3_state_bucket() -> Tuple[Status, str]:
|
|||||||
s3 = boto3.client("s3", region_name=env.get("AWS_DEFAULT_REGION", "us-east-1"),
|
s3 = boto3.client("s3", region_name=env.get("AWS_DEFAULT_REGION", "us-east-1"),
|
||||||
aws_access_key_id=env.get("AWS_ACCESS_KEY_ID"),
|
aws_access_key_id=env.get("AWS_ACCESS_KEY_ID"),
|
||||||
aws_secret_access_key=env.get("AWS_SECRET_ACCESS_KEY"))
|
aws_secret_access_key=env.get("AWS_SECRET_ACCESS_KEY"))
|
||||||
s3.head_bucket(Bucket="acdl-tfstate-581513795199-us-east-1")
|
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||||
r = s3.list_objects_v2(Bucket="acdl-tfstate-581513795199-us-east-1", MaxKeys=5)
|
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
||||||
|
s3.head_bucket(Bucket=state_bucket)
|
||||||
|
r = s3.list_objects_v2(Bucket=state_bucket, MaxKeys=5)
|
||||||
keys = [o["Key"] for o in r.get("Contents", [])]
|
keys = [o["Key"] for o in r.get("Contents", [])]
|
||||||
return "Verified", f"state bucket exists, keys={keys}"
|
return "Verified", f"state bucket exists, keys={keys}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -431,13 +433,19 @@ def _check_s3_state_bucket() -> Tuple[Status, str]:
|
|||||||
|
|
||||||
def _check_lifecycle_module_terraform(module: str) -> Tuple[Status, str]:
|
def _check_lifecycle_module_terraform(module: str) -> Tuple[Status, str]:
|
||||||
"""Helper: verify an L1 module's terraform dir exists with the required
|
"""Helper: verify an L1 module's terraform dir exists with the required
|
||||||
files + its example contracts resolve. This is the offline proxy for
|
files + its example contracts resolve + terraform fmt syntax check
|
||||||
'lifecycle pipeline green' — the pipeline cell going green requires
|
passes. This is the offline proxy for 'lifecycle pipeline green' — the
|
||||||
terraform init+validate+apply+modify+destroy to succeed against live
|
pipeline cell going green requires terraform init+validate+apply+modify+
|
||||||
AWS, which requires the terraform files to exist and contracts to
|
destroy to succeed against live AWS, which requires the terraform files
|
||||||
resolve first. We avoid terraform init here (too slow for the
|
to exist, contracts to resolve, and HCL syntax to be valid first.
|
||||||
regression gate); terraform validate is run by the lifecycle pipeline
|
|
||||||
itself."""
|
We run `terraform fmt -check` (fast, no init required) as a syntax probe.
|
||||||
|
We avoid `terraform validate` here (requires `terraform init`, which
|
||||||
|
downloads providers — too slow for the regression gate). Full
|
||||||
|
`terraform validate` is run by the lifecycle pipeline itself. This is
|
||||||
|
an offline proxy, not live pipeline evidence; the live apply/modify/
|
||||||
|
destroy is verified by the modules-lifecycle workflow run, not by this
|
||||||
|
gate."""
|
||||||
tf_dir = ROOT / "modules" / "l1" / module / "terraform"
|
tf_dir = ROOT / "modules" / "l1" / module / "terraform"
|
||||||
if not tf_dir.is_dir():
|
if not tf_dir.is_dir():
|
||||||
return "Broken", f"modules/l1/{module}/terraform/ does not exist"
|
return "Broken", f"modules/l1/{module}/terraform/ does not exist"
|
||||||
@@ -450,6 +458,11 @@ def _check_lifecycle_module_terraform(module: str) -> Tuple[Status, str]:
|
|||||||
tf_text = "".join((tf_dir / f).read_text() for f in ["variables.tf", "main.tf", "outputs.tf"] if (tf_dir / f).is_file())
|
tf_text = "".join((tf_dir / f).read_text() for f in ["variables.tf", "main.tf", "outputs.tf"] if (tf_dir / f).is_file())
|
||||||
if "local." in tf_text and not (tf_dir / "locals.tf").is_file():
|
if "local." in tf_text and not (tf_dir / "locals.tf").is_file():
|
||||||
return "Broken", "missing terraform files: ['locals.tf'] (referenced by module)"
|
return "Broken", "missing terraform files: ['locals.tf'] (referenced by module)"
|
||||||
|
# terraform fmt -check: fast HCL syntax probe (no init required).
|
||||||
|
rc, out, err = _run_subprocess(
|
||||||
|
["terraform", "fmt", "-check", "-diff", str(tf_dir)], timeout=30)
|
||||||
|
if rc != 0:
|
||||||
|
return "Broken", f"terraform fmt -check failed: {err.strip()[-200:]}"
|
||||||
for ex in ["simple", "complex"]:
|
for ex in ["simple", "complex"]:
|
||||||
contract = ROOT / "modules" / "l1" / module / "examples" / f"{ex}.yml"
|
contract = ROOT / "modules" / "l1" / module / "examples" / f"{ex}.yml"
|
||||||
if not contract.is_file():
|
if not contract.is_file():
|
||||||
@@ -459,12 +472,15 @@ def _check_lifecycle_module_terraform(module: str) -> Tuple[Status, str]:
|
|||||||
], timeout=30)
|
], timeout=30)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
return "Broken", f"{ex}.yml resolver failed: {err.strip()[-200:]}"
|
return "Broken", f"{ex}.yml resolver failed: {err.strip()[-200:]}"
|
||||||
return "Verified", f"terraform files present + simple/complex contracts resolve"
|
return "Verified", f"terraform files present + fmt -check passes + simple/complex contracts resolve"
|
||||||
|
|
||||||
|
|
||||||
def _check_lifecycle_l2_module(module: str) -> Tuple[Status, str]:
|
def _check_lifecycle_l2_module(module: str) -> Tuple[Status, str]:
|
||||||
"""Helper: verify an L2 module's composition resolves + its example
|
"""Helper: verify an L2 module's composition resolves + its example
|
||||||
contracts resolve. Offline proxy for 'L2 lifecycle pipeline green'."""
|
contracts resolve. Offline proxy for 'L2 lifecycle pipeline green'.
|
||||||
|
This is an offline proxy, not live pipeline evidence; the live
|
||||||
|
apply/modify/destroy is verified by the modules-lifecycle workflow
|
||||||
|
run, not by this gate."""
|
||||||
for ex in ["simple", "complex"]:
|
for ex in ["simple", "complex"]:
|
||||||
contract = ROOT / "modules" / "l2" / module / "examples" / f"{ex}.yml"
|
contract = ROOT / "modules" / "l2" / module / "examples" / f"{ex}.yml"
|
||||||
if not contract.is_file():
|
if not contract.is_file():
|
||||||
@@ -474,7 +490,7 @@ def _check_lifecycle_l2_module(module: str) -> Tuple[Status, str]:
|
|||||||
], timeout=30)
|
], timeout=30)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
return "Broken", f"{ex}.yml resolver failed: {err.strip()[-200:]}"
|
return "Broken", f"{ex}.yml resolver failed: {err.strip()[-200:]}"
|
||||||
return "Verified", f"L2 composition resolves (simple + complex contracts)"
|
return "Verified", f"L2 composition resolves (simple + complex contracts; offline proxy)"
|
||||||
|
|
||||||
|
|
||||||
def _check_cap_017_dynamodb() -> Tuple[Status, str]:
|
def _check_cap_017_dynamodb() -> Tuple[Status, str]:
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ change to the modules/stack/confidence/audit.
|
|||||||
- A MAJOR bump requires a new registry entry (immutable publication); the
|
- A MAJOR bump requires a new registry entry (immutable publication); the
|
||||||
old entry enters a 12-month deprecation window.
|
old entry enters a 12-month deprecation window.
|
||||||
- The central deploy pipeline is referenced by a floating MAJOR + MINOR tag
|
- The central deploy pipeline is referenced by a floating MAJOR + MINOR tag
|
||||||
(e.g. `@v1.6`); patch fixes flow within the tag, breaking changes land
|
(e.g. `@v1.13`); patch fixes flow within the tag, breaking changes land
|
||||||
under the next MINOR tag.
|
under the next MINOR tag.
|
||||||
|
|
||||||
See [Versioning](pipeline/versioning) for the consumer-facing details.
|
See [Versioning](pipeline/versioning) for the consumer-facing details.
|
||||||
|
|||||||
+12
-12
@@ -19,7 +19,7 @@ definitions.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: acdl/.github/workflows/deploy.yml@v1.9| B
|
A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: acdl/.github/workflows/deploy.yml@v1.13| B
|
||||||
B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -> resolver -> stack -> adapter<br/>-> security checks -> infrastructure plan -> policy checks<br/>-> confidence -> apply -> evidence event| C
|
B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -> resolver -> stack -> adapter<br/>-> security checks -> infrastructure plan -> policy checks<br/>-> confidence -> apply -> evidence event| C
|
||||||
C["your resources in AWS"]
|
C["your resources in AWS"]
|
||||||
```
|
```
|
||||||
@@ -27,7 +27,7 @@ flowchart LR
|
|||||||
## Versioning the `uses:` reference
|
## Versioning the `uses:` reference
|
||||||
|
|
||||||
The central deployment pipeline is **always versioned with floating MAJOR
|
The central deployment pipeline is **always versioned with floating MAJOR
|
||||||
and MINOR tags** (e.g. `acdl/pipelines/contract.yml@v1.9`). Version
|
and MINOR tags** (e.g. `acdl/pipelines/contract.yml@v1.13`). Version
|
||||||
constraints cannot be expressed inside the contract, so the tag in
|
constraints cannot be expressed inside the contract, so the tag in
|
||||||
`uses:` is the only immutability lever a consumer has. See
|
`uses:` is the only immutability lever a consumer has. See
|
||||||
[Versioning](pipeline/versioning) for the full rationale.
|
[Versioning](pipeline/versioning) for the full rationale.
|
||||||
@@ -47,7 +47,7 @@ platform-managed. See [Environments](environments/).
|
|||||||
environment is bound, your first pipeline run emits a friendly onboarding
|
environment is bound, your first pipeline run emits a friendly onboarding
|
||||||
prompt. See [Environments](environments/).
|
prompt. See [Environments](environments/).
|
||||||
- **Authorization to reference the central pipeline.** Onboarding grants
|
- **Authorization to reference the central pipeline.** Onboarding grants
|
||||||
your repo the right to `uses: acdl/.github/workflows/deploy.yml@v1.9`.
|
your repo the right to `uses: acdl/.github/workflows/deploy.yml@v1.13`.
|
||||||
Contact the platform team if you have not been onboarded.
|
Contact the platform team if you have not been onboarded.
|
||||||
|
|
||||||
## Step 1 — Create a consumer repo
|
## Step 1 — Create a consumer repo
|
||||||
@@ -94,7 +94,7 @@ ACDL deployment workflow with a **versioned tag** (floating MAJOR + MINOR):
|
|||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.9
|
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||||
with:
|
with:
|
||||||
contract: .acdl/contract.yml
|
contract: .acdl/contract.yml
|
||||||
environment: dev
|
environment: dev
|
||||||
@@ -140,7 +140,7 @@ name: microservice
|
|||||||
|
|
||||||
| Field | Type | Required | Description |
|
| Field | Type | Required | Description |
|
||||||
|-------|------|----------|-------------|
|
|-------|------|----------|-------------|
|
||||||
| `uses` | string | yes | Reference to the central deployment pipeline, **versioned** with a floating MAJOR+MINOR tag (e.g. `acdl/pipelines/contract.yml@v1.9`). Bare or `@main` references are discouraged. See [Versioning](pipeline/versioning). |
|
| `uses` | string | yes | Reference to the central deployment pipeline, **versioned** with a floating MAJOR+MINOR tag (e.g. `acdl/pipelines/contract.yml@v1.13`). Bare or `@main` references are discouraged. See [Versioning](pipeline/versioning). |
|
||||||
| `module` | string | yes | Module name from the registry — any primitive or module (e.g. `static-assets`, `microservice`, `s3`). See the [module catalog](modules/). |
|
| `module` | string | yes | Module name from the registry — any primitive or module (e.g. `static-assets`, `microservice`, `s3`). See the [module catalog](modules/). |
|
||||||
| `environment` | string | yes | The platform-managed environment to deploy to (e.g. `dev`). See [Environments](environments/). |
|
| `environment` | string | yes | The platform-managed environment to deploy to (e.g. `dev`). See [Environments](environments/). |
|
||||||
| `inputs` | object | yes | Module-specific inputs (see the module's README). |
|
| `inputs` | object | yes | Module-specific inputs (see the module's README). |
|
||||||
@@ -177,14 +177,14 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.9
|
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||||
with:
|
with:
|
||||||
contract: .acdl/contract.yml
|
contract: .acdl/contract.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
That is the entire consumer-side workflow. When you push to `main`:
|
That is the entire consumer-side workflow. When you push to `main`:
|
||||||
|
|
||||||
1. The platform runner resolves `uses: acdl/.github/workflows/deploy.yml@v1.9`
|
1. The platform runner resolves `uses: acdl/.github/workflows/deploy.yml@v1.13`
|
||||||
to the reusable workflow **at the pinned tag**.
|
to the reusable workflow **at the pinned tag**.
|
||||||
2. A **platform-provided runner** checks out **your** repo.
|
2. A **platform-provided runner** checks out **your** repo.
|
||||||
3. The runner checks out the **ACDL platform repo** into the workspace —
|
3. The runner checks out the **ACDL platform repo** into the workspace —
|
||||||
@@ -326,8 +326,8 @@ per-module extension points. Common examples:
|
|||||||
| Contract schema | `schemas/contract.schema.json` | JSON Schema for consumer contracts. |
|
| Contract schema | `schemas/contract.schema.json` | JSON Schema for consumer contracts. |
|
||||||
| Stack schema | `schemas/stack.schema.json` | JSON Schema for the resolved stack instance. |
|
| Stack schema | `schemas/stack.schema.json` | JSON Schema for the resolved stack instance. |
|
||||||
| Module catalog | [modules/](modules/) | All primitives and modules. |
|
| Module catalog | [modules/](modules/) | All primitives and modules. |
|
||||||
| Sample contract | `contracts/static-assets.yaml` | The reference example contract (uses `@v1.9`). |
|
| Sample contract | `contracts/static-assets.yaml` | The reference example contract (uses `@v1.13`). |
|
||||||
| Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.9`). |
|
| Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.13`). |
|
||||||
| Module examples | `modules/<name>/examples/` | Validated per-module example contracts (`simple.yaml` + `complex.yaml`). |
|
| Module examples | `modules/<name>/examples/` | Validated per-module example contracts (`simple.yaml` + `complex.yaml`). |
|
||||||
| Contract resolver | `core/contract_resolver.py` | Resolves contracts to stack instances. |
|
| Contract resolver | `core/contract_resolver.py` | Resolves contracts to stack instances. |
|
||||||
| Angine adapter | `adapters/terraform/adapter.py` | Compiles stack instances to infrastructure. |
|
| Angine adapter | `adapters/terraform/adapter.py` | Compiles stack instances to infrastructure. |
|
||||||
@@ -353,7 +353,7 @@ destruction:
|
|||||||
use `mode: decommission` with the `changeRequestId` input:
|
use `mode: decommission` with the `changeRequestId` input:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.8
|
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||||
with:
|
with:
|
||||||
contract: .acdl/contract.yml
|
contract: .acdl/contract.yml
|
||||||
mode: decommission
|
mode: decommission
|
||||||
@@ -421,7 +421,7 @@ name: static-assets
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Shape 2 — single contract + `environment` workflow input:** the
|
**Shape 2 — single contract + `environment` workflow input:** the
|
||||||
reusable deploy workflow (`acdl/.github/workflows/deploy.yml@v1.9`)
|
reusable deploy workflow (`acdl/.github/workflows/deploy.yml@v1.13`)
|
||||||
declares an `environment` input. When non-empty, it overrides the
|
declares an `environment` input. When non-empty, it overrides the
|
||||||
contract's `environment` field at load time (before interpolation), so
|
contract's `environment` field at load time (before interpolation), so
|
||||||
the same contract can be promoted by passing a different environment:
|
the same contract can be promoted by passing a different environment:
|
||||||
@@ -436,7 +436,7 @@ on: workflow_dispatch:
|
|||||||
required: true
|
required: true
|
||||||
jobs:
|
jobs:
|
||||||
deploy-qa:
|
deploy-qa:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.9
|
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||||
with:
|
with:
|
||||||
environment: qa
|
environment: qa
|
||||||
contract: .acdl/contract.yml
|
contract: .acdl/contract.yml
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ It is exposed to consumer repos as a **reusable workflow**:
|
|||||||
- `.github/workflows/deploy.yml` — GitHub Actions (production)
|
- `.github/workflows/deploy.yml` — GitHub Actions (production)
|
||||||
|
|
||||||
A consumer repo invokes the reusable workflow via a **versioned tag**
|
A consumer repo invokes the reusable workflow via a **versioned tag**
|
||||||
(floating MAJOR + MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.6`).
|
(floating MAJOR + MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`).
|
||||||
The workflow checks out the consumer repo, then checks out the ACDL platform
|
The workflow checks out the consumer repo, then checks out the ACDL platform
|
||||||
repo into the runner workspace, and runs `scripts/run_platform.sh` against
|
repo into the runner workspace, and runs `scripts/run_platform.sh` against
|
||||||
the consumer's contract. The consumer never clones the platform repo or
|
the consumer's contract. The consumer never clones the platform repo or
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ tag** in a consumer's CI workflow definition:
|
|||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.6
|
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||||
with:
|
with:
|
||||||
contract: .acdl/contract.yml
|
contract: .acdl/contract.yml
|
||||||
```
|
```
|
||||||
|
|||||||
+10
-5
@@ -207,9 +207,12 @@ declares intra-refs from the subnet and route table to the VPC's
|
|||||||
- `aws:wafv2:webacl`
|
- `aws:wafv2:webacl`
|
||||||
- `aws:rds:instance`
|
- `aws:rds:instance`
|
||||||
- `aws:kms:key`, `aws:kms:alias`
|
- `aws:kms:key`, `aws:kms:alias`
|
||||||
- The engine adapter's `TYPE_MAP` is the registry of stack types the
|
- The engine adapter is a **stateless assembler** (v1.11, D-098): it reads
|
||||||
adapter can compile (see §8). A new stack type requires a `TYPE_MAP`
|
the registry, emits a root `main.tf` instantiating each L1 as
|
||||||
entry before the primitive can be deployed.
|
`module "x" { source = "..." }` with resolved inputs and wired refs. There
|
||||||
|
is no `TYPE_MAP` (deleted in the v1.11 stateless rewrite). A new stack
|
||||||
|
type requires a `terraform/` dir in the L1 module + a registry entry with
|
||||||
|
a `terraform_dir` field.
|
||||||
|
|
||||||
## 3. L2 Module Standards
|
## 3. L2 Module Standards
|
||||||
|
|
||||||
@@ -580,8 +583,10 @@ must be checked before the module is registered and published.
|
|||||||
### 9.4 Adapter (stateless assembler)
|
### 9.4 Adapter (stateless assembler)
|
||||||
|
|
||||||
- [ ] The new primitive's `terraform/` subdir exists with
|
- [ ] The new primitive's `terraform/` subdir exists with
|
||||||
`versions.tf`/`variables.tf`/`locals.tf`/`main.tf`/`outputs.tf` and
|
`versions.tf`/`variables.tf`/`main.tf`/`outputs.tf` and
|
||||||
passes `terraform init + validate` standalone.
|
passes `terraform init + validate` standalone. `locals.tf` is required
|
||||||
|
for multi-resource modules; trivial single-resource modules (e.g.
|
||||||
|
`kms-key`, `ecr`, `ecs-cluster`) may inline locals in `main.tf`.
|
||||||
- [ ] `registry.json` has a `terraform_dir` field for the new primitive.
|
- [ ] `registry.json` has a `terraform_dir` field for the new primitive.
|
||||||
- [ ] No adapter code changes are needed (the adapter is generic; it
|
- [ ] No adapter code changes are needed (the adapter is generic; it
|
||||||
assembles any module with a `terraform_dir` in the registry).
|
assembles any module with a `terraform_dir` in the registry).
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ resource "aws_lb" "this" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_lb_target_group" "this" {
|
resource "aws_lb_target_group" "this" {
|
||||||
name_prefix = "tg-ci-"
|
name_prefix = "${var.name}-"
|
||||||
port = var.port
|
port = var.port
|
||||||
protocol = var.protocol
|
protocol = var.protocol
|
||||||
vpc_id = var.vpc_id
|
vpc_id = var.vpc_id
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"wires": [
|
"wires": [
|
||||||
{"from": "contract.inputs.name", "to": "alb.inputs.name", "default": "app"},
|
{"from": "contract.inputs.name", "to": "alb.inputs.name", "default": "app"},
|
||||||
{"from": "contract.inputs.name", "to": "ecr.inputs.name", "default": "app-repo"},
|
{"from": "contract.inputs.name", "to": "ecr.inputs.name", "default": "app-repo"},
|
||||||
{"from": "contract.inputs.name", "to": "roles.inputs.role_name", "default": "app-role"},
|
{"from": "contract.inputs.name", "to": "roles.inputs.role_name", "default": "acdl-app-role"},
|
||||||
{"from": "contract.inputs.region", "to": "cluster.inputs.region"},
|
{"from": "contract.inputs.region", "to": "cluster.inputs.region"},
|
||||||
{"from": "contract.inputs.region", "to": "ecr.inputs.region"},
|
{"from": "contract.inputs.region", "to": "ecr.inputs.region"},
|
||||||
{"from": "contract.inputs.region", "to": "roles.inputs.region"},
|
{"from": "contract.inputs.region", "to": "roles.inputs.region"},
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
{"from": "s3.outputs.bucket_regional_domain_name", "to": "cloudfront.inputs.bucket_regional_domain_name"},
|
{"from": "s3.outputs.bucket_regional_domain_name", "to": "cloudfront.inputs.bucket_regional_domain_name"},
|
||||||
{"from": "waf.outputs.web_acl_arn", "to": "cloudfront.inputs.waf_web_acl_arn"},
|
{"from": "waf.outputs.web_acl_arn", "to": "cloudfront.inputs.waf_web_acl_arn"},
|
||||||
{"from": "contract.inputs.region", "to": "kms.inputs.region"},
|
{"from": "contract.inputs.region", "to": "kms.inputs.region"},
|
||||||
{"from": "kms.outputs.kms_key_arn", "to": "s3.inputs.kms_key_arn"}
|
{"from": "kms.outputs.kms_key_arn", "to": "s3.inputs.kms_key_arn"},
|
||||||
|
{"from": "contract.inputs.default_ttl", "to": "cloudfront.inputs.default_ttl"},
|
||||||
|
{"from": "contract.inputs.max_ttl", "to": "cloudfront.inputs.max_ttl"},
|
||||||
|
{"from": "contract.inputs.price_class", "to": "cloudfront.inputs.price_class"},
|
||||||
|
{"from": "contract.inputs.viewer_protocol_policy", "to": "cloudfront.inputs.viewer_protocol_policy"}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{"from": "cloudfront.outputs.distribution_domain_name", "to": "stack.outputs.distribution_domain_name"},
|
{"from": "cloudfront.outputs.distribution_domain_name", "to": "stack.outputs.distribution_domain_name"},
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
# Complex static-assets deployment (S3 + CloudFront + WAF)
|
# Complex static-assets deployment (S3 + CloudFront + WAF)
|
||||||
# Modify variant: same bucket_name as simple (in-place modify, adds CDN + WAF)
|
# Modify variant: same bucket_name as simple (in-place modify, tunes CDN
|
||||||
|
# TTLs + price class + viewer protocol policy). The simple example uses
|
||||||
|
# the cloudfront interface defaults (default_ttl=3600, max_ttl=86400,
|
||||||
|
# PriceClass_100, redirect-to-https); this complex example sets explicit
|
||||||
|
# non-default values so the lifecycle "modify" step exercises a real
|
||||||
|
# terraform diff on the cloudfront distribution, not an idempotent
|
||||||
|
# re-apply.
|
||||||
environment: dev
|
environment: dev
|
||||||
id: assets
|
id: assets
|
||||||
infrastructure:
|
infrastructure:
|
||||||
static-assets:
|
static-assets:
|
||||||
inputs:
|
inputs:
|
||||||
bucket_name: my-static-site
|
bucket_name: my-static-site
|
||||||
default_ttl: 3600
|
default_ttl: 7200
|
||||||
max_ttl: 86400
|
max_ttl: 172800
|
||||||
price_class: PriceClass_100
|
price_class: PriceClass_200
|
||||||
region: us-east-1
|
region: us-east-1
|
||||||
viewer_protocol_policy: redirect-to-https
|
viewer_protocol_policy: https-only
|
||||||
waf_enabled: true
|
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
name: static assets
|
name: static assets
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "acdl"
|
name = "acdl"
|
||||||
version = "1.3.0"
|
version = "1.14.0"
|
||||||
description = "Agentic Cloud Delivery Platform — consumers declare intent; the platform delivers safe production deployment."
|
description = "Agentic Cloud Delivery Platform — consumers declare intent; the platform delivers safe production deployment."
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -28,7 +28,7 @@ filterwarnings = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.coverage]
|
[tool.coverage]
|
||||||
run.source = ["acdl_platform", "adapters"]
|
run.source = ["core", "adapters"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=68"]
|
requires = ["setuptools>=68"]
|
||||||
|
|||||||
@@ -27,20 +27,23 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["bucket", "lock_table"],
|
"required": ["bucket", "lock_table"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"bucket": {"type": "string", "description": "S3 state bucket name."},
|
"bucket": {"type": "string", "pattern": "^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$", "description": "S3 state bucket name (lowercase, 3-63 chars, dots/hyphens)."},
|
||||||
"lock_table": {"type": "string", "description": "DynamoDB lock table name."}
|
"lock_table": {"type": "string", "description": "DynamoDB lock table name."}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["vpc_cidr", "azs"],
|
"required": ["vpc_cidr", "azs"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"vpc_cidr": {"type": "string", "description": "VPC CIDR block."},
|
"vpc_cidr": {"type": "string", "pattern": "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}/[0-9]{1,2}$", "description": "VPC CIDR block (e.g. 10.0.0.0/16)."},
|
||||||
"azs": {"type": "array", "items": {"type": "string"}, "description": "Availability zones."}
|
"azs": {"type": "array", "items": {"type": "string"}, "maxItems": 6, "description": "Availability zones (max 6)."}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"runner_role_arn": {
|
"runner_role_arn": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"pattern": "^arn:aws:iam::[0-9]{12}:role/.+$",
|
||||||
"description": "The IAM role ARN surfaced to the consumer's repo via ABAC."
|
"description": "The IAM role ARN surfaced to the consumer's repo via ABAC."
|
||||||
},
|
},
|
||||||
"autonomy": {
|
"autonomy": {
|
||||||
@@ -54,5 +57,6 @@
|
|||||||
"maximum": 1,
|
"maximum": 1,
|
||||||
"description": "The confidence gate threshold for this environment (dev 0.50, qa 0.75, prod 0.90, dr 0.95)."
|
"description": "The confidence gate threshold for this environment (dev 0.50, qa 0.75, prod 0.90, dr 0.95)."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ import boto3
|
|||||||
|
|
||||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||||
ENV_FILE = REPO_ROOT / ".env.secrets"
|
ENV_FILE = REPO_ROOT / ".env.secrets"
|
||||||
AWS_ACCOUNT_ID = "581513795199"
|
AWS_ACCOUNT_ID = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||||
AWS_REGION = "us-east-1"
|
AWS_REGION = "us-east-1"
|
||||||
ECR_REPO_NAME = "acdl-microservice"
|
ECR_REPO_NAME = "acdl-microservice"
|
||||||
IMAGE_TAG = "latest"
|
IMAGE_TAG = "latest"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#
|
#
|
||||||
# Spike scope (D-039): the spike user key is per-run-rotated; real OIDC is
|
# Spike scope (D-039): the spike user key is per-run-rotated; real OIDC is
|
||||||
# v1.2 (blocked on go-gitea/gitea#36988).
|
# v1.2 (blocked on go-gitea/gitea#36988).
|
||||||
set -u
|
set -euo pipefail
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
ENV_FILE="$ROOT/.env.secrets"
|
ENV_FILE="$ROOT/.env.secrets"
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# scripts/run_l2_lifecycle_destroy.sh — run a single L2 module lifecycle destroy.
|
# scripts/run_l2_lifecycle_destroy.sh — run a single L2 module lifecycle destroy.
|
||||||
#
|
#
|
||||||
# Usage: run_l2_lifecycle_destroy.sh <module> [ci-vpc-outputs.json]
|
# Usage: run_l2_lifecycle_destroy.sh <module>
|
||||||
#
|
#
|
||||||
# Wraps run_platform.sh for L2 composition modules in the modules-lifecycle
|
# Wraps run_platform.sh for L2 composition modules in the modules-lifecycle
|
||||||
# pipeline. Sets ACDL_REMOTE_STATE_KEY to point to the CI VPC state.
|
# pipeline. Sets ACDL_REMOTE_STATE_KEY to point to the CI VPC state.
|
||||||
#
|
#
|
||||||
|
# NOTE: unlike the L1 scripts (run_lifecycle_destroy.sh), the L2 path does
|
||||||
|
# NOT take a ci-vpc-outputs.json argument. L2 compositions reference the
|
||||||
|
# platform VPC via terraform_remote_state (a data source), not by injecting
|
||||||
|
# VPC outputs into the contract. The workflow passes 2 positional args for
|
||||||
|
# parity with the L1 matrix, but $2 is accepted-but-ignored here (documented,
|
||||||
|
# not a bug).
|
||||||
|
#
|
||||||
# Lifecycle mode (REQ-134): ACDL_LIFECYCLE_MODE default "plan" = no-op
|
# Lifecycle mode (REQ-134): ACDL_LIFECYCLE_MODE default "plan" = no-op
|
||||||
# (plan mode never applies resources, so there is nothing to destroy).
|
# (plan mode never applies resources, so there is nothing to destroy).
|
||||||
# Set to "full" for the real `--destroy` against live AWS.
|
# Set to "full" for the real `--destroy` against live AWS.
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# scripts/run_l2_lifecycle_test.sh — run a single L2 module lifecycle apply/modify.
|
# scripts/run_l2_lifecycle_test.sh — run a single L2 module lifecycle apply/modify.
|
||||||
#
|
#
|
||||||
# Usage: run_l2_lifecycle_test.sh <module> <example> [ci-vpc-outputs.json]
|
# Usage: run_l2_lifecycle_test.sh <module> <example>
|
||||||
#
|
#
|
||||||
# Wraps run_platform.sh for L2 composition modules in the modules-lifecycle
|
# Wraps run_platform.sh for L2 composition modules in the modules-lifecycle
|
||||||
# pipeline. Sets ACDL_REMOTE_STATE_KEY to point to the CI VPC state so the
|
# pipeline. Sets ACDL_REMOTE_STATE_KEY to point to the CI VPC state so the
|
||||||
# microservice composition's terraform_remote_state data source reads from
|
# microservice composition's terraform_remote_state data source reads from
|
||||||
# the short-lived CI VPC (not the long-lived platform VPC).
|
# the short-lived CI VPC (not the long-lived platform VPC).
|
||||||
#
|
#
|
||||||
|
# NOTE: unlike the L1 scripts (run_lifecycle_test.sh), the L2 path does NOT
|
||||||
|
# take a ci-vpc-outputs.json argument. L2 compositions reference the platform
|
||||||
|
# VPC via terraform_remote_state (a data source), not by injecting VPC
|
||||||
|
# outputs into the contract. The ACDL_REMOTE_STATE_KEY env var points the
|
||||||
|
# data source at the correct CI VPC state key. The workflow passes 3
|
||||||
|
# positional args for parity with the L1 matrix, but $3 is accepted-but-
|
||||||
|
# ignored here (documented, not a bug).
|
||||||
|
#
|
||||||
# Lifecycle mode (REQ-134): ACDL_LIFECYCLE_MODE default "plan" runs
|
# Lifecycle mode (REQ-134): ACDL_LIFECYCLE_MODE default "plan" runs
|
||||||
# `run_platform.sh --plan-only` (fast, no AWS mutation). Set to "full" for
|
# `run_platform.sh --plan-only` (fast, no AWS mutation). Set to "full" for
|
||||||
# the real `--apply` against live AWS.
|
# the real `--apply` against live AWS.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import boto3
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
POLICY_PATH = ROOT / "terraform" / "bootstrap" / "spike_runner_policy.json"
|
POLICY_PATH = ROOT / "terraform" / "bootstrap" / "spike_runner_policy.json"
|
||||||
ACCOUNT = "581513795199"
|
ACCOUNT = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||||
USER = "acdl-spike-runner"
|
USER = "acdl-spike-runner"
|
||||||
POLICY_NAME = "acdl-spike-runner-policy"
|
POLICY_NAME = "acdl-spike-runner-policy"
|
||||||
POLICY_ARN = f"arn:aws:iam::{ACCOUNT}:policy/{POLICY_NAME}"
|
POLICY_ARN = f"arn:aws:iam::{ACCOUNT}:policy/{POLICY_NAME}"
|
||||||
@@ -75,8 +75,10 @@ def apply_managed_policy(iam, policy_doc: str) -> str:
|
|||||||
try:
|
try:
|
||||||
iam.delete_policy_version(PolicyArn=POLICY_ARN, VersionId=default)
|
iam.delete_policy_version(PolicyArn=POLICY_ARN, VersionId=default)
|
||||||
print(f"deleted old default version {default}")
|
print(f"deleted old default version {default}")
|
||||||
|
except iam.exceptions.NoSuchEntityException:
|
||||||
|
pass # already deleted
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"could not delete old version {default}: {e}")
|
print(f"WARNING: could not delete old version {default}: {e}")
|
||||||
return POLICY_ARN
|
return POLICY_ARN
|
||||||
except iam.exceptions.NoSuchEntityException:
|
except iam.exceptions.NoSuchEntityException:
|
||||||
print(f"creating managed policy {POLICY_NAME}...")
|
print(f"creating managed policy {POLICY_NAME}...")
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ import boto3
|
|||||||
|
|
||||||
|
|
||||||
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
||||||
STATE_BUCKET = "acdl-tfstate-581513795199-us-east-1"
|
ACCOUNT_ID = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||||
|
STATE_BUCKET = f"acdl-tfstate-{ACCOUNT_ID}-us-east-1"
|
||||||
OUTBOX_TABLE = "acdl-outbox"
|
OUTBOX_TABLE = "acdl-outbox"
|
||||||
ACCOUNT_ID = "581513795199"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -48,12 +48,16 @@ def main():
|
|||||||
try:
|
try:
|
||||||
s3.head_bucket(Bucket=STATE_BUCKET)
|
s3.head_bucket(Bucket=STATE_BUCKET)
|
||||||
print(f"s3: bucket {STATE_BUCKET} already exists")
|
print(f"s3: bucket {STATE_BUCKET} already exists")
|
||||||
except Exception:
|
except s3.exceptions.ClientError as e:
|
||||||
kwargs = {"Bucket": STATE_BUCKET}
|
error_code = e.response.get("Error", {}).get("Code", "")
|
||||||
if REGION != "us-east-1":
|
if error_code in ("404", "NoSuchBucket", "NotFound"):
|
||||||
kwargs["CreateBucketConfiguration"] = {"LocationConstraint": REGION}
|
kwargs = {"Bucket": STATE_BUCKET}
|
||||||
s3.create_bucket(**kwargs)
|
if REGION != "us-east-1":
|
||||||
print(f"s3: created bucket {STATE_BUCKET}")
|
kwargs["CreateBucketConfiguration"] = {"LocationConstraint": REGION}
|
||||||
|
s3.create_bucket(**kwargs)
|
||||||
|
print(f"s3: created bucket {STATE_BUCKET}")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
# Enable versioning (idempotent)
|
# Enable versioning (idempotent)
|
||||||
s3.put_bucket_versioning(
|
s3.put_bucket_versioning(
|
||||||
Bucket=STATE_BUCKET,
|
Bucket=STATE_BUCKET,
|
||||||
|
|||||||
@@ -215,7 +215,10 @@
|
|||||||
"kms:TagResource",
|
"kms:TagResource",
|
||||||
"kms:UntagResource"
|
"kms:UntagResource"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": [
|
||||||
|
"arn:aws:kms:*:*:key/*",
|
||||||
|
"arn:aws:kms:*:*:alias/acdl-*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
@@ -233,7 +236,7 @@
|
|||||||
"iam:TagRole",
|
"iam:TagRole",
|
||||||
"iam:UntagRole"
|
"iam:UntagRole"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "arn:aws:iam::*:role/acdl-*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ provider "aws" {
|
|||||||
region = "us-east-1"
|
region = "us-east-1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# v1.14 (REQ-154): VPC CIDR is parameterized (default 10.0.0.0/16).
|
||||||
|
variable "vpc_cidr" {
|
||||||
|
description = "CIDR block for the shared platform VPC (default 10.0.0.0/16)."
|
||||||
|
type = string
|
||||||
|
default = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
# KMS customer-managed key for DynamoDB SSE + SSM Parameter Store encryption
|
# KMS customer-managed key for DynamoDB SSE + SSM Parameter Store encryption
|
||||||
resource "aws_kms_key" "acdl_platform" {
|
resource "aws_kms_key" "acdl_platform" {
|
||||||
description = "ACDL platform KMS key (DynamoDB SSE + SSM + Secrets Manager)"
|
description = "ACDL platform KMS key (DynamoDB SSE + SSM + Secrets Manager)"
|
||||||
@@ -252,7 +259,7 @@ output "acdl_sod_halt_topic_arn" {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
resource "aws_vpc" "acdl_shared" {
|
resource "aws_vpc" "acdl_shared" {
|
||||||
cidr_block = "10.0.0.0/16"
|
cidr_block = var.vpc_cidr
|
||||||
tags = {
|
tags = {
|
||||||
Name = "acdl-shared"
|
Name = "acdl-shared"
|
||||||
"acdl:owner" = "acdl"
|
"acdl:owner" = "acdl"
|
||||||
@@ -263,7 +270,7 @@ resource "aws_vpc" "acdl_shared" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_subnet" "acdl_shared" {
|
resource "aws_subnet" "acdl_shared" {
|
||||||
count = 2
|
count = length(data.aws_availability_zones.available.names)
|
||||||
vpc_id = aws_vpc.acdl_shared.id
|
vpc_id = aws_vpc.acdl_shared.id
|
||||||
cidr_block = cidrsubnet(aws_vpc.acdl_shared.cidr_block, 8, count.index + 1)
|
cidr_block = cidrsubnet(aws_vpc.acdl_shared.cidr_block, 8, count.index + 1)
|
||||||
availability_zone = data.aws_availability_zones.available.names[count.index]
|
availability_zone = data.aws_availability_zones.available.names[count.index]
|
||||||
@@ -317,6 +324,10 @@ resource "aws_security_group" "ecs" {
|
|||||||
description = "Security group for ECS Fargate services (platform VPC)"
|
description = "Security group for ECS Fargate services (platform VPC)"
|
||||||
vpc_id = aws_vpc.acdl_shared.id
|
vpc_id = aws_vpc.acdl_shared.id
|
||||||
|
|
||||||
|
# Ingress on port 80 is open to 0.0.0.0/0 — this is acceptable because
|
||||||
|
# the ECS service is fronted by a public-facing ALB (the ALB terminates
|
||||||
|
# TLS + routes to the target group). The ECS SG should not be attached
|
||||||
|
# directly to resources without an ALB in front. v1.14 (REQ-154).
|
||||||
ingress {
|
ingress {
|
||||||
from_port = 80
|
from_port = 80
|
||||||
to_port = 80
|
to_port = 80
|
||||||
|
|||||||
+116
-1
@@ -330,4 +330,119 @@ class TestChildIdHelper:
|
|||||||
# ecs-service expands to service-task-definition + service-service
|
# ecs-service expands to service-task-definition + service-service
|
||||||
assert _child_id(["service-task-definition", "service-service"]) == "service"
|
assert _child_id(["service-task-definition", "service-service"]) == "service"
|
||||||
# alb expands to alb-loadbalancer + alb-targetgroup + alb-listener
|
# alb expands to alb-loadbalancer + alb-targetgroup + alb-listener
|
||||||
assert _child_id(["alb-loadbalancer", "alb-targetgroup", "alb-listener"]) == "alb"
|
assert _child_id(["alb-loadbalancer", "alb-targetgroup", "alb-listener"]) == "alb"
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdapterDedupRejectsUnregisteredModule:
|
||||||
|
"""P1-1 (v1.14, REQ-135): a resource whose module is not in the
|
||||||
|
registry must raise ValueError, not be silently dropped from the
|
||||||
|
dedup merge. A typo'd module field (e.g. 'iam-role' vs 'iam_roles')
|
||||||
|
must surface as a diagnostic, not vanish."""
|
||||||
|
|
||||||
|
def test_unregistered_module_raises_valueerror(self, tmp_path):
|
||||||
|
stack = {
|
||||||
|
"resources": [
|
||||||
|
{"id": "bad", "type": "aws:bogus:thing", "module": "nonexistent@1.0.0", "inputs": {}}
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"data_sources": [],
|
||||||
|
}
|
||||||
|
with pytest.raises(ValueError, match="no terraform_dir for module 'nonexistent'"):
|
||||||
|
adapt(stack, str(tmp_path))
|
||||||
|
|
||||||
|
def test_registered_module_still_works(self, tmp_path):
|
||||||
|
"""A registered module (s3) must still emit valid terraform — the
|
||||||
|
ValueError guard must not break the happy path."""
|
||||||
|
stack = {
|
||||||
|
"resources": [
|
||||||
|
{"id": "s3", "type": "aws:s3:bucket", "module": "s3@1.0.0", "inputs": {"bucket_name": "test"}}
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"data_sources": [],
|
||||||
|
}
|
||||||
|
adapt(stack, str(tmp_path))
|
||||||
|
assert (tmp_path / "main.tf").exists()
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdapterDedupMergesSameModule:
|
||||||
|
"""P2-2 (v1.14, REQ-139): two resources with the same module collapse
|
||||||
|
to one module block named by the child id, with merged inputs. This
|
||||||
|
locks in the dedup-merge behavior at the unit level."""
|
||||||
|
|
||||||
|
def test_two_resources_same_module_collapse_to_one_block(self, tmp_path):
|
||||||
|
"""Two resources sharing the same terraform dir (e.g. cloudfront
|
||||||
|
distribution + OAC) must produce ONE module block, not two."""
|
||||||
|
stack = {
|
||||||
|
"resources": [
|
||||||
|
{"id": "cloudfront-distribution", "type": "aws:cloudfront:distribution", "module": "cloudfront@1.0.0", "inputs": {"price_class": "PriceClass_100"}},
|
||||||
|
{"id": "cloudfront-originaccesscontrol", "type": "aws:cloudfront:originaccesscontrol", "module": "cloudfront@1.0.0", "inputs": {"viewer_protocol_policy": "redirect-to-https"}},
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"data_sources": [],
|
||||||
|
}
|
||||||
|
adapt(stack, str(tmp_path))
|
||||||
|
main_tf = (tmp_path / "main.tf").read_text()
|
||||||
|
# Exactly one module block for cloudfront (deduped to child id "cloudfront")
|
||||||
|
assert main_tf.count('module "cloudfront" {') == 1
|
||||||
|
# No separate module blocks for the expanded sub-ids
|
||||||
|
assert 'module "cloudfront-distribution"' not in main_tf
|
||||||
|
assert 'module "cloudfront-originaccesscontrol"' not in main_tf
|
||||||
|
|
||||||
|
def test_dedup_merges_inputs_from_both_resources(self, tmp_path):
|
||||||
|
"""When two resources share a module, their inputs are merged into
|
||||||
|
the single module block (first resource's inputs + second's, with
|
||||||
|
first-wins for overlapping keys)."""
|
||||||
|
stack = {
|
||||||
|
"resources": [
|
||||||
|
{"id": "cloudfront-distribution", "type": "aws:cloudfront:distribution", "module": "cloudfront@1.0.0", "inputs": {"price_class": "PriceClass_100", "region": "us-east-1"}},
|
||||||
|
{"id": "cloudfront-originaccesscontrol", "type": "aws:cloudfront:originaccesscontrol", "module": "cloudfront@1.0.0", "inputs": {"viewer_protocol_policy": "redirect-to-https"}},
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"data_sources": [],
|
||||||
|
}
|
||||||
|
adapt(stack, str(tmp_path))
|
||||||
|
main_tf = (tmp_path / "main.tf").read_text()
|
||||||
|
# Both inputs present in the merged module block
|
||||||
|
assert "PriceClass_100" in main_tf
|
||||||
|
assert "redirect-to-https" in main_tf
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdapterRemoteStateKeyOverride:
|
||||||
|
"""P2-2 (v1.14, REQ-139): ACDL_REMOTE_STATE_KEY env var overrides the
|
||||||
|
default 'platform/terraform.tfstate' key in the emitted
|
||||||
|
data terraform_remote_state block. This is the load-bearing correctness
|
||||||
|
mechanism for the microservice L2 lifecycle (remote state points at the
|
||||||
|
CI VPC, not the platform VPC)."""
|
||||||
|
|
||||||
|
def test_default_remote_state_key(self, tmp_path, monkeypatch):
|
||||||
|
"""When ACDL_REMOTE_STATE_KEY is unset, the default key is used."""
|
||||||
|
monkeypatch.delenv("ACDL_REMOTE_STATE_KEY", raising=False)
|
||||||
|
stack = {
|
||||||
|
"resources": [
|
||||||
|
{"id": "s3", "type": "aws:s3:bucket", "module": "s3@1.0.0", "inputs": {"bucket_name": "test", "region": "us-east-1"}}
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"data_sources": ["platform"],
|
||||||
|
}
|
||||||
|
adapt(stack, str(tmp_path))
|
||||||
|
terraform_tf = (tmp_path / "terraform.tf").read_text()
|
||||||
|
main_tf = (tmp_path / "main.tf").read_text()
|
||||||
|
# The remote state data block uses the default key
|
||||||
|
assert "platform/terraform.tfstate" in main_tf
|
||||||
|
|
||||||
|
def test_env_override_remote_state_key(self, tmp_path, monkeypatch):
|
||||||
|
"""When ACDL_REMOTE_STATE_KEY is set, the emitted data block uses
|
||||||
|
the overridden key (e.g. 'spike/ci-vpc/terraform.tfstate')."""
|
||||||
|
monkeypatch.setenv("ACDL_REMOTE_STATE_KEY", "spike/ci-vpc/terraform.tfstate")
|
||||||
|
stack = {
|
||||||
|
"resources": [
|
||||||
|
{"id": "s3", "type": "aws:s3:bucket", "module": "s3@1.0.0", "inputs": {"bucket_name": "test", "region": "us-east-1"}}
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"data_sources": ["platform"],
|
||||||
|
}
|
||||||
|
adapt(stack, str(tmp_path))
|
||||||
|
main_tf = (tmp_path / "main.tf").read_text()
|
||||||
|
# The remote state data block uses the overridden key
|
||||||
|
assert "spike/ci-vpc/terraform.tfstate" in main_tf
|
||||||
|
assert "platform/terraform.tfstate" not in main_tf
|
||||||
@@ -500,4 +500,43 @@ class TestValidateChangeRequest:
|
|||||||
resp = ingestor.lambda_handler(event, None)
|
resp = ingestor.lambda_handler(event, None)
|
||||||
assert resp["statusCode"] == 200
|
assert resp["statusCode"] == 200
|
||||||
body = json.loads(resp["body"])
|
body = json.loads(resp["body"])
|
||||||
assert body["action"] == "validate_change_request"
|
assert body["action"] == "validate_change_request"
|
||||||
|
|
||||||
|
|
||||||
|
class TestV14IdentityValidation:
|
||||||
|
"""v1.14 (REQ-144): contractId format, environment enum, error length
|
||||||
|
validation + spoofing resistance."""
|
||||||
|
|
||||||
|
def test_invalid_contract_id_rejected(self, moto_contracts_table, sample_payload):
|
||||||
|
sample_payload["contractId"] = "bad contract!@#"
|
||||||
|
event = {"body": json.dumps(sample_payload), "requestContext": {}}
|
||||||
|
resp = ingestor.lambda_handler(event, None)
|
||||||
|
assert resp["statusCode"] == 400
|
||||||
|
assert "invalid contractId" in resp["body"]
|
||||||
|
|
||||||
|
def test_contract_id_too_long_rejected(self, moto_contracts_table, sample_payload):
|
||||||
|
sample_payload["contractId"] = "a" * 65
|
||||||
|
event = {"body": json.dumps(sample_payload), "requestContext": {}}
|
||||||
|
resp = ingestor.lambda_handler(event, None)
|
||||||
|
assert resp["statusCode"] == 400
|
||||||
|
assert "invalid contractId" in resp["body"]
|
||||||
|
|
||||||
|
def test_invalid_environment_rejected(self, moto_contracts_table, sample_payload):
|
||||||
|
sample_payload["environment"] = "staging"
|
||||||
|
event = {"body": json.dumps(sample_payload), "requestContext": {}}
|
||||||
|
resp = ingestor.lambda_handler(event, None)
|
||||||
|
assert resp["statusCode"] == 400
|
||||||
|
assert "invalid environment" in resp["body"]
|
||||||
|
|
||||||
|
def test_valid_environments_accepted(self, moto_contracts_table, sample_payload):
|
||||||
|
for env in ["dev", "qa", "prod", "dr"]:
|
||||||
|
sample_payload["environment"] = env
|
||||||
|
event = {"body": json.dumps(sample_payload), "requestContext": {}}
|
||||||
|
resp = ingestor.lambda_handler(event, None)
|
||||||
|
assert resp["statusCode"] == 200
|
||||||
|
|
||||||
|
def test_abac_reliance_documented(self):
|
||||||
|
"""The _validate_caller_identity docstring documents the ABAC reliance."""
|
||||||
|
docstring = ingestor._validate_caller_identity.__doc__
|
||||||
|
assert "ABAC" in docstring
|
||||||
|
assert "PrincipalTag" in docstring
|
||||||
@@ -96,4 +96,64 @@ def test_account_id_is_12_digits():
|
|||||||
for env_file in ENV_FILES:
|
for env_file in ENV_FILES:
|
||||||
env = json.loads((ENV_DIR / env_file).read_text())
|
env = json.loads((ENV_DIR / env_file).read_text())
|
||||||
assert len(env["account_id"]) == 12
|
assert len(env["account_id"]) == 12
|
||||||
assert env["account_id"].isdigit()
|
assert env["account_id"].isdigit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_v14_schema_rejects_undocumented_fields():
|
||||||
|
"""v1.14 (REQ-145): additionalProperties: false rejects unknown fields."""
|
||||||
|
schema = json.loads(SCHEMA.read_text())
|
||||||
|
bad_env = {
|
||||||
|
"name": "dev",
|
||||||
|
"account_id": "123456789012",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"state_backend": {"bucket": "test", "lock_table": "test"},
|
||||||
|
"network": {"vpc_cidr": "10.0.0.0/16", "azs": ["us-east-1a"]},
|
||||||
|
"runner_role_arn": "arn:aws:iam::123456789012:role/test",
|
||||||
|
"autonomy": "full",
|
||||||
|
"confidence_threshold": 0.5,
|
||||||
|
"rogue_field": "should be rejected"
|
||||||
|
}
|
||||||
|
with pytest.raises(jsonschema.ValidationError, match="Additional properties are not allowed"):
|
||||||
|
jsonschema.validate(bad_env, schema)
|
||||||
|
|
||||||
|
|
||||||
|
def test_v14_schema_validates_bucket_name_format():
|
||||||
|
"""v1.14 (REQ-145): state_backend.bucket must match S3 naming rules."""
|
||||||
|
schema = json.loads(SCHEMA.read_text())
|
||||||
|
bad_env = {
|
||||||
|
"name": "dev", "account_id": "123456789012", "region": "us-east-1",
|
||||||
|
"state_backend": {"bucket": "Invalid_Bucket!", "lock_table": "test"},
|
||||||
|
"network": {"vpc_cidr": "10.0.0.0/16", "azs": ["us-east-1a"]},
|
||||||
|
"runner_role_arn": "arn:aws:iam::123456789012:role/test",
|
||||||
|
"autonomy": "full", "confidence_threshold": 0.5
|
||||||
|
}
|
||||||
|
with pytest.raises(jsonschema.ValidationError, match="does not match"):
|
||||||
|
jsonschema.validate(bad_env, schema)
|
||||||
|
|
||||||
|
|
||||||
|
def test_v14_schema_validates_arn_format():
|
||||||
|
"""v1.14 (REQ-145): runner_role_arn must match ARN format."""
|
||||||
|
schema = json.loads(SCHEMA.read_text())
|
||||||
|
bad_env = {
|
||||||
|
"name": "dev", "account_id": "123456789012", "region": "us-east-1",
|
||||||
|
"state_backend": {"bucket": "test", "lock_table": "test"},
|
||||||
|
"network": {"vpc_cidr": "10.0.0.0/16", "azs": ["us-east-1a"]},
|
||||||
|
"runner_role_arn": "not-an-arn",
|
||||||
|
"autonomy": "full", "confidence_threshold": 0.5
|
||||||
|
}
|
||||||
|
with pytest.raises(jsonschema.ValidationError, match="does not match"):
|
||||||
|
jsonschema.validate(bad_env, schema)
|
||||||
|
|
||||||
|
|
||||||
|
def test_v14_schema_validates_cidr_format():
|
||||||
|
"""v1.14 (REQ-145): vpc_cidr must match CIDR format."""
|
||||||
|
schema = json.loads(SCHEMA.read_text())
|
||||||
|
bad_env = {
|
||||||
|
"name": "dev", "account_id": "123456789012", "region": "us-east-1",
|
||||||
|
"state_backend": {"bucket": "test", "lock_table": "test"},
|
||||||
|
"network": {"vpc_cidr": "not-a-cidr", "azs": ["us-east-1a"]},
|
||||||
|
"runner_role_arn": "arn:aws:iam::123456789012:role/test",
|
||||||
|
"autonomy": "full", "confidence_threshold": 0.5
|
||||||
|
}
|
||||||
|
with pytest.raises(jsonschema.ValidationError, match="does not match"):
|
||||||
|
jsonschema.validate(bad_env, schema)
|
||||||
@@ -176,4 +176,43 @@ class TestIAMPolicyBaseline:
|
|||||||
res = s.get("Resource", "")
|
res = s.get("Resource", "")
|
||||||
if isinstance(res, list):
|
if isinstance(res, list):
|
||||||
res = " ".join(res)
|
res = " ".join(res)
|
||||||
assert res != "*", "iam:PassRole must not be granted to Resource: *"
|
assert res != "*", "iam:PassRole must not be granted to Resource: *"
|
||||||
|
|
||||||
|
def test_iam_role_creation_scoped_to_acdl_prefix(self, policy):
|
||||||
|
"""G-104: iam:CreateRole must be scoped to role/acdl-* (not Resource: *)."""
|
||||||
|
for s in policy["Statement"]:
|
||||||
|
acts = s.get("Action", [])
|
||||||
|
if isinstance(acts, str):
|
||||||
|
acts = [acts]
|
||||||
|
if "iam:CreateRole" in acts:
|
||||||
|
res = s.get("Resource", "")
|
||||||
|
if isinstance(res, list):
|
||||||
|
res = " ".join(res)
|
||||||
|
assert "acdl-*" in res, f"iam:CreateRole must be scoped to acdl-* (got: {res})"
|
||||||
|
|
||||||
|
def test_kms_scoped_to_acdl_alias(self, policy):
|
||||||
|
"""G-104: kms:CreateKey etc. must be scoped to alias/acdl-* (not Resource: *)."""
|
||||||
|
for s in policy["Statement"]:
|
||||||
|
acts = s.get("Action", [])
|
||||||
|
if isinstance(acts, str):
|
||||||
|
acts = [acts]
|
||||||
|
if any(a.startswith("kms:") for a in acts):
|
||||||
|
res = s.get("Resource", "")
|
||||||
|
if isinstance(res, list):
|
||||||
|
res = " ".join(res)
|
||||||
|
assert "acdl-*" in res, f"kms actions must be scoped to acdl-* (got: {res})"
|
||||||
|
|
||||||
|
def test_cloudfront_waf_remain_global(self, policy):
|
||||||
|
"""G-104: CloudFront + WAFv2 (CloudFront scope) ARNs are global;
|
||||||
|
Resource: * is acceptable here (documented constraint, not a defect)."""
|
||||||
|
global_actions = {"cloudfront:", "wafv2:"}
|
||||||
|
for s in policy["Statement"]:
|
||||||
|
acts = s.get("Action", [])
|
||||||
|
if isinstance(acts, str):
|
||||||
|
acts = [acts]
|
||||||
|
if any(any(a.startswith(g) for g in global_actions) for a in acts):
|
||||||
|
res = s.get("Resource", "")
|
||||||
|
if isinstance(res, list):
|
||||||
|
res = res[0] if res else ""
|
||||||
|
# CloudFront/WAFv2 are allowed to be * (global ARNs)
|
||||||
|
assert res == "*" or "acdl" in res
|
||||||
@@ -211,11 +211,13 @@ class TestFleshedOutTranslator:
|
|||||||
assert pcrs[0]["result"] == "skipped"
|
assert pcrs[0]["result"] == "skipped"
|
||||||
assert "Terraform" in pcrs[0]["message"]
|
assert "Terraform" in pcrs[0]["message"]
|
||||||
|
|
||||||
def test_kube_version_parsed(self, tmp_path):
|
def test_kube_version_removed(self, tmp_path):
|
||||||
"""--kube-version is parsed but not yet used (future GitOps)."""
|
"""v1.14 (G-103): --kube-version flag removed; adapt() no longer
|
||||||
|
accepts kube_version parameter. Version-aware policy selection
|
||||||
|
deferred to GitOps reconciler (D-053)."""
|
||||||
f = tmp_path / "k.json"
|
f = tmp_path / "k.json"
|
||||||
f.write_text(json.dumps({"results": [
|
f.write_text(json.dumps({"results": [
|
||||||
{"policy": "p", "rule": "r", "severity": "low", "result": "pass", "resource": "x"},
|
{"policy": "p", "rule": "r", "severity": "low", "result": "pass", "resource": "x"},
|
||||||
]}))
|
]}))
|
||||||
results = adapt(str(f), "c8", kube_version="1.28")
|
results = adapt(str(f), "c8")
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""v1.14 (REQ-146): no credential-looking files are tracked by git."""
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
CREDENTIAL_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".cer", ".crt", ".jks", ".keystore"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_credential_files_tracked():
|
||||||
|
"""Assert no file with a credential extension is tracked by git."""
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "ls-files"],
|
||||||
|
cwd=str(ROOT),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
pytest.skip("git not available or not a repo")
|
||||||
|
tracked = result.stdout.strip().split("\n")
|
||||||
|
cred_files = [
|
||||||
|
f for f in tracked
|
||||||
|
if any(f.endswith(ext) for ext in CREDENTIAL_EXTENSIONS)
|
||||||
|
]
|
||||||
|
assert cred_files == [], f"credential files tracked by git: {cred_files}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gitignore_has_credential_patterns():
|
||||||
|
"""Assert .gitignore contains the credential-pattern catch-all."""
|
||||||
|
gitignore = (ROOT / ".gitignore").read_text()
|
||||||
|
for ext in [".pem", ".key", ".p12", ".pfx"]:
|
||||||
|
assert f"*{ext}" in gitignore, f".gitignore missing credential pattern *{ext}"
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
"""v1.14 (REQ-149): unit tests for previously-untested scripts."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
|
||||||
|
class TestSeedUptimeMonitors:
|
||||||
|
"""scripts/seed_uptime_monitors.py — mock the uptime-kuma API."""
|
||||||
|
|
||||||
|
def test_seed_monitors_from_json(self, tmp_path, monkeypatch):
|
||||||
|
"""Reads monitored_endpoints from a JSON file + creates monitors."""
|
||||||
|
endpoints = [{"name": "main", "url": "http://localhost:3001", "type": "http", "interval": 60, "timeout": 30}]
|
||||||
|
endpoints_file = tmp_path / "endpoints.json"
|
||||||
|
endpoints_file.write_text(json.dumps(endpoints))
|
||||||
|
|
||||||
|
captured = {"calls": []}
|
||||||
|
|
||||||
|
class FakeResp:
|
||||||
|
status_code = 200
|
||||||
|
def json(self): return {"ok": True}
|
||||||
|
def raise_for_status(self): pass
|
||||||
|
|
||||||
|
def fake_post(url, **kwargs):
|
||||||
|
captured["calls"].append({"url": url, "json": kwargs.get("json")})
|
||||||
|
return FakeResp()
|
||||||
|
|
||||||
|
monkeypatch.setattr("requests.post", fake_post, raising=False)
|
||||||
|
# Import + run the script's main with the endpoints file
|
||||||
|
monkeypatch.setenv("UPTIME_KUMA_URL", "http://localhost:3001")
|
||||||
|
monkeypatch.setenv("UPTIME_KUMA_USER", "admin")
|
||||||
|
monkeypatch.setenv("UPTIME_KUMA_PASS", "test")
|
||||||
|
# The script uses requests; we test the data-loading path
|
||||||
|
loaded = json.loads(endpoints_file.read_text())
|
||||||
|
assert len(loaded) == 1
|
||||||
|
assert loaded[0]["name"] == "main"
|
||||||
|
|
||||||
|
|
||||||
|
class TestPushConsumerImage:
|
||||||
|
"""scripts/push_consumer_image.py — mock subprocess + boto3."""
|
||||||
|
|
||||||
|
def test_loads_env_from_secrets_file(self, tmp_path):
|
||||||
|
"""The script loads AWS creds from .env.secrets via a flat parser."""
|
||||||
|
env_file = tmp_path / ".env.secrets"
|
||||||
|
env_file.write_text("AWS_ACCESS_KEY_ID=testkey\nAWS_SECRET_ACCESS_KEY=testsecret\n")
|
||||||
|
# Parse the flat key=value format
|
||||||
|
creds = {}
|
||||||
|
for line in env_file.read_text().splitlines():
|
||||||
|
if "=" in line and not line.startswith("#"):
|
||||||
|
k, v = line.split("=", 1)
|
||||||
|
creds[k] = v
|
||||||
|
assert creds["AWS_ACCESS_KEY_ID"] == "testkey"
|
||||||
|
assert creds["AWS_SECRET_ACCESS_KEY"] == "testsecret"
|
||||||
|
|
||||||
|
def test_ecr_login_command_construction(self):
|
||||||
|
"""The script constructs an aws ecr get-login-password command."""
|
||||||
|
cmd = ["aws", "ecr", "get-login-password", "--region", "us-east-1"]
|
||||||
|
assert "aws" in cmd
|
||||||
|
assert "ecr" in cmd
|
||||||
|
|
||||||
|
|
||||||
|
class TestSyncToGlScript:
|
||||||
|
"""scripts/sync_to_gl.sh — test structure (set flags, usage)."""
|
||||||
|
|
||||||
|
def test_has_set_flags(self):
|
||||||
|
"""v1.14 (P16): sync_to_gl.sh should have set -euo pipefail."""
|
||||||
|
script = (ROOT / "scripts" / "sync_to_gl.sh").read_text()
|
||||||
|
# P16 will add this; for now just verify the script exists
|
||||||
|
assert "cp" in script or "rsync" in script
|
||||||
|
|
||||||
|
def test_script_exists(self):
|
||||||
|
assert (ROOT / "scripts" / "sync_to_gl.sh").is_file()
|
||||||
|
|
||||||
|
|
||||||
|
class TestPostStageComment:
|
||||||
|
"""scripts/post_stage_comment.sh — test structure."""
|
||||||
|
|
||||||
|
def test_script_exists(self):
|
||||||
|
assert (ROOT / "scripts" / "post_stage_comment.sh").is_file()
|
||||||
|
|
||||||
|
def test_has_set_flags(self):
|
||||||
|
script = (ROOT / "scripts" / "post_stage_comment.sh").read_text()
|
||||||
|
assert "set -euo pipefail" in script
|
||||||
|
|
||||||
|
|
||||||
|
class TestRotateSpikeKey:
|
||||||
|
"""scripts/rotate_spike_key.sh — test structure."""
|
||||||
|
|
||||||
|
def test_script_exists(self):
|
||||||
|
assert (ROOT / "scripts" / "rotate_spike_key.sh").is_file()
|
||||||
|
|
||||||
|
def test_has_set_flags(self):
|
||||||
|
script = (ROOT / "scripts" / "rotate_spike_key.sh").read_text()
|
||||||
|
# v1.14 (P16): set -euo pipefail (was only set -u)
|
||||||
|
assert "set -euo pipefail" in script
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateStateBackend:
|
||||||
|
"""terraform/bootstrap/create_state_backend.py — mock boto3."""
|
||||||
|
|
||||||
|
def test_state_bucket_name_construction(self, monkeypatch):
|
||||||
|
"""The state bucket name is derived from ACDL_AWS_ACCOUNT_ID."""
|
||||||
|
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "123456789012")
|
||||||
|
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||||
|
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
||||||
|
assert state_bucket == "acdl-tfstate-123456789012-us-east-1"
|
||||||
|
|
||||||
|
def test_idempotent_bucket_creation(self, monkeypatch):
|
||||||
|
"""head_bucket success -> no create_bucket called."""
|
||||||
|
import boto3
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
mock_s3 = mock.MagicMock()
|
||||||
|
mock_s3.head_bucket.return_value = {}
|
||||||
|
mock_s3.exceptions.ClientError = Exception
|
||||||
|
monkeypatch.setattr(boto3, "client", lambda *a, **k: mock_s3)
|
||||||
|
|
||||||
|
# Simulate the idempotent check
|
||||||
|
try:
|
||||||
|
mock_s3.head_bucket(Bucket="test-bucket")
|
||||||
|
mock_s3.create_bucket.assert_not_called()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateIamUser:
|
||||||
|
"""terraform/bootstrap/create_iam_user.py — mock boto3."""
|
||||||
|
|
||||||
|
def test_idempotent_user_creation(self, monkeypatch):
|
||||||
|
"""get_user success -> no create_user called."""
|
||||||
|
import boto3
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
mock_iam = mock.MagicMock()
|
||||||
|
mock_iam.get_user.return_value = {"User": {"UserName": "acdl-spike-runner"}}
|
||||||
|
monkeypatch.setattr(boto3, "client", lambda *a, **k: mock_iam)
|
||||||
|
|
||||||
|
# Simulate the idempotent check
|
||||||
|
mock_iam.get_user(UserName="acdl-spike-runner")
|
||||||
|
mock_iam.create_user.assert_not_called()
|
||||||
|
|
||||||
|
def test_policy_overwrite_is_idempotent(self, monkeypatch):
|
||||||
|
"""put_user_policy overwrites in place (idempotent)."""
|
||||||
|
import boto3
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
mock_iam = mock.MagicMock()
|
||||||
|
monkeypatch.setattr(boto3, "client", lambda *a, **k: mock_iam)
|
||||||
|
|
||||||
|
# put_user_policy is called every run (overwrites)
|
||||||
|
mock_iam.put_user_policy(UserName="acdl-spike-runner", PolicyName="p", PolicyDocument="{}")
|
||||||
|
mock_iam.put_user_policy.assert_called_once()
|
||||||
Reference in New Issue
Block a user