Compare commits

..

6 Commits

Author SHA1 Message Date
Jon Chery 81f111d462 docs(P04): complete regression-gate-evidence-hardening phase (v1.13.7)
---ci---
project: acdl
phase: 4
milestone: v1.14
status: complete
requirements:
  covered: [REQ-138]
  partial: []
---/ci---
2026-07-29 20:32:59 +00:00
Jon Chery 79e7a4a304 docs(P03): complete lifecycle-script-arg-cleanup phase (v1.13.6)
---ci---
project: acdl
phase: 3
milestone: v1.14
status: complete
requirements:
  covered: [REQ-137]
  partial: []
---/ci---
2026-07-29 20:24:13 +00:00
Jon Chery 8ae307affc docs(P02): complete static-assets-wiring-fix phase (v1.13.5)
---ci---
project: acdl
phase: 2
milestone: v1.14
status: complete
requirements:
  covered: [REQ-136]
  partial: []
---/ci---
2026-07-29 20:21:12 +00:00
Jon Chery 6e1a1bd7db docs(P01): complete adapter-dedup-diagnostic phase (v1.13.4)
---ci---
project: acdl
phase: 1
milestone: v1.14
status: complete
requirements:
  covered: [REQ-135]
  partial: []
---/ci---
2026-07-29 20:17:55 +00:00
Jon Chery 040abc0fb7 docs(ship): v1.13.3 complete — v1.14 pre-execution phase shipped (Gitea release id 255)
---ci---
project: acdl
phase: 0
milestone: v1.14
status: complete
---/ci---
2026-07-29 20:14:30 +00:00
Jon Chery 71bd61ceb1 docs(P00): complete pre-execution phase — v1.14 NFR Refinement milestone established
Phase 0 (pre-execution) complete. All pre-execution stages shipped:
SPECIFY -> CLARIFY -> RESEARCH -> IDEATE -> PLAN -> GRILL.

Established v1.14 NFR Refinement milestone (20 execution phases + 1
final). NFR milestone — final patch IS the release. Tags on v1.13.x
line: v1.13.3 (this phase) -> v1.13.24 (P21 = milestone release).

6 grill binding decisions (G-101..G-106) applied to PLAN.md. 1
escalation (E-001) auto-resolved at full autonomy (D-101).

---ci---
project: acdl
phase: 0
milestone: v1.14
status: complete
---/ci---
2026-07-29 20:13:49 +00:00
43 changed files with 479 additions and 2132 deletions
+1 -163
View File
@@ -570,166 +570,4 @@ emulator + live-AWS terraform init/validate/plan.
7. CloudFront OAC + WAF deprecated arg names (AWS provider v5):
`signing_behavior`, `signing_protocol`, `origin_access_control_id`,
`s3_origin_config.origin_access_identity`, `origin_id`, `rule`
(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.0v1.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 P2P4; 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.
(singular), `scope=CLOUDFRONT` (uppercase).
+1 -128
View File
@@ -243,131 +243,4 @@ Compared with `.ciagent/` files:
added a v1.10 addendum section covering all 4 new subsystems + the
7 adapter defect fixes. Verified all v1.10 components now referenced.
## 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 120:** 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 P0P21, 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.11v1.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 P0P21, 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/00phase/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 120 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.
## Audit result: PASS
+3 -4
View File
@@ -1,9 +1,8 @@
{
"phase": 0,
"stage": "ship",
"milestone": "v1.15",
"stage": "complete",
"milestone": "v1.14",
"phase_role": "pre_execution",
"attempts": 0,
"updated_at": "2026-07-30T00:05:00Z",
"milestone_complete": false
"updated_at": "2026-07-29T20:30:00Z"
}
+3 -3
View File
@@ -1,8 +1,8 @@
# ACDL AWS Cost Report (v1.0 → v1.14)
# ACDL AWS Cost Report (v1.0 → v1.10)
> **Query date:** 2026-07-29 (updated v1.14 P19)
> **Query date:** 2026-07-28
> **Source:** AWS Cost Explorer (`ce:GetCostAndUsage`)
> **Window:** 2026-07-21 → 2026-07-29 (v1.0 ship → v1.14 active)
> **Window:** 2026-07-21 → 2026-07-28 (v1.0 ship → v1.10 complete)
> **Account:** 581513795199 (us-east-1)
> **Closes:** G-008 (no cost documentation despite live AWS resources)
-266
View File
@@ -6,13 +6,7 @@
Two escalations must be resolved before the leadership pitch:
- **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.
**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.11v1.14.
The project is reclassified as an **OSS reference implementation** (G-003),
not a sponsored product. The grill's sponsor/ROI/budget/timeline axes apply
@@ -310,263 +304,3 @@ autonomy with assumption logging.
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 P0P4 —
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 (P2P4) + the migration guide (P1). The top
3 assumptions: A1 (no live apply — conf 0.9, verified by the established
v1.11v1.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 P0P4.
(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 P2P4 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 (P2P4) 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 P0P4 (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.
+1 -52
View File
@@ -148,55 +148,4 @@ 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
across territories is likely (e.g. backend + general on the adapter +
`run_platform.sh` boundary; data + general on the examples + pipeline
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.
boundary); `warn` keeps it frictionless.
+353 -309
View File
@@ -1,349 +1,393 @@
---
phase: P0
name: pre-execution
milestone: v1.15
requirements: [REQ-155, REQ-156, REQ-157, REQ-158, REQ-159, REQ-160, REQ-161, REQ-162, REQ-163, REQ-164]
milestone: v1.14
requirements: [REQ-135, REQ-136, REQ-137, REQ-138, REQ-139, REQ-140, REQ-141, REQ-142, REQ-143, REQ-144, REQ-145, REQ-146, REQ-147, REQ-148, REQ-149, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154]
wave: 0
depends_on: []
---
# v1.15 — Nova Rebrand Plan (4 execution phases + 1 final)
# v1.14 — NFR Refinement Plan (20 execution phases + 1 final)
**Milestone:** v1.15 (Nova Rebrand — Major/breaking)
**Type:** Major (breaking — consumer path, env vars, SSM path, tag keys,
AWS resource names all change). Per the branch-strategy precedent
(v1.10.2 → v1.11.0, v1.9.x → v1.10.0 — breaking/feature milestones tag
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` (P1P4) → `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>`
**Milestone:** v1.14 (NFR — bug fixes, security, stubs, tests, docs)
**Type:** NFR (all phases fix/test/docs/chore/refactor). Final patch IS
the release. Tags: `v1.13.3` (P0) → `v1.13.4..v1.13.23` (P1P20) →
`v1.13.24` (P21 = milestone release).
**Branch:** `milestone/v1.14-refinement``phase/NN-<slug>`
## Wave ordering (D-098 v1.15 analogue)
## Wave ordering (D-098)
- **Wave 1 (P1):** docs/decks/prose — no runtime impact; establishes
the Nova vocabulary + ships the consumer migration guide. REQ-155,
REQ-156, REQ-157. Independent (first phase).
- **Wave 2 (P2):** code + env vars (dual-read) + consumer path —
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 P1P4.
- **Wave 1 (P1P6):** bug fixes. P1→P2 sequential (composition depends
on dedup correctness); P3P6 independent. **G-105: full regression
gate run after P4** (validates the hardened gate before W2).
- **Wave 2 (P7P12):** security. P8→P9 sequential (IAM ARNs reference
externalized account ID); rest independent. **G-106: mid-milestone
regression-gate checkpoint after P12** (offline gate run; non-Verified
halts W3 until fixed).
- **Wave 3 (P13P17):** stub/test/CI/hygiene. P15 depends on P7
(hardened errors before script tests); P17 depends on P14 (both touch
config.json); P13 independent.
- **Wave 4 (P18P20):** standards/docs/VPC. P19 depends on P1P18
(reflects all prior phases); P18 + P20 independent.
## Execution approach
Each phase: EXECUTE (persona-assigned task groups) → VERIFY (4 layers +
regression gate stays 16/16) → SHIP (patch tag on v1.14.x line). Phase
regression gate at milestone complete) → SHIP (patch tag). Phase
boundary checkpoint resets context. The execute workflow reads this
PLAN.md + ROADMAP.md §v1.15 + PERSONAS.md §v1.15 for task decomposition.
**Binding constraint (capability gate):** the regression gate
(CAP-001..CAP-016, `scripts/run_regression.sh`) MUST stay 16/16 Verified
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.
PLAN.md + ROADMAP.md §v1.14 + PERSONAS.md for task decomposition.
---
## Wave 1 — Docs / Decks / Prose (P1)
## Wave 1 — Bug Fixes (P1P6)
### P1 — docs-decks-prose (REQ-155, REQ-156, REQ-157)
### P1 — adapter-dedup-diagnostic (REQ-135)
**Persona:** backend-engineer
**Territory:** `adapters/terraform/adapter.py`
**Tasks:**
1. In the dedup loop (`adapter.py:159-170`), when `tf_dir` is `None`,
raise `ValueError(f"no terraform_dir in registry for module
{module}")` instead of silently skipping.
2. Verify registered-module dedup behavior preserved (multi-resource L1s
still merge into one `module "x" { ... }` block).
3. Run `pytest tests/test_adapter.py` + `run_ci.sh`.
### P2 — static-assets-wiring-fix (REQ-136)
**Persona:** data-engineer
**Territory:** `modules/l2/static-assets/`
**Tasks:**
1. Wire `default_ttl`/`max_ttl`/`price_class`/`viewer_protocol_policy`
in `composition.json` to the cloudfront child's inputs.
2. Add a `waf_enabled` feature flag (default true) to the
static-assets composition; make the WAF child conditional on it.
3. Update `examples/complex.yml` to set `waf_enabled: true` + non-default
TTLs so it resolves to a different resource set than `simple.yml`.
4. Run `pytest` + `run_ci.sh`.
### P3 — lifecycle-script-arg-cleanup (REQ-137)
**Persona:** backend-engineer
**Territory:** `scripts/run_l2_lifecycle_*.sh`
**Tasks:**
1. Remove the `[ci-vpc-outputs.json]` token from the usage strings of
`run_l2_lifecycle_test.sh` + `run_l2_lifecycle_destroy.sh`, OR add a
comment documenting the L2-uses-remote-state design + parity reason.
2. Run `pytest` + `run_ci.sh`.
### P4 — regression-gate-evidence-hardening (REQ-138)
**Persona:** backend-engineer
**Territory:** `core/regression_verify.py`, `.ciagent/CAPABILITY_INVENTORY.md`
**Binding decisions:** G-105 (gate must pass clean post-P4 before W2)
**Tasks:**
1. Add a `terraform validate` step to
`_check_lifecycle_module_terraform` (or document why it's too slow +
fall back to a `terraform fmt -check` syntax probe).
2. Tighten CAPABILITY_INVENTORY + docstrings to "offline proxy; live
apply/modify/destroy verified by the modules-lifecycle workflow run,
not by this gate."
3. **Run the full regression gate immediately after P4 lands** (G-105).
Gate must pass clean before W2 begins.
4. Run `pytest` + `run_ci.sh`.
### P5 — adapter-behavior-tests (REQ-139)
**Persona:** backend-engineer
**Territory:** `tests/test_adapter.py`
**Tasks:**
1. Add `test_adapter_dedup_merges_same_module` — two resources with the
same `module` collapse to one `module "<first_id>" { ... }` block with
merged inputs.
2. Add `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.
3. Run `pytest` + `run_ci.sh`.
### P6 — alb-name-prefix-fix (REQ-140)
**Persona:** data-engineer
**Territory:** `modules/l1/alb/terraform/main.tf`
**Tasks:**
1. Change `name_prefix = "tg-ci-"` to `name_prefix = "${var.name}-"` so
the consumer's name prefixes the target group.
2. Run `terraform validate` in the alb module dir standalone.
3. Run `pytest` + `run_ci.sh`.
---
## Wave 2 — Security (P7P12)
### P7 — swallowed-error-hardening (REQ-141)
**Persona:** backend-engineer
**Territory:** `core/local_emulators.py`, `core/lambda/contract_ingestor.py`,
`terraform/bootstrap/create_state_backend.py`, `core/output_publisher.py`,
`terraform/bootstrap/apply_iam_baseline.py`
**Tasks:**
1. `local_emulators.py:374` — narrow `except Exception: pass` to catch
`AttributeError`/`TypeError` (monkeypatch setup); log + re-raise if
patching fails (prevents network egress).
2. `contract_ingestor.py:157` — catch `urllib.error.URLError`/
`HTTPError` specifically; log the search failure; keep `existing = []`
only on `404`/network, re-raise on auth errors.
3. `create_state_backend.py:51` — catch `ClientError` with
`NoSuchBucket`/`404` error code; re-raise on permissions/network.
4. `output_publisher.py:100,168` — catch `ClientError`/`HTTPError`
specifically; log with context.
5. `apply_iam_baseline.py:78` — catch `NoSuchEntityException` on
old-version delete; re-raise on other errors.
6. Run `pytest` + `run_ci.sh`.
### P8 — account-id-externalization (REQ-142)
**Persona:** backend-engineer + data-engineer
**Territory:** `adapters/terraform/adapter.py`, `terraform/bootstrap/`,
`scripts/push_consumer_image.py`, terraform resource ARNs
**Binding decisions:** G-101 (grep excludes backend blocks), G-102
(fallback bound to live account ID + workflow env wiring)
**Tasks:**
1. `adapter.py:125,140` — read `ACDL_AWS_ACCOUNT_ID` env; build the
state-bucket name dynamically. **Fallback constant = `581513795199`**
(the live account ID, NOT a placeholder — G-102). Documented for
offline tests.
2. `apply_iam_baseline.py:33`, `create_state_backend.py:33,35` — read
from env (same fallback).
3. `push_consumer_image.py:32` — read from env.
4. Terraform: use `data.aws_caller_identity.current.account_id` for
**resource ARNs** in `spike_runner_policy.json` + resource names.
**Exclude terraform `backend "s3"` blocks** (`terraform/*/terraform.tf`,
`terraform/ci-vpc/main.tf`, `terraform/platform/main.tf`,
`terraform/microservice/terraform.tf`) — backend `bucket` args are
static-config-only, evaluated pre-init (G-101). Leave backend blocks
literal or move to `terraform init -backend-config` (separate change,
not in P8 scope).
5. **Lifecycle workflow env wiring (G-102):** the `modules-lifecycle.yml`
full-mode jobs must set `ACDL_AWS_ACCOUNT_ID` from
`aws sts get-caller-identity --query Account --output text` before
any `run_platform.sh`/lifecycle invocation. No full-mode run proceeds
with the env unset.
6. Run `pytest` + `run_ci.sh`; verify
`grep -rn "581513795199" adapters/ scripts/ terraform/bootstrap/ core/`
returns 0 hits (excluding tests + docs + terraform backend blocks).
### P9 — iam-policy-least-privilege (REQ-143)
**Persona:** data-engineer
**Territory:** `terraform/bootstrap/spike_runner_policy.json`,
`tests/test_iam_policy_baseline.py`, `modules/l1/*/terraform/main.tf`,
`modules/l2/*/composition.json`
**Binding decisions:** G-104 (verify acdl-* naming before merge)
**Tasks:**
1. Scope `iam:CreateRole` etc. (line 236) to
`arn:aws:iam::*:role/acdl-*`.
2. Scope KMS (line 218) to `arn:aws:kms::*:key/acdl-*` (or
`alias/acdl-*`).
3. CloudFront (line 117) + WAFv2 (line 129) remain `Resource: "*"` with
a documented global-ARN constraint (CloudFront ARNs are global;
cannot be account-scoped — G-104).
4. **Verify acdl-* naming (G-104):** grep/audit
`modules/l1/*/terraform/main.tf` + `modules/l2/*/composition.json`
for every IAM role + KMS key name created by the lifecycle pipeline.
If any non-`acdl-*` name is found, rename the resource or widen that
one statement (documented).
5. Add a regression test in `test_iam_policy_baseline.py` asserting no
new `Resource: "*"` on non-global actions.
6. Run `pytest` + `run_ci.sh`.
### P10 — contract-ingestor-identity-validation (REQ-144)
**Persona:** backend-engineer
**Territory:** `core/lambda/contract_ingestor.py`, `tests/test_contract_ingestor.py`
**Tasks:**
1. Add `contractId` format validation (regex, ≤64 chars).
2. Add `environment` enum validation (dev/qa/prod/dr).
3. Add `error` length cap (truncate `stackTrace` at a reasonable limit).
4. Document the ABAC reliance in the `_validate_caller_identity`
docstring + add a note to ARCHITECTURE.md (P19 will land it).
5. Add a spoofing-resistance test (caller submits a `consumerRepo` they
don't own → rejected if ABAC misconfigured; documented best-effort).
6. Run `pytest` + `run_ci.sh`.
### P11 — schema-input-validation-hardening (REQ-145)
**Persona:** backend-engineer
**Territory:** `schemas/contract.schema.json`, `schemas/environment.schema.json`,
`tests/test_environment_schema.py`, `tests/test_contract_schema.py`
**Tasks:**
1. Add `"additionalProperties": false` to both schemas' top-level
objects.
2. Add `maxItems`/`maxProperties` bounds to `infrastructure` map +
`monitored_endpoints` array.
3. Add `pattern` validation for `state_backend.bucket` (S3 naming
rules: lowercase, 3-63 chars, no underscores).
4. Add `pattern` validation for `runner_role_arn` (ARN format).
5. Add `pattern` validation for `vpc_cidr` (CIDR format).
6. Add tests asserting rejection of undocumented fields + malformed
values.
7. Run `pytest` + `run_ci.sh`.
### P12 — gitignore-credential-hygiene (REQ-146)
**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
**Territory:** `.gitignore`, `tests/test_no_secrets_tracked.py`
**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
P2P4: (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 (P2P4) + 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`).
1. Add credential-pattern catch-all to `.gitignore`:
`*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.cer`, `*.crt`, `*.jks`.
2. Create `tests/test_no_secrets_tracked.py` — runs
`git ls-files | grep -E '\.(pem|key|p12|pfx|cer|crt|jks)$'` and
asserts 0 hits.
3. Run `pytest` + `run_ci.sh`.
---
## Wave 2Code / Env Vars / Consumer Path (P2)
## Wave 3Stub / Test / CI / Hygiene (P13P17)
### P2code-envvars-consumer-path (REQ-158, REQ-159, REQ-160)
**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`
### P13kyverno-kube-version-resolution (REQ-147)
**Persona:** backend-engineer
**Territory:** `adapters/kyverno/kyverno_adapter.py`, `tests/test_kyverno_adapter.py`
**Binding decisions:** G-103 (removal+documentation path, NOT implementation)
**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.
1. **Remove the `--kube-version` flag** from
`kyverno_adapter.py:11,115-116` (G-103 — implementing version-aware
policy selection would be a new feature, violating D-095).
2. Add a docstring documenting the deferral to the GitOps reconciler
roadmap (D-053): the Kyverno adapter is inactive for Terraform-only
stacks; `--kube-version` will be relevant when the GitOps reconciler
emits K8s manifests.
3. Update `test_kyverno_adapter.py` to remove the `--kube-version` test
cases + assert the flag is absent.
4. Run `pytest` + `run_ci.sh`.
### P14 — orphan-artifact-and-dead-config-cleanup (REQ-148)
**Persona:** lead-developer
**Territory:** `scripts/__pycache__/`, `pyproject.toml`, `.ciagent/config.json`
**Tasks:**
1. Delete the orphan
`scripts/__pycache__/verify_deploy_microservice.cpython-312.pyc`.
2. Fix `pyproject.toml` coverage source: `acdl_platform``core`.
3. Bump `pyproject.toml` version `1.3.0` → current (v1.14).
4. Remove dead JS allowlist entries from `config.json`
`bash_allowlist.allowed_commands` (npm/node/npx/pnpm/yarn/jest/eslint/
tsc/prettier — no package.json).
5. Run `pytest` + `run_ci.sh`.
### P15 — untested-scripts-coverage (REQ-149)
**Persona:** backend-engineer
**Territory:** `tests/` (new test files for 7 scripts)
**Tasks:**
1. `tests/test_seed_uptime_monitors.py` — mock the uptime-kuma API;
assert monitor creation from a JSON file.
2. `tests/test_push_consumer_image.py` — mock `subprocess.run` (docker
login/build/push) + boto3 ECR; assert the flow.
3. `tests/test_sync_to_gl.sh` (shell test) — dry-run mode; assert the
copy + push commands are constructed correctly.
4. `tests/test_post_stage_comment.sh` (shell test) — no-op when not in
a PR context; assert the `gh api` call structure when in PR.
5. `tests/test_rotate_spike_key.sh` (shell test) — mock `aws iam`;
assert deactivate/create/update-secret flow.
6. `tests/test_create_state_backend.py` — mock boto3 S3/DynamoDB;
assert idempotent creation.
7. `tests/test_create_iam_user.py` — mock boto3 IAM; assert idempotent
user/policy/key creation.
8. Run `pytest` + `run_ci.sh`.
### P16 — workflow-parity-and-script-flags (REQ-150)
**Persona:** backend-engineer
**Territory:** `.gitea/workflows/`, `scripts/rotate_spike_key.sh`,
`scripts/sync_to_gl.sh`
**Tasks:**
1. Either mirror the 4 GitHub-only workflows (patterns-plan,
platform-test, primitives-plan, release) to `.gitea/workflows/`, or
add a README documenting the Gitea limitation (Gitea runners don't
use release/primitives-plan/patterns-plan; release is GitHub-only by
design).
2. Add `set -euo pipefail` to `rotate_spike_key.sh` (currently only
`set -u`).
3. Add `set -euo pipefail` to `sync_to_gl.sh` (currently no `set`
flags).
4. Run `pytest` + `run_ci.sh`.
### P17 — config-and-persona-hygiene (REQ-151)
**Persona:** lead-developer
**Territory:** `.ciagent/config.json`, `.ciagent/PERSONAS.md`
**Tasks:**
1. Mark `frontend-engineer` persona `active: false` in `config.json`
`personas.personas[]` (PERSONAS.md:80 already says inactive).
2. Fix `branching_strategy: "phase"` — either change to `"flat"` or
document that the field is advisory + the project uses flat workflow
(committed directly to main per established convention).
3. Configure `ollama-cloud` backend: set `base_url` to the actual
endpoint OR add a comment documenting why it's intentionally unset
(the runtime uses the `glm-5.2` model via the opencode backend, not
the `llm_backends` config).
4. Run `pytest` + `run_ci.sh`.
---
## Wave 3 — SSM Path + Tag Keys (P3)
## Wave 4 — Standards / Docs / VPC (P18P20)
### P3ssm-tagkeys (REQ-161, REQ-162)
**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
### P18module-standards-consistency (REQ-152)
**Persona:** data-engineer
**Territory:** `modules/STANDARDS.md`, `modules/l1/{ecr,ecs-cluster,rds}/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.
1. Either add `locals.tf` to `ecr`, `ecs-cluster`, `rds` (extract
inlined locals from `main.tf`), OR reconcile STANDARDS §9.4 to
explicitly allow inlining for trivial single-resource modules.
2. Remove the stale `TYPE_MAP` reference in STANDARDS §8 (deleted in
the v1.11 stateless rewrite).
3. Run `pytest` + `run_ci.sh`.
### P19 — documentation-sync-v1.14 (REQ-153)
**Persona:** lead-developer
**Territory:** `.ciagent/ARCHITECTURE.md`, `docs/`, `README.md`,
`.ciagent/COST.md`, `.ciagent/GRILL.md`, `.ciagent/IAM_POLICY.md`,
`docs/presentations/`
**Tasks:**
1. ARCHITECTURE.md: add v1.11 addendum (stateless adapter, platform VPC,
ACDL_LIFECYCLE_MODE), v1.12 addendum (CAP-013 fix, plan-only
default), v1.13 addendum (config.json schema migration, badge
cleanup, platform-architecture diagram), v1.14 addendum (all 20
phases). Record D-083 deferral explicitly.
2. Bump stale `@v1.6``@v1.9` `@v1.13` across `README.md:225`,
`docs/consumer-guide.md` (12 sites), `docs/architecture.md:233`,
`docs/pipeline/versioning.md:29`, `docs/pipeline/index.md:42`.
3. Sync decks to v1.13.2 reality (version refs, capability claims).
4. Update COST.md window to v1.11v1.14 (lifecycle pipeline live-runs +
teardown).
5. Resolve G-005/G-008 in GRILL.md (CAP-017..022 now Verified via
lifecycle pipeline; COST.md now exists + covers v1.11+).
6. Update IAM_POLICY.md for v1.12/v1.13/v1.14 (plan-only default,
config.json schema, v1.14 IAM scoping from P9).
7. Run `pytest` + `run_ci.sh`; verify
`grep -rn "@v1\.[6-9]" docs/ README.md` returns 0 hits.
### P20 — platform-vpc-parameterization (REQ-154)
**Persona:** data-engineer
**Territory:** `terraform/platform/main.tf`
**Tasks:**
1. Add a `vpc_cidr` variable (default `10.0.0.0/16`); replace the
hardcoded `cidr_block`.
2. Replace `count = 2` subnets with
`count = length(data.aws_availability_zones.available.names)`.
3. Add a `data "aws_availability_zones" "available" {}` block.
4. Document the `0.0.0.0/0` ingress on port 80 (ALB-fronted, acceptable
for a public-facing service; add a comment).
5. Run `terraform validate` + `pytest` + `run_ci.sh`.
---
## Wave 4 — AWS Resource Name Migration (P4)
## Final Phase — P21 (review + audit + ship)
### 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)
**Persona:** lead-developer (review coordination) + ci-code-reviewer +
ci-debugger (audit)
**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 (P1P4 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.
---
1. Multi-persona code review across all v1.14 phases (P1P20). Auto-apply
P0 fixes; flag P1+ for post-hoc review. If P1+ found, fix in-phase.
2. Audit: reconstruction test (git log vs `.ciagent/` files), file
discipline, branch hygiene, commit discipline. Fix critical issues
in-phase.
3. Complete: update REQUIREMENTS.md (REQ-135..154 → complete),
ROADMAP.md (v1.14 complete), PROJECT.md.
4. Tag `v1.13.24` (IS the milestone release). Merge
`milestone/v1.14-refinement``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.
1. All 20 REQ-135..REQ-154 marked complete in REQUIREMENTS.md.
2. Review: 0 new P0; all P1-1..P1-5 + P2-1..P2-4 resolved.
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.
4. Regression gate (D-091) clean against the v1.14 state.
5. `pytest` passes; `run_ci.sh` exits 0; `run_platform.sh --check-only`
exits 0.
6. Tag `v1.13.24` created; milestone merged to main.
+1 -78
View File
@@ -1,14 +1,5 @@
# 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 P1P4;
> this header is updated in P1.
## Vision / Core Value
Consumers declare intent; the platform delivers safe production
@@ -920,72 +911,4 @@ D-095+ to continue from v1.10's D-094):
| D-098 | Wave ordering: W1 (P1P6 bug fixes), W2 (P7P12 security), W3 (P13P17 stub/test/CI/hygiene), W4 (P18P20 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. |
| 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. |
+20 -147
View File
@@ -684,26 +684,26 @@ in a 20-phase sweep.
| 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 |
| REQ-135 | P1 | pending |
| REQ-136 | P2 | pending |
| REQ-137 | P3 | pending |
| REQ-138 | P4 | pending |
| REQ-139 | P5 | pending |
| REQ-140 | P6 | pending |
| REQ-141 | P7 | pending |
| REQ-142 | P8 | pending |
| REQ-143 | P9 | pending |
| REQ-144 | P10 | pending |
| REQ-145 | P11 | pending |
| REQ-146 | P12 | pending |
| REQ-147 | P13 | pending |
| REQ-148 | P14 | pending |
| REQ-149 | P15 | pending |
| REQ-150 | P16 | pending |
| REQ-151 | P17 | pending |
| REQ-152 | P18 | pending |
| REQ-153 | P19 | pending |
| REQ-154 | P20 | pending |
### Out of Scope (v1.14)
- New features (feat phases). v1.14 is NFR-only.
@@ -713,130 +713,3 @@ in a 20-phase sweep.
- 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` (P1P4) → `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.
-153
View File
@@ -869,156 +869,3 @@ 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 P0P4
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.11v1.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`.
+1 -199
View File
@@ -1063,7 +1063,7 @@ Docs-only NFR patch (no code changes).
---
## v1.14 (complete — NFR Refinement: bug fixes, security, stubs, tests, docs, tag `v1.13.24`)
## v1.14 (active — NFR Refinement: bug fixes, security, stubs, tests, docs)
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
@@ -1432,201 +1432,3 @@ on the v1.13.x line: `v1.13.3` (P0) → `v1.13.4..v1.13.23` (P1P20) →
- 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` (P1P4) → `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 P2P4.
- **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.
+7 -10
View File
@@ -8,7 +8,7 @@
],
"active_project": "acdl",
"active_projects": ["acdl"],
"active_milestone": "v1.15",
"active_milestone": "v1.14",
"autonomy": {
"level": "full",
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
@@ -37,13 +37,14 @@
"escalate_high_severity": true,
"bash_allowlist": {
"allowed_commands": [
"npm", "node", "npx", "pnpm", "yarn",
"git", "ls", "cat", "head", "tail", "wc",
"echo", "mkdir", "cp", "mv", "rm", "touch",
"pwd", "which", "env", "printenv",
"python3", "pytest", "pip",
"terraform", "checkov",
"jest", "eslint", "tsc", "prettier",
"curl", "wget",
"docker", "docker-compose"
"docker", "docker-compose",
"ts-node", "tsx"
],
"max_output_bytes": 1048576,
"timeout_ms": 30000,
@@ -58,8 +59,7 @@
}
},
"git": {
"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.",
"branching_strategy": "phase",
"auto_commit": true,
"auto_push": true
},
@@ -125,7 +125,6 @@
},
"ollama-cloud": {
"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",
"model_profile": "quality",
"timeout_ms": 60000
@@ -192,11 +191,9 @@
{
"name": "frontend-engineer",
"domain": "frontend",
"active": false,
"frameworks": ["react", "next.js"],
"constraints": ["component-first", "server-components", "minimal-client-js"],
"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."
"territory": ["**/components/**", "**/pages/**", "**/hooks/**", "**/styles/**", "**/*.tsx", "**/*.css", "**/*.vue"]
}
]
},
-40
View File
@@ -1,40 +0,0 @@
# 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.
+1 -11
View File
@@ -18,14 +18,4 @@ terraform/bootstrap/.bootstrap_state.json
**/.terraform/
**/.terraform.lock.hcl
**/tfplan
**/*.tfstate*
# Credential patterns (v1.14, REQ-146)
*.pem
*.key
*.p12
*.pfx
*.cer
*.crt
*.jks
*.keystore
**/*.tfstate*
+1 -1
View File
@@ -222,7 +222,7 @@ The workflow implements the same stages as `pipelines/contract.yml`
(validate-contract → resolve-stack → security checks → infrastructure plan
→ policy checks → confidence → evidence event → apply). A consumer repo
invokes the reusable workflow via a **versioned tag** (floating MAJOR +
MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`). The workflow checks
MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.6`). 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 the consumer's
contract — the consumer never clones the platform repo or invokes its
+13 -8
View File
@@ -8,16 +8,13 @@ v1.9 (REQ-111): the translator is fleshed out — full PolicyReport →
PolicyCheckResult mapping with severity + skip-with-reason handling. It
remains inactive for Terraform-only stacks (guard preserved emits a
single SKIPPED `KYVERNO_INACTIVE_TF_STACK` record when no K8s manifests).
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.
A `--kube-version` stub is parsed but not yet used (for future GitOps).
D-053: the platform emits Terraform, not K8s manifests. This adapter
activates when the GitOps reconciler (roadmap) emits K8s manifests.
Sample policies are included as documentation at adapters/kyverno/policies/.
CLI: kyverno_adapter.py <policyreport.json> <contract-id>
CLI: kyverno_adapter.py <policyreport.json> <contract-id> [--kube-version <ver>]
"""
import datetime
@@ -103,7 +100,7 @@ def _emit_inactive_tf(contract_id):
}
def adapt(policyreport_json_path, contract_id):
def adapt(policyreport_json_path, contract_id, kube_version=None):
with open(policyreport_json_path, "r", encoding="utf-8") as fh:
data = json.load(fh)
out = []
@@ -115,6 +112,8 @@ def adapt(policyreport_json_path, contract_id):
out.append(_to_pcr(entry, contract_id))
if not out:
out.append(_emit_inactive_tf(contract_id))
# kube_version is parsed but not yet used (future GitOps reconciler).
_ = kube_version
return out
@@ -124,8 +123,14 @@ def adapt_inactive(contract_id):
if __name__ == "__main__":
kube_ver = None
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:
print("usage: kyverno_adapter.py <policyreport.json> <contract-id>", file=sys.stderr)
print("usage: kyverno_adapter.py <policyreport.json> <contract-id> [--kube-version <ver>]", file=sys.stderr)
sys.exit(2)
print(json.dumps(adapt(args[0], args[1]), indent=2))
print(json.dumps(adapt(args[0], args[1], kube_version=kube_ver), indent=2))
+2 -4
View File
@@ -112,8 +112,6 @@ def adapt(stack_instance, out_dir):
stack_name = stack.get("name", "spike")
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 {\n'
' required_version = ">= 1.9, < 1.10"\n'
@@ -124,7 +122,7 @@ def adapt(stack_instance, out_dir):
' }\n'
' }\n'
' backend "s3" {\n'
f' bucket = "{state_bucket}"\n'
' bucket = "acdl-tfstate-581513795199-us-east-1"\n'
f' key = "spike/{stack_name}/{environment}/terraform.tfstate"\n'
' region = "us-east-1"\n'
' }\n'
@@ -139,7 +137,7 @@ def adapt(stack_instance, out_dir):
'data "terraform_remote_state" "platform" {\n'
' backend = "s3"\n'
' config = {\n'
f' bucket = "{state_bucket}"\n'
' bucket = "acdl-tfstate-581513795199-us-east-1"\n'
f' key = "{remote_state_key}"\n'
' region = "us-east-1"\n'
' }\n'
+11 -42
View File
@@ -17,7 +17,6 @@ requests. The invoke policy is scoped via ABAC (consumer repo identity).
import datetime
import json
import os
import urllib.error
import urllib.parse
import boto3
@@ -155,16 +154,7 @@ def _report_error(payload):
with urllib.request.urlopen(req, timeout=10) as resp:
search_result = json.loads(resp.read())
existing = search_result.get("items", [])
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)
except Exception:
existing = []
body = f"""## Deploy Failure Report
@@ -238,42 +228,21 @@ def _validate_caller_identity(event, payload):
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).
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", {})
caller_arn = identity.get("userArn", "")
if not caller_arn:
pass # no identity available — rely on IAM ABAC enforcement
return # no identity available — rely on IAM ABAC enforcement
payload_repo = payload.get("consumerRepo", "")
if payload_repo:
# consumerRepo must be org/repo format, <=128 chars
if "/" not in payload_repo or len(payload_repo) > 128:
raise ValueError(f"invalid consumerRepo format: {payload_repo!r}")
# v1.14 (REQ-144): contractId format validation
contract_id = payload.get("contractId", "")
if contract_id:
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]
if not payload_repo:
return
# Extract the session name or principal tag from the ARN. The ABAC policy
# scopes via aws:PrincipalTag/acdl:owner = <consumerRepo>. The Function URL
# 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
# repo identifier (org/repo format). Full enforcement is at the IAM layer.
if "/" not in payload_repo or len(payload_repo) > 128:
raise ValueError(f"invalid consumerRepo format: {payload_repo!r}")
def _validate_change_request(payload):
+2 -3
View File
@@ -371,9 +371,8 @@ class LocalLambdaStub:
return _FakeResponse(
json.dumps([{"number": 1, "title": "stub"}]).encode())
urllib.request.urlopen = _fake_urlopen
except (AttributeError, TypeError) as e:
import sys
print(f"WARNING: could not patch urlopen for local Lambda stub: {e}", file=sys.stderr)
except Exception:
pass
try:
event = {
+3 -7
View File
@@ -97,10 +97,8 @@ def publish_to_ssm(outputs, environment, contract_id):
Overwrite=True,
)
results[name] = param_name
except Exception as e:
# 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)
except Exception:
# Don't fail the pipeline if one output fails to publish
results[name] = None
return results
@@ -167,9 +165,7 @@ def post_github_comment(comment_text, token=None, repo=None, pr_number=None):
req.add_header("Accept", "application/vnd.github+json")
urllib.request.urlopen(req, timeout=10)
return True
except Exception as e:
import sys
print(f"WARNING: GitHub PR comment failed: {e}", file=sys.stderr)
except Exception:
return False
+2 -4
View File
@@ -421,10 +421,8 @@ def _check_s3_state_bucket() -> Tuple[Status, str]:
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_secret_access_key=env.get("AWS_SECRET_ACCESS_KEY"))
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
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)
s3.head_bucket(Bucket="acdl-tfstate-581513795199-us-east-1")
r = s3.list_objects_v2(Bucket="acdl-tfstate-581513795199-us-east-1", MaxKeys=5)
keys = [o["Key"] for o in r.get("Contents", [])]
return "Verified", f"state bucket exists, keys={keys}"
except Exception as e:
+1 -1
View File
@@ -230,7 +230,7 @@ change to the modules/stack/confidence/audit.
- A MAJOR bump requires a new registry entry (immutable publication); the
old entry enters a 12-month deprecation window.
- The central deploy pipeline is referenced by a floating MAJOR + MINOR tag
(e.g. `@v1.13`); patch fixes flow within the tag, breaking changes land
(e.g. `@v1.6`); patch fixes flow within the tag, breaking changes land
under the next MINOR tag.
See [Versioning](pipeline/versioning) for the consumer-facing details.
+12 -12
View File
@@ -19,7 +19,7 @@ definitions.
```mermaid
flowchart LR
A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: acdl/.github/workflows/deploy.yml@v1.13| B
A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: acdl/.github/workflows/deploy.yml@v1.9| B
B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -&gt; resolver -&gt; stack -&gt; adapter<br/>-&gt; security checks -&gt; infrastructure plan -&gt; policy checks<br/>-&gt; confidence -&gt; apply -&gt; evidence event| C
C["your resources in AWS"]
```
@@ -27,7 +27,7 @@ flowchart LR
## Versioning the `uses:` reference
The central deployment pipeline is **always versioned with floating MAJOR
and MINOR tags** (e.g. `acdl/pipelines/contract.yml@v1.13`). Version
and MINOR tags** (e.g. `acdl/pipelines/contract.yml@v1.9`). Version
constraints cannot be expressed inside the contract, so the tag in
`uses:` is the only immutability lever a consumer has. See
[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
prompt. See [Environments](environments/).
- **Authorization to reference the central pipeline.** Onboarding grants
your repo the right to `uses: acdl/.github/workflows/deploy.yml@v1.13`.
your repo the right to `uses: acdl/.github/workflows/deploy.yml@v1.9`.
Contact the platform team if you have not been onboarded.
## Step 1 — Create a consumer repo
@@ -94,7 +94,7 @@ ACDL deployment workflow with a **versioned tag** (floating MAJOR + MINOR):
```yaml
jobs:
deploy:
uses: acdl/.github/workflows/deploy.yml@v1.13
uses: acdl/.github/workflows/deploy.yml@v1.9
with:
contract: .acdl/contract.yml
environment: dev
@@ -140,7 +140,7 @@ name: microservice
| 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.13`). 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.9`). 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/). |
| `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). |
@@ -177,14 +177,14 @@ on:
branches: [main]
jobs:
deploy:
uses: acdl/.github/workflows/deploy.yml@v1.13
uses: acdl/.github/workflows/deploy.yml@v1.9
with:
contract: .acdl/contract.yml
```
That is the entire consumer-side workflow. When you push to `main`:
1. The platform runner resolves `uses: acdl/.github/workflows/deploy.yml@v1.13`
1. The platform runner resolves `uses: acdl/.github/workflows/deploy.yml@v1.9`
to the reusable workflow **at the pinned tag**.
2. A **platform-provided runner** checks out **your** repo.
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. |
| Stack schema | `schemas/stack.schema.json` | JSON Schema for the resolved stack instance. |
| Module catalog | [modules/](modules/) | All primitives and modules. |
| Sample contract | `contracts/static-assets.yaml` | The reference example contract (uses `@v1.13`). |
| Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.13`). |
| Sample contract | `contracts/static-assets.yaml` | The reference example contract (uses `@v1.9`). |
| Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.9`). |
| 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. |
| Angine adapter | `adapters/terraform/adapter.py` | Compiles stack instances to infrastructure. |
@@ -353,7 +353,7 @@ destruction:
use `mode: decommission` with the `changeRequestId` input:
```yaml
uses: acdl/.github/workflows/deploy.yml@v1.13
uses: acdl/.github/workflows/deploy.yml@v1.8
with:
contract: .acdl/contract.yml
mode: decommission
@@ -421,7 +421,7 @@ name: static-assets
```
**Shape 2 — single contract + `environment` workflow input:** the
reusable deploy workflow (`acdl/.github/workflows/deploy.yml@v1.13`)
reusable deploy workflow (`acdl/.github/workflows/deploy.yml@v1.9`)
declares an `environment` input. When non-empty, it overrides the
contract's `environment` field at load time (before interpolation), so
the same contract can be promoted by passing a different environment:
@@ -436,7 +436,7 @@ on: workflow_dispatch:
required: true
jobs:
deploy-qa:
uses: acdl/.github/workflows/deploy.yml@v1.13
uses: acdl/.github/workflows/deploy.yml@v1.9
with:
environment: qa
contract: .acdl/contract.yml
+1 -1
View File
@@ -39,7 +39,7 @@ It is exposed to consumer repos as a **reusable workflow**:
- `.github/workflows/deploy.yml` — GitHub Actions (production)
A consumer repo invokes the reusable workflow via a **versioned tag**
(floating MAJOR + MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`).
(floating MAJOR + MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.6`).
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
the consumer's contract. The consumer never clones the platform repo or
+1 -1
View File
@@ -26,7 +26,7 @@ tag** in a consumer's CI workflow definition:
```yaml
jobs:
deploy:
uses: acdl/.github/workflows/deploy.yml@v1.13
uses: acdl/.github/workflows/deploy.yml@v1.6
with:
contract: .acdl/contract.yml
```
+5 -10
View File
@@ -207,12 +207,9 @@ declares intra-refs from the subnet and route table to the VPC's
- `aws:wafv2:webacl`
- `aws:rds:instance`
- `aws:kms:key`, `aws:kms:alias`
- The engine adapter is a **stateless assembler** (v1.11, D-098): it reads
the registry, emits a root `main.tf` instantiating each L1 as
`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.
- The engine adapter's `TYPE_MAP` is the registry of stack types the
adapter can compile (see §8). A new stack type requires a `TYPE_MAP`
entry before the primitive can be deployed.
## 3. L2 Module Standards
@@ -583,10 +580,8 @@ must be checked before the module is registered and published.
### 9.4 Adapter (stateless assembler)
- [ ] The new primitive's `terraform/` subdir exists with
`versions.tf`/`variables.tf`/`main.tf`/`outputs.tf` and
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`.
`versions.tf`/`variables.tf`/`locals.tf`/`main.tf`/`outputs.tf` and
passes `terraform init + validate` standalone.
- [ ] `registry.json` has a `terraform_dir` field for the new primitive.
- [ ] No adapter code changes are needed (the adapter is generic; it
assembles any module with a `terraform_dir` in the registry).
+1 -1
View File
@@ -6,7 +6,7 @@ resource "aws_lb" "this" {
}
resource "aws_lb_target_group" "this" {
name_prefix = "${var.name}-"
name_prefix = "tg-ci-"
port = var.port
protocol = var.protocol
vpc_id = var.vpc_id
+1 -1
View File
@@ -18,7 +18,7 @@
"wires": [
{"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": "roles.inputs.role_name", "default": "acdl-app-role"},
{"from": "contract.inputs.name", "to": "roles.inputs.role_name", "default": "app-role"},
{"from": "contract.inputs.region", "to": "cluster.inputs.region"},
{"from": "contract.inputs.region", "to": "ecr.inputs.region"},
{"from": "contract.inputs.region", "to": "roles.inputs.region"},
+2 -2
View File
@@ -1,6 +1,6 @@
[project]
name = "acdl"
version = "1.14.0"
version = "1.3.0"
description = "Agentic Cloud Delivery Platform — consumers declare intent; the platform delivers safe production deployment."
requires-python = ">=3.10"
dependencies = [
@@ -28,7 +28,7 @@ filterwarnings = [
]
[tool.coverage]
run.source = ["core", "adapters"]
run.source = ["acdl_platform", "adapters"]
[build-system]
requires = ["setuptools>=68"]
+6 -10
View File
@@ -27,23 +27,20 @@
"type": "object",
"required": ["bucket", "lock_table"],
"properties": {
"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)."},
"bucket": {"type": "string", "description": "S3 state bucket name."},
"lock_table": {"type": "string", "description": "DynamoDB lock table name."}
},
"additionalProperties": false
}
},
"network": {
"type": "object",
"required": ["vpc_cidr", "azs"],
"properties": {
"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"}, "maxItems": 6, "description": "Availability zones (max 6)."}
},
"additionalProperties": false
"vpc_cidr": {"type": "string", "description": "VPC CIDR block."},
"azs": {"type": "array", "items": {"type": "string"}, "description": "Availability zones."}
}
},
"runner_role_arn": {
"type": "string",
"pattern": "^arn:aws:iam::[0-9]{12}:role/.+$",
"description": "The IAM role ARN surfaced to the consumer's repo via ABAC."
},
"autonomy": {
@@ -57,6 +54,5 @@
"maximum": 1,
"description": "The confidence gate threshold for this environment (dev 0.50, qa 0.75, prod 0.90, dr 0.95)."
}
},
"additionalProperties": false
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ import boto3
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
ENV_FILE = REPO_ROOT / ".env.secrets"
AWS_ACCOUNT_ID = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
AWS_ACCOUNT_ID = "581513795199"
AWS_REGION = "us-east-1"
ECR_REPO_NAME = "acdl-microservice"
IMAGE_TAG = "latest"
+1 -1
View File
@@ -13,7 +13,7 @@
#
# Spike scope (D-039): the spike user key is per-run-rotated; real OIDC is
# v1.2 (blocked on go-gitea/gitea#36988).
set -euo pipefail
set -u
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
ENV_FILE="$ROOT/.env.secrets"
+2 -4
View File
@@ -30,7 +30,7 @@ import boto3
ROOT = Path(__file__).resolve().parent.parent.parent
POLICY_PATH = ROOT / "terraform" / "bootstrap" / "spike_runner_policy.json"
ACCOUNT = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
ACCOUNT = "581513795199"
USER = "acdl-spike-runner"
POLICY_NAME = "acdl-spike-runner-policy"
POLICY_ARN = f"arn:aws:iam::{ACCOUNT}:policy/{POLICY_NAME}"
@@ -75,10 +75,8 @@ def apply_managed_policy(iam, policy_doc: str) -> str:
try:
iam.delete_policy_version(PolicyArn=POLICY_ARN, VersionId=default)
print(f"deleted old default version {default}")
except iam.exceptions.NoSuchEntityException:
pass # already deleted
except Exception as e:
print(f"WARNING: could not delete old version {default}: {e}")
print(f"could not delete old version {default}: {e}")
return POLICY_ARN
except iam.exceptions.NoSuchEntityException:
print(f"creating managed policy {POLICY_NAME}...")
+8 -12
View File
@@ -30,9 +30,9 @@ import boto3
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
ACCOUNT_ID = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
STATE_BUCKET = f"acdl-tfstate-{ACCOUNT_ID}-us-east-1"
STATE_BUCKET = "acdl-tfstate-581513795199-us-east-1"
OUTBOX_TABLE = "acdl-outbox"
ACCOUNT_ID = "581513795199"
def main():
@@ -48,16 +48,12 @@ def main():
try:
s3.head_bucket(Bucket=STATE_BUCKET)
print(f"s3: bucket {STATE_BUCKET} already exists")
except s3.exceptions.ClientError as e:
error_code = e.response.get("Error", {}).get("Code", "")
if error_code in ("404", "NoSuchBucket", "NotFound"):
kwargs = {"Bucket": STATE_BUCKET}
if REGION != "us-east-1":
kwargs["CreateBucketConfiguration"] = {"LocationConstraint": REGION}
s3.create_bucket(**kwargs)
print(f"s3: created bucket {STATE_BUCKET}")
else:
raise
except Exception:
kwargs = {"Bucket": STATE_BUCKET}
if REGION != "us-east-1":
kwargs["CreateBucketConfiguration"] = {"LocationConstraint": REGION}
s3.create_bucket(**kwargs)
print(f"s3: created bucket {STATE_BUCKET}")
# Enable versioning (idempotent)
s3.put_bucket_versioning(
Bucket=STATE_BUCKET,
+2 -5
View File
@@ -215,10 +215,7 @@
"kms:TagResource",
"kms:UntagResource"
],
"Resource": [
"arn:aws:kms:*:*:key/*",
"arn:aws:kms:*:*:alias/acdl-*"
]
"Resource": "*"
},
{
"Effect": "Allow",
@@ -236,7 +233,7 @@
"iam:TagRole",
"iam:UntagRole"
],
"Resource": "arn:aws:iam::*:role/acdl-*"
"Resource": "*"
}
]
}
+2 -13
View File
@@ -29,13 +29,6 @@ provider "aws" {
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
resource "aws_kms_key" "acdl_platform" {
description = "ACDL platform KMS key (DynamoDB SSE + SSM + Secrets Manager)"
@@ -259,7 +252,7 @@ output "acdl_sod_halt_topic_arn" {
# ---------------------------------------------------------------------------
resource "aws_vpc" "acdl_shared" {
cidr_block = var.vpc_cidr
cidr_block = "10.0.0.0/16"
tags = {
Name = "acdl-shared"
"acdl:owner" = "acdl"
@@ -270,7 +263,7 @@ resource "aws_vpc" "acdl_shared" {
}
resource "aws_subnet" "acdl_shared" {
count = length(data.aws_availability_zones.available.names)
count = 2
vpc_id = aws_vpc.acdl_shared.id
cidr_block = cidrsubnet(aws_vpc.acdl_shared.cidr_block, 8, count.index + 1)
availability_zone = data.aws_availability_zones.available.names[count.index]
@@ -324,10 +317,6 @@ resource "aws_security_group" "ecs" {
description = "Security group for ECS Fargate services (platform VPC)"
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 {
from_port = 80
to_port = 80
+1 -85
View File
@@ -361,88 +361,4 @@ class TestAdapterDedupRejectsUnregisteredModule:
"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
assert (tmp_path / "main.tf").exists()
+1 -40
View File
@@ -500,43 +500,4 @@ class TestValidateChangeRequest:
resp = ingestor.lambda_handler(event, None)
assert resp["statusCode"] == 200
body = json.loads(resp["body"])
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
assert body["action"] == "validate_change_request"
+1 -61
View File
@@ -96,64 +96,4 @@ def test_account_id_is_12_digits():
for env_file in ENV_FILES:
env = json.loads((ENV_DIR / env_file).read_text())
assert len(env["account_id"]) == 12
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)
assert env["account_id"].isdigit()
+1 -40
View File
@@ -176,43 +176,4 @@ class TestIAMPolicyBaseline:
res = s.get("Resource", "")
if isinstance(res, list):
res = " ".join(res)
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
assert res != "*", "iam:PassRole must not be granted to Resource: *"
+3 -5
View File
@@ -211,13 +211,11 @@ class TestFleshedOutTranslator:
assert pcrs[0]["result"] == "skipped"
assert "Terraform" in pcrs[0]["message"]
def test_kube_version_removed(self, tmp_path):
"""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)."""
def test_kube_version_parsed(self, tmp_path):
"""--kube-version is parsed but not yet used (future GitOps)."""
f = tmp_path / "k.json"
f.write_text(json.dumps({"results": [
{"policy": "p", "rule": "r", "severity": "low", "result": "pass", "resource": "x"},
]}))
results = adapt(str(f), "c8")
results = adapt(str(f), "c8", kube_version="1.28")
assert len(results) == 1
-35
View File
@@ -1,35 +0,0 @@
"""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}"
-159
View File
@@ -1,159 +0,0 @@
"""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()