Compare commits

..

19 Commits

Author SHA1 Message Date
Jon Chery 7a4e43d11a docs(P17): complete config-and-persona-hygiene phase (v1.13.20)
---ci---
project: acdl
phase: 17
milestone: v1.14
status: complete
requirements:
  covered: [REQ-151]
  partial: []
---/ci---
2026-07-29 21:21:08 +00:00
Jon Chery cda06ab421 docs(P16): complete workflow-parity-and-script-flags phase (v1.13.19)
---ci---
project: acdl
phase: 16
milestone: v1.14
status: complete
requirements:
  covered: [REQ-150]
  partial: []
---/ci---
2026-07-29 21:18:15 +00:00
Jon Chery ec2311a3e0 docs(P15): complete untested-scripts-coverage phase (v1.13.18)
---ci---
project: acdl
phase: 15
milestone: v1.14
status: complete
requirements:
  covered: [REQ-149]
  partial: []
---/ci---
2026-07-29 21:13:03 +00:00
Jon Chery 4d694ba2e9 docs(P14): complete orphan-artifact-and-dead-config-cleanup phase (v1.13.17)
---ci---
project: acdl
phase: 14
milestone: v1.14
status: complete
requirements:
  covered: [REQ-148]
  partial: []
---/ci---
2026-07-29 21:10:20 +00:00
Jon Chery 3d9dd06411 docs(P13): complete kyverno-kube-version-resolution phase (v1.13.16)
---ci---
project: acdl
phase: 13
milestone: v1.14
status: complete
requirements:
  covered: [REQ-147]
  partial: []
---/ci---
2026-07-29 21:07:10 +00:00
Jon Chery b257846981 docs(P12): complete gitignore-credential-hygiene phase (v1.13.15)
---ci---
project: acdl
phase: 12
milestone: v1.14
status: complete
requirements:
  covered: [REQ-146]
  partial: []
---/ci---
2026-07-29 21:00:34 +00:00
Jon Chery 986171a165 docs(P11): complete schema-input-validation-hardening phase (v1.13.14)
---ci---
project: acdl
phase: 11
milestone: v1.14
status: complete
requirements:
  covered: [REQ-145]
  partial: []
---/ci---
2026-07-29 20:57:56 +00:00
Jon Chery 099ed015ac docs(P10): complete contract-ingestor-identity-validation phase (v1.13.13)
---ci---
project: acdl
phase: 10
milestone: v1.14
status: complete
requirements:
  covered: [REQ-144]
  partial: []
---/ci---
2026-07-29 20:52:42 +00:00
Jon Chery cc97a9308d docs(P09): complete iam-policy-least-privilege phase (v1.13.12)
---ci---
project: acdl
phase: 9
milestone: v1.14
status: complete
requirements:
  covered: [REQ-143]
  partial: []
---/ci---
2026-07-29 20:49:36 +00:00
Jon Chery c2ca0e4631 docs(P08): complete account-id-externalization phase (v1.13.11)
---ci---
project: acdl
phase: 8
milestone: v1.14
status: complete
requirements:
  covered: [REQ-142]
  partial: []
---/ci---
2026-07-29 20:46:28 +00:00
Jon Chery 225de0f613 docs(P07): complete swallowed-error-hardening phase (v1.13.10)
---ci---
project: acdl
phase: 7
milestone: v1.14
status: complete
requirements:
  covered: [REQ-141]
  partial: []
---/ci---
2026-07-29 20:43:08 +00:00
Jon Chery 69d8496107 docs(P06): complete alb-name-prefix-fix phase (v1.13.9)
---ci---
project: acdl
phase: 6
milestone: v1.14
status: complete
requirements:
  covered: [REQ-140]
  partial: []
---/ci---
2026-07-29 20:38:14 +00:00
Jon Chery 1aa525f234 docs(P05): complete adapter-behavior-tests phase (v1.13.8)
---ci---
project: acdl
phase: 5
milestone: v1.14
status: complete
requirements:
  covered: [REQ-139]
  partial: []
---/ci---
2026-07-29 20:35:53 +00:00
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
437 changed files with 12173 additions and 35098 deletions
+298 -384
View File
@@ -1,28 +1,16 @@
# Nova — Architecture
# ACDL — Architecture (v1.1 target)
> **Compressed.** The full v1.0v1.24 architecture history (v1.1 spike
> scope, v1.2 build-out, v1.8v1.16 addenda) is preserved verbatim at
> `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md`. This file retains the
> durable target architecture (§1–§12, the four layers + six cross-cutting
> concerns) + the three addenda that describe the **current state**:
> v1.11 (stateless adapter), v1.15 (Nova rebrand — current naming), and
> v1.17 (telemetry/observability layer + §12.7 Policy Engine Registry).
> Intermediate addenda (v1.1 spike scope, v1.2 build-out, v1.8/1.9/1.10/
> 1.12/1.13/1.14/1.16) describe evolved or superseded states and are
> preserved in the archive snapshot.
>
> Source of truth for **how**: `docs/architecture.md` (v0.2) is the
> upstream draft; this file is the Nova-repo operating copy, refined at
> phase boundaries. Where this file and `docs/vision.md` conflict, the
> vision wins.
> Target architecture for the real Agentic Cloud Delivery Platform.
> Source of truth for **how**: `docs/architecture.md` (v0.2) is the upstream
> draft; this file is the ACDL-repo operating copy, refined at phase
> boundaries. Where this file and `docs/vision.md` conflict, the vision wins.
## Status
Architecture is at **v0.2** upstream (`docs/architecture.md`). Milestone
v1.1 **finalized it to v1.0** in Phase 07 by resolving the 11 open
decisions (see `PROJECT.md` open-decision resolutions table). The v1.11
addendum (stateless adapter) and the v1.17 addendum (telemetry layer +
§12.7 Policy Engine Registry) record the current-state refinements.
Architecture is at **v0.2** upstream (`docs/architecture.md`). Milestone v1.1
**finalizes it to v1.0** in Phase 07 by resolving the 11 open decisions
(see `PROJECT.md` open-decision resolutions table). This file records the
locked commitments and the v1.1 spike scope.
## Overview
@@ -67,9 +55,8 @@ the same policy envelope, and the same evidence stream.
### Layer 1 — Foundational Primitives
Single-purpose, **engine-agnostic** primitive modules. L1 modules do
not compose with other L1s; L1 takes its environment as input. The L1
interface is defined against the **Target Stack IR**, not against
Terraform directly (the IR is shaped to round-trip to Terraform in v1,
per §12.1).
interface is defined against the **Target Stack IR**, not against Terraform
directly (the IR is shaped to round-trip to Terraform in v1, per §12.1).
- No inter-L1 references. L1 may call Terraform data sources.
- Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (W3.D).
@@ -81,8 +68,8 @@ Combine L1 primitives into deployable shapes. Each codebase maps to one
canonical L2 stack (`multiStack: true` only per W1.B). Shape X
(parameterized module) or Shape Y (thin-composition layer). Hierarchical
composition, max depth 5, only registered L1s. The thin-composition tree's
`wires` field is defined against the IR's relationship type, not a
Terraform module block.
`wires` field is defined against the IR's relationship type, not a Terraform
module block.
Pipeline quality checks: secrets-in-plaintext, public ingress, IAM
wildcard, KMS key reference, tag compliance, naming convention. Restricted
@@ -162,10 +149,6 @@ before contract submission ack); RTO = async worker's dead-letter recovery.
Single-region in v1. The outbox also stores per-contract QA and prod
approver identities (the only durable record outside GitHub's audit log).
> **v1.17 update:** the Decision Ledger (SQLite hash-chain, D-121) is the
> pilot's audit record. S3 Object Lock / JWS (D-083) is deferred — see
> the v1.17 addendum below.
### Human-in-the-Loop mechanics (§10)
Pre-execution gates. qa, prod, dr are PR-based attestation gates backed by
GitHub Environments with required reviewers. No partial deployment to roll
@@ -198,28 +181,28 @@ platform does not run the skill. Stateless agents, all state in the
platform. Skills are reviewed for sensitive data before release (Infra &
Ops owns the review; it is the mandatory release gate).
### Engine execution (§12) — the binding constraint
**Target Stack IR** (locked): an engine-neutral description of resources
### Angine execution (§12) — the binding constraint
**Target Stack IR** (locked): a engine-neutral description of resources
(typed inputs/outputs/NFRs), relationships (single parent per child),
composition (tree, max depth 5), and policy hooks. The L1 registry, L2
thin-composition tree, contract YML, and PolicyCheckResult schema are all
defined against the IR — none against any specific engine.
**Engine adapters** are the only engine-specific code. An adapter
compiles the IR into an engine execution plan. **v1 ships exactly one
**Angine adapters** are the only engine-specific code. An adapter
compiles the IR into a engine execution plan. **v1 ships exactly one
adapter: the Terraform adapter.** v2+ may add OpenTofu, Pulumi, K8s CRDs
without architectural change.
v1 reality: the IR is shaped to round-trip cleanly to Terraform (nearly
isomorphic). As more adapters appear, the IR gets more expressive and the
adapters gain translation logic; the L1 content, the YML standard, and
the thin-composition tree do not change.
adapters gain translation logic; the L1 content, the YML standard, and the
thin-composition tree do not change.
> **v1.11 update:** the Terraform adapter is now a **stateless assembler**
> (~80 lines, emits `module "x" { source }` blocks) — see the v1.11
> addendum below. The §12 "thin layer that translates IR → Terraform
> variable/output blocks" framing is superseded by the stateless-assembler
> model; the L1-owns-its-shape invariant is the new contract.
**Terraform adapter (v1):** translates IR-typed L1 interface → Terraform
`variable`/`output` blocks; IR-typed L2 thin-composition tree → Terraform
root module; IR-typed relationships → module references; emits a
`terraform plan` from the IR. The adapter is a thin layer; it does not own
L1/L2 content.
State storage: S3 (state) + DynamoDB (locking), cloud-managed,
single-region in v1.
@@ -228,10 +211,6 @@ Policy toolchain: **Checkov** for Terraform plan policy (the L2 checks +
tag/naming); **Kyverno** for K8s-native/platform-internal policy; **OPA**
reserved for cross-resource cases, explicitly last resort.
> **v1.25 update:** the policy toolchain is now unified under the
> swappable `PolicyEngine` protocol — see §12.7 below. Checkov and Wiz
> remain as raw-finding adapters feeding into kyverno-json meta-policies.
**Policy result normalization (§12.6):** the confidence signal consumes a
normalized `PolicyCheckResult` schema, not raw engine output.
@@ -263,397 +242,332 @@ Contract→IR resolution: the contract declares intent in IR-typed terms;
the pipeline resolves it to a target stack (list of L1 instances + inputs +
relationships); the Terraform adapter compiles the target stack to a plan.
---
## v1.1 spike scope
## v1.11 Addendum — Stateless Adapter + Pipeline-Driven Lifecycle Testing (current state)
The spike (Phases 0810) materializes the **minimum** that proves the IR
commitments hold (no polyglot mess):
**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.
- One L1: `l1-s3` (IR-typed interface; the only AWS resource in the spike).
- One L2 thin-composition: `l2-static-assets` (references `l1-s3` only).
- Terraform adapter: IR → `terraform plan` against AWS via OIDC.
- One contract submission → contract→IR → `terraform plan` → Checkov
`PolicyCheckResult` → confidence signal → evidence event to the DynamoDB
outbox.
- State: S3 + DynamoDB (real AWS, single-region).
**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.
Out of spike scope: full HITL matrix wiring, Kyverno, OPA, MCP skill
catalog, GitOps reconciler, multi-region, prod/dr environments, the 5-skill
L3B catalog. Those are post-spike (v1.2+) platform build-out.
**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.
## Gitea API surface (carried from v1.0, refined)
**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`).
| Capability | Gitea support | ACDL approach (v1.1) |
|------------|---------------|----------------------|
| Org-scoped repo create | `POST /api/v1/orgs/{org}/repos` | Used for any new repos |
| Native Pages | **None** | Serve `acdl-evidence` via raw file URLs (unchanged from v1.0) |
| Environments API | **None**; act_runner ignores `environment:` | Model HITL gates via `workflow_dispatch` approval inputs (v1.0 D-013 pattern) — **refined in Phase 07** for the real pre-execution gate model |
| `repository_dispatch` | Not supported | Cross-repo trigger via `workflow_dispatch` API (unchanged) |
| Reusable workflows | Supported | `acdl/.gitea/workflows/pipeline.yml` via `uses: ...@<ref>` |
| `id-token: write` / OIDC | **Not supported** (RESEARCH TARGET 1, conf 0.95). Gitea docs list `id-token` as an unsupported GitHub-only scope; open proposal go-gitea/gitea#33681; draft PR go-gitea/gitea#36988 unmerged. Even Gitea's own CI uses long-lived AWS keys (issue #37980). | **Spike waiver D-039:** per-run-rotated long-lived key (rotated after each run by `scripts/rotate_spike_key.sh`). Real OIDC deferred to v1.2, blocked on PR #36988. |
| `actions/configure-aws-credentials` | Unusable without OIDC | Spike uses static AWS creds from a (rotated) Gitea Actions secret via the `aws-actions/configure-aws-credentials@v4` `access-key-id`/`secret-access-key` inputs, or plain `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` env vars. v1.2 switches to `role-to-assume` when OIDC lands. |
**NOVA_LIFECYCLE_MODE (v1.12, REQ-134; renamed ACDL→NOVA in v1.15 P2).**
The lifecycle pipeline defaults to plan-only (fast, no AWS mutation, no
cost). A CI variable `NOVA_LIFECYCLE_MODE` (default `plan`) overrides to
`full` for the real apply→modify→destroy. (P2P4 dual-read fallback to
`ACDL_LIFECYCLE_MODE`; fallback removed in P5 per the v1.15 addendum.)
### Branch pinning rule (refined for W2.A)
---
- Dev/qa contracts reference the reusable workflow by **tag**
(`@v1.1-spike`).
- Prod-bound workflows reference by **SHA**; the platform CLI
(`platform/cli/resolve-tag.ts`, Phase 07) resolves the current tag to its
SHA. (Spike scope: the CLI is a stub; the real CLI lands in v1.2.)
## v1.15 Addendum — Nova Rebrand (current naming)
### Verification toolchain
**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. v1.15 tags run on the **v1.15.x
minor line**: `v1.15.0` (P0) → `v1.15.4` (P5 final = release). (G-104
binding.)
ACDL has no `package.json`. The verification gate substitutes:
- **typecheck:** `terraform validate`, `python3 -m py_compile`, JSON Schema
validation (`ajv` or `python -m jsonschema`) against `schemas/`.
- **test:** per-phase `scripts/verify_phaseNN.sh` (Phase 06: archive integrity;
Phase 07: schema validation + decision-resolution completeness; Phase 08:
OIDC assume-role + state backend; Phase 09: IR + L1 + adapter `terraform
plan`; Phase 10: end-to-end contract submission).
- **build:** `terraform init` (real build for the spike).
- See `PERSONAS.md` verification_toolchain.
### Naming conventions (rebranded — current)
## Build order (v1.1)
| 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 |
1. Phase 06 — archive demo, reorient repo.
2. Phase 07 — finalize architecture v1.0; author schemas + designs.
3. Phase 08 — AWS OIDC bootstrap (use temp key once, rotate).
4. Phase 09 — IR + `l1-s3` + Terraform adapter → `terraform plan`.
5. Phase 10 — `l2-static-assets` + contract→IR → end-to-end spike.
6. COMPLETE gate — review → ship `v1.2.0` → audit. **DONE.**
### Unchanged conventions (out of scope)
## v1.2 build-out 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`.
v1.2 takes the v1.1 spike (dev-only, `plan`-only, single S3 L1) to a real,
simpler, better-documented platform that delivers a microservice to AWS ECS
Fargate end-to-end. The locked architecture (§1–§12) is unchanged — v1.2
extends the *implementation*, not the design.
> The full migration ordering (P1P5), capability gate, and rollback
> runbook are preserved in `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md`
> §v1.15 Addendum.
### In scope (five axes, user-directed 2026-07-21)
---
1. **Re-evaluate the current state.** go-gitea/gitea#36988 (OIDC for Gitea
Actions) re-checked 2026-07-21: still **open** (last updated 2026-05-27,
not merged). Real OIDC remains deferred to v1.3+; v1.2 extends the D-039
per-run-rotated-key waiver as **D-047**. The waiver continues to satisfy
§12.5's *intent* (no *persistently* long-lived key): the spike key is
rotated after each run by `scripts/rotate_spike_key.sh`, and Phase 12
tightens the IAM scoping + rotation hygiene.
2. **NFR improvements on the existing spike.** Least-privilege IAM audit of
`spike_runner_policy.json`; idempotent `create_state_backend.py` /
`create_iam_user.py`; proper exit codes / error handling; P1-1 redaction
(two AWS access key IDs in `.ciagent/VERIFY.md` Phase 09 narrative).
3. **Streamline / simplify the current setup.** Consolidate
`run_spike_plan.sh` + `run_spike_e2e.sh` into one
`scripts/run_platform.sh`; remove dead code and stale `platform/` paths.
4. **README.md fully up to date on how the platform works.** Reflect v1.1
complete; document the actual spike flow, `scripts/run_platform.sh`, the
real repo layout, and the v1.2 objective.
5. **Bootstrap a consumer repo with a basic microservice deployed to ECS
end-to-end.** New Gitea repo `acdl-consumer-microservice` (org
`continuous-intelligence`); new IR-typed L1s (`l1-vpc`, `l1-ecs-cluster`,
`l1-ecs-service`, `l1-iam-role`, `l1-alb`, `l1-ecr`); new
`l2-microservice` thin-composition; one contract submission →
`terraform apply` (dev, autonomous per §10, confidence ≥ 0.50) → a live
ECS Fargate service serving HTTP 200 → evidence event to the DynamoDB
outbox → acdl-evidence timeline.
## v1.17 Addendum — Strategic Direction, Leadership Metrics & Unified Story (current telemetry layer)
### Angine extension (ECS Fargate)
The v1.17 milestone added a telemetry/observability layer, a Decision
Ledger, a metrics export pipeline, a unified narrative deck, and a
durable strategic-direction artifact. This addendum documents the
architecture; the full research findings are in RESEARCH.md §v1.17.
The Terraform adapter (§12) remains the only engine-specific code. v1.2
expands the adapter `TYPE_MAP` to cover the six new ECS-shaped IR resource
types. The L1 interface shape (IR-typed inputs/outputs/NFRs, registered in
`modules-ir/registry.json`) is unchanged — only the set of registered L1s
grows. The IR commitments (REQ-28) continue to hold: `modules-ir/`,
`schemas/`, `contracts/`, `core/confidence_signal.py`,
`core/contract_resolver.py`, `core/outbox_writer.py`
remain engine-agnostic.
### New components
### `terraform apply` (dev only)
| Component | Path | Purpose |
|-----------|------|---------|
| Event envelope | `core/metrics/event_envelope.py` | CloudEvents 1.0 envelope + `platform.*` semantic conventions (P1, REQ-187) |
| Per-run manifest writer | `core/metrics/run_manifest.py` | Emits `nova.run.started/completed/failed` events + writes `metrics/runs/<run_id>.json` (P1, REQ-187) |
| Decision Ledger (SQLite) | `core/metrics/decision_ledger.py` | Extends `outbox_writer.py` → SQLite append-only hash-chain table; `ai.decision.made` + `attestation.recorded` events + outcome backfill (P1, REQ-188, D-121) |
| Infracost post-processor | `core/metrics/infracost_adapter.py` | Runs Infracost on plan JSON; emits `nova.cost.estimated{delta_usd}` (P1, REQ-187, D-120) |
| Metrics collector | `core/metrics/collector.py` | Reads all grounded signals (files + events) → SQLite cold store at `metrics/nova_metrics.db` (P2, REQ-189) |
| PowerBI export | `core/metrics/powerbi_export.py` | Emits CSV/JSON views to `metrics/powerbi/` (fact + dim + 8 deferred placeholder views) (P3, REQ-190) |
| Metrics schemas | `schemas/metrics_*.schema.json` | Schemas for all event types + fact/dim tables (P1P2, REQ-187/189) |
| Metrics catalog | `docs/METRICS.md` + `docs/metrics/<kpi>.md` | Canonical catalog + per-KPI definition-of-success docs (P4, REQ-195, D-127) |
| Unified narrative deck | `docs/presentations/nova-no-humans-platform.md` | Merged deck: Problem→Vision→How→Proof→Roadmap; x3 arc at deck+slide level (P5, REQ-196/197, D-130) |
| Strategic direction | `.ciagent/NORTH_STAR.md` | PO-authored durable vision/objectives/anti-goals/targets; read by CIAgent in every future `/ci-run` (P0, REQ-185/186) |
v1.2 lifts the engine execution from `plan` to `apply` for the `dev`
environment only. Dev is autonomous per §10 (confidence ≥ 0.50, no HITL).
`apply` for qa/prod/dr remains HITL-gated and out of scope for v1.2. The
apply result (resources created, plan diff) is captured in the evidence
stream as a `terraform.apply` event.
### Modified components
### Out of scope for v1.2 (deferred to v1.3+)
| Component | Change | Phase |
|-----------|--------|-------|
| `core/outbox_writer.py` | Extended to emit to SQLite append-only hash-chain table (Decision Ledger); `ai.decision.made` + `attestation.recorded` events added (P1, D-121) | P1 |
| `scripts/run_platform.sh` | Per-run manifest writer invoked; `$WORK/*.json` persisted to `metrics/runs/`; Infracost post-processor invoked after plan (P1) | P1 |
| `core/hitl_gates.py` | Emits `attestation.recorded` event to Decision Ledger on qa/prod/dr gate (P1, D-132) | P1 |
| `core/confidence_signal.py` | Emits `nova.confidence.computed` + `nova.ai.decision.made` events (P1, D-122) | P1 |
| `adapters/terraform/policy/checkov_adapter.py` | Emits `nova.policy.evaluated` event (P1) | P1 |
| `core/regression_verify.py` | Emits `nova.capability.verified` event; CAP-023 (metrics collector) + CAP-024 (deck structure) added (P1, P6) | P1, P6 |
| `pyproject.toml` | `addopts` gains `--junitxml=metrics/test-results.xml` + `--json-report` (P1, D-120) | P1 |
| `docs/presentations/` | Two old decks retired (deleted); unified deck added (P5, D-130) | P5 |
| Feature | Reason |
|---------|--------|
| Real OIDC federation | go-gitea/gitea#36988 still open. v1.2 extends D-039 waiver (D-047); real OIDC is v1.3+. |
| Full HITL matrix wiring (qa/prod/dr) | v1.2 is dev-only autonomous `apply`; HITL wiring is v1.3. |
| Kyverno + OPA policy engines | v1.2 keeps Checkov only; Kyverno/OPA are v1.3. |
| MCP skill catalog + real L3B agent | v1.2 keeps the L3B stub; the 5-skill catalog is v1.3. |
| Audit ledger build-out (S3 Object Lock + JWS + async worker + DLQ + daily checkpoints) | v1.2 keeps the v1.1 outbox; the regulatory ledger is v1.3. |
| Multi-region state / outbox | Single-region in v1 (§9, §12.3); multi-region is v1.3+. |
| Prod/dr environments | v1.2 is dev-only; prod/dr are v1.3. |
| GitOps reconciler (ArgoCD/Flux) | v1.3+. |
### Telemetry/observability layer architecture (D-120)
## Build order (v1.2)
```
┌─────────────────────────────────────────────────────────────────────┐
│ Nova platform components (existing) │
│ run_platform.sh · confidence_signal · checkov_adapter · │
│ hitl_gates · regression_verify · outbox_writer · contract_ingestor │
└────────────────────┬──────────────────────────────────────────────┘
│ CloudEvents 1.0 envelope (new emitters, P1)
┌─────────────────────────────────────────────────────────────────────┐
│ metrics/events.jsonl (append-only CloudEvents log) │
│ metrics/runs/<run_id>.json (per-run manifests) │
│ metrics/decision_ledger.db (SQLite hash-chain, D-121) │
│ metrics/test-results.xml (junit, P1) │
└────────────────────┬──────────────────────────────────────────────┘
│ collector reads (P2)
┌─────────────────────────────────────────────────────────────────────┐
│ metrics/nova_metrics.db (SQLite cold store, D-126) │
│ fact_run · fact_capability · fact_policy_check · fact_confidence │
│ fact_test · fact_decision · fact_cost_estimate │
│ dim_capability · dim_milestone │
│ + 8 empty placeholder views (deferred metrics) │
└────────────────────┬──────────────────────────────────────────────┘
│ powerbi_export (P3)
┌─────────────────────────────────────────────────────────────────────┐
│ metrics/powerbi/ (CSV/JSON views, folder connector, D-129) │
│ → PowerBI dashboards (external) │
└─────────────────────────────────────────────────────────────────────┘
```
1. Phase 11 — re-eval #36988 + NFR audit + simplification findings + README rewrite.
2. Phase 12 — NFR harden + simplify (idempotent bootstrap, one `run_platform.sh`, IAM audit, redactions).
3. Phase 13 — six ECS L1s + adapter `TYPE_MAP` expansion.
4. Phase 14 — `l2-microservice` + contract schema extension.
5. Phase 15 — consumer repo + `terraform apply` (dev) → live ECS service.
6. Phase 16 — capstone e2e: consumer commit → live HTTP 200 → evidence → timeline.
7. COMPLETE gate — review → ship `v1.3.0` → audit.
**Hot path: deferred (D-126).** No live ops dashboard; SQLite is
cold-only (batch/historical). The hot path activates when live AWS is
re-provisioned (D-096 lift — the v1.26 milestone lifts this for the pilot
estate).
## v1.8 Architecture Addendum
### NORTH_STAR integration point (REQ-186)
> Milestone v1.8 (complete, tag `v1.8.0`). Adds encryption-by-default,
> deletion-protection-by-default, uptime monitoring, decommission alias,
> engineering standards, and path documentation.
`.ciagent/NORTH_STAR.md` is read by CIAgent in context-loading for all
future milestones. The integration mechanism: a reference from
`PROJECT.md` + `ARCHITECTURE.md` (this section) + a config entry in
`config.json` (`strategic_direction_file: ".ciagent/NORTH_STAR.md"`)
that the run workflow reads at SPECIFY. This ensures the strategic
direction survives across milestones without being overwritten by status
updates.
### New Primitives
### §12.7 — Policy Engine Registry (v1.25, REQ-291 — current)
- **`kms-key`** (`aws:kms:key`) — Per-stack customer-managed KMS key with
`enable_key_rotation = true`. One key per L2 deployment (no shared keys).
Wired into both L2 compositions as a child, with its `kms_key_arn` output
connected to all children's `kms_key_arn` input. Adapter emits
`aws_kms_key` + `enable_key_rotation`.
- **`uptime`** (`aws:ecs:uptime-service`) — Uptime-kuma on ECS Fargate with
a feature flag (`feature_flag_enabled`), monitored endpoints (HTTP/DNS/TCP),
alert channels (Teams/email/SMS/GitHub issues). Deployed by default after
any L2 module with a separate terraform state. When the feature flag is
false, the adapter emits no resources.
The policy-engine abstraction is first-class: a swappable `PolicyEngine`
protocol so the engine may change without touching the confidence
signal, the pipeline, or the `PolicyCheckResult` schema. This is the
**swap boundary** that keeps the platform's compliance posture
replaceable (Strategic Objective #2 — provable trust via a replaceable
substrate, not a vendor lock-in).
### Encryption by Default
```
contract.yml ─┐ ┌─→ list[PolicyCheckResult] ─┐
stack IR ─────┼─→ PolicyEngine.evaluate ├─→ list[PolicyCheckResult] ─┼─→ confidence_signal
plan JSON ────┤ (protocol) └─→ list[PolicyCheckResult] ─┘ (engine-agnostic,
PCR list ─────┘ unchanged)
┌─ KyvernoJsonEngine (shells to `kj scan`; engine: "kyverno")
└─ OpaEngine (future — same protocol; engine: "opa")
All 12 L1 primitives have `encryption_enabled` NFR (default true). Primitives
with at-rest data (s3, rds, ecr, ecs-service, ecs-cluster) have an optional
`kms_key_arn` input. The adapter emits encryption blocks (SSE-KMS for S3,
storage_encrypted for RDS, encryption_configuration for ECR) referencing the
per-stack CMK when provided. Managed KMS fallback with stderr warning for
standalone L1 deployments.
checkov/wiz ──→ raw findings ──→ (merged PCR list is the meta-policy payload)
```
### Deletion Protection by Default
**The protocol (`core/policy_engine.py`):**
```python
class PolicyEngine(Protocol):
@property
def name(self) -> str: ...
def is_configured(self) -> bool: ...
def evaluate(self, payload, policy_dir: Path, contract_id: str) -> list[dict]: ...
```
All 12 L1 primitives have `deletion_protection` NFR (default true). The
adapter emits `lifecycle { prevent_destroy = true }` when true. L2 modules
expose a `features.deletion_protection` flag (default true) propagated to
all children via the resolver. Setting `inputs.deletion_protection: false`
in the contract disables it for the whole stack.
**The registry** reads `config.json.policy.engine` (default
`"kyverno-json"`) and returns the active engine. A `NullEngine` is the
fallback when the `policy` key is absent (emits `SKIPPED` PCRs —
backward compatibility for tests that don't set the key). The
confidence signal is **untouched** — it already consumes
`list[PolicyCheckResult]` engine-agnostically (§12.6). v1.25 only
changes *who produces* the PCR list, not *what* the list is.
### Decommission Alias
**Engine enum reuse (D-116):** kyverno-json PCR records carry
`engine: "kyverno"` (no new enum value). The `engine` field records the
policy-engine *family*, not the specific binary. The K8s Kyverno adapter
and the kyverno-json engine are distinguished by `ruleId` prefix
(`KYVERNO_` vs `KJ_`) and `evidence` payload shape (`namespace`/`kind`
vs `assertion`/`jmespath`).
A `mode: decommission` on the deploy pipeline implements a 2-step destroy:
1. Disable deletion protection (resolve with `deletion_protection: false`,
terraform plan/apply, HITL SRE gate via GitHub environment).
2. Zero counts + destroy (`decommission_transform` zeroes all scalable counts,
terraform plan/apply, second HITL SRE gate).
**Defense-in-depth (D-119):** the declarative meta-policy
`block-on-any-critical` (asserts no PCR has `severity: critical` +
`result: fail`) is the *source of truth* for "critical = block". The
`confidence_signal.py` `PENALTY["critical"]: None` hard-override stays
as the *imperative* safety net — the meta-policy runs *before* the
confidence signal (produces PCRs that flow in), the hard-override runs
*inside* it (the last gate). Removing the hard-override would make the
"critical = block" guarantee depend on a single policy file — a
regression in provable trust.
CMDB validation via DynamoDB `acdl-change-requests` table. The Lambda
`validate_change_request` action queries the table and asserts
`status == "approved"` + `consumerRepo` match.
**Graceful degradation (D-120):** `KyvernoJsonEngine.is_configured()`
returns false when `which kj` is absent → `evaluate()` returns a single
`SKIPPED` PCR (`ruleId: "KJ_ENGINE_NOT_CONFIGURED"`). The platform
functions without the binary (the "platform functions without AI /
deterministic scripts" tenet holds — kyverno-json is deterministic, not
AI; the `is_configured()` guard ensures the platform runs even when the
binary is not installed).
### Adapter Expansion
### §12.8 — Pilot Estate (v1.26, live)
TYPE_MAP grew from 16 to 19 entries (+ `aws:kms:key`, `aws:kms:alias`,
`aws:ecs:uptime-service`). Specialized emission branches added for KMS key
rotation, S3 SSE-KMS configuration, uptime ECS Fargate task, and
`prevent_destroy` lifecycle on all resources.
The first real consumer estate is **`nova-blockchain-exchange`** — a
blockchain stock exchange on a homegrown Proof-of-Authority chain,
equities only, dev only (D-020/D-200/D-201). The live apply landed on
2026-08-19 against AWS account `581513795199`. This is the estate that
activated the Post-Pilot metric denominators (see `docs/METRICS.md`).
### Pipeline Stages
**The live apply (run id `blkex-pilot-apply-v0.2`):**
- Target: account `581513795199`, environment `dev`, autonomous (no
HITL — dev is the only autonomous environment, confidence ≥ 0.50).
- The microservice L2 composition (ECS Fargate running nginx) + the
`dynamodb` L1 (the `nova-blkex-ledger-dev` table) + the `s3` L1 (the
`nova-blkex-blocks-dev-581513795199-us-east-1` bucket).
- The platform VPC prerequisite (`vpc-0d7c8867e6cc080f1` + 6 subnets +
the ECS SG) is read via `terraform_remote_state` — the L2 composition
does not own the network boundary (the "restricted from
thin-composition" rule from §Layer 2).
- Confidence signal: score **0.800**, band **pass**; `human_override`
false; `escalation_reason` absent (clean apply).
The deploy pipeline grew from 8 to 9 stages (+ `deploy-uptime` after
`publish-outputs`). The `deploy-uptime` stage constructs a synthetic uptime
contract from the L2 stack outputs, resolves + adapts it to a separate
terraform state directory, and publishes the uptime URL via PR comment.
**The Gitea adapter (SPEC §10 Q1):** Gitea Actions does not support
cross-repo `uses:`, so the consumer's `deploy.yml` is an **inline
adapter** — `actions/checkout@v4` the consumer, `actions/checkout@v4`
`acdl/acdl` @ `ref: v1.25` into `platform/`, then
`bash platform/scripts/run_platform.sh ...`. The platform's own
`.github/workflows/deploy.yml` stays as the GitHub Actions reference
impl (the reusable `workflow_call` workflow). See `adapters/README.md`
§Consumers for the adapter note.
### Forge-Agnostic API URLs
**The Decision Ledger evidence stream** (the apply produces these
events in order):
```
nova.confidence.computed (score 0.800, band pass)
nova.ai.decision.made (decision_id blkex-pilot-apply-v0.2,
chosen_action pass, human_override false)
nova.attestation.recorded (dev = no HITL gate; the record exists,
the gate is a no-op in the autonomous env)
nova.run.completed (apply succeeded)
nova.outcome.backfilled (outcome pending → succeeded, REQ-317;
backfilled_at 2026-08-19T03:05:04Z)
```
The SQLite hash-chain is valid (0 breaks). S3 Object Lock / JWS
(D-083) stays deferred — the SQLite Decision Ledger is the pilot's
audit record (D-204).
The platform Lambda (`contract_ingestor.py`) reads `GITHUB_API_BASE` env
for forge-agnostic API URLs. GitHub uses `/search/issues`; Gitea uses
`/repos/{owner}/{repo}/issues`. Detection via `/api/v1` in the base URL.
**Live outputs (account 581513795199):**
- ALB DNS: `app-254671247.us-east-1.elb.amazonaws.com`
- ECS service: `arn:aws:ecs:us-east-1:581513795199:service/nova-cluster/nova-microservice`
- DynamoDB table: `nova-blkex-ledger-dev` (PK `block_index`, PAY_PER_REQUEST)
- S3 bucket: `nova-blkex-blocks-dev-581513795199-us-east-1` (versioning + SSE)
## v1.9 Addendum (2026-07-23)
The full evidence (every ARN, the confidence JSON, the Decision Ledger
rows, the module-completeness gaps the live apply uncovered) is in
`.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` (archived v1.27).
### New Components
### §12.9 — Secret Rotation (v1.26 P3 W7, SPEC §5.9 — current)
- **`core/contract_resolver.py` interpolation** (D-081): the resolver
now expands `${env.<field>}` + `${contract.<field>}` tokens
post-schema-validation, pre-IR-resolution. The env context is the
loaded environment onboarding JSON (`core/environments/<name>.json`,
schema `schemas/environment.schema.json`). The resolver's
`child_input_map` routes L2 wires to the sub-resource that declares the
input (P1-1 — `desired_count``aws:ecs:service`, `family`
`aws:ecs:task_definition`).
- **`core/environment_check.py` `load()`** (REQ-104): loads + returns the
parsed environment JSON; emits a stderr warning for placeholder
`account_id` when env != dev.
- **`core/hitl_gates.py`** (REQ-108, D-084): the HITL pre-execution
attestation gate. Records the approver identity to the DynamoDB outbox
(`approver_qa`/`approver_prod`/`approver_dr`), runs the separation-of-
duties check on prod, invokes the attestation matrix, returns
`(ok, reason)`. Dev skips (autonomous). `run_platform.sh` calls
`attest` before apply for qa/prod/dr.
- **`core/attestation_matrix.py`** (REQ-109, D-084): the 8-concern
attestation matrix from `hitl_matrix_design.md` §10.4. Offline-testable
concerns (contract NFRs, schema validity, policy pass) run for real;
operator-supplied concerns accept signed evidence artifacts validated
for freshness + schema. Signature verification skips when
`ACDL_ATTESTATION_SIGNING_KEY_ID` is unset (D-089).
- **`core/separation_of_duties.py` `route_halt_artifact`** (REQ-107):
real SNS publish (`acdl-sod-halt` topic, ARN from
`ACDL_SOD_HALT_TOPIC_ARN`) + outbox fallback
(`SEPARATION_OF_DUTIES_VIOLATION` event). The SNS topic is defined in
`terraform/platform/main.tf`.
- **`adapters/wiz/wiz_adapter.py` `WizClient`** (REQ-110): real GraphQL
API client (`<WIZ_API_URL>/graphql`, Bearer auth, pagination via
`pageInfo.hasNextPage`). `fetch_and_adapt` translates issues →
`PolicyCheckResult`. Graceful degrade when unconfigured.
- **`adapters/kyverno/kyverno_adapter.py`** (REQ-111): fleshed-out
`PolicyReport``PolicyCheckResult` mapping (pass/fail/skip/warn +
severity + skip-with-reason + resource construction). Inactive-for-TF
guard preserved.
The platform-managed scheduled workflow `workflows-src/rotate-aws-key.yml`
rotates the `NOVA_AWS_*` static key daily (cron `0 0 * * *`) and on
`workflow_dispatch`. v0.2 scope: the mechanism exists (SPEC §5.9 —
exists-not-ran); the v0.2 deploy uses the currently-active key. The
rotation is idempotent — `scripts/rotate_spike_key.sh` deactivates the old
key only after the new one propagates to the consumer's Actions secret
store, verified by a post-PUT GET; on upload/verify failure the old key is
left Active and the run exits non-zero. The synced workflow file is
forge-agnostic (REQ-230): forge base URL / owner / consumer repo come from
repository secrets (`NOVA_FORGE_*`, `NOVA_CONSUMER_REPO`), not literals.
### Per-Environment Promotion (D-082)
### §12.10 — Nova-idp Identity Layer (v1.28, current)
The deploy workflow (`.github/workflows/deploy.yml` +
`.gitea/workflows/deploy.yml`, byte-identical) declares an `environment`
`workflow_call` input. When non-empty, `run_platform.sh --environment
<name>` overrides the contract's `environment` field before schema
validation (D-088). One CI job per environment; promotion = running the
matching job, no `environment:` field editing. Per-env contract files
(`contracts/<module>.<env>.yaml`) use interpolation for env-specific
values.
Nova owns its identity layer end-to-end. Two (optionally three) Lambda
functions + four DynamoDB tables + one KMS asymmetric signing key + one
kyverno-json ABAC policy. **No Cognito, no IAM Identity Center (INV-15).**
The `nova-cli` Lambda layer carries the Nova wheel + `argon2-cffi` +
`cryptography` + `pyjwt` + the `kj` Go binary, making the same code
importable in both the CLI and the Lambda (REQ-329 dual-use, NFR-7).
### Adapter Parameterization (P1-1, D-085)
**Components:**
The adapter (`adapters/terraform/adapter.py`) reads ECS/ALB/VPC defaults
from L1 `interface.json` inputs (`desired_count`, `launch_type`,
`family`, `target_type`, `load_balancer_type`, `name`). The adapter is a
thin translator; the `child_input_map` routes wires to the declaring
sub-resource.
- `nova-idp-auth` Lambda — sign-up, sign-in, session creation. Argon2id
password hashing (D-228: bundled abi3 wheel; fail-closed on
`ImportError`, no pure-Python fallback). DynamoDB: `nova-users`
(PK `user_id`, Argon2id `password_hash`), `nova-sessions` (PK
`session_id`, TTL `expires_at`), `nova-password-resets` (PK
`reset_token`, TTL 15m). Function URL with IAM auth.
- `nova-idp-token-vend` Lambda — accepts a PAT (or session token),
validates revocation (`nova-pats.GetItem(jti, ConsistentRead=True)`
D-229, 60s SLO), evaluates the kyverno-json ABAC policy at
`platform/abac/token-vend.policy` (D-227, INV-17), KMS-signs an
ECDSA P-256 JWT (`ES256`), converts DER→raw ECDSA signature (RFC 7515
§3.1.3), returns the OIDC token. The `policy_version` (git SHA,
D-231) is recorded in every `token.vend.allowed/denied` audit event.
- `nova-idp-jwks` Lambda (optional, separation of concerns) — function
URL with `AuthType: NONE` (public key only), `Cache-Control: max-age=3600`.
`kms.get_public_key` → DER SPKI → JWK via `cryptography`. Custom
domain + WAF via CloudFront is OPTIONAL (`--public-jwks-domain` flag
on `nova idp setup`, D-230).
- `nova-pats` DynamoDB table — PK `jti`, GSI1 `sub` (list PATs for
user), GSI2 `pat_hash` (lookup by hash). Only the hash stored (not
raw PAT, REQ-343). Revoked PATs retained for audit.
### Deferred (D-083)
**CLI surface (`nova` package, greenfield):**
S3 Object Lock + JWS detached signatures + async worker + DLQ + daily
checkpoints (audit ledger build-out) — deferred to a future milestone.
The hash-chain + DynamoDB-outbox path remains the v1.9 production audit
record.
- Entry point: `[project.scripts] nova = "nova.cli:main"` (argparse-only,
no click/typer — repo convention). `nova/cli.py` auto-discovers
`nova/<module>.py` subcommands via `pkgutil.iter_modules`, dispatches,
emits the `cli.invocation` audit event (INV-12) with `mode`,
`selection_reason`, `credential_type`, `command`, `args`.
- Each `nova/<module>.py` is ≤50 lines, delegates to `core/` (CAP-034
AST scan). Subgroups: `nova auth login/revoke/status`, `nova idp
setup --check/--apply/--verify`, `nova init`, `nova apply --local`.
- `core/mode_resolver.py` — flag → env (`NOVA_CLIENT_MODE`) → credential
type → `sys.stdin.isatty()` (D-226). CLI-only; Lambdas don't resolve
modes. Property-tested with `hypothesis` (REQ-349).
- `core/env.py:+synthesize_local_env()` — synthesizes a local env dict
from a contract + `--local` flag (REQ-330). No cloud provisioning.
## v1.10 Addendum — Regression VERIFY + Local Emulators + Capability Re-Verification
**Packaging (NFR-6, CAP-035):**
### Regression-Class VERIFY (D-091, `core/regression_verify.py`)
- CI publishes a wheel to CodeArtifact AND a Lambda layer with identical
version strings on every merge affecting `core/`/`adapters/`/`nova/`.
Version mapping recorded in SSM `/nova/layer/nova-cli/version`.
If either publish fails, the merge is blocked (REQ-323).
- `nova cli-action` composite action at
`.github/actions/nova-cli/action.yml`, referenced by both GitHub +
Gitea (`uses: continuous-intelligence/acdl/.github/actions/nova-cli@v1.28`).
Python 3.12 pinned. Byte-identical behavior verified by CI matrix
(REQ-326, NFR-11).
The standard VERIFY stage was diff-scoped (it checked the phase diff
only, never re-ran underlying capability). This let 8 NFR-patch phases
(v1.9.1v1.9.8) pass while the platform decayed. The regression-class
VERIFY (`core/regression_verify.py`) re-runs capability checks against
the current codebase and tags each Verified/Decayed/Broken. It fails
closed on any non-Verified capability, blocking milestone completion.
**Data flows:**
The registry (`CAPABILITY_REGISTRY`) holds 16 capability checks
(CAP-001..CAP-016): 12 local-tier + 4 live-AWS. Adding a capability is
a single function + one registry entry. The gate runs via
`scripts/run_regression.sh` and writes `.ciagent/REGRESSION_REPORT.md`
+ `.json`.
1. Sign-up → `nova-idp-auth` → Argon2id → `nova-users` PutItem → session
`nova-sessions` PutItem → return session token.
2. Token vend (hot path) → `nova-idp-token-vend``nova-pats` strong
read (revocation) → kyverno-json ABAC eval → if allow → KMS sign →
DER→raw → return OIDC JWT. Audit at every step.
3. JWKS fetch → `nova-idp-jwks``kms.get_public_key` → DER→JWK →
`{"keys":[...]}`. Cached 1h at CloudFront (if custom domain) / client.
4. PAT revoke → `nova auth revoke --pat <jti>` → `nova-pats.UpdateItem(
status=revoked)` → audit. Strong read on next vend → 403 (within 60s).
### Local Emulating Adapters (D-092, `core/local_emulators.py`)
**`nova idp setup` (REQ-340, NFR-10):** generates a CloudFormation
template (raw dict → JSON, no troposphere dep), presents for review
(`$PAGER` + resource summary), requires explicit `y/N` approval before
`cloudformation deploy --capabilities CAPABILITY_IAM`. `--check` reports
prerequisites + IAM policy delta; `--verify` runs the KMS round-trip
test. New IAM grants required: `cloudformation:*`, `codeartifact:*`.
Four local adapters let the platform run the full headline E2E without
cloud credentials:
- `FlatFileOutbox` — flat-file DynamoDB outbox emulator (hash-chained
JSONL; resumable across instances; chain verification).
- `LocalEcsEmulator` — local ECS Fargate HTTP 200 emulator (binds port
0 on 127.0.0.1; daemon thread; clean destroy).
- `LocalS3StateBackend` — rewrites the terraform S3 backend to a local
backend (per-stack tfstate in a temp folder).
- `LocalLambdaStub` — invokes the contract_ingestor handler in-process
(patches `_get_dynamodb`/`_get_secrets_client`/`urllib.urlopen`;
DynamoDB writes redirected to the FlatFileOutbox).
`run_local_e2e()` runs the full pipeline: contract → resolver → adapter
→ local S3 backend → local ECS (HTTP 200) → flat-file outbox (chain
verified) → local Lambda (200). Gated on `ACDL_LOCAL_TIER=1`.
### Capability Re-Verification Sweep (D-093)
`.ciagent/CAPABILITY_INVENTORY.md` enumerates 16 auto-verified
capabilities + 6 IAM-gated escalated resources. The sweep found and
fixed 7 adapter defects in `adapters/terraform/adapter.py` (duplicate
outputs, duplicate args, missing required args, deprecated AWS provider
v5 arg names). The headline E2E now passes at both tiers: local
emulator + live-AWS terraform init/validate/plan.
### Adapter Defect Fixes (P54)
7 defects fixed in `adapters/terraform/adapter.py`:
1. Duplicate output definitions (per-resource + stack-level both emitted).
2. Duplicate `desired_count`/`launch_type` on ECS service.
3. Duplicate `target_type`/`family`/`load_balancer_type`.
4. Missing `assume_role_policy`/`role_name` on IAM role (L2 composition gap).
5. Missing `cidr_block`/`vpc_id`/`name` defaults on VPC/subnet/route_table/
ECS cluster/ECR repository.
6. ECR `kms_key_arn` unsupported arg → `encryption_configuration` block.
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).
+246
View File
@@ -0,0 +1,246 @@
# ACDL v1.9 — Audit Report
> Audit date: 2026-07-23. Auditor: ci-debugger. Milestone: v1.9. Result: PASS.
## Step 1: Reconstruction Test
- 16 v1.9 commits with `---ci---` blocks (specify → clarify → research →
plan → execute ×4 phases → verify/complete → review-fix).
- Reconstructed state: milestone v1.9, phase 43, status complete.
- Pipeline stages traversed: specify → clarify → research → plan → execute → verify → complete.
- Decisions D-080..D-089 all present in git log + `.ciagent/` files.
- config.json (v1.9 complete), PROJECT.md (v1.9 complete), REQUIREMENTS.md
(v1.9 complete, 12 reqs), ROADMAP.md (v1.9 complete, phases 3943),
REVIEW.md (READY TO SHIP), PERSONAS.md (v1.9), VERIFY.md, AUDIT.md.
**PASS.**
## Step 2: File Discipline
- `.ciagent/config.json`: valid JSON; mode, projects[] present. **PASS.**
- `.ciagent/PROJECT.md`: Vision/Core Value (≡ "What This Is"), Key
Decisions (v1.9 D-080..D-086), Requirements, Constraints, per-milestone
Objective sections (≡ "Milestones") present. Section names follow the
v1.0 established conventions (not the generic audit template). **PASS.**
- `.ciagent/ROADMAP.md`: phases 3943 present; all marked complete.
**PASS.**
- `.ciagent/REQUIREMENTS.md`: v1.9 traceability table complete (12/12
REQ-100..111 marked `complete (v1.9.0)`). **PASS.**
- `.ciagent/ARCHITECTURE.md`: **fixed during audit** — v1.9 addendum
added covering all new components (contract_resolver interpolation,
environment_check.load, hitl_gates, attestation_matrix,
separation_of_duties.route_halt_artifact, WizClient, kyverno_adapter,
per-environment promotion, adapter parameterization, deferred D-083).
All 9 v1.9 code components now referenced. **PASS (after fix).**
## Step 3: Branch Hygiene
- Local: `main` only. Remote: `origin/main` only.
- No phase or milestone branches remain (all 5 v1.9 phase branches merged
+ pruned during the run/ship workflow).
- No orphan branches.
**PASS.**
## Step 4: Commit Discipline
- 16/16 v1.9 commits have `---ci---` blocks with project/phase/milestone/
status fields.
- No stale implementation decisions (D-081..D-085, D-087..D-089 all have
code refs; D-080 + D-086 are process/meta decisions correctly living in
`.ciagent/` files).
- No unresolved v1.9 escalations (the 3 `audit(...)` commits in history
are from prior milestones v1.0/v1.6/v1.7).
**PASS.**
## Issues fixed during audit
1. **ARCHITECTURE.md missing v1.9 addendum** — the architecture doc had
no coverage of the v1.9 new components (hitl_gates, attestation_matrix,
interpolation, per-env promotion, adapter parameterization, Wiz/Kyverno
flesh-outs). Fixed: added a v1.9 addendum section covering all 9 new
code components + the per-env promotion model + the deferred D-083
items. Verified all 9 components now referenced.
## Audit result: PASS
---
# ACDL v1.10 Phase 52 — Audit Addendum
> Audit date: 2026-07-27. Auditor: ci-debugger. Phase: 52 (pipeline
> regression-VERIFY fix). Result: PASS.
## Process defect recorded (D-091)
The prior VERIFY stage was diff-scoped: it checked the phase diff only
and never re-ran underlying platform capability. This structural defect
let 8 NFR-patch phases (v1.9.1→v1.9.8, deck rework) pass VERIFY while the
platform they described decayed underneath. The defect is recorded as
D-091 and remediated in Phase 52 by `core/regression_verify.py` +
`scripts/run_regression.sh`.
## Phase 52 audit
- **Reconstruction:** Phase 52 commits present with `---ci---` blocks
(plan + execute + verify). Decisions D-090..D-094 recorded in
PROJECT.md. Requirements REQ-112..REQ-115 recorded in REQUIREMENTS.md.
**PASS.**
- **File discipline:** `core/regression_verify.py`,
`scripts/run_regression.sh`, `tests/test_verify_regression_mode.py`
present. `.ciagent/PLAN.md`, `ROADMAP.md`, `PROJECT.md`,
`REQUIREMENTS.md`, `VERIFY.md` updated for v1.10. **PASS.**
- **Behavioral:** 502 fast tests pass (was 493; +9 new). 3 slow
integration tests pass. `run_regression.sh` runs and reports honestly.
**PASS.**
- **Commit discipline:** Phase 52 commits carry `---ci---` blocks with
project/phase/milestone/status. **PASS.**
## Note on prior "audit CLEAN" claims
The v1.1v1.9 "audit CLEAN" claims were point-in-time true (the
capabilities ran at the time of tagging). They do not assert current
reproducibility. The capability decay surfaced in the 2026-07-27
CLARIFY/RESEARCH stages is being re-verified in Phase 54 (D-093). The
v1.10 audit will re-assert current reproducibility after the sweep.
## Phase 52 audit result: PASS
---
# ACDL v1.10 — Milestone Audit
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10.
> Result: PASS.
## Step 1: Reconstruction Test
- 5 v1.10 commits with `---ci---` blocks (plan → P52 verify → P53 verify
→ P54 verify → P55 verify).
- Reconstructed state: milestone v1.10, phase 55, status verify.
- Pipeline stages traversed: plan → execute → verify (×4 phases).
- Decisions D-090..D-094 all present in git log + `.ciagent/` files.
- config.json (v1.10 complete), PROJECT.md (Capability Status section
+ decay disclosure), REQUIREMENTS.md (REQ-112..115 complete),
ROADMAP.md (v1.10 section, phases 5255 complete), REVIEW.md (READY
TO SHIP), VERIFY.md (Phase 55 PASS), AUDIT.md (this file),
CAPABILITY_INVENTORY.md (16 Verified + 6 escalated), REGRESSION_REPORT
(16/16 Verified).
**PASS.**
## Step 2: File Discipline
- `.ciagent/config.json`: valid JSON; mode, projects[] present; milestone
v1.10 complete. **PASS.**
- `.ciagent/PROJECT.md`: Capability Status section + decay disclosure +
D-090..D-094 decision rows present. **PASS.**
- `.ciagent/ROADMAP.md`: v1.10 section with phases 5255 all marked
complete; v1.9.8 annotated as last deck-polish before freeze. **PASS.**
- `.ciagent/REQUIREMENTS.md`: v1.10 traceability table complete (4/4
REQ-112..115 marked `complete (v1.9.9..v1.9.12)`). **PASS.**
- `.ciagent/CAPABILITY_INVENTORY.md`: 16 Verified + 6 IAM-gated
escalated, with evidence per capability. **PASS.**
- `.ciagent/REGRESSION_REPORT.md` + `.json`: 16/16 Verified, gate passes.
**PASS.**
- `.ciagent/REVIEW.md`: READY TO SHIP (0 P0, 0 P1, 1 P2 post-hoc).
**PASS.**
## Step 3: Branch Hygiene
- Local: `main` only. Remote: `origin/main` only.
- No phase or milestone branches remain (single-project mode, flat
`.ciagent/` paths, no phase branches per config.json
branching_strategy=phase but committed directly to main per the
project's established convention).
**PASS.**
## Step 4: Commit Discipline
- 5/5 v1.10 commits have `---ci---` blocks with project/phase/milestone/
status fields.
- Decisions D-090..D-094 all have code/doc refs.
- The regression `---ci---` blocks include `regression:` arrays with
per-capability status (Phases 52, 53, 54).
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
**PASS.**
## Audit result: PASS
The v1.10 milestone is complete. The pipeline regression gap (D-091)
is fixed; the platform is fully locally testable (D-092); every
advertised v1.1v1.8 capability is re-verified (D-093, 16/16 Verified);
the docs/decks match verified reality (D-094). 0 P0, 0 P1 from review;
1 P2 (post-hoc: expand regression registry to uptime-kuma + RDS stacks).
513 offline tests pass; the regression gate covers 16 capabilities
including 4 live-AWS checks. Ready to tag `v1.10.0`.
---
# ACDL v1.10 — Post-Ship Audit (ciagent-audit workflow)
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10
> (shipped, tag `v1.10.0`). Result: PASS (1 issue fixed during audit).
## Step 1: Reconstruction Test — PASS
Parsed all `---ci---` blocks from `v1.9.8..HEAD` (9 commits).
Reconstructed state:
- Phases: 52, 53, 54, 55 (+ boundary commits 0, 51)
- Milestone: v1.10
- Final status: complete
- Decisions: D-090..D-094
- Requirements: REQ-112..REQ-115
- Regression caps: CAP-001..CAP-016
Compared with `.ciagent/` files:
- config.json: milestone v1.10, status complete. **MATCH.**
- ROADMAP.md: phases 5255 present, all complete. **MATCH.**
- REQUIREMENTS.md: REQ-112..115 all complete. **MATCH.**
- PROJECT.md: D-090..D-094 decision rows present. **MATCH.**
- CAPABILITY_INVENTORY.md: CAP-001..CAP-016 all Verified. **MATCH.**
**Reconstruction: PASS** — state fully reconstructable from git log.
## Step 2: .ciagent/ File Discipline — PASS (1 issue fixed)
- `config.json`: valid JSON, required fields present. **PASS.**
- `PROJECT.md`: all required sections present (Vision, North Star,
Capability Status, Requirements, Key Decisions, Constraints,
Anti-Goals). **PASS.**
- `ROADMAP.md`: phases 5255 present, v1.10 marked complete. **PASS.**
- `REQUIREMENTS.md`: REQ-112..115 all complete in traceability table.
**PASS.**
- `ARCHITECTURE.md`: **FIXED DURING AUDIT** — had 0 references to
v1.10 components (regression_verify, local_emulators,
REGRESSION_REPORT, CAPABILITY_INVENTORY). Added a v1.10 addendum
section covering the regression-class VERIFY, local emulating
adapters, capability re-verification sweep, and the 7 adapter defect
fixes. Now references all v1.10 components. **PASS (after fix).**
## Step 3: Branch Hygiene — PASS
- Local: `main` only. Remote: `origin/main` only.
- No phase or milestone branches (flat workflow per project convention).
- No orphan branches.
**PASS.**
## Step 4: Commit Discipline — PASS
- 9/9 v1.10 commits have `---ci---` blocks with project/phase/milestone/
status fields.
- Decisions D-090..D-094: D-091/D-092/D-093 have code refs
(`core/regression_verify.py`); D-090/D-094 are process/meta decisions
with extensive `.ciagent/` doc refs (PLAN, ROADMAP, PROJECT,
CAPABILITY_INVENTORY, AUDIT, VERIFY). No stale decisions.
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
**PASS.**
## Issues fixed during audit
1. **ARCHITECTURE.md missing v1.10 addendum** — the architecture doc
had no coverage of the v1.10 new components (regression_verify,
local_emulators, capability inventory, adapter defect fixes). Fixed:
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
@@ -1,4 +1,4 @@
# Nova Capability Inventory — v1.1→v1.8 Re-Verification Sweep
# ACDL Capability Inventory — v1.1→v1.8 Re-Verification Sweep
> Generated: 2026-07-27. Phase 54 (D-093). Milestone v1.10.
> Source: PROJECT.md + ROADMAP.md v1.1→v1.8 advertised capabilities.
+4 -15
View File
@@ -1,19 +1,8 @@
{
"phase": 1,
"phase": 0,
"stage": "complete",
"milestone": "v1.28",
"phase_role": "execution",
"milestone": "v1.14",
"phase_role": "pre_execution",
"attempts": 0,
"updated_at": "2026-08-19T21:30:00Z",
"project": "acdl",
"projects": ["acdl", "nova-blockchain-exchange"],
"active_milestone": "v1.28",
"milestone_branch": "milestone/v1.28-cli-identity",
"phase_branch": "phase/01-cli-substrate",
"tag_line": "v1.27.x",
"phase_name": "cli-substrate",
"reqs_covered": ["REQ-323", "REQ-324", "REQ-325", "REQ-326", "REQ-327", "REQ-328"],
"caps_verified": ["CAP-033", "CAP-034", "CAP-035"],
"tests": {"p1_specific": 45, "total_passing": 809, "failures": 0, "deselected": 5},
"notes": "v1.28 P1 SHIP. cli-substrate complete. Tag v1.27.1. Merged phase/01 -> milestone/v1.28-cli-identity. 6 REQs covered (REQ-323..328), 3 CAPs verified (CAP-033/034/035). Next: P2 lambda-packaging."
"updated_at": "2026-07-29T20:30:00Z"
}
-276
View File
@@ -1,276 +0,0 @@
# CLARIFY — v1.28 CLI Canonicalization + Identity Layer
> **Autonomy:** full. Auto-resolution with assumption logging per
> `config.autonomy.level: "full"`. No human escalation unless confidence
> < 0.60. The user-approved re-mapping plan (v1.18 spec → v1.28) resolved
> the headline discrepancy. This file records the remaining ambiguities
> and the grounding gaps surfaced in pre-flight.
---
## Method
The clarify stage identifies ambiguities in the v1.28 specification and
resolves them at full autonomy. The v1.28 spec is the user-provided
"Universal Feature Specification — v1.18 CLI Canonicalization + Identity
Layer," re-mapped to v1.28 (milestone number, tag line, and all
ID namespaces) per the user-approved plan. Each ambiguity gets a
decision ID (D-226+, continuing from v1.27's D-214..D-225), a resolution,
a confidence score, and a rationale.
---
## Prior-conversation resolutions (already locked, restated for the record)
These were resolved by the user-approved re-mapping plan in the
conversation that spawned v1.28. They are load-bearing for v1.28
execution.
### Q-P1 — The source spec is titled "v1.18" but v1.18 already shipped. What milestone is this?
**Resolution:** Re-map the spec's *content* (CLI Canonicalization +
Identity Layer) to **v1.28**, the next milestone after v1.27 (complete).
Tags run on the **v1.27.x** line (P0 = `v1.27.0`). Milestone branch:
`milestone/v1.28-cli-identity`.
**Confidence:** 1.0 (user-confirmed — "Re-map to v1.28"). **Decision:** n/a (milestone identity, not a D-ID).
### Q-P2 — The spec's "locked inputs" (D-NEW-26, kj engine, Nova-idp, INV-63/64/65, CAP-025..030, REQ-001..031) don't exist in the repo. How to handle?
**Resolution:** Author them fresh in this milestone's CLARIFY/RESEARCH as
**D-226..D-231, INV-12..17, CAP-033..038, REQ-323..353**. The `kj` engine
is mapped to the existing **kyverno-json** engine (INV-4 swappable) — no
new engine is built. CAP/INV/REQ IDs are re-allocated to avoid collisions
with shipped history (CAP-025..032 and INV-1..11 are blockchain/pilot).
**Confidence:** 1.0 (user-confirmed — "Re-map to v1.28"). **Decision:** D-227 (kj→kyverno-json), plus the ID-allocation block in REQUIREMENTS.md.
### Q-P3 — The spec claims a "Cognito drop." No Cognito exists in the repo. What does NFR-5 mean?
**Resolution:** NFR-5 (no AWS-managed identity in the path) is a
**greenfield constraint**, not a migration. Nova-idp is built fresh; no
Cognito/IAM Identity Center is *introduced*. The "drop" framing is
aspirational language from the source spec, not a literal removal.
**Confidence:** 1.0. **Decision:** D-226 (recorded below; NFR-5 restated
as a greenfield constraint in INV-15).
---
## Open questions from the spec's §7 (auto-resolved at full autonomy)
### Q1 — Argon2 native dependency in Lambda runtime
`argon2-cffi` has a C extension that may not build cleanly in the Lambda
Python 3.12 runtime.
**Resolution (D-228):** Use `argon2-cffi` with bundled wheels; if the
extension fails to load, fall back to the pure-Python implementation. If
both fail, document the Fargate migration path for the auth Lambda.
CAP-036 covers end-to-end verification.
**Confidence:** 0.85. **Rationale:** Bundled wheels are the standard
workaround for Lambda native deps; the pure-Python fallback is a safe
degradation. Fargate is the escape hatch if Lambda's runtime is
fundamentally incompatible. RESEARCH will validate wheel availability for
Python 3.12 + the Lambda execution environment.
**Impact if wrong:** Auth Lambda migrates to Fargate, adding ~1 week to P2.
### Q2 — PAT revocation propagation latency
The 60-second SLO (NFR-4) depends on whether the token-vend Lambda reads
PAT revocation state from DynamoDB on every request (eventually
consistent reads) or via a cached/denylist mechanism.
**Resolution (D-229):** Read-on-every-request with strongly consistent
reads on the PAT hash table. Cost is acceptable given expected request
volume (token vending is not a hot path — it precedes a deploy, not every
request). REV-351 verifies the SLO in CI.
**Confidence:** 0.90. **Rationale:** Strongly consistent DynamoDB reads
have single-digit-ms latency at expected volume; the 60s SLO has >10x
headroom. A cache layer adds invalidation complexity that the SLO does
not require.
**Impact if wrong:** If read latency exceeds 60s under load, introduce a
DynamoDB TTL + cache layer; SLO must be re-verified.
### Q3 — JWKS endpoint: Lambda function URL vs. API Gateway
A function URL is simpler and cheaper but lacks throttling, WAF, and
custom domains out of the box.
**Resolution (D-230):** Start with a Lambda function URL behind a custom
domain; rate limiting configured at the DNS/CDN layer. API Gateway
migration deferred to v1.19+ if throttling requirements grow.
**Confidence:** 0.80. **Rationale:** The JWKS endpoint is public-key
only (no secrets); the threat surface is low. Function URL + CDN rate-
limiting covers the v1.28 volume. API Gateway is over-engineering until
traffic patterns are known.
**Impact if wrong:** If throttling becomes a requirement, API Gateway
migration adds ~3-5 days.
### Q4 — Mode resolver precedence with invalid `NOVA_CLIENT_MODE` value
What happens if the env var is set to something other than `agent` or
`interactive` (e.g., `NOVA_CLIENT_MODE=auto`)?
**Resolution (D-226):** Invalid env var values are ignored, falling
through to credential type. A warning is logged. Behavior is documented
in the `nova-cli` README. This is a sub-clause of the mode-resolution
priority decision.
**Confidence:** 0.90. **Rationale:** Ignoring + warning is the least
surprising behavior for an operator debugging mode issues. Failing hard
would block legitimate workflows that set a stale/typo'd env var.
**Impact if wrong:** Operators debugging mode issues may be confused;
non-blocking.
### Q5 — Service-account PAT vs. developer PAT in the same session
What if both credential types are available (e.g., a developer explicitly
exports a service-account PAT)?
**Resolution (D-226):** The most recently acquired credential wins.
Documented in `nova auth login` output. The credential type is what
drives mode resolution (INV-14), so the operator sees which mode was
selected and why.
**Confidence:** 0.85. **Rationale:** "Most recent wins" is the simplest
deterministic rule that matches operator mental models of "I just logged
in as X." The audit event records the winning credential type, so the
selection is traceable.
**Impact if wrong:** Mode selection may surprise the operator; non-
blocking, but `nova auth status` must make the active credential explicit.
### Q6 — ABAC policy ownership and versioning
`platform/abac/token-vend.policy` is referenced, but who owns changes?
How are policy versions tracked in audit?
**Resolution (D-231):** Policy changes require PR review; the policy
version (git SHA) is recorded in every token-vend audit event. Owner:
Platform Security. The policy file lives in the platform repo at
`platform/abac/token-vend.policy` and is reviewed like any other
production config.
**Confidence:** 0.90. **Rationale:** Git SHA is the natural version
identifier for a repo-resident policy; recording it in the audit event
makes every allow/deny decision reconstructable to the exact policy text.
**Impact if wrong:** Untracked policy changes could lead to unexpected
allow/deny decisions in production, undermining audit defensibility.
---
## Grounding gaps surfaced in pre-flight (auto-resolved)
### G1 — The `kj` engine does not exist; the spec treats it as locked.
**Resolution (D-227):** The token-vend Lambda uses the existing
**kyverno-json** engine (INV-4 swappable) as the ABAC evaluator. The
policy at `platform/abac/token-vend.policy` is a kyverno-json policy.
No new `kj` engine is built in v1.28. If a distinct `kj` engine is
desired later, it is a separate research spike (not this milestone).
**Confidence:** 0.95. **Rationale:** The repo already has a swappable
policy engine (INV-4) implemented as kyverno-json. Building a second
engine to do the same job violates the swappable-engine invariant's
spirit. kyverno-json's `evaluate` semantics cover the spec's ABAC needs
(subject, claims, resource, environment → allow/deny).
**Impact if wrong:** If the user actually wants a new `kj` engine, v1.28
scope expands significantly (engine design + implementation + migration).
This was flagged as caveat #3 in the approved plan; the recommended path
(kyverno-json) is locked here.
### G2 — The spec's INV-18..21, INV-34, INV-63/64/65 don't exist.
**Resolution:** Re-allocated as **INV-12..INV-17** (see REQUIREMENTS.md
§v1.28 Invariants). The 1:1 mapping:
- INV-63 (mode observability) → INV-12
- INV-64 (mode determinism) → INV-13
- INV-65 (credential type encodes role) → INV-14
- INV-18..21 (attestation invariants) → INV-15 (no AWS-managed identity),
INV-16 (password storage), INV-17 (ABAC discipline). The spec's
attestation invariants INV-18..21 are partially covered by existing
invariants (INV-6 immutable audit) + INV-17; the JWS-from-PAT behavior
(REQ-332) is a requirement, not a separate invariant, in this mapping.
- INV-34 (MFA enforcement) → deferred to v1.21+ (out of scope per §2.2);
no INV allocated in v1.28.
**Confidence:** 0.85. **Rationale:** The mapping preserves the spec's
intent without colliding with the repo's INV-1..11. INV-34 (MFA) is
explicitly deferred per the spec's own §2.2 out-of-scope table.
**Impact if wrong:** If the user wants the exact INV-18..21 semantics as
separate invariants, INV-12..17 can be re-numbered; non-blocking.
### G3 — The spec's CAP-025..030 collide with blockchain/pilot CAPs.
**Resolution:** Re-allocated as **CAP-033..CAP-038** (see REQUIREMENTS.md
§v1.28 + REQ-352). The 1:1 mapping:
- CAP-025 (CLI subcommand surface) → CAP-033
- CAP-026 (subcommand delegates to core/) → CAP-034
- CAP-027 (layer matches wheel) → CAP-035
- CAP-028 (Nova-idp auth flow) → CAP-036
- CAP-029 (token-vend signs via KMS) → CAP-037
- CAP-030 (PAT issuance + revocation) → CAP-038
**Confidence:** 1.0. **Rationale:** Existing CAP-025..032 are
blockchain/pilot capabilities (STATE.md); re-use would corrupt the
capability registry. The re-allocated IDs are the next available.
**Impact if wrong:** None — this is a numbering decision, not a semantic
one.
### G4 — The spec's REQ-001..031 collide / don't exist.
**Resolution:** Re-allocated as **REQ-323..REQ-353** (1:1 with the spec's
REQ-001..031). Full text in REQUIREMENTS.md §v1.28. Max existing REQ =
REQ-322.
**Confidence:** 1.0. **Rationale:** Same as G3 — avoid collision, use
next available range.
### G5 — `platform/abac/`, `nova/` subcommand dir, `nova-idp-*` Lambdas don't exist.
**Resolution:** These are **greenfield deliverables** of v1.28 execution
phases, not pre-existing "locked architectures." RESEARCH will design
them; PLAN will sequence them; EXECUTE will build them. The spec's
"Operating Principle 1" (incremental delivery) is honored — v1.28 is
net-new work.
**Confidence:** 1.0. **Rationale:** The spec itself describes these as
new ("introducing Nova-idp"). The mis-framing was in calling them
"locked" — they are locked in *scope*, not in *prior existence*.
**Impact if wrong:** None — this is a framing correction.
---
## Decision ledger (v1.28 — D-226..D-231)
| ID | Title | Confidence | Load-bearing for |
|----|-------|------------|------------------|
| D-226 | Mode resolution priority + invalid-env + dual-credential | 0.90 | REQ-327, INV-12, INV-13, INV-14 |
| D-227 | ABAC engine = kyverno-json (no `kj` engine built) | 0.95 | REQ-336, REQ-339, INV-17, NFR-9 |
| D-228 | Argon2id in Lambda: bundled wheels + pure-Python fallback + Fargate path | 0.85 | REQ-333, REQ-334, INV-16, NFR-8 |
| D-229 | PAT revocation: strongly-consistent DDB read-on-every-request, 60s SLO | 0.90 | REQ-342, REQ-343, REQ-351, NFR-4 |
| D-230 | JWKS endpoint: Lambda function URL + custom domain + CDN rate-limit | 0.80 | REQ-338, NFR-5 |
| D-231 | ABAC policy ownership: Platform Security, git SHA in audit | 0.90 | REQ-339, NFR-9 |
---
## Assumptions logged (full autonomy, no human escalation)
1. **CodeArtifact is provisionable** in AWS account `581513795199` (the
pilot account). RESEARCH will confirm IAM permissions + repository
creation. If not, v1.28 falls back to a private PyPI server or a
Gitea-hosted wheel index; the CLI subcommand surface (REQ-324) and
identity layer (REQ-333+) are unaffected.
2. **Python 3.12** is the target runtime for both the CLI wheel and the
Lambda functions (spec §4 REQ-004.3). The repo's current Python
version will be confirmed in RESEARCH; if it differs, the CLI pins
3.12 and Lambda uses the 3.12 runtime regardless.
3. **KMS asymmetric signing** (RSA-2048 or ECDSA P-256) is available in
the target account. RESEARCH will confirm. If only symmetric KMS is
available, the token-vend Lambda uses symmetric signing + a public-key
publication step (less ideal, but functional); INV-15 is unaffected.
4. **The Forge action** (REQ-326) is the existing `nova cli-action`
pattern, extended to both GitHub and Gitea marketplaces. The repo's
current Forge/Gitea workflow conventions (`.gitea/workflows/`,
`deploy.yml@v1.25`) are the baseline.
5. **MFA/TOTP** code path ships in v1.28 (per spec §2.2) but enforcement
for prod/dr is deferred to v1.21+. This is a doc/test-only path in
v1.28 — no enforcement gate.
---
## CLARIFY complete
All material ambiguities resolved at full autonomy (6 open questions +
5 grounding gaps → D-226..D-231, confidence ≥ 0.80). No human escalation
triggered (all confidences ≥ 0.60 threshold). REQUIREMENTS.md updated
with the decision ledger + invariants. Next: RESEARCH.
@@ -1,8 +1,8 @@
# Nova 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)
+294 -98
View File
@@ -1,110 +1,306 @@
# GRILL — v1.28 CLI Canonicalization + Identity Layer
# CIAgent Grill Report
> Adversarial review of the v1.28 SPECIFY + CLARIFY + RESEARCH + PLAN.
> Griller: ci-griller subagent. Autonomy: full. All 9 axes reviewed;
> every claim verified against the live codebase.
## Run: 2026-07-27 19:30 (mode: interactive, focus: all)
### Verdict: Proceed with conditions (confidence: 0.72)
Two escalations must be resolved before the leadership pitch:
- **G-005 (risks):** 6 cloud capabilities (CAP-017..022) are deploy-unverified.
- **G-008 (budget):** No cost documentation exists despite live AWS resources.
The project is reclassified as an **OSS reference implementation** (G-003),
not a sponsored product. The grill's sponsor/ROI/budget/timeline axes apply
in weakened form; the adoption, architecture, and risks axes apply in full.
### Axis 1 — Business Case
- **Q1**: What problem does this actually solve, and is that problem still the top priority?
- Evidence: PROJECT.md:3-21 (vision + North Star); G-003 reframing (OSS reference)
- Answer: ACDL is an OSS reference implementation showing the shape of an agentic cloud delivery platform. The problem (cognitive load of infra + operational work of safe change) is documented in docs/vision.md.
- Confidence: 0.85
- Decision: G-003 — reframe as OSS reference implementation; no sponsor/ROI required.
- **Q2**: Who is the named executive sponsor, and when did they last make a decision under pressure?
- Evidence: MISSING (no named sponsor in any .ciagent/ file)
- Answer: Not applicable for an OSS reference implementation (G-003). Senior leadership requesting the pitch is interest, not sponsorship.
- Confidence: 0.85
- Decision: G-003 (carries forward).
- **Q3**: What happens to the business if the project is cancelled?
- Evidence: PROJECT.md:487 ("0 consumer adoption"); 10 milestones shipped with no consumers
- Answer: If cancelled, no consumer loses a deployed system. The reference value (clonable shape) persists in the repo. Cancellation cost is low — consistent with OSS reference framing.
- Confidence: 0.80
- Decision: G-003 (carries forward).
- **Q4**: Is the ROI calculated against a counterfactual?
- Evidence: MISSING (no ROI calculation anywhere)
- Answer: Not applicable for an OSS reference implementation. The bar is "is it a credible, demonstrable reference?" not "is there a paying customer?"
- Confidence: 0.85
- Decision: G-003 (carries forward).
### Axis 2 — Scope and Requirements
- **Q1**: Is the scope expanding, contracting, or genuinely stable?
- Evidence: ROADMAP.md (v1.0→v1.10, 55 phases); v1.7 added uptime-kuma + decommission + RDS; v1.9.x added decks; v1.10 added regression-class VERIFY + local emulators
- Answer: Expanding. The Out-of-Scope table (REQUIREMENTS.md:61-72) is scoped to v1.1 only; later milestones added scope without boundary updates.
- Confidence: 0.70
- Decision: G-010 — OSS scope is contributor-bounded; no out-of-scope table needed.
- **Q2**: Who owns the requirements, and have they been frozen?
- Evidence: REQUIREMENTS.md (115 REQs, REQ-01..REQ-115); config.json autonomy=full
- Answer: The user owns requirements via CLARIFY auto-resolution under full autonomy. Not frozen — each milestone adds REQs.
- Confidence: 0.70
- Decision: G-010 (carries forward).
- **Q3**: What is explicitly out of scope?
- Evidence: REQUIREMENTS.md:61-72 (v1.1 Out-of-Scope table only); PROJECT.md:42-51 (Domain Boundaries)
- Answer: Domain Boundaries section (PROJECT.md:42-51) defines durable out-of-scope: application business logic, IDE workflows, product backlog, node/OS-level compute. No per-milestone out-of-scope updates since v1.1.
- Confidence: 0.65
- Decision: G-010 — contributor-bounded scope accepted for OSS reference.
- **Q4**: Are there hidden requirements only disclosed late in delivery?
- Evidence: v1.10 milestone (decay disclosure, PROJECT.md:59-67) — 7 adapter defects undisclosed across 8 phases
- Answer: Yes — the v1.10 decay incident is a late-disclosed hidden requirement (reproducibility). D-091 regression gate is the mitigation.
- Confidence: 0.72
- Decision: G-007 (carries forward — milestone-level regression gate catches late-disclosed decay).
### Axis 3 — Architecture and Technical Feasibility
- **Q1**: Has the proposed architecture been validated by the people who will build and operate it?
- Evidence: PERSONAS.md (agent personas only); ARCHITECTURE.md (29KB); no human reviewer sign-off
- Answer: Validated by the agent that built it, not by a downstream platform team. Acceptable for an OSS reference (G-002 — Platform Team joins post-clone).
- Confidence: 0.72
- Decision: G-002 (carries forward).
- **Q2**: What is the integration surface?
- Evidence: ARCHITECTURE.md; adapters/ (terraform, wiz, kyverno, local emulators); contracts/ schema
- Answer: Contract schema (upstream) + engine adapters (downstream). Integration is bounded by the IR + PolicyCheckResult schemas.
- Confidence: 0.78
- Decision: (resolved by existing architecture; no new binding decision)
- **Q3**: Is there an existing system being replaced?
- Evidence: PROJECT.md:7-8 (vision: absorb cognitive load + operational work)
- Answer: ACDL replaces manual platform engineering + ticket-driven delivery. No existing system in this repo; downstream teams replace their own.
- Confidence: 0.75
- Decision: (resolved by G-002 white-label framing)
- **Q4**: What is the technical debt being inherited, and is it budgeted for?
- Evidence: v1.10 decay (7 adapter defects); D-091 regression gate at milestone completion (not per-phase)
- Answer: Diff-scoped VERIFY debt was paid down in v1.10. Per-phase regression gap is accepted debt (G-007).
- Confidence: 0.70
- Decision: G-007 — milestone-level regression gate is correct; inter-milestone decay is an accepted trade-off.
### Axis 4 — People, Skills, and Organization
- **Q1**: Which 2-3 people, if they left, would the project fail?
- Evidence: PERSONAS.md (agent personas); all binding decisions made by the user (D-034, D-090, G-001..G-012)
- Answer: One person — the user. Bus factor is 1.
- Confidence: 0.82
- Decision: G-011 — single-maintainer is normal for OSS reference; no action.
- **Q2**: Are the assigned resources actually allocated at the percentages claimed?
- Evidence: config.json (autonomy=full, max_concurrent_agents=5)
- Answer: The agent is the resource; allocation is 100% when invoked, 0% otherwise. No BAU fire-fighting claim to verify.
- Confidence: 0.78
- Decision: G-011 (carries forward).
- **Q3**: Is there a product owner with actual authority to prioritize?
- Evidence: config.json (autonomy=full, decision_confidence_threshold=0.6)
- Answer: The user is the product owner with absolute authority (full autonomy within user-locked constraints).
- Confidence: 0.80
- Decision: G-011 (carries forward).
- **Q4**: Is the team building capability they don't have?
- Evidence: RESEARCH.md (101KB); local emulating adapters (Phase 53) — capability was built and proven
- Answer: No — the agent built and verified the capability. Not a prototype-hoping-to-learn scenario.
- Confidence: 0.78
- Decision: (resolved by existing evidence)
### Axis 5 — Timeline and Estimates
- **Q1**: Was the deadline set before or after the scope was understood?
- Evidence: ROADMAP.md (v1.0 07-21 → v1.10 07-27, 6 days); no deadline documented anywhere
- Answer: No deadline. Milestones complete when the agent finishes committing.
- Confidence: 0.78
- Decision: G-006 — autonomous OSS build has no deadline; cadence is fine.
- **Q2**: What is the project's critical path?
- Evidence: MISSING (no critical path analysis)
- Answer: Not applicable — no deadline means no critical path to push.
- Confidence: 0.75
- Decision: G-006 (carries forward).
- **Q3**: Are the estimates evidence-based?
- Evidence: MISSING (no estimates; phases complete in agent-time)
- Answer: No estimates. The cadence is a function of agent speed, not engineering sizing.
- Confidence: 0.72
- Decision: G-006 (carries forward — acceptable for autonomous OSS reference).
- **Q4**: Is there a working definition of done?
- Evidence: VERIFY.md; AUDIT.md; 4-layer verify gate (structural, behavioral, security, quality)
- Answer: Yes — the 4-layer verify gate + regression gate (D-091) is the definition of done. "Done" is not "whatever the latest demo shows"; it is a gated, audited state.
- Confidence: 0.80
- Decision: (resolved by existing verify gate)
### Axis 6 — Budget and Financial Realism
- **Q1**: What percentage of the budget is already spent vs. remaining?
- Evidence: MISSING (no budget file in .ciagent/)
- Answer: Unresolved — no budget documented.
- Confidence: 0.50
- Decision: G-008 — ESCALATION.
- **Q2**: Are there predictable cost drivers not in the original budget?
- Evidence: config.json escalation_hooks (deploy, delete_data); CAP-013..016 verified against live AWS account 581513795199
- Answer: Yes — live AWS resources exist (S3 state, DynamoDB outbox, ECS, CloudFront). No cost driver documentation.
- Confidence: 0.60
- Decision: G-008 (carries forward — escalation).
- **Q3**: What's the burn rate, and how long until the money runs out?
- Evidence: MISSING
- Answer: Unresolved.
- Confidence: 0.40
- Decision: G-008 (carries forward — escalation).
- **Q4**: Is the budget contingent on something that hasn't happened yet?
- Evidence: MISSING
- Answer: Unresolved — likely contingent on the leadership pitch yielding a pilot platform team (G-001).
- Confidence: 0.55
- Decision: G-008 (carries forward — escalation).
### Axis 7 — Risks, Assumptions, and Dependencies
- **Q1**: What are the top 3 assumptions the plan rests on?
- Evidence: PROJECT.md:79-88 (CAP-017..022 IAM-gated); D-039 (OIDC federation deferred, blocked on go-gitea/gitea#36988); D-090 (no cap on re-verification sweep)
- Answer: (1) Terraform plan path proves deployability. (2) Local emulators prove runtime behavior. (3) Gitea OIDC will eventually merge.
- Confidence: 0.72
- Decision: (resolved by G-005 escalation)
- **Q2**: What are you dependent on outside the team?
- Evidence: PROJECT.md:79-88 (admin principal needed for IAM re-bootstrap); go-gitea/gitea#36988 (OIDC blocker)
- Answer: An admin AWS principal (for CAP-017..022) and the Gitea OIDC PR (for D-039 waiver closure).
- Confidence: 0.78
- Decision: G-005 (carries forward — escalation).
- **Q3**: What is the single risk that, if it materializes, kills the project?
- Evidence: CAPABILITY_INVENTORY.md §"Cloud capabilities NOT re-verified" (6 of 22 capabilities, 27%)
- Answer: The unverifiable deploy path for CAP-017..022. If the terraform plan path does not translate to a real deploy, 27% of advertised capability is fictional.
- Confidence: 0.80
- Decision: G-005 — ESCALATION.
- **Q4**: Have you done a pre-mortem?
- Evidence: MISSING (no pre-mortem document)
- Answer: No pre-mortem on file. The v1.10 decay incident is the closest thing to a post-mortem.
- Confidence: 0.65
- Decision: (flagged; no binding decision — user accepted autonomous governance in G-009)
### Axis 8 — Governance, Decision-Making, and Communication
- **Q1**: Who is the decision-maker when two executives disagree?
- Evidence: config.json (autonomy=full); no human governance body documented
- Answer: The user is the single decision-maker. No executive disagreement is possible because there is no executive body.
- Confidence: 0.78
- Decision: G-009 — autonomous CI is the governance.
- **Q2**: How often does governance meet, and what's the escalation pattern?
- Evidence: config.json (escalation_hooks: deploy, delete_data, merge_to_main; escalation_timeout_ms: 300000)
- Answer: Governance is event-driven (escalation hooks), not cadence-driven. 5-minute timeout.
- Confidence: 0.72
- Decision: G-009 (carries forward).
- **Q3**: What is being omitted from the status reports?
- Evidence: v1.10 decay disclosure (PROJECT.md:59-67) — 8 phases omitted the decay from status
- Answer: The v1.10 incident is direct evidence that status reports (decks) omitted material decay. D-094 (rewrite to verified reality) is the correction.
- Confidence: 0.75
- Decision: (resolved by D-094 + G-007 regression gate)
- **Q4**: Is there a "stop the project" trigger?
- Evidence: MISSING (no stop-trigger documented)
- Answer: No formal stop-trigger. The user is the single point of cancellation authority.
- Confidence: 0.68
- Decision: G-009 — autonomous CI is the governance; no human stop-trigger needed.
### Axis 9 — Change, Adoption, and Operational Readiness
- **Q1**: Who will use this, and what is in it for them?
- Evidence: PROJECT.md:487 ("0 consumer adoption"); G-001 (MVP for leadership pitch + pilot consumers)
- Answer: Pilot platform teams (post-pitch) will clone, customize, and deploy for their internal consumers. The value to them is a working reference shape.
- Confidence: 0.65
- Decision: G-001 — feature-complete MVP for pitch + pilot consumers in parallel.
- **Q2**: Is the operations/support team involved now or being handed a finished product?
- Evidence: MISSING (no Platform Team involvement in 55 phases); G-002 (white-label, out-of-repo)
- Answer: Intentionally out-of-scope — ACDL is white-label; Platform Team customization happens outside this repo.
- Confidence: 0.78
- Decision: G-002 — white-label; Platform Team customization is out-of-repo.
- **Q3**: What is the rollback plan if it goes wrong?
- Evidence: D-070 (decommission mode, 2-step pipeline with HITL SRE gates)
- Answer: Decommission mode exists for deployed stacks. For the reference repo itself, rollback = git revert (no production state to roll back).
- Confidence: 0.75
- Decision: (resolved by existing D-070 decommission mode)
- **Q4**: Has anyone validated the success criteria with the people who will judge success?
- Evidence: PROJECT.md (leadership pitch requested); no documented success-criteria validation with leadership
- Answer: The leadership pitch IS the validation moment. Success criteria for an OSS reference = "leadership says this is a credible shape."
- Confidence: 0.68
- Decision: G-001 (carries forward — pitch is the validation).
### Meta — Closing Review
- **Q1**: If you were the auditor, what would you flag?
- Evidence: This grill run
- Answer: (1) 6 unverifiable cloud capabilities (G-005). (2) No cost documentation (G-008). (3) Vision doc vs. OSS-reference framing tension (G-004 — resolved by keeping vision as target-state description).
- Confidence: 0.78
- Decision: (aggregated; G-005 + G-008 are the actionable flags)
- **Q2**: What is the project not doing that it should?
- Evidence: MISSING (no pre-mortem, no cost doc, no Platform Team engagement, no stop-trigger)
- Answer: Documenting the operating model (cost, deploy verification, governance) for a downstream team. The grill surfaced this across G-005, G-008, G-009.
- Confidence: 0.75
- Decision: (aggregated; G-005 + G-008 are the actionable items)
- **Q3**: What is the simplest possible version that could deliver 80% of the value?
- Evidence: ROADMAP.md (v1.1 spike, Phase 10, REQ-27 — core E2E proven); v1.2-v1.10 (45 phases of expansion)
- Answer: The v1.1 spike (contract → IR → terraform plan → Checkov → confidence → outbox) is the 80%-value version. The full 115-requirement build is accepted as the reference value (G-012).
- Confidence: 0.68
- Decision: G-012 — full catalog is the value; no minimal release needed.
- **Q4**: What would have to be true for this to succeed in the next 90 days, and is it true today?
- Evidence: G-001 (pitch + pilot); G-005 (IAM re-bootstrap); G-008 (cost doc)
- Answer: (1) Leadership pitch yields a pilot platform team — NOT TRUE today (pitch not yet delivered). (2) CAP-017..022 deploy path is verifiable — NOT TRUE today (G-005 escalation). (3) Cost operating model is documented — NOT TRUE today (G-008 escalation).
- Confidence: 0.72
- Decision: (aggregated; G-005 + G-008 + G-001 pitch are the 90-day conditions)
### Binding Decisions
| ID | Axis | Decision | Confidence |
|----|------|----------|-----------|
| G-001 | adoption | Feature-complete MVP for leadership pitch + pilot consumers in parallel; CIAgent builds, Platform Team deploys | 0.65 |
| G-002 | adoption | ACDL is white-label; Platform Team customization is out-of-repo; resolves ops-handoff concern | 0.78 |
| G-003 | business | Reframe as OSS reference implementation; no sponsor/ROI required | 0.85 |
| G-004 | business | Keep production-deployment vision; reference describes target state | 0.75 |
| G-005 | risks | ESCALATION — re-bootstrap IAM or mark CAP-017..022 deploy-unverified in decks | 0.80 |
| G-006 | timeline | Autonomous OSS build has no deadline; cadence acceptable | 0.72 |
| G-007 | architecture | Milestone-level regression gate is correct; system worked as designed | 0.70 |
| G-008 | budget | ESCALATION — add COST.md or document zero-cloud-cost operating model | 0.74 |
| G-009 | governance | Autonomous CI is the governance; no human stop-trigger needed | 0.68 |
| G-010 | scope | OSS scope is contributor-bounded; no out-of-scope table needed | 0.65 |
| G-011 | people | Single-maintainer is normal for OSS reference; no action | 0.70 |
| G-012 | meta | Full catalog is the value; no minimal release needed | 0.68 |
### Escalations
- **[G-005] risks** — 6 cloud capabilities (CAP-017..022: DynamoDB contracts table, Lambda contract-ingestor, ECS service live, CloudFront production stack, uptime-kuma, OIDC role) are deploy-unverified. The `acdl-spike-runner` IAM user cannot fix its own IAM (chicken-and-egg). Either re-bootstrap IAM with an admin principal to re-verify, or explicitly mark these 6 as "design-verified, deploy-unverified" in every leadership deck before the pitch. Resolves: project-killing risk (Axis 7 Q3).
- **[G-008] budget** — No cost documentation exists in `.ciagent/` despite live AWS resources (account 581513795199, CAP-013..016 verified). Either add a `COST.md` documenting monthly AWS spend, or explicitly document that ACDL runs at zero cloud cost (local emulators are the primary tier; live-AWS is a one-off spike per milestone). Resolves: financial-control gap (Axis 6 Q1-Q4).
---
## Overall verdict: **PROCEED-WITH-CONDITIONS** · Confidence 0.76
## Run: 2026-07-29 20:25 (mode: adversarial, focus: v1.14 NFR plan)
The plan is fundamentally sound — architecture correct, re-mapping
clean (no ID collisions), technical depth accurate (DER→raw, strong-
read revocation, stdin TTY), highest-risk item (kj binary) has a
Fargate fallback. Not unfeasible, not over-scoped beyond an agent-driven
repo's capacity, not security-broken by design.
### Verdict: FEASIBLE WITH BINDING DECISIONS (confidence: 0.72)
**3 critical conditions (must-fix before P1) + 16 tracked conditions.**
No escalations (all axes ≥ 0.70 confidence).
The v1.14 milestone is a sound, well-evidenced NFR sweep with a genuine,
traceable backlog. Not fundamentally infeasible. Four binding decisions
close plan defects + unverified assumptions that would otherwise re-expose
the v1.11 4-VPC failure mode. One escalation (E-001) auto-resolved at full
autonomy with assumption logging.
---
### 9-Axis scores
## Axis verdicts
| Axis | Confidence | Forcing question (short) |
|------|-----------|---------------------------|
| 1 Business | 0.80 | Real backlog (5 P1 + 4 P2 + 6 swallowed errors + 15+ hardcoded IDs); cancellation survivable but inherits decay risk |
| 2 Scope | 0.70 | User-directed + frozen; P13 has a hidden feature door (implement vs remove); P2 conditional-child edges past wiring |
| 3 Architecture | 0.62 | P8 grep unsatisfiable for backend blocks; P8 state-bucket continuity unguarded; P9 IAM naming unverified; P4/P8 file overlap |
| 4 People | 0.85 | Agentic single-operator; runtime availability is the key-person risk |
| 5 Timeline | 0.68 | No deadline; 20-phase unverified span is the longest since G-007; P8 is the latent multi-phase-rework risk |
| 6 Budget | 0.85 | NFR-only, no new AWS resources; P8 re-creation is a one-shot accident not structural cost |
| 7 Risks | 0.60 | A1 (acdl-* naming unverified), A2 (fallback constant unbound), A3 (P4 gate hardening); kill-risk = P8 orphans state |
| 8 Governance | 0.72 | Full autonomy; no mid-milestone stop trigger; per-phase "green" ≠ "capabilities Verified" |
| 9 Adoption | 0.70 | No external users; rollback is git-level for code, AWS-state rollback unaddressed if P8 misfires pre-detection |
| Axis | Verdict | Confidence | Critical condition |
|------|---------|-----------|-------------------|
| §1 Feasibility | PROCEED-WITH-CONDITIONS | 0.82 | C-1.1 KMS asym verify; C-1.2 Argon2 fail-closed test |
| §2 Scope | PROCEED-WITH-CONDITIONS | 0.74 | C-2.1 fold P5 into P4; C-2.2 P4 overload |
| §3 Cost | PROCEED-WITH-CONDITIONS | 0.70 | C-3.1 cost estimate; C-3.2 CodeArtifact P1 task |
| §4 Schedule | PROCEED-WITH-CONDITIONS | 0.76 | C-4.1 P4 critical path; C-4.2 per-phase exit |
| §5 Technical Depth | PROCEED-WITH-CONDITIONS | 0.80 | C-5.1 ABAC shape; **C-5.2 JWS KDF** |
| §6 Operational Readiness | PROCEED-WITH-CONDITIONS | 0.72 | **C-6.1 ABAC fail-closed**; C-6.2 threat model; C-6.3 ops guide |
| §7 Security Posture | PROCEED-WITH-CONDITIONS | 0.73 | **C-7.1 ABAC fail-closed**; C-7.2 Argon2 params; C-7.3 cred file |
| §8 Dependency Risk | PROCEED-WITH-CONDITIONS | 0.83 | C-8.1 CodeArtifact P1; C-8.2 pin kj version |
| §9 Re-mapping Integrity | PROCEED-WITH-CONDITIONS | 0.84 | **C-9.1 traceability fix**; C-9.2 INV audit |
### Binding Decisions
---
| ID | Axis | Decision | Confidence |
|----|------|----------|-----------|
| G-101 | architecture | P8 grep scope amended to exclude terraform `backend "s3"` blocks (bucket arg is static-config-only, evaluated pre-init; cannot reference `data.aws_caller_identity`). Resource ARNs in policy/code ARE externalized; backend blocks stay literal or move to `-backend-config` (separate change). | 0.80 |
| G-102 | risks | P8 must bind `ACDL_AWS_ACCOUNT_ID` fallback to the live account ID (not a placeholder) AND the lifecycle workflow (full-mode jobs) must set `ACDL_AWS_ACCOUNT_ID` from `aws sts get-caller-identity` before any lifecycle invocation. No full-mode run proceeds with the env unset. | 0.78 |
| G-103 | scope | P13 must take the removal+documentation path (remove `--kube-version` + document deferral to GitOps reconciler roadmap), NOT the implementation path. Implementing version-aware policy selection is a new feature, violating D-095. | 0.85 |
| G-104 | architecture | P9 must verify (grep/audit of `modules/l1/*/terraform/main.tf` + `modules/l2/*/composition.json`) that every IAM role + KMS key created by the lifecycle pipeline matches `acdl-*` prefix before merge. CloudFront + WAFv2 (CloudFront scope) remain `Resource: "*"` with a documented global-ARN constraint. | 0.70 |
| G-105 | governance | P4's regression-gate hardening must be validated by running the full regression gate immediately after P4 lands (not deferred to P21). Gate must pass clean post-P4 before W2 begins. | 0.70 |
| G-106 | governance | A mid-milestone regression-gate checkpoint is added after W2 (P12), before W3 begins. Gate runs offline (D-091); a non-Verified result halts W3 until fixed. Not a re-litigation of G-007 (per-phase stays deferred) — a single checkpoint at the natural seam after the security wave. | 0.65 |
## Critical conditions (the 3 must-fix-before-P1)
### Escalations
### 🔴 C-6.1 / C-7.1 — ABAC fail-closed
The token-vend Lambda's behavior on `kj` absence/error is unspecified.
Without fail-closed, INV-17 is documentation, not a runtime guarantee —
a `kj` load failure would bypass the ABAC gate (every PAT gets a token).
**Fix applied to PLAN.md P4 Wave 4 Task 4.1:** "If
`KyvernoJsonEngine.is_configured()` returns false or `evaluate()`
raises, return 403 + audit `token.vend.denied` (reason:
`abac_eval_failed`). Never fail open. Test: `tests/test_abac_fail_closed.py`."
### 🔴 C-5.2 — JWS-from-PAT key derivation
REQ-332's AC ("public key derivable from the PAT") is unimplementable
without a specified KDF. A PAT is a JWT, not a keypair.
**Fix applied to PLAN.md P2 Wave 2 Task 2.3 + REQ-332 AC:** the JWS
uses HMAC-SHA256 with a key derived via
`HKDF-SHA256(PAT_bytes, salt='nova-local-attestation', info='jws-signing-key')`
→ 32-byte symmetric key. The "public key derivable" AC is re-interpreted:
the *verification key* is derived from the PAT via the same KDF (the
PAT is the shared secret). This is a symmetric scheme, not asymmetric.
### 🔴 C-9.1 — Traceability drift
REQUIREMENTS.md §v1.28 traceability table mapped 16 REQs to P2;
PLAN.md splits them across P2/P3/P4/P5/P6. **Fix applied to
REQUIREMENTS.md** — traceability table updated to match PLAN.md phase
structure.
---
## Tracked conditions (16 — applied to PLAN.md as amendments)
- **C-1.1** KMS asymmetric key verification before P4 Wave 3 (one
`aws kms create-key --key-spec ECC_NIST_P256` call).
- **C-1.2** Argon2 fail-closed test in P3 Wave 2 (Lambda returns 503
on `ImportError`, not a crash or pure-Python hash).
- **C-2.1** Fold P5 (idp-setup) into P4 as P4 Wave 8 → **reduces to 6
execution phases** (P1..P6, P7 = final). Applied.
- **C-2.2** P4 is a double-length phase; acknowledged in P4 header.
- **C-3.1** Cost envelope subsection added to PLAN.md.
- **C-3.2 / C-8.1** CodeArtifact provisioning = P1 Wave 0 task with
binary go/no-go gate; Gitea wheel index fallback documented.
- **C-4.1** P4 flagged as critical-path phase (kj spike = highest-
probability schedule slip; Fargate = +1 week).
- **C-4.2** Per-phase exit criteria added to PLAN.md.
- **C-5.1** `requested_claims` = list of claim names (the policy
asserts the subject is *allowed* to request those claims).
- **C-6.2** Threat model (REQ-347) adds: JWKS DDoS surface, PAT theft
+ max TTL (≤24h dev, ≤1h service-account), ABAC fail-closed,
INV-18..21 compression audit.
- **C-6.3** Operator guide (REQ-345) adds: KMS rotation, layer update,
PITR restore, emergency PAT revocation.
- **C-7.2** Argon2id parameters: t=3, m=65536 KiB, p=1 (OWASP min).
- **C-7.3** `~/.nova/credentials.json` stores OIDC token + PAT metadata
(jti, exp, type), NOT the raw PAT.
- **C-8.2** `kj` pinned to a specific release + SHA256 recorded.
- **C-9.2** Threat model includes INV-18..21 compression audit
(verify spec's attestation invariant semantics are captured by
INV-15/16/17 + REQ-332).
---
## Escalations
None. All 9 axes resolved at confidence ≥ 0.70. No human escalation
required (full autonomy).
---
## Grill complete
The plan proceeds with the 3 critical fixes and 16 tracked conditions
applied to PLAN.md + REQUIREMENTS.md. The binding decisions above are
the authoritative grill record. Next: MVP/UX CHECK → SHIP phase 0.
- **[E-001] risks** — P8 state-bucket continuity re-exposes the v1.11 4-VPC
root cause. G-102 proposes a binding mitigation (bind fallback + wire env
into workflow), but the residual risk (a future full-mode lifecycle run
with a misconfigured env orphans live state and re-creates resources)
cannot be reduced below 0.20 by plan-level decisions alone. **Auto-
resolved at full autonomy (D-101):** accept the residual risk; G-102's
binding mitigation (fallback bound to live account ID + workflow env
wiring) is the control. The lifecycle pipeline defaults to plan-only
(REQ-134) — full-mode runs are workflow_dispatch only, reducing the
accident surface. If the user prefers zero residual risk, direct that
P8 exclude the state-bucket name from externalization entirely
(externalize only resource ARNs, leave the backend `bucket` literal).
Confidence 0.55; auto-resolved per `config.autonomy.level=full`.
+3 -4
View File
@@ -1,4 +1,4 @@
# Nova — IAM Policy Baseline (v1.11, REQ-116)
# ACDL — IAM Policy Baseline (v1.11, REQ-116)
> Source of truth: `terraform/bootstrap/spike_runner_policy.json`.
> Applied as: customer-managed policy `acdl-spike-runner-policy`
@@ -56,8 +56,7 @@ and covered by the baseline test.
## OIDC act_runner role (CAP-022, Phase 56)
The OIDC role for the Gitea `act_runner` was created in Phase 08 and
gone since (`archive/CAPABILITY_INVENTORY-v1.10.md` CAP-022, archived
v1.27). Phase 56 re-creates it
gone since (CAPABILITY_INVENTORY.md CAP-022). Phase 56 re-creates it
with a trust policy for the Gitea runner ARN. The role grants the
spike-runner-equivalent permissions to the runner via `sts:AssumeRole`,
so the runner does not need a long-lived access key. This closes the
@@ -74,7 +73,7 @@ bootstrap root key; the runner then assumes the role.
The OIDC role for the Gitea `act_runner` was planned in Phase 08 but
never created (the spike used a long-lived key per D-039 waiver).
`archive/CAPABILITY_INVENTORY-v1.10.md` CAP-022 recorded "iam:ListRoles shows no acdl*
CAPABILITY_INVENTORY.md CAP-022 recorded "iam:ListRoles shows no acdl*
roles." Phase 56 re-created the role:
- **Role name:** `acdl-act-runner-role`
-194
View File
@@ -1,194 +0,0 @@
# IDEATE — v1.26 Live Pilot Estate Activation
> **Autonomy:** full. 3-tier ideation per `config.json ideation.enabled:
> true`. `cross_project.enabled: false` → cross-project tier scoped to
> multi-project (deferred ideas only, no cross-project candidates
> accepted). `confidence_threshold: 0.6`, `max_ideas: 20`.
> Categories: security, quality, architecture, coverage, improvement.
## Tier 1 — Mechanical (pattern-driven, codebase-grounded)
### I1 — Outcome-backfill emitter ✅ ACCEPTED (REQ-317)
**Category:** quality, coverage
**Confidence:** 0.92
**Pattern:** stuck `pending` status → backfilled from a later event
(the most direct metric-grounding pattern).
**Source:** `core/metrics/decision_ledger.py:210-211` documents the
event chain `confidence.computed → ai.decision.made →
attestation.recorded → run.completed/failed`. `collector.py:262`
inserts `fact_decision.outcome` as `"pending"` — no backfill step
wires `run.completed/failed` back into the decision's outcome. The AI
Decision Accuracy metric (`trust_snapshot.py:70-85`) reads
`decisions WHERE outcome='succeeded' ÷ total` → 0% today (all pending).
**Idea:** `core/metrics/outcome_backfill.py` reads run-manifest
`completed`/`failed` events and updates `fact_decision.outcome` +
`fact_decision.backfilled_at`. The collector invokes backfill after run
completion. Grounds AI Decision Accuracy (Post-Pilot target).
**Accepted into:** REQ-317. Phase P3.
### I2 — `reason='confidence'` escalation tag ✅ ACCEPTED (REQ-318)
**Category:** quality, coverage
**Confidence:** 0.90
**Pattern:** boolean field → discriminated field (the metric-numerator
precision pattern).
**Source:** `core/confidence_signal.py:184` — a `block` band sets
`human_override=True`. The Human Escalation Frequency metric
(`docs/metrics/human_escalation_frequency.md:11-12`) is defined as
`count(runs WHERE hitl_block=1 AND reason='confidence') ÷ total runs`.
The `reason='confidence'` discriminator is not stored today.
**Idea:** `ai.decision.made` gains `escalation_reason: 'confidence'`
when `band == 'block'`. The collector persists it into `fact_run`.
Grounds Human Escalation Frequency numerator.
**Accepted into:** REQ-318. Phase P3.
### I3 — Env-JSON `state_backend` wiring reconciliation ✅ ACCEPTED (REQ-319)
**Category:** architecture, improvement
**Confidence:** 0.88
**Pattern:** unused config field → wired config field (the
single-source-of-truth pattern).
**Source:** `adapters/terraform/adapter.py:116-117` computes the state
bucket as `nova-tfstate-<AWS_ACCOUNT_ID>-us-east-1` from the
`AWS_ACCOUNT_ID` env var — **not** from the env JSON's
`state_backend.bucket`. The env JSON's `state_backend` field is
currently unused by the live apply path.
**Idea:** The adapter reads `env.state_backend.bucket` when present
(falling back to the computed name for backwards compat). `dev.json`
gets the real bucket name. Closes the wiring gap so the pilot's env
JSON is the single source of truth.
**Accepted into:** REQ-319. Phase P3.
### I4 — Pilot-readiness kyverno-json policy ✅ ACCEPTED (REQ-320)
**Category:** security, architecture
**Confidence:** 0.85
**Pattern:** runtime guard → declarative policy (the v1.25 thesis
applied to pilot onboarding).
**Source:** `core/environment_check.py:48-53` emits a stderr warning
(non-fatal) when `account_id == "000000000000"` and env != dev. A
warning is not a gate. The pilot should fail-closed if someone tries
to apply against a placeholder account.
**Idea:** A kyverno-json policy over the env JSON asserting
`account_id != "000000000000"` before any apply. Declarative
fail-closed gate. Extends v1.25's policy engine to the pilot-onboarding
domain.
**Accepted into:** REQ-320. Phase P3.
## Tier 2 — Backend-enriched (signal-driven)
### I5 — Settlement-finality kyverno-json policy ✅ ACCEPTED (REQ-315)
**Category:** security, coverage
**Confidence:** 0.82
**Pattern:** domain invariant → declarative policy (the v1.25 thesis
applied to the securities domain — the most novel use of kyverno-json
in v1.26).
**Source:** The pilot's settlement service records matches as
transactions on the chain; settlement finality = block commit. The
NORTH_STAR Objective #2 (provable trust) says trust should be a policy
artifact, not a promise. Today settlement finality is a runtime
property of the chain; making it a declarative policy turns it into an
auditable gate.
**Idea:** A kyverno-json policy over the settlement-service status JSON
asserting `all_committed: true` before any promotion (qa→prod). The
securities-specific extension of v1.25's policy engine. The policy is
skip-when-kj-absent (graceful).
**Accepted into:** REQ-315. Phase P3.
### I6 — Pilot-estate regression capability (CAP-025) ✅ ACCEPTED (REQ-316)
**Category:** quality, coverage
**Confidence:** 0.88
**Pattern:** manual e2e → regression-gated capability (the v1.0 CAP
pattern applied to the pilot).
**Source:** `core/regression_verify.py` has CAP-013..024 (live-AWS +
local tiers). The pilot estate is a new live-AWS capability —
"contract resolve → adapter compile → terraform plan → policy scan →
confidence signal → attestation → outbox record" against
`581513795199`. Without a regression CAP, the pilot could silently
decay.
**Idea:** CAP-025 (live-pilot-apply) in the regression gate. The
round-trip assertion. Grounds the pilot as a maintained capability,
not a one-shot demo.
**Accepted into:** REQ-316. Phase P3.
### I7 — DynamoDB L1 primitive ✅ ACCEPTED (REQ-322)
**Category:** architecture, coverage
**Confidence:** 0.95
**Pattern:** missing primitive → authored module (the v1.7 + v1.8
module-build-out pattern).
**Source:** RESEARCH §3.4 — no `modules/l1/dynamodb/` exists. The
blockchain exchange's ledger table needs it. The adapter is
stateless/registry-driven (no `TYPE_MAP`); a new stack type requires a
new L1 module, not an adapter change.
**Idea:** Author `modules/l1/dynamodb/` (interface.json +
terraform/main.tf + README.md + instance.json + registry.json entry).
The single platform-side module build-out for the milestone. Follows
the `s3`/`rds` primitive template. Encryption + PITR enabled per v1.8
NFR defaults.
**Accepted into:** REQ-322. Phase P3.
### I8 — Stale `adapters/README.md` TYPE_MAP references ❌ DEFERRED (scope)
**Category:** improvement
**Confidence:** 0.70 (above threshold, but scoped into REQ-321)
**Pattern:** stale doc → corrected doc.
**Source:** `adapters/README.md:49-54` references the deleted
`TYPE_MAP`/`INPUT_MAP`/`OUTPUT_MAP` — contradicts `adapter.py:1-11` +
`modules/STANDARDS.md:212-214`.
**Idea:** Fix the stale references as part of the docs phase.
**Reason deferred as a standalone idea:** Already captured in REQ-321
(docs + adapter README). No new requirement needed — the fix lands in
P4 docs.
## Tier 3 — Cross-project (deferred — multi-project, but cross-project sharing disabled)
### I9 — Cross-project policy sharing ❌ DEFERRED (config)
**Category:** improvement
**Confidence:** N/A
**Pattern:** policies shared across projects in a multi-project org.
**Source:** `config.json ideation.cross_project.enabled: false`.
**Idea:** In a multi-project org, kyverno-json policies could be shared
across projects (a tagging standard policy applies to all projects).
**Reason deferred:** `cross_project.enabled: false`. Even though
v1.26 is multi-project (acdl + nova-blockchain-exchange),
cross-project *ideation* is disabled in config. Recorded for when the
org grows + the flag is enabled.
### I10 — Consumer-repo CI scaffolding as a reusable template ❌ DEFERRED
**Category:** improvement
**Confidence:** 0.55 (below threshold — deferred, not rejected)
**Pattern:** one-off CI → reusable template.
**Source:** The consumer repo (`nova-blockchain-exchange`) needs its
own CI (`ci.yml` — lint + pytest). If Nova expects many consumers, a
reusable consumer-CI template would reduce onboarding friction.
**Idea:** A `nova-consumer-template` repo (or a
`.github/workflow-templates/` dir) that new consumers instantiate.
**Reason deferred:** Nova has 1 consumer today (the pilot). A template
is premature abstraction until the 2nd consumer arrives. The pilot's
CI is authored directly (REQ-310..312 tests). Recorded for when the
3rd consumer onboards.
## Summary
- 7 ideas accepted (I1..I7) → already captured as REQ-315, REQ-316,
REQ-317, REQ-318, REQ-319, REQ-320, REQ-322.
- 3 ideas deferred (I8 scoped into REQ-321; I9 config-disabled; I10
below threshold) with documented blocking reasons.
- 0 ideas rejected (below-threshold ideas are deferred, not rejected —
they may activate when their blockers lift).
- The accepted ideas are the **quality improvement** the `--ideate` flag
drives: I1 + I2 ground the Post-Pilot metrics (outcome backfill +
escalation reason); I3 closes the env-JSON wiring gap; I4 + I5 extend
v1.25's policy engine to the pilot domain (pilot-readiness +
settlement-finality); I6 gates the pilot as a maintained capability;
I7 is the single platform-side module build-out.
- No new requirements added beyond REQ-310..322 (the accepted ideas are
already scoped into the existing requirements). The IDEATE pass
validated the requirement set rather than expanding it — the ideas
were anticipated in the SPECIFY + RESEARCH stages.
-256
View File
@@ -1,256 +0,0 @@
# NORTH_STAR — Nova
> **Status:** Draft (pending interactive GRILL → final)
> **Milestone:** v1.21 — Nova Deck Refinement & Pipeline Hardening
> **Owner:** Product Owner
> **Purpose:** Durable strategic intent. Read by CIAgent in every future
> `/ci-run` so the platform's direction survives across milestones. This
> is NOT a status document (that's PROJECT.md) and NOT an engineering
> architecture (that's the telemetry reference in RESEARCH.md/
> ARCHITECTURE.md). It is the PO's committed direction: what we're
> building toward, what we refuse to build, and how we'll know we won.
---
## Vision
> **Infrastructure operations become visible. Every environment
> provisioned, every incident healed, every risk remediated — by an
> autonomous system whose trustworthiness is provable, not promised.
> Human attestation remains required at stage gates — QA signs off for
> production, SRE greenlights based on operational readiness — but the
> operator is never in the loop of normal operations.**
Nova is the autonomous infrastructure layer that lets product teams ship
without engaging an operator, and lets executives trust the platform not
because it never fails but because every decision is captured, scored,
and accountable. The recurring theme across the platform is that
**infrastructure operations become visible** — security posture,
remediation velocity, reliability, and lead time are surfaced as
queryable signals rather than hidden in tribal knowledge.
---
## Strategic Objectives (4)
**1. Demonstrate production-grade zero-touch operations.**
Nova must run real customer estates with no human in the loop of normal
operations — autonomy as the default, not the demo. Stage-gate
attestation (QA for production, SRE for operational readiness) remains
human by design; operational escalations (AI confidence too low to
proceed) are the failure mode we drive toward zero. Everything else
collapses if autonomy isn't real.
**2. Establish provable trust in automated decisions.**
Trust is established by deterministic scripts that calculate a score and
a band outcome that gates the action — the platform functions without AI.
"AI decisions" are really automated decisions. The audit substrate —
Decision Ledger, confidence scoring, circuit breakers, blast-radius
controls — turns "autonomous" from a marketing claim into a defensible
one. Trust is the moat. Features can be copied; an immutable, queryable
decision history cannot.
**3. Deliver compounding, quantifiable ROI for customers.**
Each quarter on Nova must show measurable improvement on four CTO-grade
metrics, all of which flow into PowerBI views and are captured by the
telemetry pipeline:
- **Lead Time** — from PR merge to production deployment (downward trend).
- **Infrastructure Vulnerability Count** — open findings on deployed
resources (downward trend, demonstrating that proactive scanning +
remediation keeps up with the AI-era 0-day pace).
- **MTTR** — for platform-detected and platform-remediated incidents.
- **Cloud Spend Reduction** — on pilot estates vs. the pre-Nova
baseline.
If leadership cannot point to a number that improves quarter-over-quarter
on these four axes, Nova fails its commercial test, regardless of how
clever the automation is.
**4. Integrate with externally owned development platforms — regardless of source.**
Nova integrates with externally owned PDLC, SDLC, Agentic, and Citizen
Developer platforms with no regard for the source of the intent. Nova
provides a set of skills and MCP endpoints that help the developer or AI
agent make their application production-grade. Regardless of the source,
all intents to deploy to production go through the same rigorous
controls, quality gates, attestation, and evidence stream. Nova is the
layer any of those platforms reach for first when an agent needs to
deploy — not a vendor arriving late to that market.
---
## Anti-Goals (4 — what Nova is fundamentally NOT)
1. **Not a general-purpose AI agent platform.** We are purpose-built for
infrastructure operations. Breadth here produces shallow tools; depth
here wins the category.
2. **Not a system that removes humans from accountability.** Only from
normal operations. Every automated decision lands in an immutable
ledger. Every stage-gate promotion (qa/prod/dr) requires a human
attestation recorded with approver identity, separation-of-duties
check, and the evidence matrix. The absence of an operator in the
loop is never the absence of a record.
3. **Not an upstream development platform.** Nova does not own the
product backlog, IDE workflows, code authorship, or application
business logic. The PDLC is upstream; Nova integrates with it through
a validated contract boundary — Nova never reaches into it.
4. **Not a replacement for the Product Development Lifecycle (PDLC).**
Nova governs infrastructure + delivery only. Product lifecycle
decisions (what to build, when to ship, for whom) remain with the
product team. Nova makes their intent production-grade; it does not
own the intent.
---
## Non-Goals (v1.17 milestone scope — deferred work, not permanent boundaries)
> Anti-Goals are what Nova *fundamentally is not*. Non-Goals are what we
> *will not do this milestone* — deferred work, not permanent boundaries.
> Each Non-Goal cites the controlling decision ID.
1. **Live AWS re-provisioning** (deferred — D-096). Metrics that require
live infrastructure ship as placeholder PowerBI views with documented
schemas.
2. **Onboarding auto-grant** (deferred — D-113/D-114/D-119). Only the
request-path metric is grounded; the requested→granted funnel is a
placeholder.
3. **ML anomaly-forecasting / predictive remediation** (no emitter today).
The Predictive-vs-Reactive metric ships as a placeholder.
4. **Drift detection scheduled job** (deferred — D-096 + no scheduler).
Drift metrics ship as placeholders.
5. **Live cost CUR reconciliation** (deferred — D-096). Pre-apply Infracost
estimates are grounded; actual-spend reconciliation is a placeholder.
6. **S3 Object Lock / JWS tamper-evident ledger** (deferred — D-083). The
Decision Ledger uses a local SQLite hash-chain this milestone; the
Object-Lock/JWS build-out is a future milestone.
7. **Multi-cloud support** (Azure/GCP/K8s). Nova is AWS-only this milestone.
---
## 1218 Month Targets
Targets are committed, not aspirational. Each is a number a board member
can repeat back to us. The grounding column records whether the metric is
measurable this milestone, and if not, what blocks it.
> **Honesty note (GRILL G-Q6 binding):** Nova has 0 consumer adoption
> today (`PROJECT.md:495`). Three targets (Touchless Resolution, Human
> Escalation, AI Decision Accuracy) are scoped "across production
> estates" — the measurement *pipeline* is grounded this milestone, but
> the *denominator* is zero until a pilot estate activates. These
> targets are reclassified as **Post-Pilot** (the pipeline works; the
> numbers fill when consumers exist). This is the same honesty model as
> Cloud Spend Reduction (partial: pipeline grounded, actuals deferred).
### Current-milestone targets (grounded or derived this milestone)
| Domain | Target | Grounding (v1.17) | Note |
|---|---|---|---|
| **MTTR (p95)** | < 60 seconds | grounded (platform-run MTTR) | apply.failed → successful retry; infra-incident MTTR deferred (no incident detection) |
| **Cloud Spend Reduction** | ≥ 25% on pilot estates vs. 12-month pre-Nova baseline | partial | pre-apply estimate grounded (Infracost); actual-spend deferred (D-096 CUR) |
| **L1 / L2 Ops Hours Avoided** | ≥ 70% of pre-Nova FTE allocation | derived | formula over run count × manual baseline (computed on N internal runs; production-denominator activates post-pilot) |
| **Platform ROI** | ≥ 250% measured annually | derived | formula (labor savings + cloud savings + avoided downtime) ÷ platform op cost (computed on N internal runs; production-denominator activates post-pilot) |
| **Decision Ledger Coverage** | 100% of AI actions with backfilled outcome | grounded (this milestone builds it) | outbox_writer.py → SQLite hash-chain |
| **Attestation Coverage** | 100% of prod/dr promotions attested by a human | grounded | hitl_gates.py + outbox approver_* attributes; separation-of-duties on prod |
### Post-Pilot targets (pipeline grounded this milestone; denominator activates when a pilot estate runs)
| Domain | Target | Grounding (v1.17) | Note |
|---|---|---|---|
| **Touchless Resolution Rate** | ≥ 99% across production estates | partial (pipeline grounded; denominator = 0 today) | runs completing without *operational* HITL block ÷ total runs (attestation gates excluded); activates post-pilot |
| **Human Escalation Frequency** | < 0.1% of platform actions | partial (pipeline grounded; denominator = 0 today) | *operational* HITL blocks only (confidence-driven); attestation sign-offs excluded; activates post-pilot |
| **AI Decision Accuracy** | ≥ 99.5% (no rollback, no follow-up incident within 5 min of action) | partial (pipeline grounded; denominator = 0 today) | decisions not followed by apply.failed/incident within 5min; activates post-pilot |
### Deferred targets (measurement requires future systems)
| Domain | Target | Grounding (v1.17) | Note |
|---|---|---|---|
| **Predictive vs. Reactive Ratio** | ≥ 3 : 1 (prevention dominates reaction) | deferred | requires ML forecasting service (future emitter) |
| **Drift Auto-Reversal Rate** | ≥ 95% within one detection cycle | deferred | requires drift detection (D-096 + scheduler) |
> Committed targets whose measurement is deferred remain committed — the
> target is the destination; the metric is the odometer, and some
> odometers aren't built yet. Each deferred metric ships as a placeholder
> PowerBI view + a definition-of-success doc recording the dependency.
> Post-Pilot targets are committed targets whose measurement pipeline is
> grounded this milestone; the numbers activate when a pilot estate runs.
### Future Horizons (strategic direction, not committed targets)
| Domain | Aspiration | Note |
|---|---|---|
| **AI-Agent Intent Share** | ≥ 40% of total intent volume originated by non-human consumers | Strategic Objective #4 direction. No backing requirement, no placeholder view, no emitter today. Moves to a committed target when agentic consumption is real. |
---
## Success Criteria (v1.17 — what constitutes success for THIS milestone)
> Distinct from the 1218mo targets: those are the destination. These are
> the milestone's exit criteria.
v1.17 is a success if:
1. **Decision Ledger emits `ai.decision.made` for 100% of platform runs**
with outcome backfill, AND **`attestation.recorded` events for 100%
of qa/prod/dr promotions** (event completeness — all 3 gates captured;
grounded in `outbox_writer.py` → SQLite hash-chain; honors D-083).
The **Attestation Coverage metric** (target 100%) measures prod/dr
promotions specifically — see REQ-194.
2. **`docs/METRICS.md` catalogs every executive KPI** with a `grounded` /
`derived` / `deferred` status, a source file or decision ID, and a
per-KPI definition-of-success doc in `docs/metrics/`.
3. **The PowerBI export produces all fact/dimension views** + 8 empty
placeholder views for deferred metrics (with documented schemas ready
to fill when their blocking decisions lift).
4. **The unified narrative deck ships** with the x3 arc
(Problem→Vision→How→Proof→Roadmap) at deck + slide level, per-slide
benefit callouts, and fluid transitions; both old decks retired.
5. **`NORTH_STAR.md` is wired into CIAgent context-loading** so every
future `/ci-run` reads it.
6. **CAP-023 (metrics collector) + CAP-024 (deck structure) pass** in the
regression gate.
---
## What "won" looks like
By month 18, Nova is the layer enterprise leadership points to when they
say *"we don't have an infrastructure ops team anymore, and the audit
trail is stronger than it ever was"* — and it is the default substrate
their AI engineering teams reach for first when an agent needs to deploy.
---
## Relationship to v1.17 engineering
- **Pillar A (this file):** strategic direction — durable, PO-authored.
- **Pillar B (engineering):** the telemetry reference architecture
(adapted from the PO's technical-direction input) lives in
RESEARCH.md/ARCHITECTURE.md. It is the *how*; this file is the *why*.
- **Pillar C (story):** the unified narrative deck proves Pillars A+B to
leadership. The deck's Proof section cites grounded metrics; its
Roadmap section cites deferred targets honestly.
## Relationship to engineering files (v1.27 update)
- **NORTH_STAR.md** (this file) = the *why* — PO-authored strategic
direction, loaded every ci-run via `config.strategic_direction_file`.
- **STATE.md** = the *what exists* — PO-owned capability catalog,
additive, updated at every milestone ship (P-final Wave 3). The PO
reads STATE.md before writing new REQ-NNN specs to avoid re-spec'ing
existing capability and to respect the invariants.
- **ARCHITECTURE.md** = the *how* — the durable target architecture.
- **CHECKPOINT.json** = the *now* — authoritative live phase/ship
state.
## v1.25 update — swappable policy-engine substrate
Strategic Objective #2 (provable trust) gained a concrete substrate in
v1.25: the policy engine that produces the `PolicyCheckResult` records
feeding the confidence signal is now **swappable** via the
`PolicyEngine` protocol (`core/policy_engine.py`). `kyverno-json` is
the v1.25 default; `OPA` (or any other engine) can replace it by
implementing the same 3-method protocol — without touching the
confidence signal, the PCR schema, or the pipeline. See
ARCHITECTURE.md §12.7. The trust moat is a *replaceable* engine, not a
vendor lock-in.
+133 -101
View File
@@ -1,119 +1,151 @@
---
project: acdl
milestone: v1.28
generated_at: 2026-08-19
milestone: v1.14
generated_at: 2026-07-29
generator: lead-developer
verification_toolchain:
typecheck: "python3 -m py_compile core/mode_resolver.py nova/cli.py 2>&1 | head -5 || true"
test: "pytest tests/test_mode_resolver.py tests/test_cli_subcommands.py -q 2>&1 | tail -15 || true"
lint: "ruff check nova/ core/lambda/nova_idp_*.py 2>/dev/null || true"
typecheck: "terraform validate && python3 -m py_compile core/**/*.py && python3 -m jsonschema schemas/*.schema.json"
test: "bash scripts/run_primitive_plan.sh --check-only <primitive> # pipeline-driven (D-102); no per-module pytest"
build: "terraform init && terraform plan"
note: |
v1.28 is a feature milestone (CLI Canonicalization + Identity Layer).
Four active personas: backend-engineer (Lambda/DynamoDB/KMS/CodeArtifact),
security-engineer (Argon2id/KMS/ABAC/threat model), cli-engineer
(subcommand surface/mode_resolver/argparse/CAP-034), lead-developer
(plan/review/ship/capability gate). frontend-engineer + data-engineer
deactivated (no UI, no data pipelines). The kj-binary-in-Lambda-layer
risk (D-227, RESEARCH §7) is the highest-risk item; P2 spike confirms.
ACDL has no package.json. The execute/verify/ship workflows substitute
`terraform validate` + `python -m py_compile` + JSON Schema validation
for npm run typecheck, a per-phase verify script (or the
modules-lifecycle pipeline cell) for npm test, and `terraform init` +
`terraform plan` for npm run build. v1.11 testing is pipeline-driven
(D-102): the modules-lifecycle pipeline matrix-runs each L1 module's
examples/{simple,complex}.yml contracts through apply→modify→destroy
against live AWS. No per-module Python/pytest. This override is
documented here as the single source of truth; the ci-* agents read
PERSONAS.md before running verification commands.
v1.14 note: NFR-only milestone (bug fixes, security, tests, docs).
Roster carries forward from v1.11 unchanged. frontend-engineer stays
inactive (no frontend; decks are markdown = lead-developer
territory). No custom personas needed (no new domains).
---
# Personas — v1.28 CLI Canonicalization + Identity Layer
# ACDL — Persona Roster (project-level, v1.11 RESTART)
## Roster
> v1.11 is a restart (D-097). The v1.9 roster is superseded. Three
> structural corrections: (1) stateless adapter (D-098), (2) terraform
> owns lifecycle (D-101), (3) pipeline-driven testing (D-102). The roster
> is simplified to the three active domains: data (terraform foundation),
> backend (adapter/resolver), general (pipelines/workflows).
### backend-engineer
```yaml
active: true
domain: "Lambda functions, DynamoDB, KMS integration, dual-use packaging, CodeArtifact publish, CloudFormation generation"
frameworks: ["Python 3.12", "boto3", "argparse", "pytest", "moto[dynamodb]", "CloudFormation"]
constraints: ["INV-15", "INV-16", "INV-17", "D-228", "D-229", "D-230", "NFR-5", "NFR-6", "NFR-7", "NFR-8"]
territory:
- "core/lambda/**"
- "core/metrics/**"
- "core/env.py"
- "core/outbox_writer.py"
- "terraform/bootstrap/**"
- ".gitea/workflows/publish.yml"
- ".github/workflows/publish.yml"
- ".github/actions/nova-cli/**"
```
### security-engineer
```yaml
active: true
domain: "Argon2id hashing, KMS asymmetric signing (ECDSA P-256 / ES256), ABAC policy, JWKS exposure, PAT lifecycle, threat model, DER→raw ECDSA conversion"
frameworks: ["argon2-cffi", "cryptography", "pyjwt", "kyverno-json", "JMESPath", "KMS Sign/Verify/GetPublicKey"]
constraints: ["INV-15", "INV-16", "INV-17", "NFR-5", "NFR-8", "NFR-9", "D-227", "D-231"]
territory:
- "platform/abac/**"
- "core/policy_engine.py"
- "adapters/kyverno-json/**"
- "core/lambda/nova_idp_auth.py"
- "core/lambda/nova_idp_token_vend.py"
- "core/lambda/nova_idp_jwks.py"
- "docs/threat-model.md"
```
### cli-engineer
```yaml
active: true
domain: "CLI subcommand surface, mode_resolver, argparse, [project.scripts] entry-point, CAP-034 AST scan, nova auth/idp subgroups, property tests"
frameworks: ["Python 3.12", "argparse", "setuptools [project.scripts]", "hypothesis", "pkgutil"]
constraints: ["INV-12", "INV-13", "INV-14", "D-226", "NFR-1", "NFR-2", "NFR-3"]
territory:
- "nova/**"
- "core/mode_resolver.py"
- "pyproject.toml"
- "tests/test_mode_resolver.py"
- "tests/test_cli_subcommands.py"
```
## Active personas
### lead-developer
```yaml
active: true
domain: "Phase plan, persona roster, review gates, milestone ship, capability gate (CAP-033..038), ROADMAP/STATE/PROJECT wiring"
frameworks: ["git", "Gitea Actions", "semver tagging", ".ciagent/ discipline"]
constraints: ["INV-1..17 (cross-cutting)", "v1.28 hard constraints", "NFR-6", "NFR-11"]
territory:
- ".ciagent/**"
- "PLAN.md"
- "CHECKPOINT.json"
- "STATE.md"
- "REQUIREMENTS.md"
- "ROADMAP.md"
```
- **Domain:** coordination
- **Active:** true
- **Phase-specific:** false
- **Reason:** Owns CIAgent metadata, cross-phase verification scripts, the v1.11 phase orchestration (D-107: P56a + P56b split), and arbitrates persona conflicts. Resolves the milestone decomposition and the STANDARDS.md §8 rewrite (the adapter extension pattern is replaced by the per-module terraform subdir pattern).
### frontend-engineer
```yaml
active: false
phase_specific: false
reason: "No UI in v1.28 (CLI + JSON endpoints only). JWKS serves application/json; no HTML/CSS/JS surface."
```
### backend-engineer
- **Domain:** backend
- **Active:** true
- **Phase-specific:** false
- **Reason:** Owns the adapter rewrite (D-098: stateless assembler — deletes TYPE_MAP/INPUT_MAP/OUTPUT_MAP + 39 type-specific branches, becomes a ~80-line assembler that emits `module "x" { source = "..." ... }` blocks) and the contract resolver env-aware state keys (D-106: `spike/{id}/{env}/terraform.tfstate`). The adapter holds no module content; the engine binding lives in the per-module `terraform/` subdir. Co-authoring expected on the adapter + `run_platform.sh` boundary (general adds `--apply`/`--destroy` modes that invoke the adapter).
- **Territory:** `adapters/terraform/adapter.py` (rewrite to stateless assembler), `core/contract_resolver.py` (env-aware state keys, deterministic composition), `schemas/stack.schema.json` (if the stack instance shape changes), `tests/test_adapter*.py` (regression baseline — the s3 instance.json round-trip must still pass).
### data-engineer
```yaml
active: false
phase_specific: false
reason: "No data pipelines / metrics / PowerBI work in v1.28. The metrics layer is v1.17-complete; v1.28 adds audit events but no new fact/dim tables."
```
- **Domain:** data
- **Active:** true
- **Phase-specific:** false
- **Reason:** Reactivated for v1.11. Owns the heaviest territory: the per-module `terraform/` subdirs (D-098/D-099/D-100 — the engine binding) for all 12 L1 modules, plus the single platform VPC (D-105: `terraform/platform` owns ONE VPC; the microservice composition drops its `vpc` child and references the platform VPC via data source). Each L1 module ships a real terraform module dir (versions/variables/locals/main/outputs.tf) owning its resource shape, nested blocks, and defaults. `locals.tf` is used heavily to centralize default interpolation (D-099). Multi-resource modules get the full 5-file split; trivial single-resource modules may inline locals in main.tf. This is the binding constraint — the stateless adapter cannot be written until the reference s3 module exists (D-107: P56a proves the design with s3 first).
- **Territory:** `terraform/` (platform VPC, D-105), `modules/l1/*/terraform/` (per-module terraform subdirs — the engine binding), `modules/l1/*/interface.json` (defaults move from adapter to interface inputs), `modules/registry.json` (terraform_dir field), `modules/l2/microservice/composition.json` (drop the vpc child, D-105), `modules/STANDARDS.md` §8 (rewrite the adapter extension pattern → per-module terraform subdir pattern).
## Territory overlap notes
### general (lead-developer + backend-engineer pipeline work)
- **Domain:** coordination + pipelines
- **Active:** true
- **Phase-specific:** false
- **Reason:** Owns the pipeline-driven testing (D-102/D-103/D-104) and the terraform lifecycle modes (D-101). The 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. `run_platform.sh` gains `--apply` and `--destroy` modes; Python never runs terraform. `verify_deploy_microservice.py` is deleted (D-101). Co-authoring expected on the `run_platform.sh` boundary (backend-engineer rewrites the adapter that `run_platform.sh` invokes).
- **Territory:** `pipelines/modules-lifecycle.yml`, `.gitea/workflows/modules-lifecycle.yml` + `.github/workflows/modules-lifecycle.yml` (byte-identical, D-102), `scripts/run_platform.sh` (`--apply`/`--destroy` modes, D-101), `scripts/run_primitive_plan.sh` (if extended for lifecycle), `scripts/run_pattern_plan.sh` (if extended), `pipelines/README.md` (document the new pipeline), `schemas/deploy-pipeline.schema.json` (if the lifecycle stages are added to the contract).
- `core/lambda/contract_ingestor.py` (dual-use refactor, REQ-329) =
backend-engineer territory. `core/lambda/nova_idp_auth.py` +
`nova_idp_token_vend.py` are **co-owned** by backend-engineer (Lambda
plumbing, DynamoDB, function URLs) + security-engineer (crypto, ABAC,
Argon2id logic inside).
- `core/mode_resolver.py` = cli-engineer. `core/policy_engine.py` =
security-engineer (the ABAC evaluation path).
- `nova/idp/setup.py` = cli-engineer (the subcommand + arg parsing) +
backend-engineer (the CloudFormation generation + deploy).
- `nova/auth/*` = cli-engineer (subcommands) + security-engineer (the
token exchange + credential storage logic).
## Deactivated personas
## Phase-specific personas
### lambda-engineer (custom, v1.9 — deactivated for v1.11)
- **Domain:** serverless
- **Active:** false
- **Phase-specific:** false
- **Reason:** No per-module Python this milestone (D-102: testing is pipeline-driven, not pytest). The v1.9 Lambda (`core/lambda/contract_ingestor.py`) and the `terraform/platform/main.tf` Lambda/DynamoDB/KMS/Secrets definitions persist from v1.9 but are not touched in v1.11. The `acdl-sod-halt` SNS topic and the attestation matrix are out of scope. Removed from the roster for v1.11; reactivates if a future milestone touches the Lambda.
None. All four active personas span the full milestone. The
security-engineer is heaviest in P2 (identity layer) + P3 (threat model);
the cli-engineer is heaviest in P1 (CLI substrate); the backend-engineer
spans P1 (CodeArtifact/layer) + P2 (Lambdas/DynamoDB).
### platform-engineer (custom, v1.9 — folded into data-engineer for v1.11)
- **Domain:** infra
- **Active:** false
- **Phase-specific:** false
- **Reason:** The v1.11 scope (D-097..D-107) is terraform module authoring + adapter rewrite + pipelines — not the v1.9-era L1/L2 IR-typed module authoring or the AWS OIDC bootstrap. The platform-engineer's v1.9 territory (`adapters/terraform/**`, `modules/**`, `terraform/**`) is split: the adapter goes to backend-engineer (rewrite), the per-module terraform subdirs + platform VPC go to data-engineer (the heaviest v1.11 work). Folded into data-engineer for v1.11; reactivates if a future milestone does IR-shaped module authoring or OIDC bootstrap work.
### security-engineer (custom, v1.9 — deactivated for v1.11)
- **Domain:** security
- **Active:** false
- **Phase-specific:** false
- **Reason:** The v1.11 scope does not touch Wiz/Kyverno/Checkov adapters, the HITL matrix, separation-of-duties, or the audit ledger. The security-engineer's v1.9 territory persists but is not touched. Removed from the roster for v1.11; reactivates if a future milestone touches security adapters or HITL gates.
### frontend-engineer
- **Domain:** frontend
- **Active:** false
- **Phase-specific:** false
- **Reason:** The evidence timeline UI (`evidence-ui/**`) is unchanged from v1.0 and not touched in v1.11. Removed from the active roster; reactivates if a future milestone touches the timeline UI.
### data-engineer (v1.9 — was deactivated, reactivated for v1.11)
- **Domain:** data
- **Active:** true (reactivated)
- **Phase-specific:** false
- **Reason:** See the active `data-engineer` entry above. The v1.9 deactivation rationale ("No ORM/persistence framework") no longer applies — v1.11's data-engineer owns terraform module authoring, not a data persistence layer.
### infra-stub-engineer (custom, v1.0 only)
- **Domain:** backend
- **Active:** false
- **Reason:** Owned L1 stub modules in the v1.0 demo. The demo is archived to `demo/`; real L1 modules are owned by data-engineer (v1.11). Not reactivated.
## Phase-specific overrides
| Phase | Personas active | Notes |
|-------|------------------|-------|
| 56a adapter-rewrite-and-s3-reference-module | data-engineer (lead: s3 reference terraform module — proves the design), backend-engineer (lead: stateless adapter rewrite — emits module blocks for s3), general (run_platform.sh --apply/--destroy skeleton) | security/lambda/frontend idle |
| 56b remaining-11-l1-module-terraform-subdirs | data-engineer (lead: author 11 L1 module terraform subdirs — vpc, ecs-cluster, ecs-service, iam-role, alb, ecr, cloudfront, waf, rds, kms-key, uptime), backend-engineer (adapter: confirm each module round-trips through the assembler), general (modules-lifecycle pipeline wiring) | security/lambda/frontend idle |
| (modules-lifecycle pipeline) | general (lead: byte-identical Gitea+GitHub workflow + matrix apply→modify→destroy), data-engineer (examples/{simple,complex}.yml contracts as the modify variants), backend-engineer (adapter confirms the lifecycle cells resolve) | security/lambda/frontend idle |
| (platform VPC + composition drop) | data-engineer (lead: terraform/platform VPC + microservice composition drops vpc child, D-105), backend-engineer (resolver: env-aware state keys, D-106) | general/security/lambda/frontend idle |
| verify | lead-developer (lead: 4-layer verification), all active personas (review their territory) | — |
| review-audit-complete | lead-developer (lead: review + audit + milestone completion), all active personas (review participation) | — |
## Domain priority (used by TaskDecomposer)
`data → backend → general`
Rationale: in v1.11, the terraform foundation (per-module `terraform/`
subdirs + platform VPC) is the binding constraint — the stateless adapter
cannot be written until the reference s3 module exists (D-107: P56a
proves the design with s3 first). Backend (adapter/resolver) follows once
the module shape is proven. General (pipelines/workflows) wires the
lifecycle modes last, once the adapter + modules produce valid terraform.
## Conflict resolutions (lead-developer arbitration)
- `backend-engineer` vs `data-engineer` over `modules/l1/*/interface.json`:
data-engineer owns the interface defaults (defaults move from the
adapter to the interface inputs, D-100); backend-engineer owns the
adapter that reads them. Co-authoring is expected; conflict goes to
lead-developer.
- `backend-engineer` vs `general` over `scripts/run_platform.sh`:
backend-engineer rewrites the adapter that `run_platform.sh` invokes;
general adds the `--apply`/`--destroy` modes. The interface (the CLI
flags + the adapter invocation) is co-authored; conflicts go to
lead-developer.
- `data-engineer` vs `general` over `modules/l1/*/examples/`:
data-engineer owns the example contracts (the modify variants,
D-103); general owns the pipeline that matrix-runs them. Co-authoring
is expected; conflicts go to lead-developer.
- `lead-developer` vs any: lead-developer owns `.ciagent/**` + `docs/**`
meta + verification scripts + `modules/STANDARDS.md` §8 rewrite; persona
engineers do not edit CIAgent metadata or the vision/architecture
source docs.
## Territory enforcement mode
`warn` — config.json has no `personas.territory_enforcement` field, so the
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.
+360 -456
View File
@@ -1,489 +1,393 @@
# PLAN — v1.28 CLI Canonicalization + Identity Layer
---
phase: P0
name: pre-execution
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: []
---
> **Milestone:** v1.28 (feature — CLI substrate + Nova-idp identity
> layer). Tags on the **v1.27.x** line: `v1.27.0` (P0) →
> `v1.27.1..v1.27.6` (P1..P6) → `v1.27.7` (P7 final = milestone
> release). The final phase's patch IS the milestone release.
> **Branch:** `milestone/v1.28-cli-identity`. Phase branches:
> `phase/00-pre-execution`, `phase/01-cli-substrate`,
> `phase/02-lambda-packaging`, `phase/03-idp-auth`,
> `phase/04-token-vend-pat`, `phase/05-docs-integration`,
> `phase/06-final-review-ship`.
>
> **Tags:** `v1.27.0` (P0) → `v1.27.1..v1.27.5` (P1..P5) →
> `v1.27.6` (P6 final = milestone release). 6 execution phases
> (P5 idp-setup folded into P4 Wave 8 per grill C-2.1).
# v1.14 — NFR Refinement Plan (20 execution phases + 1 final)
## Milestone goal
**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>`
The Nova CLI is installable from internal PyPI (CodeArtifact); every
`core/` module is reachable as a `nova <subcommand>`; the CLI and
Lambda functions share a single `core/` source tree; and Nova owns its
identity layer end-to-end (Nova-idp: `nova-idp-auth` +
`nova-idp-token-vend` Lambdas, KMS-signed OIDC tokens, kyverno-json
ABAC token vending, PAT lifecycle). No AWS-managed identity services
in the path (INV-15).
## Wave ordering (D-098)
## Requirements
- **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.
31 requirements: REQ-323..REQ-353 (full text in
`.ciagent/REQUIREMENTS.md` §v1.28). 6 capabilities: CAP-033..CAP-038.
6 invariants: INV-12..INV-17. 6 decisions: D-226..D-231 (CLARIFY) +
RESEARCH amendments (D-228 fail-closed, D-229 strong-read-on-PK).
## Execution approach
## Phase breakdown
### Phase P1 — cli-substrate (REQ-323..REQ-328)
**Goal:** CodeArtifact wheel + Lambda layer pipeline; `nova/` CLI
package with a subcommand per `core/` module; `nova init`; `nova
cli-action` composite action; `core/mode_resolver.py`; audit emission
with `mode` + `selection_reason`. The CLI is installable and every
`core/` module is reachable.
**Exit criterion:** CAP-033 + CAP-034 + CAP-035 Verified + all REQ-323..328
tests pass. CodeArtifact provisioned (Wave 0 gate).
#### Wave 0 — CodeArtifact provisioning (backend-engineer) [C-3.2/C-8.1]
- **Task 0.1** (backend-engineer): provision CodeArtifact domain
(`nova`) + repository (`nova-pypi`) in `581513795199`. Verify
`codeartifact:*` IAM grant on `nova-spike-runner`. **Binary go/no-go
gate for Wave 4.** If fail: activate Gitea wheel index fallback
(CLARIFY assumption #1) and document in PLAN.md.
#### Wave 1 — pyproject + entry point (cli-engineer)
- **Task 1.1** (cli-engineer): `pyproject.toml` — add
`[project.scripts] nova = "nova.cli:main"`; add
`[tool.setuptools.packages.find]` including `nova`, `nova.*`, `core`,
`core.*`, `adapters.*`; bump `requires-python` to `>=3.12`; add
`argon2-cffi`, `cryptography`, `pyjwt`, `hypothesis` to deps/test-deps.
Verify `pip install -e .` produces a `nova` executable.
#### Wave 2 — CLI dispatch + subcommands (cli-engineer)
- **Task 2.1** (cli-engineer): `nova/__init__.py` + `nova/cli.py`
(~80 lines, auto-discovers `nova/<module>.py` via `pkgutil.iter_modules`,
dispatches, emits `cli.invocation` audit event stub with INV-12 fields).
- **Task 2.2** (cli-engineer): `nova/<module>.py` for each `core/`
module (≤50 lines, `add_parser` + `run` delegates to `core/`). Cover:
`resolve`, `decommission`, `env-transition`, `env-check`, `hitl`,
`onboard`, `outbox`, `publish-outputs`, `policy`, `regression`, `sod`,
`readiness`, `attestation-matrix`, `confidence`. Skip internal-only
(`env`, `local_emulators`, `output_publisher` if not user-facing).
- **Task 2.3** (cli-engineer): `nova/init.py` (REQ-325) — scaffolds
`.nova/`, `.nova/contract.yml.attestations/`, `.gitignore` (excludes
secrets, `~/.nova/credentials.json`).
#### Wave 3 — mode_resolver + audit (cli-engineer)
- **Task 3.1** (cli-engineer): `core/mode_resolver.py`
`resolve_mode(flag, env_var, credential_type, stdin_isatty)` per D-226.
`sys.stdin.isatty()` is the TTY check (RESEARCH §11). Invalid env →
warn + fall through. Returns `(mode, selection_reason)`.
- **Task 3.2** (cli-engineer): wire `mode_resolver` into `nova/cli.py`
— resolve mode before dispatch, emit `cli.invocation` with `mode`,
`selection_reason`, `credential_type`, `command`, `args` (INV-12,
REQ-328).
- **Task 3.3** (cli-engineer): `tests/test_mode_resolver.py`
`hypothesis` property tests (REQ-349): deterministic, flag-wins,
invalid-env-ignored, no-silent-fallback. Edge cases: TTY + piped
stdout, missing credential, conflicting flag/env, invalid env value.
#### Wave 4 — CodeArtifact + layer pipeline (backend-engineer)
- **Task 4.1** (backend-engineer): `.gitea/workflows/publish.yml` +
`.github/workflows/publish.yml` (byte-identical) — build wheel →
CodeArtifact `twine upload` → build layer (`pip install --target
layer/python/` + `argon2-cffi` + `cryptography` + `pyjwt`) →
`lambda publish-layer-version` → SSM `/nova/layer/nova-cli/version`
mapping (CAP-035). Fail either → job fails (merge blocked, REQ-323).
Pin version to `<semver>+<sha7>` for idempotent re-runs.
#### Wave 5 — composite action (cli-engineer + backend-engineer)
- **Task 5.1** (cli-engineer): `.github/actions/nova-cli/action.yml`
composite action, `setup-python@v5` (3.12), CodeArtifact login +
`pip install nova`, `nova ${{ inputs.command }}`. `NOVA_CLIENT_MODE`
from input.
- **Task 5.2** (backend-engineer): byte-identical integration test —
CI matrix runs the action on GitHub `ubuntu-latest` + Gitea
`act_runner`; assert same stdout/exit code (REQ-326 AC2, NFR-11).
#### Wave 6 — CAP-033/034 gate (cli-engineer)
- **Task 6.1** (cli-engineer): `tests/test_cli_subcommands.py`
CAP-033 (`nova --help` lists a subcommand for every `core/` module)
+ CAP-034 (AST scan: ≤50 lines, ≤3 defs, all calls resolve to `core.`,
no conditionals beyond `if __name__`). Wire into CI merge gate.
### Phase P2 — lambda-packaging (REQ-329, REQ-330, REQ-331)
**Goal:** Dual-use `core/lambda/contract_ingestor.py` (Lambda + CLI
paths share ≥80% code); `core/env.py:+synthesize_local_env()` for
`nova apply --local`; `.nova/contract.yml.attestations/` scaffolded;
JWS-from-PAT key derivation (C-5.2).
**Exit criterion:** all REQ-329..331 tests pass + JWS-from-PAT KDF
specified.
#### Wave 1 — dual-use refactor (backend-engineer)
- **Task 1.1** (backend-engineer): refactor
`core/lambda/contract_ingestor.py` — extract the shared logic into
importable functions; the Lambda handler + the CLI `__main__` block
both call them. The `__main__` block already exists (the dual-use
precedent per RESEARCH §1.2). Verify ≥80% code share (CAP-034 / code
review). Local path via `core/local_emulators.py:LocalLambdaStub`.
#### Wave 2 — local env synthesizer + JWS KDF (backend-engineer)
- **Task 2.1** (backend-engineer): `core/env.py:+synthesize_local_env()
` — produces a local env dict (account_id placeholder, region local,
no real AWS) from a contract + `--local` flag. Mirrors
`core/onboarding.py:generate_env_file()`.
- **Task 2.2** (cli-engineer): `nova/apply.py` (≤50 lines) — `nova
apply --local` delegates to `core.env.synthesize_local_env()` +
`core.contract_resolver.resolve()`.
- **Task 2.3** (security-engineer): JWS-from-PAT key derivation
(C-5.2). HKDF-SHA256(PAT_bytes, salt='nova-local-attestation',
info='jws-signing-key') → 32-byte symmetric key. The JWS is
HMAC-SHA256 (symmetric, not asymmetric). The "public key derivable
from the PAT" AC (REQ-332) is re-interpreted: the *verification key*
is derived from the PAT via the same KDF (the PAT is the shared
secret). Document in `docs/developer-guide-auth.md`. Update REQ-332
AC accordingly.
#### Wave 3 — attestations dir (cli-engineer)
- **Task 3.1** (cli-engineer): verify `nova init` (P1 Wave 2 Task 2.3)
creates `.nova/contract.yml.attestations/` (empty). REQ-331 test.
### Phase P3 — idp-auth (REQ-333, REQ-334, REQ-335)
**Goal:** `nova-idp-auth` Lambda (sign-up, sign-in, session) with
Argon2id hashing + DynamoDB tables. CAP-036 target.
**Exit criterion:** CAP-036 Verified (E2E sign-up → sign-in → session
passes in CI).
#### Wave 1 — DynamoDB schema (backend-engineer)
- **Task 1.1** (backend-engineer): define the 4 DynamoDB table schemas
(`nova-users`, `nova-sessions`, `nova-password-resets`, `nova-pats`)
in a CloudFormation snippet (reused by P4 Wave 8 `nova idp setup`).
PITR enabled on each (REQ-335).
#### Wave 2 — Argon2id (security-engineer) [C-1.2/C-7.2]
- **Task 2.1** (security-engineer): `core/lambda/nova_idp_auth.py` —
Argon2id password hashing via `argon2-cffi` (D-228: bundled abi3
wheel; **fail-closed on `ImportError` → 503, no pure-Python
fallback**). **Parameters: t=3, m=65536 KiB, p=1** (OWASP-recommended
minimum). Lambda memory ≥512 MB (m=64 MiB + Python overhead fits).
Raw passwords never in logs/traces/env/DDB (INV-16, REQ-334).
- **Task 2.2** (security-engineer): `tests/test_argon2_fail_closed.py`
— mock `argon2.low_level` import failure → assert auth Lambda
returns 503 (not a crash, not a weak hash). C-1.2.
#### Wave 3 — auth Lambda (backend-engineer + security-engineer)
- **Task 3.1** (backend-engineer): `nova-idp-auth` Lambda handler —
sign-up, sign-in, session creation endpoints. Function URL + IAM
auth. DynamoDB via lazy `boto3.resource` (the existing pattern).
- **Task 3.2** (security-engineer): session token issuance + session
storage in `nova-sessions` (TTL `expires_at`). Password reset flow
in `nova-password-resets` (TTL 15m).
#### Wave 4 — CAP-036 E2E (backend-engineer)
- **Task 4.1** (backend-engineer): `tests/test_idp_auth.py` — sign-up
→ sign-in → session round-trip (moto[dynamodb] for local; deployed
for CI). CAP-036 verification.
### Phase P4 — token-vend-pat (REQ-336..REQ-344, REQ-340, REQ-341) [was P4+P5]
**Goal:** `nova-idp-token-vend` Lambda (KMS-signed OIDC, kyverno-json
ABAC), JWKS endpoint, PAT lifecycle, `nova auth` commands, **and**
`nova idp setup` (folded from P5 per C-2.1). CAP-037 + CAP-038 target.
**Highest-risk phase — critical-path.** The kj-binary spike (Wave 1)
is the single highest-probability schedule slip; Fargate fallback adds
~1 week (D-227). This is a **double-length phase** (8 waves).
**Exit criterion:** CAP-037 + CAP-038 Verified + `nova idp setup
--check/--apply/--verify` works against a fresh AWS account.
#### Wave 1 — kj-binary spike (backend-engineer + security-engineer) [C-8.2]
- **Task 1.1** (backend-engineer): confirm the `kj` Go binary
(~40 MB Linux amd64) runs in the Lambda Python 3.12 runtime on
AL2023. Bundle it in the `nova-cli` layer (`wget` a **pinned
release** (e.g. `kj@v1.x.y`) + record SHA256 into `layer/kj.sha256`
— C-8.2, supply-chain safety) into `layer/bin/kj`, `chmod +x`.
Verify `KyvernoJsonEngine.is_configured()` finds `/opt/bin/kj`.
**If this fails:** fall back to Fargate for the token-vend Lambda
(D-227 risk, RESEARCH §7). Escalate to user only if both fail (full
autonomy: log assumption + proceed with Fargate).
#### Wave 2 — ABAC policy (security-engineer) [C-5.1]
- **Task 2.1** (security-engineer): `platform/abac/token-vend.policy`
— kyverno-json `ValidatingPolicy` (D-227). Payload:
`{subject, requested_claims, target_resource, environment, pat_jti,
policy_version}`. **`requested_claims` = list of claim names** (the
policy asserts the subject is *allowed* to request those claims; the
values are assigned by the Lambda, not the requestor — C-5.1).
JMESPath checks for role/scope/env/owner. Severity `critical` = deny
on fail.
- **Task 2.2** (security-engineer): `policy_version` = git SHA of the
policy file, baked into the Lambda layer (D-231). Recorded in every
`token.vend.allowed/denied` audit event.
#### Wave 3 — KMS signing (security-engineer) [C-1.1]
- **Task 3.1** (security-engineer): **verify KMS asymmetric key
support** before implementation: `aws kms create-key --key-spec
ECC_NIST_P256 --key-usage SIGN_VERIFY` in the target account
(C-1.1). If fail: fall back to RSA-2048 (also supported, larger
tokens) or escalate. Do not discover this mid-Wave.
- **Task 3.2** (security-engineer): KMS key `alias/nova-oidc-signing`
(ECC_NIST_P256, SIGN_VERIFY). Token-vend Lambda signs via
`kms.sign(SigningAlgorithm="ECDSA_SHA_256")` → DER→raw ECDSA
conversion (`decode_dss_signature` → `r.to_bytes(32) + s.to_bytes(32)`,
RESEARCH §5). JWT header `{"alg":"ES256","typ":"JWT","kid":"..."}`.
#### Wave 4 — token-vend Lambda (backend-engineer + security-engineer) [C-6.1/C-7.1 ABAC FAIL-CLOSED]
- **Task 4.1** (backend-engineer): `core/lambda/nova_idp_token_vend.py`
— accepts PAT/session, validates revocation (`nova-pats.GetItem(jti,
ConsistentRead=True)` — D-229), evaluates ABAC (Wave 2), signs (Wave
3), returns OIDC JWT. Audit at every step.
- **Task 4.2** (security-engineer): token claims `sub, aud, iss, exp,
iat, jti, roles` (REQ-336).
- **Task 4.3** (security-engineer) 🔴: **ABAC fail-closed.** If
`KyvernoJsonEngine.is_configured()` returns false or `evaluate()`
raises, return 403 + audit `token.vend.denied` (reason:
`abac_eval_failed`). **Never fail open.** This is INV-17's runtime
enforcement (C-6.1/C-7.1 — the grill's #1 finding). Test:
`tests/test_abac_fail_closed.py` — mock `kj` absent → assert 403 +
audit event.
#### Wave 5 — JWKS endpoint (backend-engineer)
- **Task 5.1** (backend-engineer): `core/lambda/nova_idp_jwks.py` —
function URL `AuthType: NONE`, `Cache-Control: max-age=3600`.
`kms.get_public_key` → DER SPKI → JWK via `cryptography`. Returns
`{"keys":[...]}`. Custom domain + WAF = optional (D-230).
#### Wave 6 — PAT lifecycle (security-engineer + cli-engineer) [C-7.3]
- **Task 6.1** (security-engineer): PAT issuance — signed JWT
(`typ: "developer_pat"`, INV-14), `nova-pats` PutItem (jti, pat_hash,
status=active). Only hash stored (REQ-343). Revoked PATs retained.
**Max TTL: ≤24h for developer PATs, ≤1h for service-account PATs**
(C-6.2 threat model).
- **Task 6.2** (cli-engineer): `nova/auth/{login,revoke,status}.py` —
`nova auth login` (session→OIDC token, store in
`~/.nova/credentials.json` 0600), `nova auth revoke --pat <jti>`,
`nova auth status` (active credential, mode, selection_reason).
All emit audit events (REQ-344). **C-7.3: `~/.nova/credentials.json`
stores the OIDC token + PAT metadata (jti, exp, type) ONLY — NOT the
raw PAT.** The raw PAT is entered once at `nova auth login` and not
persisted (reduces filesystem-compromise blast radius).
#### Wave 7 — CAP-037/038 (security-engineer)
- **Task 7.1** (security-engineer): `tests/test_kms_roundtrip.py`
(REQ-350, CAP-037) — sign test JWT via token-vend, fetch JWKS,
verify with `pyjwt`. `tests/test_pat_revocation.py` (REQ-351,
CAP-038) — issue → vend → revoke → assert 403 within 60s P95.
#### Wave 8 — nova idp setup (cli-engineer + backend-engineer) [folded from P5 per C-2.1]
**Goal:** `nova idp setup` command with `--check/--apply/--verify`
modes; CloudFormation template generation + review (REQ-340, REQ-341).
- **Task 8.1** (backend-engineer): `nova/idp/setup.py` (+ backend
helper) — generates the Nova-idp CloudFormation template (raw dict →
JSON): 2-3 Lambdas, 4 DDB tables, KMS key, function URLs, IAM roles,
optional CloudFront/WAF/ACM (`--public-jwks-domain` flag).
- **Task 8.2** (cli-engineer): `--check` (prerequisites + IAM policy
delta), `--apply` (generate → `$PAGER` → `y/N` → `cloudformation
deploy --capabilities CAPABILITY_IAM`, NFR-10), `--dry-run` (resource
list only), `--verify` (KMS round-trip, delegates to REQ-350 test).
- **Task 8.3** (backend-engineer): IAM policy delta computation —
compares current `nova-spike-runner` grants to required
`cloudformation:*` + `codeartifact:*` + `kms:*` + `lambda:*` +
`dynamodb:*` + `ssm:*`.
### Phase P5 — docs-integration (REQ-345..REQ-351)
**Goal:** Operator guide, developer guide, threat model; E2E
integration test; property tests; KMS round-trip; PAT revocation SLO.
**Exit criterion:** all REQ-345..351 tests pass + docs published +
threat model reviewed.
#### Wave 1 — docs (lead-developer + security-engineer) [C-6.2/C-6.3/C-9.2]
- **Task 1.1** (lead-developer): `docs/operator-guide-idp.md` (REQ-345)
— `nova idp setup --check/--apply/--verify`, prerequisite IAM policy,
CloudFormation review flow. **C-6.3 additions:** KMS key rotation
procedure (90 days), Lambda layer update procedure, DDB PITR restore
procedure, emergency PAT revocation (DDB-level, not CLI).
- **Task 1.2** (lead-developer): `docs/developer-guide-auth.md`
(REQ-346) — signup, signin, login, mode resolution, TTY vs piped
stdout behavior, JWS-from-PAT KDF (P2 Wave 2 Task 2.3).
- **Task 1.3** (security-engineer): `docs/threat-model.md` (REQ-347) —
Argon2id storage, KMS signing, JWKS exposure, PAT revocation SLO,
ABAC token vending, no-AWS-managed-identity (INV-15), DER→raw ECDSA
gotcha. **C-6.2 additions:** (a) JWKS unauthenticated endpoint DDoS
surface + reserved-concurrency mitigation; (b) PAT theft + max TTL
(≤24h dev, ≤1h service-account); (c) ABAC fail-closed guarantee
(C-6.1). **C-9.2 addition:** INV-18..21 compression audit — verify
the spec's attestation invariant semantics are fully captured by
INV-15/16/17 + REQ-332.
#### Wave 2 — integration tests (backend-engineer + security-engineer)
- **Task 2.1** (backend-engineer): `tests/test_e2e_idp.py` (REQ-348) —
sign-up → sign-in → token-vend → apply → audit. Verifiable audit
chain. Runs in CI against deployed Nova-idp.
- **Task 2.2** (security-engineer): verify REQ-349 (mode_resolver
property tests, P1 Wave 3 Task 3.3) + REQ-350 (KMS round-trip, P4
Wave 7 Task 7.1) + REQ-351 (PAT revocation SLO, P4 Wave 7 Task 7.1)
pass in CI.
### Phase P6 — final-review-ship (Final Phase)
**Goal:** Multi-persona code review across P1..P5; project-health
audit; milestone ship to main; CAP-033..038 Verified.
#### Wave 1 — review (lead-developer)
- **Task 1.1** (lead-developer): `ciagent-review` across all phases.
Auto-fix P0; flag P1+ for post-hoc. If P1+ found, fix in this phase.
#### Wave 2 — audit (lead-developer)
- **Task 2.1** (lead-developer): `ciagent-audit` — reconstruction test
(git log ↔ `.ciagent/`), file/branch/commit discipline. Fix critical
issues in this phase.
#### Wave 3 — milestone ship (lead-developer)
- **Task 3.1** (lead-developer): `ciagent-ship` — merge `phase/06` →
`milestone/v1.28-cli-identity` → `main`; tag `v1.27.6` (= the v1.28
release); Gitea release with full milestone summary; delete all
milestone branches. Update REQUIREMENTS.md (mark REQ-323..353
complete), ROADMAP.md (mark v1.28 complete), STATE.md (append
CAP-033..038 + INV-12..17), NORTH_STAR.md.
Each phase: EXECUTE (persona-assigned task groups) → VERIFY (4 layers +
regression gate at milestone complete) → SHIP (patch tag). Phase
boundary checkpoint resets context. The execute workflow reads this
PLAN.md + ROADMAP.md §v1.14 + PERSONAS.md for task decomposition.
---
## User-Facing Surface
## Wave 1 — Bug Fixes (P1P6)
> MVP/UX CHECK §1 (REQ-MVP-UX-001).
### 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`.
1. **CLI flag:** `nova --help` lists every subcommand; `nova init`
scaffolds a project; `nova auth login` authenticates; `nova apply
--local` runs locally; `nova idp setup` deploys the identity stack.
2. **README quickstart:** `docs/developer-guide-auth.md` (REQ-346)
documents signup → signin → login → `nova apply` in a quickstart.
3. **`.feature` Scenario:** `tests/test_e2e_idp.py` (REQ-348) is the
E2E happy path (sign-up → sign-in → token-vend → apply → audit).
### 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`.
## Happy Path
### 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`.
> MVP/UX CHECK §2 (REQ-MVP-UX-001). End-to-end scenario written BEFORE
> execute.
### 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`.
**Journey 2 — Dev authenticates and deploys locally:**
1. `nova auth signup` → `nova-idp-auth` Lambda → Argon2id hash →
`nova-users` PutItem → session token.
2. `nova auth signin` → `nova-idp-auth` → Argon2id verify → session.
3. `nova auth login` → `nova-idp-token-vend` (exchanges session for
Nova OIDC token; stores in `~/.nova/credentials.json` 0600).
4. `nova init` in a project dir → `.nova/`, `.gitignore`,
`.nova/contract.yml.attestations/`.
5. `nova apply --local --sign-local-review` →
`core.env.synthesize_local_env()` → `core.contract_resolver.resolve()`
→ JWS attestation signed with a key derived from the PAT → local
ledger entry.
### 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`.
The E2E test (`tests/test_e2e_idp.py`, REQ-348) verifies this chain +
the audit event chain in CI against a deployed Nova-idp.
## UX Acceptance Criteria
> MVP/UX CHECK §3 (REQ-MVP-UX-001).
1. `nova --help` exits 0 and lists a subcommand for every `core/`
module (CAP-033).
2. `nova init` in an empty dir creates `.nova/`,
`.nova/contract.yml.attestations/`, `.gitignore` (secrets excluded).
3. `nova auth login` at a TTY resolves `mode=interactive,
selection_reason=credential:developer_pat` (INV-12, INV-14).
4. `nova apply --local` produces a JWS attestation verifiable with the
public key derived from the PAT (REQ-332).
5. `nova idp setup --check` reports prerequisites + IAM policy delta;
`--apply` presents the CloudFormation template for review before any
resource is created (NFR-10); `--verify` confirms the KMS round-trip.
6. The Forge action (`nova cli-action`) runs `nova apply` in
`mode=agent, selection_reason=credential:service_account_pat` with
no TTY dependency (Journey 3, INV-12).
7. PAT revocation takes effect within 60s P95 (NFR-4, CAP-038).
### 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`.
---
## Capability gate (CAP-033..CAP-038)
## Wave 2 — Security (P7P12)
| CAP | Name | Phase | Gate rule |
|-----|------|-------|-----------|
| CAP-033 | CLI subcommand surface exists | P1 | `nova --help` lists a subcommand for every `core/` module |
| CAP-034 | Subcommand delegates to `core/` | P1 | Every `nova/<module>.py` ≤50 lines, no business logic, AST scan |
| CAP-035 | Layer matches wheel | P1 | Lambda layer ARN version matches `nova-cli` wheel version (SSM mapping) |
| CAP-036 | Nova-idp auth flow works | P3 | E2E test (sign-up → sign-in → session) passes in CI |
| CAP-037 | Token-vend signs via KMS | P4 | KMS round-trip test (REQ-350) passes in CI |
| CAP-038 | PAT issuance + revocation | P4 | Issue → vend → revoke → 403 within 60s P95 (REQ-351) passes in CI |
**Release gate (§6 of the spec):** CAP-001..CAP-032 remain Verified;
CAP-033..CAP-038 are Verified; all v1.28 release-gate criteria met.
### 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:** `.gitignore`, `tests/test_no_secrets_tracked.py`
**Tasks:**
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`.
---
## Test evidence required for v1.28 release
## Wave 3 — Stub / Test / CI / Hygiene (P13P17)
- [ ] Code coverage ≥ 80% on new modules (`mode_resolver.py`,
`nova-idp-auth`, `nova-idp-token-vend`, PAT lifecycle).
- [ ] CI/CD pipeline GREEN: wheel + Lambda layer publish on every merge
(REQ-323, CAP-035).
- [ ] QA sign-off: all four happy-path journeys (J1J4) pass integration
tests in CI.
- [ ] Security/compliance review: threat model published, Argon2id
verified, ABAC policy reviewed.
- [ ] Capability gate GREEN: CAP-001..032 remain Verified; CAP-033..038
Verified.
- [ ] Mode resolver property tests pass (all four priority levels + edge
cases; REQ-349).
- [ ] KMS round-trip test passes against deployed JWKS (REQ-350).
- [ ] PAT revocation SLO verified: ≤60s P95 in CI (REQ-351, NFR-4).
- [ ] Operator + developer guides published.
- [ ] `nova idp setup` succeeds in a fresh AWS account.
- [ ] Byte-identical Forge action on GitHub + Gitea (REQ-326, NFR-11).
### P13 — kyverno-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. **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`.
---
## Plan completeness checklist
## Wave 4 — Standards / Docs / VPC (P18P20)
- [x] Every REQ-323..353 mapped to a phase + wave + task.
- [x] Every CAP-033..038 mapped to a phase + gate rule.
- [x] Every INV-12..17 referenced in persona constraints.
- [x] Every D-226..231 referenced in task rationale.
- [x] Vertical slices: each phase ships independently (P1 CLI substrate
is useful before P2 packaging; P2 before P3 auth; etc.).
- [x] Wave ordering within phases (no wave N+1 depends on wave N work
in the same phase).
- [x] Persona assignments per task (4 active personas).
- [x] MVP/UX CHECK: 3 sections present (User-Facing Surface, Happy Path,
UX Acceptance Criteria).
- [x] Highest-risk item flagged (P4 Wave 1 kj-binary spike).
- [x] Grill conditions applied (3 critical + 16 tracked; see GRILL.md).
### P18 — module-standards-consistency (REQ-152)
**Persona:** data-engineer
**Territory:** `modules/STANDARDS.md`, `modules/l1/{ecr,ecs-cluster,rds}/terraform/`
**Tasks:**
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`.
---
## Cost envelope (C-3.1)
## Final Phase — P21 (review + audit + ship)
Monthly estimate for the default (no CloudFront) Nova-idp deployment in
account `581513795199`:
**Persona:** lead-developer (review coordination) + ci-code-reviewer +
ci-debugger (audit)
**Tasks:**
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.
| Resource | Quantity | Pricing | Est. monthly |
|----------|----------|---------|-------------|
| DynamoDB (on-demand) | 4 tables | $1.25/1M write, $0.25/1M read | ~$1 (pilot volume) |
| DynamoDB PITR | 4 tables | $0.20/GB-month | ~$1 (small tables) |
| KMS asymmetric key | 1 key | $1/key-month + $0.03/10k signs | ~$1 |
| Lambda invocations | 3 Lambdas | $0.20/1M req + $0.000016/GB-s | ~$2 (low volume) |
| Lambda layer storage | ~50 MB | $0.02/GB-month | <$1 |
| CodeArtifact | 1 domain + 1 repo | $1/domain + $1/repo | $2 |
| SSM Parameter | 1 | $0.05/param (advanced) | <$1 |
| **Total (default)** | | | **~$9/month** |
## Success Criteria (milestone gate)
Optional CloudFront + WAF + ACM (if `--public-jwks-domain`): +~$3/month
at pilot volume. ACM is free for CloudFront-attached certs.
This is a pilot-scale cost envelope. Production scale (100x volume)
would still be <$50/month. No hidden costs identified.
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) 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.
+783 -410
View File
File diff suppressed because it is too large Load Diff
+43 -44
View File
@@ -1,13 +1,12 @@
{
"run_id": "regr-1785591207",
"run_at_utc": "2026-08-01T13:33:27Z",
"run_id": "regr-1785329757",
"run_at_utc": "2026-07-29T12:55:57Z",
"milestone": "v1.10",
"phase": 52,
"summary": {
"Verified": 18,
"Verified": 22,
"Decayed": 0,
"Broken": 0,
"Skipped": 4
"Broken": 0
},
"passed": true,
"results": [
@@ -17,7 +16,7 @@
"status": "Verified",
"detail": "exit 0; 2 sample contracts validate",
"tier": "local",
"duration_ms": 235
"duration_ms": 252
},
{
"capability_id": "CAP-002",
@@ -25,7 +24,7 @@
"status": "Verified",
"detail": "exit 0; env schema validates",
"tier": "local",
"duration_ms": 201
"duration_ms": 196
},
{
"capability_id": "CAP-003",
@@ -33,7 +32,7 @@
"status": "Verified",
"detail": "exit 0; ",
"tier": "local",
"duration_ms": 261
"duration_ms": 258
},
{
"capability_id": "CAP-004",
@@ -41,7 +40,7 @@
"status": "Verified",
"detail": "exit 0; ",
"tier": "local",
"duration_ms": 259
"duration_ms": 264
},
{
"capability_id": "CAP-005",
@@ -49,7 +48,7 @@
"status": "Verified",
"detail": "exit 0; ",
"tier": "local",
"duration_ms": 337
"duration_ms": 314
},
{
"capability_id": "CAP-006",
@@ -57,7 +56,7 @@
"status": "Verified",
"detail": "exit 0; interpolation ok",
"tier": "local",
"duration_ms": 242
"duration_ms": 223
},
{
"capability_id": "CAP-007",
@@ -65,7 +64,7 @@
"status": "Verified",
"detail": "exit 0; confidence band=pass",
"tier": "local",
"duration_ms": 91
"duration_ms": 80
},
{
"capability_id": "CAP-008",
@@ -73,15 +72,15 @@
"status": "Verified",
"detail": "exit 0; outbox hash chain ok",
"tier": "local",
"duration_ms": 456
"duration_ms": 358
},
{
"capability_id": "CAP-009",
"name": "offline pytest suite passes",
"status": "Verified",
"detail": "exit 0; [ 98%]\ntests/test_wiz_adapter_real_client.py ......... [100%]\n\n================= 586 passed, 2 deselected in 71.63s (0:01:11) =================",
"detail": "exit 0; [ 98%]\ntests/test_wiz_adapter_real_client.py ......... [100%]\n\n====================== 462 passed, 2 deselected in 34.63s ======================",
"tier": "local",
"duration_ms": 72988
"duration_ms": 36065
},
{
"capability_id": "CAP-010",
@@ -89,63 +88,63 @@
"status": "Verified",
"detail": "exit 0; resource(s))\n\n=== PLATFORM CHECK OK ===\ncontract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)\ncheck-only: OK\n\n=== CI PIPELINE OK ===\n3 stages passed: lint, test, check-only",
"tier": "local",
"duration_ms": 73275
"duration_ms": 40668
},
{
"capability_id": "CAP-011",
"name": "headline E2E runs against the local emulating tier (microservice)",
"status": "Verified",
"detail": "exit 0; al-emulator\",\n \"desired_count\": 1,\n \"running_count\": 1\n },\n \"outbox_dir\": \"/tmp/nova_local_e2e_6vnrnin1/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"detail": "exit 0; al-emulator\",\n \"desired_count\": 1,\n \"running_count\": 1\n },\n \"outbox_dir\": \"/tmp/acdl_local_e2e_416d0fmr/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"tier": "local",
"duration_ms": 634
"duration_ms": 583
},
{
"capability_id": "CAP-012",
"name": "local E2E on the static-assets stack (no ECS)",
"status": "Verified",
"detail": "exit 0; nova_local_e2e_uq4kkhze/tf\",\n \"backend\": \"local\",\n \"ecs\": null,\n \"outbox_dir\": \"/tmp/nova_local_e2e_uq4kkhze/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"detail": "exit 0; acdl_local_e2e_ijhcj1z8/tf\",\n \"backend\": \"local\",\n \"ecs\": null,\n \"outbox_dir\": \"/tmp/acdl_local_e2e_ijhcj1z8/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"tier": "local",
"duration_ms": 584
"duration_ms": 489
},
{
"capability_id": "CAP-013",
"name": "terraform init+validate+plan live AWS (microservice)",
"status": "Skipped",
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice]",
"status": "Verified",
"detail": "terraform init+validate+plan OK (live AWS, microservice)",
"tier": "live-aws",
"duration_ms": 737
"duration_ms": 28811
},
{
"capability_id": "CAP-014",
"name": "terraform init+validate+plan live AWS (static-assets)",
"status": "Skipped",
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets]",
"status": "Verified",
"detail": "terraform init+validate+plan OK (live AWS, static-assets)",
"tier": "live-aws",
"duration_ms": 676
"duration_ms": 31772
},
{
"capability_id": "CAP-015",
"name": "DynamoDB outbox table exists (live AWS)",
"status": "Skipped",
"detail": "nova-outbox absent (post-v1.11-teardown steady state, D-096)",
"status": "Verified",
"detail": "acdl-outbox exists, item_count=9",
"tier": "live-aws",
"duration_ms": 664
"duration_ms": 477
},
{
"capability_id": "CAP-016",
"name": "S3 state bucket exists + readable (live AWS)",
"status": "Skipped",
"detail": "state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096)",
"status": "Verified",
"detail": "state bucket exists, keys=['platform/terraform.tfstate', 'spike/alb/dev/terraform.tfstate', 'spike/cdn/dev/terraform.tfstate', 'spike/ci-vpc/terraform.tfstate', 'spike/clus/dev/terraform.tfstate']",
"tier": "live-aws",
"duration_ms": 245
"duration_ms": 324
},
{
"capability_id": "CAP-017",
"name": "DynamoDB nova-contracts table (lifecycle pipeline evidence)",
"name": "DynamoDB acdl-contracts table (lifecycle pipeline evidence)",
"status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"detail": "terraform files present + simple/complex contracts resolve",
"tier": "lifecycle-pipeline",
"duration_ms": 586
"duration_ms": 520
},
{
"capability_id": "CAP-018",
@@ -153,39 +152,39 @@
"status": "Verified",
"detail": "LocalLambdaStub instantiates (local tier evidence)",
"tier": "lifecycle-pipeline",
"duration_ms": 138
"duration_ms": 137
},
{
"capability_id": "CAP-019",
"name": "ECS cluster + service (L2 microservice lifecycle evidence)",
"status": "Verified",
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
"detail": "L2 composition resolves (simple + complex contracts)",
"tier": "lifecycle-pipeline",
"duration_ms": 519
"duration_ms": 534
},
{
"capability_id": "CAP-020",
"name": "CloudFront + WAF (L2 static-assets lifecycle evidence)",
"status": "Verified",
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
"detail": "L2 composition resolves (simple + complex contracts)",
"tier": "lifecycle-pipeline",
"duration_ms": 521
"duration_ms": 567
},
{
"capability_id": "CAP-021",
"name": "uptime-kuma (L1 uptime lifecycle evidence)",
"status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"detail": "terraform files present + simple/complex contracts resolve",
"tier": "lifecycle-pipeline",
"duration_ms": 562
"duration_ms": 606
},
{
"capability_id": "CAP-022",
"name": "OIDC role (L1 iam-role lifecycle evidence)",
"status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"detail": "terraform files present + simple/complex contracts resolve",
"tier": "lifecycle-pipeline",
"duration_ms": 611
"duration_ms": 529
}
]
}
+28 -28
View File
@@ -1,51 +1,51 @@
# Regression Report — v1.10 Phase 52
- **Run ID:** `regr-1785591207`
- **Run at (UTC):** 2026-08-01T13:33:27Z
- **Summary:** {'Verified': 18, 'Decayed': 0, 'Broken': 0, 'Skipped': 4}
- **Run ID:** `regr-1785329757`
- **Run at (UTC):** 2026-07-29T12:55:57Z
- **Summary:** {'Verified': 22, 'Decayed': 0, 'Broken': 0}
- **Passed (milestone gate):** True
| Capability | Name | Tier | Status | Duration (ms) | Detail |
|-----------|------|------|--------|--------------|--------|
| CAP-001 | contract.schema.json validates sample contracts | local | **Verified** | 235 | exit 0; 2 sample contracts validate |
| CAP-002 | environment.schema.json validates env files | local | **Verified** | 201 | exit 0; env schema validates |
| CAP-003 | contract_resolver resolves static-assets | local | **Verified** | 261 | exit 0; |
| CAP-004 | contract_resolver resolves microservice | local | **Verified** | 259 | exit 0; |
| CAP-005 | terraform adapter emits .tf files | local | **Verified** | 337 | exit 0; |
| CAP-006 | contract interpolation expands env/contract tokens | local | **Verified** | 242 | exit 0; interpolation ok |
| CAP-007 | confidence_signal.compute returns a band | local | **Verified** | 91 | exit 0; confidence band=pass |
| CAP-008 | outbox_writer builds a hash-chained item | local | **Verified** | 456 | exit 0; outbox hash chain ok |
| CAP-009 | offline pytest suite passes | local | **Verified** | 72988 | exit 0; [ 98%]
| CAP-001 | contract.schema.json validates sample contracts | local | **Verified** | 252 | exit 0; 2 sample contracts validate |
| CAP-002 | environment.schema.json validates env files | local | **Verified** | 196 | exit 0; env schema validates |
| CAP-003 | contract_resolver resolves static-assets | local | **Verified** | 258 | exit 0; |
| CAP-004 | contract_resolver resolves microservice | local | **Verified** | 264 | exit 0; |
| CAP-005 | terraform adapter emits .tf files | local | **Verified** | 314 | exit 0; |
| CAP-006 | contract interpolation expands env/contract tokens | local | **Verified** | 223 | exit 0; interpolation ok |
| CAP-007 | confidence_signal.compute returns a band | local | **Verified** | 80 | exit 0; confidence band=pass |
| CAP-008 | outbox_writer builds a hash-chained item | local | **Verified** | 358 | exit 0; outbox hash chain ok |
| CAP-009 | offline pytest suite passes | local | **Verified** | 36065 | exit 0; [ 98%]
tests/test_wiz_adapter_real_client.py ......... [100%]
================= 586 passed, 2 |
| CAP-010 | run_ci.sh reproduces CI pipeline locally | local | **Verified** | 73275 | exit 0; resource(s))
====================== 462 passe |
| CAP-010 | run_ci.sh reproduces CI pipeline locally | local | **Verified** | 40668 | exit 0; resource(s))
=== PLATFORM CHECK OK ===
contract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)
check-only: OK
=== CI PIPELIN |
| CAP-011 | headline E2E runs against the local emulating tier (microservice) | local | **Verified** | 634 | exit 0; al-emulator",
| CAP-011 | headline E2E runs against the local emulating tier (microservice) | local | **Verified** | 583 | exit 0; al-emulator",
"desired_count": 1,
"running_count": 1
},
"outbox_dir": "/tmp/nova_local_e2e_6vnrnin1/outbox",
"outbox_dir": "/tmp/acdl_local_e2e_416d0fmr/outbox",
"outbox_events": 2,
"outbox |
| CAP-012 | local E2E on the static-assets stack (no ECS) | local | **Verified** | 584 | exit 0; nova_local_e2e_uq4kkhze/tf",
| CAP-012 | local E2E on the static-assets stack (no ECS) | local | **Verified** | 489 | exit 0; acdl_local_e2e_ijhcj1z8/tf",
"backend": "local",
"ecs": null,
"outbox_dir": "/tmp/nova_local_e2e_uq4kkhze/outbox",
"outbox_dir": "/tmp/acdl_local_e2e_ijhcj1z8/outbox",
"outbox_events": 2,
"outbox |
| CAP-013 | terraform init+validate+plan live AWS (microservice) | live-aws | **Skipped** | 737 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice] |
| CAP-014 | terraform init+validate+plan live AWS (static-assets) | live-aws | **Skipped** | 676 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets] |
| CAP-015 | DynamoDB outbox table exists (live AWS) | live-aws | **Skipped** | 664 | nova-outbox absent (post-v1.11-teardown steady state, D-096) |
| CAP-016 | S3 state bucket exists + readable (live AWS) | live-aws | **Skipped** | 245 | state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096) |
| CAP-017 | DynamoDB nova-contracts table (lifecycle pipeline evidence) | lifecycle-pipeline | **Verified** | 586 | terraform files present + fmt -check passes + simple/complex contracts resolve |
| CAP-018 | Lambda contract-ingestor (local stub + lifecycle evidence) | lifecycle-pipeline | **Verified** | 138 | LocalLambdaStub instantiates (local tier evidence) |
| CAP-019 | ECS cluster + service (L2 microservice lifecycle evidence) | lifecycle-pipeline | **Verified** | 519 | L2 composition resolves (simple + complex contracts; offline proxy) |
| CAP-020 | CloudFront + WAF (L2 static-assets lifecycle evidence) | lifecycle-pipeline | **Verified** | 521 | L2 composition resolves (simple + complex contracts; offline proxy) |
| CAP-021 | uptime-kuma (L1 uptime lifecycle evidence) | lifecycle-pipeline | **Verified** | 562 | terraform files present + fmt -check passes + simple/complex contracts resolve |
| CAP-022 | OIDC role (L1 iam-role lifecycle evidence) | lifecycle-pipeline | **Verified** | 611 | terraform files present + fmt -check passes + simple/complex contracts resolve |
| CAP-013 | terraform init+validate+plan live AWS (microservice) | live-aws | **Verified** | 28811 | terraform init+validate+plan OK (live AWS, microservice) |
| CAP-014 | terraform init+validate+plan live AWS (static-assets) | live-aws | **Verified** | 31772 | terraform init+validate+plan OK (live AWS, static-assets) |
| CAP-015 | DynamoDB outbox table exists (live AWS) | live-aws | **Verified** | 477 | acdl-outbox exists, item_count=9 |
| CAP-016 | S3 state bucket exists + readable (live AWS) | live-aws | **Verified** | 324 | state bucket exists, keys=['platform/terraform.tfstate', 'spike/alb/dev/terraform.tfstate', 'spike/cdn/dev/terraform.tfstate', 'spike/ci-vpc/terraform.tfstate', |
| CAP-017 | DynamoDB acdl-contracts table (lifecycle pipeline evidence) | lifecycle-pipeline | **Verified** | 520 | terraform files present + simple/complex contracts resolve |
| CAP-018 | Lambda contract-ingestor (local stub + lifecycle evidence) | lifecycle-pipeline | **Verified** | 137 | LocalLambdaStub instantiates (local tier evidence) |
| CAP-019 | ECS cluster + service (L2 microservice lifecycle evidence) | lifecycle-pipeline | **Verified** | 534 | L2 composition resolves (simple + complex contracts) |
| CAP-020 | CloudFront + WAF (L2 static-assets lifecycle evidence) | lifecycle-pipeline | **Verified** | 567 | L2 composition resolves (simple + complex contracts) |
| CAP-021 | uptime-kuma (L1 uptime lifecycle evidence) | lifecycle-pipeline | **Verified** | 606 | terraform files present + simple/complex contracts resolve |
| CAP-022 | OIDC role (L1 iam-role lifecycle evidence) | lifecycle-pipeline | **Verified** | 529 | terraform files present + simple/complex contracts resolve |
+715 -604
View File
File diff suppressed because it is too large Load Diff
+792 -257
View File
File diff suppressed because it is too large Load Diff
+324
View File
@@ -0,0 +1,324 @@
# ACDL v1.11 — Multi-Persona Code Review (P60P65 retrofit + new work)
**Reviewer:** ci-code-reviewer (model: glm-5.2)
**Scope:** v1.11 milestone, branch `milestone/v1.11-restart` — 22 commits
(e1bb214..8c09580), 25 files, +790/-142 lines
**Date:** 2026-07-29
## Commits reviewed
| Commit | Phase | Type | Summary |
|--------|-------|------|---------|
| e1bb214 | 60 | docs | retrofit plan — L1 lifecycle pipeline live-run |
| bc9058f | 60 | feat | L1 module lifecycle live run — module fixes (retrofit) |
| bb3ac7c | 60 | fix | WAF scope case + VPC modify DependencyViolation |
| 0c5c4d1 | 61 | docs | create phase plan — L2 lifecycle pipeline author |
| 361fe60 | 61 | feat | L2 lifecycle pipeline — extend matrix + workflows + tests |
| 9ac5720 | 61 | verify | 4-layer gate — PASS |
| 6441633 | 62 | docs | create phase plan — L2 lifecycle pipeline live run |
| 4dad967 | 60 | fix | ALB target group name_prefix — avoid orphaned conflicts |
| adfcf86 | 63 | docs | create phase plan — regression registry + cost docs |
| b71e63c | 63 | feat | CAP-017..022 regression registry + COST.md |
| beac2ef | 63 | verify | 4-layer gate — PASS |
| 06f4fc7 | 60 | fix | free disk space in lifecycle jobs |
| 92bb03e | 64 | docs | create phase plan — pre-mortem + teardown |
| 186cdde | 64 | feat | pre-mortem — v1.10 post-mortem + forward pre-mortem |
| 4102950 | 64 | feat | pre-mortem + teardown plan — HITL escalation CHG0680001 |
| 7c4fc1f | 64 | feat | teardown complete — zero live ACDL resources remain |
| a52f8a5 | 64 | verify | 4-layer gate — PASS |
| a03c019 | 60/62 | fix | ALB name_prefix + adapter dedup + L2 composition wiring |
| 93a6598 | 65 | docs | create phase plan — rewrite caps + decks |
| 6394801 | 65 | feat | rewrite caps — CAP-017..022 Verified via lifecycle pipeline |
| fc91f24 | 65 | verify | 4-layer gate — PASS |
| 8c09580 | 65 | docs | update v1.11 status — all phases complete |
## P0 issues (0)
No blocking issues found. The targeted fixes are correct for their stated
purposes. The 447 fast offline tests pass (485/490 collected; 5 slow
deselected, including 2 slow regression-integration tests that exercise the
CAPABILITY_REGISTRY against the live codebase).
## P1 issues (5 — should fix)
### P1-1: Adapter dedup silently drops resources whose module is not in the registry
[correctness] `adapters/terraform/adapter.py:159-170`
The new dedup loop only adds resources to `seen` when `tf_dir` is truthy
(in the registry). A resource whose module is missing from the registry is
**silently dropped** from `merged` — it never reaches `_emit_module_block`,
so no error is raised. The pre-dedup code (`parts.extend(... for r in
resources)`) would have raised `ValueError("no terraform_dir in registry
for module ...")` via `_emit_module_block`, surfacing the misconfiguration.
Confirmed by simulation: two resources, one with `module: nonexistent@1.0.0`,
produces a `merged` list of length 1 — the unknown-module resource vanishes
without diagnostic.
**Recommendation:** in the dedup loop, when `tf_dir` is `None`, either
(a) raise immediately (preserving the prior contract), or (b) append the
resource to a separate `unknown` list and extend `parts` with it so
`_emit_module_block` raises the descriptive error. As written, a typo in
a composition's `module` field (e.g. `iam-role@1.0.0` vs `iam_roles@1.0.0`)
will silently omit a resource from the emitted terraform — a class of
defect the v1.10 sweep was specifically created to catch.
### P1-2: L2 static-assets "modify" example is a no-op — complex ≡ simple
[correctness] `modules/l2/static-assets/examples/complex.yml`,
`modules/l2/static-assets/composition.json`
The complex.yml comment claims "Modify variant: same bucket_name as simple
(in-place modify, adds CDN + WAF)". But resolving both examples yields
**identical** resource sets: `['s3','cloudfront-distribution',
'cloudfront-originaccesscontrol','waf','kms']`. The CDN and WAF are
**always present** in the static-assets composition (they are unconditional
children + wires); the `waf_enabled`, `default_ttl`, `max_ttl`,
`price_class`, `viewer_protocol_policy` inputs in complex.yml have **no
corresponding wires** in composition.json and are silently dropped at
resolve time. So the L2 static-assets lifecycle cell's "modify" step
applies a contract that produces the same terraform as "simple" — it
exercises `terraform apply` twice with no change, not a true modify.
This is not a regression (the inputs were never wired), but the
CAPABILITY_INVENTORY claim "CAP-020 Verified live-aws via L2 static-assets
lifecycle pipeline (apply/modify/destroy exit 0)" overstates what the
modify step proves: it proves idempotent re-apply, not in-place modify.
**Recommendation:** either (a) wire `waf_enabled`/`default_ttl`/etc. in
composition.json so the complex contract genuinely differs, or (b) correct
the comment + CAPABILITY_INVENTORY wording to "apply + idempotent re-apply
+ destroy" rather than "apply/modify/destroy". The microservice complex
example, by contrast, is a real modify (desired_count 1→2) — that one is
fine.
### P1-3: L2 lifecycle scripts ignore the ci-vpc-outputs.json argument
[correctness] `scripts/run_l2_lifecycle_test.sh:14`,
`scripts/run_l2_lifecycle_destroy.sh:12`
Both L2 scripts declare `Usage: ... <module> <example> [ci-vpc-outputs.json]`
but neither reads `$3`/`$2`. The microservice composition references the
platform VPC via `terraform_remote_state` (data source), and the script
sets `ACDL_REMOTE_STATE_KEY=spike/ci-vpc/terraform.tfstate` so the data
source reads from the CI VPC state — that part is correct. But the
`ci-vpc-outputs.json` argument is positional noise: the workflow passes
it (`run_l2_lifecycle_test.sh ${{ matrix.module }} simple
/tmp/ci-vpc-outputs.json`) and it is silently ignored. The L1 scripts
(`run_lifecycle_test.sh`) inject VPC outputs by rewriting the contract in
Python; the L2 path takes a different approach (remote state) and does not
need the file, so the argument is vestigial, not a bug — but the usage
string advertises a feature the script does not provide, which will
confuse a future maintainer who assumes parity with the L1 scripts.
**Recommendation:** remove the `[ci-vpc-outputs.json]` token from the
usage strings (or add a comment explaining the L2 path uses remote state
and the arg is accepted-but-ignored for workflow-argument parity).
### P1-4: CAPABILITY_INVENTORY summary table is stale (says 16, body lists 22)
[maintainability] `.ciagent/CAPABILITY_INVENTORY.md:9-16`
The Summary table still reads "Verified 16 / Decayed 0 / Broken 0 / Total
16" — the v1.10 sweep count. The body (lines 93-110) now lists CAP-017..022
as **Verified** via the lifecycle pipeline, bringing the real total to 22.
The two counts disagree: a reader scanning the summary sees 16 Verified; a
reader scanning the inventory body sees 22 Verified. The PRE_MORTEM
(lines 82-83) and CAPABILITY_INVENTORY prose both assert all 22 are
Verified, but the headline table was not updated in the P65 rewrite.
**Recommendation:** update the Summary table to "Verified 22 / Decayed 0
/ Broken 0 / Total 22" and add CAP-017..022 rows to the Inventory table
(the body section "Cloud capabilities NOT re-verified..." is now
mis-titled — they ARE verified, just via the lifecycle-pipeline tier).
### P1-5: CAP-017..022 regression checks are offline proxies, not pipeline evidence
[adversarial] `core/regression_verify.py:432-519`,
`.ciagent/CAPABILITY_INVENTORY.md:93-110`
The CAP-017..022 checks (`_check_cap_017_dynamodb` etc.) call
`_check_lifecycle_module_terraform` / `_check_lifecycle_l2_module`, which
verify only that (a) the terraform dir + required files exist and (b) the
example contracts **resolve** (resolver exit 0). They do **not** run
`terraform validate`, do not run apply/modify/destroy, and do not query
the pipeline's actual green/red status. The CAPABILITY_INVENTORY claims
"Evidence = L1 rds module lifecycle pipeline green (terraform validate +
contracts resolve)" — but the check does not run terraform validate, and
"lifecycle pipeline green" is asserted, not verified by the regression
gate.
This means the lifecycle-pipeline evidence CAN be faked at the regression
tier: a module whose terraform is syntactically broken (e.g.
`scope = upper(var.scope)` removed, or a missing required variable) would
still pass `_check_lifecycle_module_terraform` as long as the files exist
and the resolver runs. The real green/red evidence lives only in the
workflow run history (Gitea/GitHub Actions), which the regression gate does
not read.
**Mitigation context:** the modules-lifecycle workflow IS the live
evidence — when it runs on a PR, the cells genuinely apply/modify/destroy
against live AWS. The gap is that the *regression gate* (which gates
milestone COMPLETE) trusts the workflow will be run, rather than proving it
was run and passed. A milestone could in principle be marked COMPLETE with
CAP-017..022 "Verified" if the regression gate runs but the workflow was
never executed (e.g. workflow_dispatch never triggered, or the PR was
merged without the workflow running).
**Recommendation:** (a) tighten the CAP-017..022 check docstrings + the
CAPABILITY_INVENTORY wording to "terraform files present + contracts
resolve (offline proxy; live apply/modify/destroy verified by the
modules-lifecycle workflow run, not by this gate)"; and/or (b) add a
`terraform validate` step to `_check_lifecycle_module_terraform` (slow but
cheap relative to init+apply) so at least HCL syntax is verified at the
gate. The teardown trustworthiness (P64) is good — `ci-vpc-destroy` runs
`if: always()` and the decommission `---ci---` block is the audit trail.
## P2 issues (4 — post-hoc)
### P2-1: ALB `name_prefix = "tg-ci-"` discards `var.name` entirely
[maintainability] `modules/l1/alb/terraform/main.tf:9`
The fix replaces `name = var.name` with `name_prefix = "tg-ci-"` (a
hardcoded literal). This is the correct terraform pattern for
create_before_destroy resources with name-uniqueness constraints, and the
commit message explains the orphaned-resource motivation well. However
the target group name is now non-configurable (always `tg-ci-<random>`),
and the `var.name` variable is no longer used by the target group at all
(it is still used by `aws_lb.this.name`). A consumer who sets `name:
my-app` gets an LB named `my-app` but a target group named `tg-ci-...` —
inconsistent tagging. Consider `name_prefix = "${var.name}-"` to keep the
consumer's name as a prefix while preserving uniqueness. Post-hoc: not
blocking; the lifecycle pipeline is the only current consumer and `tg-ci-`
is fine for CI.
### P2-2: No test covers the new dedup merge behavior or `ACDL_REMOTE_STATE_KEY`
[testing] `tests/test_adapter.py`, `tests/test_pipeline_contract.py`
The adapter gained (a) a dedup-merge loop for multi-resource L1s sharing a
terraform dir and (b) `ACDL_REMOTE_STATE_KEY` env override for the remote
state data block. Neither has a unit test:
- No test asserts that two resources with the same `module` collapse to one
`module "<first_id>" { ... }` block with merged inputs.
- No test asserts that `ACDL_REMOTE_STATE_KEY` overrides the default
`platform/terraform.tfstate` key in the emitted `data
terraform_remote_state` block.
- No test covers the L2 lifecycle scripts (`run_l2_lifecycle_test.sh` /
`run_l2_lifecycle_destroy.sh`) — the L1 equivalents are also untested at
the script level, so this is consistent with existing practice, but the
L2 scripts are new in this session and the `ACDL_REMOTE_STATE_KEY` wiring
is the load-bearing correctness mechanism for the microservice lifecycle.
The 485 offline tests adequately cover the *contract* (pipeline schema,
byte-identical workflows, matrix membership, job needs) — the
`TestModulesLifecyclePipeline` class is solid (89 tests pass). The gap is
adapter *behavior* at the unit level.
**Recommendation:** add a `test_adapter_dedup_merges_same_module` and a
`test_adapter_remote_state_key_override` to `tests/test_adapter.py`.
### P2-3: `waf` complex example uses `scope: CLOUDFRONT` but WAF scope is now `upper()`'d
[correctness] `modules/l1/waf/examples/complex.yml:8`,
`modules/l1/waf/terraform/locals.tf:3`
The `locals.tf` change `scope = upper(var.scope)` is the correct defensive
fix (the AWS provider requires `CLOUDFRONT`/`REGIONAL` regardless of input
case). The complex.yml was simultaneously changed from `scope: cloudfront`
to `scope: CLOUDFRONT`. Both are now correct, but the example's uppercase
value is now redundant with the `upper()` — a future reader may wonder
which is authoritative. Minor; the defensive `upper()` is the right call
and the example matching it is fine. Post-hoc only.
### P2-4: COST.md reproducibility snippet could leak the account ID via CloudTrail
[security] `.ciagent/COST.md:106`
COST.md contains the AWS account ID `581513795199` in multiple places
(summary, S3 bucket name, methodology). This is consistent with the rest of
the repo (the bucket name `acdl-tfstate-581513795199-us-east-1` is hardcoded
in `adapter.py:130` and `adapter.py:146`), so it is not new leakage and not
a regression. No actual secret material (access keys, secret access keys)
appears in COST.md, PRE_MORTEM.md, CAPABILITY_INVENTORY.md, or the workflow
files — all credential references use `${{ secrets.ACDL_AWS_* }}` or env
var names only. The `.ciagent/PROJECT.md:731` reference to a deactivated
root key is redacted (`AKIA…ROOT-DEACTIVATED`). **No credential leakage
found.** The P2 is only that the account ID is published; if the account
is meant to be opaque, this is an accepted exposure (the bucket name
already requires it).
## What is correct
- **WAF scope fix (`upper(var.scope)`):** correct and defensive; AWS
provider v5 requires uppercase. The `local.scope` indirection is clean.
- **VPC `create_before_destroy` + same-CIDR complex example:** correct
fix for the DependencyViolation on modify. Using the same CIDR means
terraform modifies in-place rather than replacing the VPC (which would
cascade-fail on dependent subnets/IGW). The `create_before_destroy`
lifecycle is the right guard.
- **ALB `name_prefix`:** correct terraform pattern for
create_before_destroy + name-uniqueness; well-documented commit message.
- **Adapter dedup (for the registered-module case):** correct —
multi-resource L1s like cloudfront (distribution + OAC) correctly merge
into one `module "cloudfront-distribution" { ... }` block. The merge
preserves first-resource inputs and union of outputs. (The
unregistered-module drop is P1-1, a separate concern.)
- **L2 composition wiring (`ecr.inputs.name`, `roles.inputs.role_name`):**
correct. Resolving microservice complex now shows `ecr.inputs.name =
"app-repo"` and `roles.inputs.role_name = "app-role"` (defaults applied
since the contract doesn't set `name`). Previously these would have hit
the "missing required arg" defect class from the v1.10 sweep.
- **Microservice complex = real modify:** `desired_count: 2` (vs simple's
default 1) is a genuine in-place modify — confirmed by resolving both
and diffing `service-service.inputs.desired_count`.
- **`ACDL_REMOTE_STATE_KEY` plumbing:** correct end-to-end — the L2 scripts
export it, the adapter reads it with a sensible default, and the
microservice composition's `terraform_remote_state` data block picks it
up. This cleanly separates the short-lived CI VPC state from the
long-lived platform VPC state.
- **Workflow structure:** `l2-lifecycle` correctly `needs: ci-vpc-apply`;
`ci-vpc-destroy` correctly `needs: [lifecycle, l2-lifecycle]` and
`if: always()`. The 7 new L2 pipeline-contract tests assert all of this.
- **Byte-identical workflows:** `.gitea` and `.github` modules-lifecycle.yml
are byte-identical (test asserts this); the `test_workflow_has_four_jobs`
rename from three→four is correct.
- **Adapter line count:** 194 lines — under the 200-line ceiling, still a
clean stateless assembler. The dedup logic added ~16 lines without
bloating.
- **Teardown verification (P64):** trustworthy in structure — the
`ci-vpc-destroy` job runs unconditionally and the decommission
`---ci---` block is the audit trail. The adversarial concern (P1-5) is
about the regression gate trusting the workflow ran, not about the
teardown itself being fakeable.
- **Security:** no credential leakage in any reviewed file. All AWS auth
in workflows uses `${{ secrets.* }}`; COST.md references only env var
names and a redacted/deactivated root key ID.
## Test coverage assessment (485 offline tests)
- **Adequate:** pipeline contract (89 tests), schema validation, contract
resolution, adapter emission (basic), confidence signal, outbox,
interpolation, local emulators, module-standards file presence, design-doc
currency.
- **Gaps (post-hoc):**
1. Adapter dedup merge behavior (P2-2) — no unit test.
2. `ACDL_REMOTE_STATE_KEY` override (P2-2) — no unit test.
3. CAP-017..022 regression checks (P1-5) — not exercised at the unit
level; the 2 slow tests in `test_verify_regression_mode.py` run the
full registry but are `@pytest.mark.slow` and deselected from the
fast suite, so a CI run of the 485 fast tests does not verify
CAP-017..022 even at the offline-proxy level.
4. WAF `upper()` scope — no test asserts the locals transform; relies
on the lifecycle pipeline cell to catch a regression.
5. ALB `name_prefix` — no test asserts the target group uses
`name_prefix` (P2-1 context).
The 485 count is honest (447 pass fast, 5 deselected slow, 485/490
collected). The gap is behavioral coverage of the new adapter + module
logic, not contract/schema coverage.
## Verdict
**PASS with P1 flags for post-hoc review.** No P0 fixes applied. The
milestone's structural controls (regression gate, mandatory teardown,
byte-identical workflows, byte-identical contract↔workflow tests) are
sound. The most material finding is P1-5 (the regression gate's
CAP-017..022 evidence is an offline proxy, not live pipeline evidence) —
this is a repeat of the v1.10 "VERIFY was diff-scoped" structural defect
in a milder form: the gate trusts the workflow was run rather than proving
it. The mitigations in PRE_MORTEM (FM-1..FM-4) acknowledge related risks;
P1-5 is the specific instance for the lifecycle-pipeline tier.
+1411 -282
View File
File diff suppressed because it is too large Load Diff
-286
View File
@@ -1,286 +0,0 @@
# Nova — System State (what exists today)
> **PO-owned catalog of shipped capabilities.** Updated at every milestone
> ship (P final). Additive only — entries are appended, never rewritten,
> unless a capability is explicitly deprecated (then marked, not deleted).
> Read by the PO upstream of the PDLC before authoring new REQ-NNN specs,
> and by CIAgent at SPECIFY for capability awareness.
>
> **Authority:** this file is *descriptive of shipped state*, not
> authoritative for live phase/ship state — that's `CHECKPOINT.json`. For
> *why*, read `NORTH_STAR.md`. For *how*, read `ARCHITECTURE.md`. For
> *what was decided*, read `PROJECT.md` load-bearing decisions.
>
> **Last milestone ship:** v1.27 (`v1.26.3`, 2026-08-19) — PO State Catalog
> & Ciagent Compression NFR milestone. No new capabilities this
> milestone (NFR); v1.27 authored this file + compressed `.ciagent/`.
> **Next update:** at v1.28 ship.
## How to use this file (PO)
- Before writing a new REQ: search this file for the capability you
intend to spec. If it exists, extend it; do not re-spec it under a new
REQ-NNN.
- Respect the **Invariants** below — they are load-bearing and
cross-cutting. A new REQ that violates an invariant requires a
`CLARIFY` decision recorded in PROJECT.md.
- Anchor each new REQ to a **Domain**; new domains require a PO
decision recorded in CLARIFY.
- When a capability is deprecated (replaced, removed, or
re-architecture), append a `Deprecated` row marking the milestone +
replacement; do not delete the original entry.
## Invariants (PO-owned — do not violate in new REQs)
> Distilled from `PROJECT.md` load-bearing decisions D-034..D-072 +
> W1..BA + Q1.3. Cite the decision ID when an REQ touches one.
- **INV-1 (Contract surface):** The only PDLC→Nova boundary is
`schemas/contract.schema.json` + `schemas/submission-readiness.schema.json`
(D-133). All consumer intent enters through one of these. Nova never
reaches into upstream PDLC.
- **INV-2 (Confidence inputs):** Six canonical inputs — policy (0.30),
validation (0.25), freshness (0.10), source (0.15), history (0.10),
nfrs (0.10). Weights frozen for v1 (D-040). `critical` severity =
hard-block via `PENALTY["critical"]: None` (defense-in-depth behind the
declarative `block-on-any-critical` meta-policy).
- **INV-3 (HITL gates):** dev = autonomous (≥0.50); qa = HITL (≥0.75);
prod = HITL (≥0.90); dr = HITL (≥0.95). Approver identity = Gitea
`gitea.actor` of the `workflow_dispatch` (D-042). Separation-of-duties
on prod reads `approver_qa` from the DynamoDB outbox.
- **INV-4 (Engine is swappable):** The policy engine is behind the
`PolicyEngine` protocol (`core/policy_engine.py`, v1.25). Confidence
signal + pipeline import only the protocol, never a concrete engine.
`kyverno-json` is the v1.25 default; `OPA` (or other) implements the
same 3-method protocol to replace it.
- **INV-5 (Adapter is stateless):** `adapters/terraform/adapter.py` owns
no module content — no `TYPE_MAP`/`INPUT_MAP`/`OUTPUT_MAP` (v1.11
rewrite). A new stack type requires a new L1 module
(`modules/l1/<name>/`) + `registry.json` entry, not an adapter change.
- **INV-6 (Audit stream is immutable):** Outbox writes via SQLite
hash-chain today (D-083 deferred). S3 Object Lock / JWS tamper-
*resistant* ledger is a future milestone. Current stream is tamper-
*evident* (any tampering breaks the chain).
- **INV-7 (PCR schema is the moat):** `schemas/policy_check_result.schema.json`
shape is frozen across adapter swaps (v1.25 hard constraint). The
`engine` enum already includes `"kyverno"` + `"opa"`; new engines add
no enum value.
- **INV-8 (Long-lived creds forbidden):** §12.5. The D-039/D-047 per-run-
rotated-key waiver satisfies the *intent* (no *persistently* long-lived
key). Real OIDC federation is blocked on `go-gitea/gitea#36988`.
- **INV-9 (Two consumer surfaces, one platform):** L3A (developer) +
L3B (citizen dev) converge on the same contract schema, the same
policy envelope, and the same evidence stream.
- **INV-10 (Nova is downstream of PDLC):** Nova governs infra + delivery
only. Product backlog, code authorship, IDE workflows, application
business logic are upstream. Integration only via the validated
contract boundary (INV-1).
- **INV-11 (Pilot scope, v1.26):** Equities only (D-200). Single-
validator PoA (D-201). D-083 (Object Lock/JWS) stays deferred. Hot
path deferred (D-126). Multi-cloud deferred. Multi-validator BFT
deferred. The pilot runs `mode: full` for `dev` only (D-209); qa/prod/dr
stay placeholder (D-208, blocked by the pilot-readiness policy).
## Domains (capability groups)
1. Contract surface
2. Modules (L1 primitives + L2 patterns)
3. Policy engine
4. Confidence signal
5. Environments & promotion
6. Evidence stream & audit
7. Telemetry & metrics
8. Consumer surfaces (developer + agentic)
9. Pilot estate (v1.26)
10. Forge / CI runtime
## Capabilities (additive — one row per shipped capability)
> Tier: **local** = runs via emulating adapters (no AWS); **live-aws** =
> runs against the live AWS account `581513795199`;
> **lifecycle-pipeline** = verified via the `modules-lifecycle`
> pipeline's apply→modify→destroy matrix cell.
> CAP-NNN IDs cross-reference the regression gate at
> `core/regression_verify.py` (the machine registry). This file is the
> PO-facing narrative; the machine registry is the source of truth for
> the gate.
### Domain 1 — Contract surface
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| CAP-001 | `contract.schema.json` validates sample contracts | v1.1 / `v1.2.0` | `schemas/contract.schema.json` | REQ-001, D-... | local | shape: id/name/environment/infrastructure |
| CAP-002 | `environment.schema.json` validates env files | v1.9 / `v1.9.0` | `schemas/environment.schema.json` | REQ-040 | local | dev/qa/prod/dr env JSONs |
| CAP-006 | Contract interpolation expands `${env.*}` / `${contract.*}` | v1.9 / `v1.9.0` | `core/contract_resolver.py` | REQ-040 | local | per-env variants |
| — | Submission-readiness gate (superset of contract schema) | v1.18 / `v1.18.0` | `schemas/submission-readiness.schema.json`, `core/submission_readiness.py` | REQ-217, REQ-218, D-133 | local | the only PDLC→Nova boundary (INV-1) |
### Domain 2 — Modules (L1 primitives + L2 patterns)
> Source: `modules/registry.json` (the authoritative module catalog).
> STATE.md lists the *capability* of having a registered module;
> registry.json is the live registry.
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| CAP-003 | contract_resolver resolves `static-assets` (L2) | v1.1 / `v1.2.0` | `core/contract_resolver.py`, `modules/l2/static-assets/` | REQ-003 | local | CloudFront+WAF+S3 pattern |
| CAP-004 | contract_resolver resolves `microservice` (L2) | v1.2 / `v1.3.0` | `core/contract_resolver.py`, `modules/l2/microservice/` | REQ-004 | local | ECS Fargate pattern (6 L1 children) |
| CAP-005 | Terraform adapter compiles resolved stack to `.tf` | v1.1 / `v1.2.0` | `adapters/terraform/adapter.py` | REQ-005 | local | stateless assembler (v1.11); emits `module "<rid>" { source }` blocks |
| — | L1 `s3` primitive | v1.1 / `v1.2.0` | `modules/l1/s3/` | REQ-005 | lifecycle | versioning + SSE-KMS by default |
| — | L1 `vpc` primitive | v1.1 / `v1.2.0` | `modules/l1/vpc/` | REQ-005 | lifecycle | shared platform VPC (v1.11) |
| — | L1 `ecs-cluster` primitive | v1.1 / `v1.2.0` | `modules/l1/ecs-cluster/` | REQ-005 | lifecycle | |
| — | L1 `ecs-service` primitive | v1.1 / `v1.2.0` | `modules/l1/ecs-service/` | REQ-005 | lifecycle | execution_role_arn + task_role_arn wired (P4 W1 fix, v1.26) |
| — | L1 `iam-role` primitive | v1.1 / `v1.2.0` | `modules/l1/iam-role/` | REQ-005 | lifecycle | |
| — | L1 `alb` primitive | v1.1 / `v1.2.0` | `modules/l1/alb/` | REQ-005 | lifecycle | requires SG wire (P4 W1 fix, v1.26) |
| — | L1 `ecr` primitive | v1.1 / `v1.2.0` | `modules/l1/ecr/` | REQ-005 | lifecycle | |
| — | L1 `cloudfront` primitive | v1.7 / `v1.7.0` | `modules/l1/cloudfront/` | REQ-049, D-049 | lifecycle | OAC + WAF (production edge) |
| — | L1 `waf` primitive | v1.7 / `v1.7.0` | `modules/l1/waf/` | REQ-049, D-049 | lifecycle | |
| — | L1 `rds` primitive | v1.7 / `v1.7.0` | `modules/l1/rds/` | REQ-059, D-059 | lifecycle | multi-engine input (postgres/mysql/...) |
| — | L1 `kms-key` primitive | v1.8 / `v1.8.0` | `modules/l1/kms-key/` | REQ-069, D-069 | lifecycle | per-stack CMK; 90-day rotation |
| — | L1 `uptime` primitive | v1.8 / `v1.8.0` | `modules/l1/uptime/` | REQ-066, D-066 | lifecycle | uptime-kuma on ECS Fargate |
| — | L1 `dynamodb` primitive | v1.26 / `v1.25.2` | `modules/l1/dynamodb/` | REQ-322 | local | PK + optional SK; PAY_PER_REQUEST; encryption + PITR by default (v1.8 NFRs) |
| CAP-013 | `terraform init+validate+plan` live AWS (microservice) | v1.2 / `v1.3.0` | `adapters/terraform/adapter.py` | REQ-013 | live-aws | 14 resources; plan saved |
| CAP-014 | `terraform init+validate+plan` live AWS (static-assets) | v1.7 / `v1.7.0` | `adapters/terraform/adapter.py` | REQ-014 | live-aws | CloudFront+WAF+S3 plan OK |
| CAP-017 | DynamoDB `nova-contracts` table | v1.7 / `v1.7.0` | `core/lambda/`, `terraform/` | REQ-068, D-068 | lifecycle | PK `changeRequestId`, SK `submittedAt` (CMDB) |
| CAP-018 | Lambda contract-ingestor | v1.7 / `v1.7.0` | `core/lambda/contract_ingestor.py` | REQ-051, D-051 | lifecycle | local stub + lifecycle evidence |
| CAP-019 | ECS cluster + service (L2 microservice) | v1.7 / `v1.7.0` | `modules/l2/microservice/` | REQ-066 | lifecycle | apply/modify/destroy exit 0 |
| CAP-020 | CloudFront + WAF production stack | v1.7 / `v1.7.0` | `modules/l2/static-assets/` | REQ-049 | lifecycle | apply/modify/destroy exit 0 |
| CAP-021 | uptime-kuma monitoring primitive | v1.8 / `v1.8.0` | `modules/l1/uptime/` | REQ-066 | lifecycle | |
| CAP-022 | OIDC role for act_runner | v1.11 / `v1.11.0` | `terraform/bootstrap/` | REQ-116, D-039 | lifecycle | real OIDC blocked on go-gitea/gitea#36988 |
### Domain 3 — Policy engine
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| — | `PolicyEngine` Protocol + `PolicyEngineRegistry` | v1.25 / `v1.24.1` | `core/policy_engine.py` | REQ-291, REQ-292 | local | selects engine from `config.json.policy.engine`; `NullEngine` fallback when key absent |
| — | `KyvernoJsonEngine` adapter (shells to `kj scan`) | v1.25 / `v1.24.1` | `adapters/kyverno-json/kyverno_json_engine.py` | REQ-293, REQ-294 | local | `is_configured()` guards on `which kj`; `SKIPPED` PCR when absent |
| — | Contract policies (4) over consumer contract JSON | v1.25 / `v1.24.2` | `adapters/kyverno-json/policies/contract/` | REQ-295, REQ-296 | local | id-pattern, env-enum, infra-min-1, forbid-unknown-fields |
| — | Stack-IR policies (3) over resolved Target Stack IR | v1.25 / `v1.24.2` | `adapters/kyverno-json/policies/stack-ir/` | REQ-297, REQ-298, REQ-299 | local | tagging-standard, public-ingress, encryption-by-default |
| — | Plan-JSON policies (3) over `terraform show -json` | v1.25 / `v1.24.3` | `adapters/kyverno-json/policies/plan-json/` | REQ-300, REQ-301, REQ-302 | local | plaintext-secrets, iam-wildcard, kms-reference |
| — | Meta-policies over merged PCR list | v1.25 / `v1.24.3` | `adapters/kyverno-json/policies/meta/` | REQ-303 | local | `block-on-any-critical` (declarative critical-block); `tagging-rules-agree` (Checkov↔kj agree) |
| — | Regression-gate policies (3) over capability-inventory JSON | v1.25 / `v1.24.4` | `adapters/kyverno-json/policies/regression/` | REQ-304, REQ-305 | local | declarative mirrors of CAP-013/023/024 imperative checks |
| — | Pilot-readiness policy (no placeholder account) | v1.26 / `v1.25.3` | `adapters/kyverno-json/policies/pilot-readiness/no-placeholder-account.json` | REQ-320 | local | fail-closed gate; blocks apply on `account_id == "000000000000"` |
| — | Settlement-finality policy | v1.26 / `v1.25.3` | `adapters/kyverno-json/policies/settlement-finality/all-matches-committed.json` | REQ-315 | local | authored + tested; enforcement deferred to milestone that binds qa/prod/dr (D-208) |
| — | Checkov adapter (raw-finding source) | v1.7 / `v1.7.0` | `adapters/terraform/checkov_adapter.py` | REQ-053 | local | feeds meta-policies; `NOVA_TAG_NAMING` custom rule is the TF-static source of truth |
| — | Wiz adapter (raw-finding source) | v1.7 / `v1.7.0` | `adapters/wiz/` | REQ-053 | local | API findings; `is_configured()` guard |
| — | K8s Kyverno adapter (documentation-only) | v1.7 / `v1.7.0` | `adapters/kyverno/` | REQ-053, D-053 | local | inactive for Terraform-only stacks; activates when GitOps emits K8s manifests |
### Domain 4 — Confidence signal
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| CAP-007 | `confidence_signal.compute` returns a band | v1.1 / `v1.2.0` | `core/confidence_signal.py` | REQ-007, D-040 | local | 6 inputs (INV-2); band ∈ {pass, block} |
| — | `escalation_reason: 'confidence'` on `band == 'block'` | v1.26 / `v1.25.3` | `core/confidence_signal.py` | REQ-318 | local | grounds Human Escalation Frequency numerator |
### Domain 5 — Environments & promotion
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| — | env-JSON `state_backend` wiring | v1.26 / `v1.25.3` | `core/environments/*.json`, `adapters/terraform/adapter.py` | REQ-319, D-... | local | adapter reads `env.state_backend.bucket` (fallback to computed name) |
| — | Environment progression (dev autonomous → qa/prod/dr HITL) | v1.1 / `v1.2.0` | `core/env_transition.py`, `core/hitl_gates.py` | REQ-042, D-042 | local | destroy-on-environment-change (v1.24) |
| — | Decommission mode (2-step, HITL SRE gates) | v1.8 / `v1.8.0` | `core/env_transition.py`, `scripts/run_platform.sh` | REQ-070, D-070 | local | `mode: decommission` requires `changeRequestId` |
| — | Per-env mandatory metadata (W3.E) | v1.1 / `v1.2.0` | `schemas/submission-readiness.schema.json` | W3.E | local | dev=stack+env; qa+=e2e+load; prod+=runbook+dashboard+oncall; dr+=drDrillRef |
### Domain 6 — Evidence stream & audit
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| CAP-008 | outbox_writer builds a hash-chained item | v1.1 / `v1.2.0` | `core/outbox_writer.py` | REQ-008 | local | tamper-evident (INV-6); tamper-resistant deferred (D-083) |
| CAP-015 | DynamoDB outbox table exists + describable | v1.1 / `v1.2.0` | `core/outbox_writer.py` | REQ-015 | live-aws | `nova-outbox` (post-v1.26 re-bootstrap) |
| CAP-016 | S3 state bucket exists + readable | v1.1 / `v1.2.0` | `terraform/bootstrap/` | REQ-016 | live-aws | `nova-tfstate-581513795199-us-east-1` |
| — | Decision Ledger (SQLite hash-chain) | v1.17 / `v1.17.0` | `core/metrics/decision_ledger.py` | REQ-185, REQ-186 | local | cold store for metrics; `ai.decision.made` + `attestation.recorded` events |
| — | SSM Parameter Store deploy outputs (SecureString, KMS) | v1.7 / `v1.7.0` | `core/output_publisher.py` | REQ-050, D-050 | live-aws | `/acdl/{env}/{contractId}/{output_name}` |
| — | GitHub PR comment / job summary deploy outputs | v1.7 / `v1.7.0` | `scripts/run_platform.sh` | REQ-050, D-050 | local | no raw secrets in logs |
| — | Uniform error reporting via Lambda `report_error` | v1.7 / `v1.7.0` | `core/lambda/contract_ingestor.py` | REQ-055, D-055 | live-aws | GitHub issue on platform repo `acdl/acdl`; idempotent |
| — | Tagging standard enforcement (4 required tags) | v1.7 / `v1.7.0` | `schemas/tagging-standard.json`, `adapters/terraform/policy/custom_rules/nova_tagging.py` | REQ-054, D-054 | local | `nova:owner`, `nova:contract`, `nova:environment`, `nova:cost-center` |
| — | Encryption + deletion-protection by default | v1.8 / `v1.8.0` | `modules/l1/*/terraform/main.tf` | REQ-062, REQ-069, D-062, D-069, D-072 | local | per-stack CMK; managed KMS fallback for standalone L1 (D-072) |
### Domain 7 — Telemetry & metrics
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| CAP-023 | metrics collector runs + emits expected schema | v1.17 / `v1.17.0` | `core/metrics/collector.py` | REQ-194 | local | fact_run, fact_decision, fact_attestation dims |
| CAP-024 | unified deck structure (slide count, x3 arc, per-slide benefits) | v1.17 / `v1.17.0` | `docs/presentations/nova-autonomous-cloud-delivery-marp.md` | REQ-194 | local | single source-of-truth marp deck |
| — | Outcome backfill (`pending``succeeded`/`failed`) | v1.26 / `v1.25.3` | `core/metrics/outcome_backfill.py` | REQ-317 | local | idempotent + terminal; grounds AI Decision Accuracy |
| — | Trust Snapshot | v1.17 / `v1.17.0` | `metrics/TRUST_SNAPSHOT.md`, `core/metrics/trust_snapshot.py` | REQ-194 | local | leadership-ready trust verdict |
| — | PowerBI export (fact/dimension views + 8 placeholder views) | v1.17 / `v1.17.0` | `metrics/powerbi/` | REQ-194 | local | deferred metrics ship as documented-schema placeholders |
| — | Pre-apply Infracost estimate | v1.17 / `v1.17.0` | `scripts/run_platform.sh` | REQ-119 | local | `nova.cost.estimated`; actual-spend CUR reconciliation deferred (D-096) |
| — | Regression gate (`scripts/run_regression.sh`) | v1.10 / `v1.10.0` | `core/regression_verify.py`, `scripts/run_regression.sh` | REQ-090, REQ-121 | local | fails closed on any non-Verified CAP; CAP-001..025 |
### Domain 8 — Consumer surfaces (developer + agentic)
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| — | Reusable deploy workflow (`deploy.yml@v1.25`) | v1.5 / `v1.5.0` | `.github/workflows/deploy.yml`, `.gitea/workflows/deploy.yml` | REQ-105 | local | `workflow_call`; modes: full/plan-only/check-only/decommission |
| — | Consumer onboarding (developer + citizen-dev paths) | v1.1 / `v1.2.0` | `docs/ONBOARDING.md`, `docs/consumer-guide.md` | BA.E, W3.E | local | both end in a sandbox dev submission that must pass the confidence gate |
| — | Atelier MCP server (agentic validation) | v1.18 / `v1.18.0` | `mcp/atelier/server.py` | REQ-221, REQ-222 | local | `atelier.validate_against_principles` tool |
| — | 9 production-grade engineering skills | v1.18 / `v1.18.0` | `skills/{api,security,data,testing,observability,errors,devops,infrastructure-as-code,compliance}.md` | REQ-221, REQ-222, BA.A | local | indexed by `docs/skills.md`; review/agent-checklist.md gate |
| — | Module examples (validated against contract schema) | v1.7 / `v1.7.0` | `modules/<name>/examples/{simple,complex}.yml` | REQ-058, D-058 | local | examples cannot drift from schema silently |
### Domain 9 — Pilot estate (v1.26)
> The first real consumer estate. `nova-blockchain-exchange` repo
> (Gitea `continuous-intelligence/nova-blockchain-exchange`, local clone
> `/root/nova-blockchain-exchange`). Homegrown PoA blockchain, equities
> only, single validator, T+1 settlement finality = block commit.
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| CAP-026 | PoA blockchain core (block + ledger + validator) | v1.26 / `v1.25.1` | `chain/block.py`, `chain/ledger.py`, `chain/validator.py` | REQ-310, D-201 | local | single validator; SHA-256 hash chain; deterministic block production |
| CAP-027 | Order-matching engine (limit order book) | v1.26 / `v1.25.1` | `engine/order_book.py`, `engine/order.py` | REQ-311 | local | price-time priority; partial fills |
| CAP-028 | T+1 settlement service | v1.26 / `v1.25.1` | `settlement/service.py` | REQ-312 | local | idempotent; finality = block commit |
| CAP-029 | Consumer `contract.yaml` (blockchain exchange) | v1.26 / `v1.25.2` | `nova-blockchain-exchange/contract.yaml`, `contracts/*.yml` | REQ-313 | local | per-env variants (dev/qa/prod); validated against contract schema |
| CAP-030 | Consumer deploy via `deploy.yml@v1.25` (inline adapter) | v1.26 / `v1.25.2` | `nova-blockchain-exchange/.github/workflows/deploy.yml`, `.gitea/workflows/deploy.yml` | REQ-314 | local | no cross-repo `uses:` (SPEC §10 Q1); checkout `acdl/acdl @ v1.25` into `platform/`, run `run_platform.sh` |
| CAP-025 | Live-pilot-apply regression capability (round-trip) | v1.26 / `v1.25.3` | `core/regression_verify.py` | REQ-316 | local | contract→adapter→plan→policy→confidence→attestation→outbox round-trip assertion |
| CAP-031 | Live pilot apply evidence (`blkex-pilot-apply-v0.2`) | v1.26 / `v1.25.4` | `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` | REQ-316, REQ-321 | live-aws | confidence 0.800 pass; outcome backfilled; hash chain valid; live apply against `581513795199` |
| CAP-032 | AWS key rotation scheduled workflow | v1.26 / `v1.25.3` | `workflows-src/rotate-aws-key.yml` | SPEC §5.9 | local | daily rotation; forge-agnostic token name (REQ-230) |
### Domain 10 — Forge / CI runtime
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|----|-----------|---------|-------|-------------|------|-------|
| CAP-009 | offline pytest suite passes | v1.1 / `v1.2.0` | `tests/` | REQ-009 | local | 844 tests (v1.26 baseline) |
| CAP-010 | `run_ci.sh` reproduces CI pipeline locally | v1.4 / `v1.4.0` | `scripts/run_ci.sh` | REQ-010 | local | offline; contract→resolver→stack→adapter→structure validated |
| CAP-011 | headline E2E — local tier (microservice) | v1.2 / `v1.3.0` | `scripts/run_local_e2e.sh` | REQ-011, D-092 | local | emulating adapters (no AWS) |
| CAP-012 | local E2E — static-assets (no ECS) | v1.1 / `v1.2.0` | `scripts/run_local_e2e.sh` | REQ-012 | local | |
| — | `platform-test.yml` CI workflow | v1.4 / `v1.4.0` | `.github/workflows/platform-test.yml` | REQ-010 | local | platform repo only (consumer CI is per-consumer) |
| — | `modules-lifecycle` pipeline (apply→modify→destroy matrix) | v1.11 / `v1.11.0` | `.github/workflows/modules-lifecycle.yml` | REQ-121, D-096 | live-aws | per-module lifecycle cell; `ci-vpc-destroy` always runs |
| — | `release.yml` (semver + floating tag maintenance) | v1.7 / `v1.7.0` | `.github/workflows/release.yml` | REQ-... | local | `v1.25` + `v1` floating tags force-moved on merge to main |
| — | IAM policy baseline (`acdl-spike-runner-policy`) | v1.11 / `v1.11.0` | `terraform/bootstrap/spike_runner_policy.json`, `.ciagent/IAM_POLICY.md` | REQ-116, D-095 | live-aws | regression-tested by `tests/test_iam_policy_baseline.py`; OIDC role `acdl-act-runner-role` (CAP-022) |
| — | Local emulating adapters (no AWS) | v1.10 / `v1.10.0` | `core/local_lambda_stub.py`, `scripts/run_local_e2e.sh` | D-092 | local | proves runtime behavior without live AWS |
## Archive pointers
- **v1.0v1.24 capability narrative + the 2026-07-27 re-verification sweep:**
`.ciagent/archive/CAPABILITY_INVENTORY-v1.10.md` (moved from
`.ciagent/CAPABILITY_INVENTORY.md` at v1.27). CAP-NNN IDs in this file
cross-reference the regression gate at `core/regression_verify.py`.
- **v1.0v1.24 milestone narrative:** `.ciagent/archive/PROJECT-v1.0-v1.24.md`.
- **v1.0v1.24 requirements (REQ-01..REQ-290):** `.ciagent/archive/REQUIREMENTS-v1.0-v1.24.md`.
- **v1.0v1.24 phase breakdowns:** `.ciagent/archive/ROADMAP-v1.0-v1.24.md`.
- **v1.0v1.24 architecture history:** `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md`.
- **v1.26 pre-execution artifacts (CLARIFY, GRILL, IDEATE, RESEARCH):**
`.ciagent/archive/{CLARIFY,GRILL,IDEATE,RESEARCH}-v1.26.md` (decisions
D-200..D-213 folded into `PROJECT.md` load-bearing decisions + PLAN.md
binding revisions at v1.27 archive time).
- **v1.26 phase verifications:** `.ciagent/archive/{VERIFY-P03,VERIFY-P04,REVIEW-AUDIT-P05}.md`.
- **v1.26 live pilot run evidence:** `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md`.
- **v1.21 autonomy thesis (folded into NORTH_STAR.md Vision):** `.ciagent/archive/AUTONOMY_THESIS-v1.21.md`.
- **v1.14 AWS cost report (predates v1.26 live pilot):** `.ciagent/archive/COST-v1.14.md`.
## Update discipline
This file is updated **once per milestone, at the P-final milestone-ship
wave** (Wave 3 "milestone ship" in `PLAN.md`), alongside
`ROADMAP.md`/`NORTH_STAR.md`/`REQUIREMENTS.md`:
1. Append new capability entries for each shipped REQ (one row per
capability; group by domain).
2. Mark any deprecated capability with a `Deprecated` row citing the
milestone + replacement.
3. Bump the "Last milestone ship" header.
4. Do not rewrite existing entries (additive only).
Enforcement: convention (the P-final ship step names this file). A
drift-check gate (assert every REQ marked `complete` in
`REQUIREMENTS.md` traceability appears in STATE.md) is a future option
if the convention drifts.
+135
View File
@@ -0,0 +1,135 @@
# ACDL v1.10 — Verify (milestone gate)
> Verify date: 2026-07-27. Verifier: ci-verifier. Milestone: v1.10 (complete, tag `v1.10.0`).
> Scope: 4 phases (5255), 5 commits (772ac72..2697775), 22 files, +2281/-256 lines.
## Layer 1: Structural — PASS
- All 8 plan-referenced files exist on disk (`core/regression_verify.py`,
`core/local_emulators.py`, `scripts/run_regression.sh`,
`tests/test_verify_regression_mode.py`,
`tests/test_local_emulating_adapters.py`,
`.ciagent/CAPABILITY_INVENTORY.md`, `REGRESSION_REPORT.md`,
`REGRESSION_REPORT.json`).
- All imports resolve (`py_compile` + runtime import OK).
- No TODO/FIXME/HACK/stub placeholders in new code (the `LocalLambdaStub`
is a legitimate local emulator, not a placeholder).
- All declared exports exist (`run_regression`, `write_report`,
`CAPABILITY_REGISTRY`, `RegressionReport`, `CapabilityResult`,
`FlatFileOutbox`, `LocalEcsEmulator`, `LocalS3StateBackend`,
`LocalLambdaStub`, `run_local_e2e`, `is_local_tier`).
## Layer 2: Behavioral — PASS
- `pytest tests/ -m "not slow"`: **513 passed**, 5 deselected.
- `pytest tests/ -m slow`: **5 passed** (2 local E2E + 3 regression
integration incl. live-AWS terraform plan).
- **Total: 518 passed, 0 failed.**
- Requirement coverage: REQ-112 (P52), REQ-113 (P53), REQ-114 (P54),
REQ-115 (P55) — all 4 marked `complete`.
- Regression gate: `bash scripts/run_regression.sh` → **16/16
capabilities Verified** (12 local + 4 live-AWS). Milestone gate open.
## Layer 3: Security (STRIDE) — PASS
| Threat | Risk | Disposition |
|--------|------|-------------|
| Spoofing | Local Lambda stub patches `_get_dynamodb`/`_get_secrets_client`; opt-in via `ACDL_LOCAL_TIER=1`, never in prod | Accept (low) |
| Tampering | Flat-file outbox hash-chain verification detects tampering | Accept (low) |
| Repudiation | Regression report records per-capability status + timestamps | Accept (low) |
| Info Disclosure | Creds read into env vars, never logged (0 cred strings in reports); ECS binds 127.0.0.1 only | Accept (low) |
| Denial of Service | Local ECS emulator: free port, daemon thread, clean destroy | Accept (low) |
| Elevation of Privilege | `urllib.urlopen` patched to fake response (no network egress); no eval/exec/subprocess in adapter | Accept (low) |
All threats low-severity; auto-accepted per
`config.json security.auto_accept_low_severity=true`.
## Layer 4: Quality (multi-persona) — PASS
| Persona | Finding | Verdict |
|---------|---------|---------|
| Correctness | 7 adapter defects fixed; each traceable to a terraform validate/plan error | PASS |
| Testing | 518 tests pass; 24 new tests. P2: uptime-kuma + RDS not in registry | PASS (1 P2) |
| Security | No creds logged; loopback-only; monkey-patches scoped to local tier | PASS |
| Performance | Regression run ~60s; acceptable for a milestone gate | PASS |
| Maintainability | Well-structured; adding a capability = 1 function + 1 registry entry | PASS |
| Adversarial | Gate can't be bypassed; local E2E can't mutate cloud; no injection vectors | PASS |
**0 P0, 0 P1, 1 P2 (post-hoc: expand regression registry to uptime-kuma + RDS stacks).**
## Verdict
**VERIFY PASS** — all 4 layers pass. The v1.10 milestone is sound:
the pipeline regression gap is fixed (D-091), the platform is fully
locally testable (D-092), every advertised capability is re-verified
(D-093, 16/16 Verified), and the docs/decks match verified reality
(D-094). 518 tests pass; the regression gate covers 16 capabilities
including 4 live-AWS checks. 0 P0, 0 P1, 1 P2 post-hoc. Ready to ship.
---
# ACDL — Verify (grill deliverable, commit ac11c01)
> Verify date: 2026-07-27. Verifier: ci-verifier. Scope: the grill
> deliverable (`.ciagent/GRILL.md`, phase 0, status `grill`) added in
> commit `ac11c01` since the v1.10 audit PASS (`ab477b3`). Docs-only;
> no code, no tests, no schema changes.
## Layer 1: Structural — PASS
- `.ciagent/GRILL.md` exists on disk (18250 bytes).
- No imports to resolve (markdown docs file).
- No TODO/FIXME/HACK/stub placeholders in the report.
- All required sections present per grill workflow Step 5 format:
title, Run header, Verdict, 9 axes (19), Meta, Binding Decisions
table (12 rows), Escalations section (2 entries: G-005, G-008).
- Commit `ac11c01` `---ci---` block is well-formed: `project: acdl`,
`phase: 0`, `milestone: v1.10`, `status: grill`, 12 decision ids
(G-001..G-012), 2 escalation lines.
## Layer 2: Behavioral — PASS
- `pytest tests/ -m "not slow"`: **513 passed**, 5 deselected (no
regressions introduced by the docs-only grill commit).
- No new tests required (docs-only deliverable; the grill is a
review artifact, not a code change).
- Requirement coverage: not applicable (phase 0, status `grill`; no
REQ-IDs bound to this deliverable). The grill's binding decisions
(G-001..G-012) are advisory and do not modify REQUIREMENTS.md per
grill workflow Step 7.
## Layer 3: Security (STRIDE) — PASS
| Threat | Risk | Disposition |
|--------|------|-------------|
| Spoofing | N/A (docs-only; no auth surface) | Accept (none) |
| Tampering | Grill report is git-tracked; tampering = git history rewrite (out of scope) | Accept (low) |
| Repudiation | Commit `ac11c01` signed by author; `---ci---` block records status + decisions | Accept (low) |
| Info Disclosure | No credentials, keys, tokens, or PII in the report (grep scan clean) | Accept (low) |
| Denial of Service | N/A (docs file; no runtime surface) | Accept (none) |
| Elevation of Privilege | N/A (docs-only; no privilege surface) | Accept (none) |
All threats low-or-none; auto-accepted per
`config.json security.auto_accept_low_severity=true`.
## Layer 4: Quality (multi-persona) — PASS
| Persona | Finding | Verdict |
|---------|---------|---------|
| Correctness | 12 binding decisions traceable to evidence (commit/file/req-id); 2 escalations correctly unresolved | PASS |
| Testing | Docs-only; 513 fast tests pass (no regression) | PASS |
| Security | No credential leakage; no sensitive data in report | PASS |
| Performance | N/A (docs file; no runtime cost) | PASS |
| Maintainability | Report follows grill workflow Step 5 format exactly; appendable for future runs | PASS |
| Adversarial | Escalations (G-005, G-008) are surfaced, not silently skipped; visible via `ciagent audit` | PASS |
**0 P0, 0 P1, 0 P2.**
## Verdict (grill deliverable)
**VERIFY PASS** — all 4 layers pass. The grill deliverable is a
well-formed docs-only artifact. 513 fast tests pass (no regression).
No credential leakage. 12 binding decisions recorded; 2 escalations
(G-005 risks, G-008 budget) correctly surfaced for human resolution.
The grill does not modify PROJECT.md, ROADMAP.md, or REQUIREMENTS.md
(per grill workflow Step 7).
-945
View File
@@ -1,945 +0,0 @@
# Nova — Architecture (v1.1 target)
> Target architecture for the real Agentic Cloud Delivery Platform (rebranded
> Nova in v1.15). Source of truth for **how**: `docs/architecture.md` (v0.2) is the upstream
> draft; this file is the Nova-repo operating copy, refined at phase
> boundaries. Where this file and `docs/vision.md` conflict, the vision wins.
## Status
Architecture is at **v0.2** upstream (`docs/architecture.md`). Milestone v1.1
**finalizes it to v1.0** in Phase 07 by resolving the 11 open decisions
(see `PROJECT.md` open-decision resolutions table). This file records the
locked commitments and the v1.1 spike scope.
## Overview
The platform is **four layers + six cross-cutting concerns**. The sixth
concern — the engine abstraction (§12) — is first-class, not an
implementation detail. The vision's "Two Consumer Surfaces, One Platform"
tenet binds everything: L3A and L3B converge on the same contract schema,
the same policy envelope, and the same evidence stream.
```
┌──────────── acdl-contracts ────────────┐
Developer ───▶ │ commit contract.yaml │ (L3A)
Citizen dev ──▶ │ Issue → agent → contract.yaml │ (L3B)
└────────────────┬───────────────────────┘
│ (push)
┌──────────────────────┐
│ central pipeline │
│ (acdl repo, Gitea │
│ Actions / act_runner) │
└────────┬─────────────┘
┌─────────────────────────┼─────────────────────────┐
▼ ▼ ▼
contract→IR resolution policy (Checkov/Kyverno) confidence signal
│ │ │
▼ ▼ ▼
Terraform adapter ──▶ terraform plan ──▶ PolicyCheckResult ──▶ {score,band}
│ │
▼ ▼
dev (autonomous, ≥0.50) qa (HITL, ≥0.75) prod (HITL, ≥0.90) dr (HITL, ≥0.95)
DynamoDB outbox ──▶ S3 Object Lock (7-yr, source of truth) ──▶ GitHub audit repo (hot index)
acdl-evidence (timeline UI)
```
## Layers
### Layer 1 — Foundational Primitives
Single-purpose, **engine-agnostic** primitive modules. L1 modules do
not compose with other L1s; L1 takes its environment as input. The L1
interface is defined against the **Target Stack IR**, not against Terraform
directly (the IR is shaped to round-trip to Terraform in v1, per §12.1).
- No inter-L1 references. L1 may call Terraform data sources.
- Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (W3.D).
- Immutability on publication. 12-month deprecation window.
- AI refinement is a flag; the trigger is the W1.A joint condition.
### Layer 2 — Composed Stacks
Combine L1 primitives into deployable shapes. Each codebase maps to one
canonical L2 stack (`multiStack: true` only per W1.B). Shape X
(parameterized module) or Shape Y (thin-composition layer). Hierarchical
composition, max depth 5, only registered L1s. The thin-composition tree's
`wires` field is defined against the IR's relationship type, not a Terraform
module block.
Pipeline quality checks: secrets-in-plaintext, public ingress, IAM
wildcard, KMS key reference, tag compliance, naming convention. Restricted
from thin-composition: IAM principal creation, network boundary creation,
key/secret creation, external data transfer. Auto-promote after 3 observed
usages.
### Layer 3A — Developer Consumer Surface
Tag-based reference to the central pipeline template. Developer-owned
workflow file, no platform auto-sync. L3A and L3B are parallel paths, not a
progression. **W2.A (Path B):** tag for dev/qa, SHA for prod; platform CLI
resolves tag→SHA for prod-bound workflows.
### Layer 3B — Agentic Consumer Surface
Hybrid runtime, skill as markdown, agent as executor. Trust model: trust
and always verify on the platform side. Skill envelope (4 dimensions).
Stateless agents, all state in the platform. `profile: agentic` marker
unlocks `naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`.
Initial skill catalog (BA.A): web API, worker, scheduled job, static asset,
basic observability bootstrap.
Environment progression:
| Environment | Autonomy | Attester | Gate |
|---|---|---|---|
| dev | Full autonomy (no HITL) | — | Confidence ≥ 0.50, all six inputs present |
| qa | Held for attestation | QA | GitHub Deployment approval + full QA matrix (§10) |
| prod | Held for attestation | SRE | GitHub Deployment approval + full SRE matrix (§10) |
| dr | Held for attestation | SRE | GitHub Deployment approval + dr-drill evidence |
**Staging is removed.** Dev is the only autonomous environment.
## Cross-cutting concerns
### Central pipeline template (§6)
JSON Schema (draft 2020-12) with a thin domain wrapper. Central repo +
generated client libraries. Multi-stage validation: schema → policy → NFR →
confidence. Distributed enrichment. GitOps reconciler (K8s API; cdlc-gitops
state → CRDs) + Terraform execution layer (§12.5). The pipeline emits one
`PolicyCheckResult` per policy rule; the confidence signal consumes them as
one normalized input.
### Contract schema (§7)
Central repo + generated client libraries. Strict fail-fast at schema
stage, multi-stage validation with reason codes from a published
vocabulary. **W3.E:** per-env mandatory inputs —
- dev: `stack`, `environment`
- qa adds: `validation.e2eSuite`, `validation.loadTest`
- prod adds: `runbook`, `dashboard`, `oncall`
- dr adds: `drDrillRef`
- `inputs` always optional; `profile: agentic` fields optional everywhere.
### Confidence signal (§8)
Six canonical inputs, weighted sum with per-input breakdown. Per-env
thresholds: dev ≥ 0.50, qa ≥ 0.75, prod ≥ 0.90, dr ≥ 0.95. Structured output
`{ score, band, perInput, reasonCodes }`. 1-year storage, no retraining in
v1. Halt with explicit reason on missing input.
Policy input = list of `PolicyCheckResult` records (engine-agnostic).
Severity → penalty: critical → hard override to mandatory block; high →
-0.2; medium → -0.05; low → -0.01; info → 0.0. One critical finding
hard-overrides the score regardless of all other inputs.
**BA.B:** thresholds frozen for v1; tuning begins v1.2 (quarterly FP/FN
tracking; override = Infra & Ops + SRE joint sign-off, itself a
confidence-event).
### Audit and evidence stream (§9)
Tiered ledger: **S3 with Object Lock in compliance mode** (cold, source of
truth, 7-year retention) + **GitHub audit repo** (`acdl-evidence`, hot
query index, not part of the chain). Daily checkpoints. Event schema: JWS
detached signature, `prev_event_hash` chain, controlled-vocabulary
`event_type`. Outbox pattern: local durable outbox + async worker.
Outbox database = **DynamoDB**. RPO = 0 (synchronous write to local outbox
before contract submission ack); RTO = async worker's dead-letter recovery.
Single-region in v1. The outbox also stores per-contract QA and prod
approver identities (the only durable record outside GitHub's audit log).
### Human-in-the-Loop mechanics (§10)
Pre-execution gates. qa, prod, dr are PR-based attestation gates backed by
GitHub Environments with required reviewers. No partial deployment to roll
back on rejection (qa, prod); dr is a separate GitHub Deployment against a
separate cluster/region.
Reviewer routing: GitHub CODEOWNERS + Environment required reviewers
(qa → QA; prod → SRE; dr → SRE). CODEOWNERS routes, does not enforce
identity distinctness.
**Separation of duties** (platform-internal, not GitHub-native, not Kyverno
in v1): on dev→qa promotion the platform writes the QA approver's GitHub
identity to the DynamoDB outbox keyed by `contractId`; on qa→prod it reads
the stored QA approver and the new SRE approver; if equal, it blocks, emits
`SEPARATION_OF_DUTIES_VIOLATION`, and routes a halt artifact to SRE on-call.
Full 8-concern attestation matrix (functional, performance, security
posture, contract NFRs, operational readiness, incident response,
capacity/cost, resilience) — see `docs/architecture.md` §10.4.
Timeout: 1 business day = warn + escalate; 2 business days = auto-freeze +
re-submit (linked via `supersedes`). Rejection returns the contract to HELD;
the audit chain is extended, not torn up.
### Agentic stack (§11)
Hybrid runtime: platform-managed control plane + consumer-owned agent.
Versioned, signed skill catalog over MCP. Skill envelope enforced on
invocation and result submission. Consumer-owned skill execution; the
platform does not run the skill. Stateless agents, all state in the
platform. Skills are reviewed for sensitive data before release (Infra &
Ops owns the review; it is the mandatory release gate).
### Angine execution (§12) — the binding constraint
**Target Stack IR** (locked): a engine-neutral description of resources
(typed inputs/outputs/NFRs), relationships (single parent per child),
composition (tree, max depth 5), and policy hooks. The L1 registry, L2
thin-composition tree, contract YML, and PolicyCheckResult schema are all
defined against the IR — none against any specific engine.
**Angine adapters** are the only engine-specific code. An adapter
compiles the IR into a engine execution plan. **v1 ships exactly one
adapter: the Terraform adapter.** v2+ may add OpenTofu, Pulumi, K8s CRDs
without architectural change.
v1 reality: the IR is shaped to round-trip cleanly to Terraform (nearly
isomorphic). As more adapters appear, the IR gets more expressive and the
adapters gain translation logic; the L1 content, the YML standard, and the
thin-composition tree do not change.
**Terraform adapter (v1):** translates IR-typed L1 interface → Terraform
`variable`/`output` blocks; IR-typed L2 thin-composition tree → Terraform
root module; IR-typed relationships → module references; emits a
`terraform plan` from the IR. The adapter is a thin layer; it does not own
L1/L2 content.
State storage: S3 (state) + DynamoDB (locking), cloud-managed,
single-region in v1.
Policy toolchain: **Checkov** for Terraform plan policy (the L2 checks +
tag/naming); **Kyverno** for K8s-native/platform-internal policy; **OPA**
reserved for cross-resource cases, explicitly last resort.
**Policy result normalization (§12.6):** the confidence signal consumes a
normalized `PolicyCheckResult` schema, not raw engine output.
```json
{
"contractId": "uuid",
"evaluatedAt": "ISO-8601",
"engine": "checkov | kyverno | opa",
"ruleId": "CKV_AWS_24 | KYVERNO_NO_PRIVILEGED | ...",
"severity": "critical | high | medium | low | info",
"result": "pass | fail | skipped | error",
"message": "human-readable",
"evidence": { "...engine-specific, opaque to the signal..." },
"resourceRef": "IR-typed resource identifier"
}
```
Execution layer: GitHub/Gitea Actions in the central pipeline repo. State
locking via DynamoDB. **AWS credentials via OIDC federation — long-lived
credentials are forbidden** (§12.5). The platform does not run
`terraform apply` against a developer's workstation; all execution is in
the central pipeline.
Registry maintenance: L1 publication updates the L1 registry in the same
PR. The registry is the IR-typed contract, not a Terraform-specific
variable schema.
Contract→IR resolution: the contract declares intent in IR-typed terms;
the pipeline resolves it to a target stack (list of L1 instances + inputs +
relationships); the Terraform adapter compiles the target stack to a plan.
## v1.1 spike scope
The spike (Phases 0810) materializes the **minimum** that proves the IR
commitments hold (no polyglot mess):
- One L1: `l1-s3` (IR-typed interface; the only AWS resource in the spike).
- One L2 thin-composition: `l2-static-assets` (references `l1-s3` only).
- Terraform adapter: IR → `terraform plan` against AWS via OIDC.
- One contract submission → contract→IR → `terraform plan` → Checkov
`PolicyCheckResult` → confidence signal → evidence event to the DynamoDB
outbox.
- State: S3 + DynamoDB (real AWS, single-region).
Out of spike scope: full HITL matrix wiring, Kyverno, OPA, MCP skill
catalog, GitOps reconciler, multi-region, prod/dr environments, the 5-skill
L3B catalog. Those are post-spike (v1.2+) platform build-out.
## Gitea API surface (carried from v1.0, refined)
| Capability | Gitea support | ACDL approach (v1.1) |
|------------|---------------|----------------------|
| Org-scoped repo create | `POST /api/v1/orgs/{org}/repos` | Used for any new repos |
| Native Pages | **None** | Serve `acdl-evidence` via raw file URLs (unchanged from v1.0) |
| Environments API | **None**; act_runner ignores `environment:` | Model HITL gates via `workflow_dispatch` approval inputs (v1.0 D-013 pattern) — **refined in Phase 07** for the real pre-execution gate model |
| `repository_dispatch` | Not supported | Cross-repo trigger via `workflow_dispatch` API (unchanged) |
| Reusable workflows | Supported | `acdl/.gitea/workflows/pipeline.yml` via `uses: ...@<ref>` |
| `id-token: write` / OIDC | **Not supported** (RESEARCH TARGET 1, conf 0.95). Gitea docs list `id-token` as an unsupported GitHub-only scope; open proposal go-gitea/gitea#33681; draft PR go-gitea/gitea#36988 unmerged. Even Gitea's own CI uses long-lived AWS keys (issue #37980). | **Spike waiver D-039:** per-run-rotated long-lived key (rotated after each run by `scripts/rotate_spike_key.sh`). Real OIDC deferred to v1.2, blocked on PR #36988. |
| `actions/configure-aws-credentials` | Unusable without OIDC | Spike uses static AWS creds from a (rotated) Gitea Actions secret via the `aws-actions/configure-aws-credentials@v4` `access-key-id`/`secret-access-key` inputs, or plain `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` env vars. v1.2 switches to `role-to-assume` when OIDC lands. |
### Branch pinning rule (refined for W2.A)
- Dev/qa contracts reference the reusable workflow by **tag**
(`@v1.1-spike`).
- Prod-bound workflows reference by **SHA**; the platform CLI
(`platform/cli/resolve-tag.ts`, Phase 07) resolves the current tag to its
SHA. (Spike scope: the CLI is a stub; the real CLI lands in v1.2.)
### Verification toolchain
ACDL has no `package.json`. The verification gate substitutes:
- **typecheck:** `terraform validate`, `python3 -m py_compile`, JSON Schema
validation (`ajv` or `python -m jsonschema`) against `schemas/`.
- **test:** per-phase `scripts/verify_phaseNN.sh` (Phase 06: archive integrity;
Phase 07: schema validation + decision-resolution completeness; Phase 08:
OIDC assume-role + state backend; Phase 09: IR + L1 + adapter `terraform
plan`; Phase 10: end-to-end contract submission).
- **build:** `terraform init` (real build for the spike).
- See `PERSONAS.md` verification_toolchain.
## Build order (v1.1)
1. Phase 06 — archive demo, reorient repo.
2. Phase 07 — finalize architecture v1.0; author schemas + designs.
3. Phase 08 — AWS OIDC bootstrap (use temp key once, rotate).
4. Phase 09 — IR + `l1-s3` + Terraform adapter → `terraform plan`.
5. Phase 10 — `l2-static-assets` + contract→IR → end-to-end spike.
6. COMPLETE gate — review → ship `v1.2.0` → audit. **DONE.**
## v1.2 build-out scope
v1.2 takes the v1.1 spike (dev-only, `plan`-only, single S3 L1) to a real,
simpler, better-documented platform that delivers a microservice to AWS ECS
Fargate end-to-end. The locked architecture (§1–§12) is unchanged — v1.2
extends the *implementation*, not the design.
### In scope (five axes, user-directed 2026-07-21)
1. **Re-evaluate the current state.** go-gitea/gitea#36988 (OIDC for Gitea
Actions) re-checked 2026-07-21: still **open** (last updated 2026-05-27,
not merged). Real OIDC remains deferred to v1.3+; v1.2 extends the D-039
per-run-rotated-key waiver as **D-047**. The waiver continues to satisfy
§12.5's *intent* (no *persistently* long-lived key): the spike key is
rotated after each run by `scripts/rotate_spike_key.sh`, and Phase 12
tightens the IAM scoping + rotation hygiene.
2. **NFR improvements on the existing spike.** Least-privilege IAM audit of
`spike_runner_policy.json`; idempotent `create_state_backend.py` /
`create_iam_user.py`; proper exit codes / error handling; P1-1 redaction
(two AWS access key IDs in `.ciagent/VERIFY.md` Phase 09 narrative).
3. **Streamline / simplify the current setup.** Consolidate
`run_spike_plan.sh` + `run_spike_e2e.sh` into one
`scripts/run_platform.sh`; remove dead code and stale `platform/` paths.
4. **README.md fully up to date on how the platform works.** Reflect v1.1
complete; document the actual spike flow, `scripts/run_platform.sh`, the
real repo layout, and the v1.2 objective.
5. **Bootstrap a consumer repo with a basic microservice deployed to ECS
end-to-end.** New Gitea repo `acdl-consumer-microservice` (org
`continuous-intelligence`); new IR-typed L1s (`l1-vpc`, `l1-ecs-cluster`,
`l1-ecs-service`, `l1-iam-role`, `l1-alb`, `l1-ecr`); new
`l2-microservice` thin-composition; one contract submission →
`terraform apply` (dev, autonomous per §10, confidence ≥ 0.50) → a live
ECS Fargate service serving HTTP 200 → evidence event to the DynamoDB
outbox → acdl-evidence timeline.
### Angine extension (ECS Fargate)
The Terraform adapter (§12) remains the only engine-specific code. v1.2
expands the adapter `TYPE_MAP` to cover the six new ECS-shaped IR resource
types. The L1 interface shape (IR-typed inputs/outputs/NFRs, registered in
`modules-ir/registry.json`) is unchanged — only the set of registered L1s
grows. The IR commitments (REQ-28) continue to hold: `modules-ir/`,
`schemas/`, `contracts/`, `core/confidence_signal.py`,
`core/contract_resolver.py`, `core/outbox_writer.py`
remain engine-agnostic.
### `terraform apply` (dev only)
v1.2 lifts the engine execution from `plan` to `apply` for the `dev`
environment only. Dev is autonomous per §10 (confidence ≥ 0.50, no HITL).
`apply` for qa/prod/dr remains HITL-gated and out of scope for v1.2. The
apply result (resources created, plan diff) is captured in the evidence
stream as a `terraform.apply` event.
### Out of scope for v1.2 (deferred to v1.3+)
| Feature | Reason |
|---------|--------|
| Real OIDC federation | go-gitea/gitea#36988 still open. v1.2 extends D-039 waiver (D-047); real OIDC is v1.3+. |
| Full HITL matrix wiring (qa/prod/dr) | v1.2 is dev-only autonomous `apply`; HITL wiring is v1.3. |
| Kyverno + OPA policy engines | v1.2 keeps Checkov only; Kyverno/OPA are v1.3. |
| MCP skill catalog + real L3B agent | v1.2 keeps the L3B stub; the 5-skill catalog is v1.3. |
| Audit ledger build-out (S3 Object Lock + JWS + async worker + DLQ + daily checkpoints) | v1.2 keeps the v1.1 outbox; the regulatory ledger is v1.3. |
| Multi-region state / outbox | Single-region in v1 (§9, §12.3); multi-region is v1.3+. |
| Prod/dr environments | v1.2 is dev-only; prod/dr are v1.3. |
| GitOps reconciler (ArgoCD/Flux) | v1.3+. |
## Build order (v1.2)
1. Phase 11 — re-eval #36988 + NFR audit + simplification findings + README rewrite.
2. Phase 12 — NFR harden + simplify (idempotent bootstrap, one `run_platform.sh`, IAM audit, redactions).
3. Phase 13 — six ECS L1s + adapter `TYPE_MAP` expansion.
4. Phase 14 — `l2-microservice` + contract schema extension.
5. Phase 15 — consumer repo + `terraform apply` (dev) → live ECS service.
6. Phase 16 — capstone e2e: consumer commit → live HTTP 200 → evidence → timeline.
7. COMPLETE gate — review → ship `v1.3.0` → audit.
## v1.8 Architecture Addendum
> Milestone v1.8 (complete, tag `v1.8.0`). Adds encryption-by-default,
> deletion-protection-by-default, uptime monitoring, decommission alias,
> engineering standards, and path documentation.
### New Primitives
- **`kms-key`** (`aws:kms:key`) — Per-stack customer-managed KMS key with
`enable_key_rotation = true`. One key per L2 deployment (no shared keys).
Wired into both L2 compositions as a child, with its `kms_key_arn` output
connected to all children's `kms_key_arn` input. Adapter emits
`aws_kms_key` + `enable_key_rotation`.
- **`uptime`** (`aws:ecs:uptime-service`) — Uptime-kuma on ECS Fargate with
a feature flag (`feature_flag_enabled`), monitored endpoints (HTTP/DNS/TCP),
alert channels (Teams/email/SMS/GitHub issues). Deployed by default after
any L2 module with a separate terraform state. When the feature flag is
false, the adapter emits no resources.
### Encryption by Default
All 12 L1 primitives have `encryption_enabled` NFR (default true). Primitives
with at-rest data (s3, rds, ecr, ecs-service, ecs-cluster) have an optional
`kms_key_arn` input. The adapter emits encryption blocks (SSE-KMS for S3,
storage_encrypted for RDS, encryption_configuration for ECR) referencing the
per-stack CMK when provided. Managed KMS fallback with stderr warning for
standalone L1 deployments.
### Deletion Protection by Default
All 12 L1 primitives have `deletion_protection` NFR (default true). The
adapter emits `lifecycle { prevent_destroy = true }` when true. L2 modules
expose a `features.deletion_protection` flag (default true) propagated to
all children via the resolver. Setting `inputs.deletion_protection: false`
in the contract disables it for the whole stack.
### Decommission Alias
A `mode: decommission` on the deploy pipeline implements a 2-step destroy:
1. Disable deletion protection (resolve with `deletion_protection: false`,
terraform plan/apply, HITL SRE gate via GitHub environment).
2. Zero counts + destroy (`decommission_transform` zeroes all scalable counts,
terraform plan/apply, second HITL SRE gate).
CMDB validation via DynamoDB `acdl-change-requests` table. The Lambda
`validate_change_request` action queries the table and asserts
`status == "approved"` + `consumerRepo` match.
### Adapter Expansion
TYPE_MAP grew from 16 to 19 entries (+ `aws:kms:key`, `aws:kms:alias`,
`aws:ecs:uptime-service`). Specialized emission branches added for KMS key
rotation, S3 SSE-KMS configuration, uptime ECS Fargate task, and
`prevent_destroy` lifecycle on all resources.
### Pipeline Stages
The deploy pipeline grew from 8 to 9 stages (+ `deploy-uptime` after
`publish-outputs`). The `deploy-uptime` stage constructs a synthetic uptime
contract from the L2 stack outputs, resolves + adapts it to a separate
terraform state directory, and publishes the uptime URL via PR comment.
### Forge-Agnostic API URLs
The platform Lambda (`contract_ingestor.py`) reads `GITHUB_API_BASE` env
for forge-agnostic API URLs. GitHub uses `/search/issues`; Gitea uses
`/repos/{owner}/{repo}/issues`. Detection via `/api/v1` in the base URL.
## v1.9 Addendum (2026-07-23)
### New Components
- **`core/contract_resolver.py` interpolation** (D-081): the resolver
now expands `${env.<field>}` + `${contract.<field>}` tokens
post-schema-validation, pre-IR-resolution. The env context is the
loaded environment onboarding JSON (`core/environments/<name>.json`,
schema `schemas/environment.schema.json`). The resolver's
`child_input_map` routes L2 wires to the sub-resource that declares the
input (P1-1 — `desired_count``aws:ecs:service`, `family`
`aws:ecs:task_definition`).
- **`core/environment_check.py` `load()`** (REQ-104): loads + returns the
parsed environment JSON; emits a stderr warning for placeholder
`account_id` when env != dev.
- **`core/hitl_gates.py`** (REQ-108, D-084): the HITL pre-execution
attestation gate. Records the approver identity to the DynamoDB outbox
(`approver_qa`/`approver_prod`/`approver_dr`), runs the separation-of-
duties check on prod, invokes the attestation matrix, returns
`(ok, reason)`. Dev skips (autonomous). `run_platform.sh` calls
`attest` before apply for qa/prod/dr.
- **`core/attestation_matrix.py`** (REQ-109, D-084): the 8-concern
attestation matrix from `hitl_matrix_design.md` §10.4. Offline-testable
concerns (contract NFRs, schema validity, policy pass) run for real;
operator-supplied concerns accept signed evidence artifacts validated
for freshness + schema. Signature verification skips when
`ACDL_ATTESTATION_SIGNING_KEY_ID` is unset (D-089).
- **`core/separation_of_duties.py` `route_halt_artifact`** (REQ-107):
real SNS publish (`acdl-sod-halt` topic, ARN from
`ACDL_SOD_HALT_TOPIC_ARN`) + outbox fallback
(`SEPARATION_OF_DUTIES_VIOLATION` event). The SNS topic is defined in
`terraform/platform/main.tf`.
- **`adapters/wiz/wiz_adapter.py` `WizClient`** (REQ-110): real GraphQL
API client (`<WIZ_API_URL>/graphql`, Bearer auth, pagination via
`pageInfo.hasNextPage`). `fetch_and_adapt` translates issues →
`PolicyCheckResult`. Graceful degrade when unconfigured.
- **`adapters/kyverno/kyverno_adapter.py`** (REQ-111): fleshed-out
`PolicyReport``PolicyCheckResult` mapping (pass/fail/skip/warn +
severity + skip-with-reason + resource construction). Inactive-for-TF
guard preserved.
### Per-Environment Promotion (D-082)
The deploy workflow (`.github/workflows/deploy.yml` +
`.gitea/workflows/deploy.yml`, byte-identical) declares an `environment`
`workflow_call` input. When non-empty, `run_platform.sh --environment
<name>` overrides the contract's `environment` field before schema
validation (D-088). One CI job per environment; promotion = running the
matching job, no `environment:` field editing. Per-env contract files
(`contracts/<module>.<env>.yaml`) use interpolation for env-specific
values.
### Adapter Parameterization (P1-1, D-085)
The adapter (`adapters/terraform/adapter.py`) reads ECS/ALB/VPC defaults
from L1 `interface.json` inputs (`desired_count`, `launch_type`,
`family`, `target_type`, `load_balancer_type`, `name`). The adapter is a
thin translator; the `child_input_map` routes wires to the declaring
sub-resource.
### Deferred (D-083)
S3 Object Lock + JWS detached signatures + async worker + DLQ + daily
checkpoints (audit ledger build-out) — deferred to a future milestone.
The hash-chain + DynamoDB-outbox path remains the v1.9 production audit
record.
## v1.10 Addendum — Regression VERIFY + Local Emulators + Capability Re-Verification
### Regression-Class VERIFY (D-091, `core/regression_verify.py`)
The standard VERIFY stage was diff-scoped (it checked the phase diff
only, never re-ran underlying capability). This let 8 NFR-patch phases
(v1.9.1v1.9.8) pass while the platform decayed. The regression-class
VERIFY (`core/regression_verify.py`) re-runs capability checks against
the current codebase and tags each Verified/Decayed/Broken. It fails
closed on any non-Verified capability, blocking milestone completion.
The registry (`CAPABILITY_REGISTRY`) holds 16 capability checks
(CAP-001..CAP-016): 12 local-tier + 4 live-AWS. Adding a capability is
a single function + one registry entry. The gate runs via
`scripts/run_regression.sh` and writes `.ciagent/REGRESSION_REPORT.md`
+ `.json`.
### Local Emulating Adapters (D-092, `core/local_emulators.py`)
Four local adapters let the platform run the full headline E2E without
cloud credentials:
- `FlatFileOutbox` — flat-file DynamoDB outbox emulator (hash-chained
JSONL; resumable across instances; chain verification).
- `LocalEcsEmulator` — local ECS Fargate HTTP 200 emulator (binds port
0 on 127.0.0.1; daemon thread; clean destroy).
- `LocalS3StateBackend` — rewrites the terraform S3 backend to a local
backend (per-stack tfstate in a temp folder).
- `LocalLambdaStub` — invokes the contract_ingestor handler in-process
(patches `_get_dynamodb`/`_get_secrets_client`/`urllib.urlopen`;
DynamoDB writes redirected to the FlatFileOutbox).
`run_local_e2e()` runs the full pipeline: contract → resolver → adapter
→ local S3 backend → local ECS (HTTP 200) → flat-file outbox (chain
verified) → local Lambda (200). Gated on `ACDL_LOCAL_TIER=1`.
### Capability Re-Verification Sweep (D-093)
`.ciagent/CAPABILITY_INVENTORY.md` enumerates 16 auto-verified
capabilities + 6 IAM-gated escalated resources. The sweep found and
fixed 7 adapter defects in `adapters/terraform/adapter.py` (duplicate
outputs, duplicate args, missing required args, deprecated AWS provider
v5 arg names). The headline E2E now passes at both tiers: local
emulator + live-AWS terraform init/validate/plan.
### Adapter Defect Fixes (P54)
7 defects fixed in `adapters/terraform/adapter.py`:
1. Duplicate output definitions (per-resource + stack-level both emitted).
2. Duplicate `desired_count`/`launch_type` on ECS service.
3. Duplicate `target_type`/`family`/`load_balancer_type`.
4. Missing `assume_role_policy`/`role_name` on IAM role (L2 composition gap).
5. Missing `cidr_block`/`vpc_id`/`name` defaults on VPC/subnet/route_table/
ECS cluster/ECR repository.
6. ECR `kms_key_arn` unsupported arg → `encryption_configuration` block.
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`).
**NOVA_LIFECYCLE_MODE (v1.12, REQ-134; renamed ACDL→NOVA in v1.15 P2).** The lifecycle pipeline defaults
to plan-only (fast, no AWS mutation, no cost). A CI variable
`NOVA_LIFECYCLE_MODE` (default `plan`) overrides to `full` for the real
apply→modify→destroy. (P2P4 dual-read fallback to `ACDL_LIFECYCLE_MODE`;
fallback removed in P5 per the v1.15 addendum.)
## 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.
---
## v1.16 Addendum — Nova Simplification (NFR, 2026-07-30)
The v1.16 NFR milestone added 6 new code components + 1 new Terraform
module + 1 new schema, all documented here for the architecture record.
### New components
| Component | Path | Purpose |
|-----------|------|---------|
| Onboarding request handler | `core/onboarding.py` | `generate_env_file(request, template_env)` — produces a `<env>.json` from a consumer onboarding request (P19, REQ-183). CLI entry point for self-service env-file generation. |
| Decommission transform | `core/decommission_transform.py` | `decommission_transform(stack)` — zero counts + disable deletion protection (REQ-92). Extracted from contract_resolver (P12, REQ-176). |
| Contract resolver CLI | `core/contract_resolver_cli.py` | `main()` CLI entry point — resolves a contract YAML to a Target Stack JSON. Extracted from contract_resolver (P12, REQ-176). |
| Regression verify CLI | `core/regression_verify_cli.py` | `main()` CLI entry point — runs the regression gate + writes the report. Extracted from regression_verify (P13, REQ-177). |
| Workflow sync generator | `scripts/sync_workflows.py` | `--check`/`--write` — generates the 3 byte-identical Gitea+GitHub workflow pairs from `workflows-src/` (P8, REQ-172). |
| Onboarding Terraform | `terraform/onboarding/` | `aws_iam_role.consumer_deploy` + `aws_iam_role_policy.consumer_invoke` (ABAC `nova:owner` tag). Offline-proven only (P20, REQ-184, D-114). |
### Modified components
| Component | Change | Phase |
|-----------|--------|-------|
| `core/contract_resolver.py` | `_load_env` delegates to `environment_check.load()` (dedup); `is_l2` uses registry `kind` field; `_load_schema` caches schemas; `decommission_transform` + CLI re-export shim (P12). | P7, P12, P14 |
| `core/regression_verify.py` | Dedup helpers (`_check_resolver`, `_check_live_terraform_plan`, `_assert_contracts_resolve`); CAP-013..016 `Skipped` on post-teardown (G-111); `passed` accepts Skipped; CLI re-export shim (P13). | P5, P9, P13 |
| `core/lambda/contract_ingestor.py` | Fail closed on missing IAM identity (P10); env enum from `core/environments/` (P10); payload size cap + schema validation (P11); `onboard_consumer` action (P18); `[NOVA-ALERT]` rebrand (P2). | P2, P10, P11, P18 |
| `core/output_publisher.py` | `SAFE_OUTPUT_NAMES` schema-driven from `interface.json`; narrowed excepts; `urllib.error` import (P4, P14). | P4, P14 |
| `core/environment_check.py` | Onboarding message rebranded Nova + self-service request path (P2, P19). | P2, P19 |
| `core/local_emulators.py` | `LocalLambdaStub` sets `NOVA_LAMBDA_LOCAL_BYPASS`; stale dual-read comments + `acdl_*` prefixes removed (P3, P10). | P3, P10 |
| `scripts/run_platform.sh` | `--help` flag; `run_hitl_gate()` fn; `NOVA_CONTRACT_ID`/`NOVA_WORK_DIR` config; decommission + uptime blocks extracted to sourced helpers (P6, P9, P15). | P6, P9, P15 |
| `adapters/terraform/adapter.py` | State bucket `nova-tfstate-*` (P1); module docstring Nova (P2). | P1, P2 |
| `adapters/kyverno/policies/require-resource-labels.yml` | `nova:*` labels (not `acdl:*`) (P1). | P1 |
| `modules/registry.json` | `kind` field (`l1`/`l2`) on all 14 entries (P7). | P7 |
### New schema
- `schemas/onboarding.schema.json` — the self-service onboarding request
(consumerRepo, requestedEnvironment, ownerId, billingTag). P18, REQ-182.
### Onboarding request-path architecture (D-113)
The no-humans onboarding flow is a 3-step request path (real AWS
provisioning deferred):
```
Consumer → POST Lambda (onboard_consumer) → pending CMDB row (P18)
→ core/onboarding.py → <env>.json binding file (P19)
→ terraform/onboarding/ → cross-account role + ABAC tag (P20, offline)
```
The Lambda Function URL (IAM auth) + `consumer_invoke_policy.json` (ABAC
`nova:owner`) are the transport; the request is accepted + a binding
generated + the role Terraform proven offline. No AWS resources are
created by the request path (D-113/D-114).
### Regression gate (G-111 binding)
The regression gate (D-091) now treats `Skipped` as acceptable for the
post-v1.11-teardown steady state (D-096): CAP-013..016 (live-AWS tier)
return `Skipped` when the resources are absent (`NoSuchBucket`/
`ResourceNotFoundException`). `RegressionReport.passed` is
`all(r.status in ("Verified", "Skipped"))`. The gate passes at 18
Verified + 4 Skipped (0 Decayed/Broken).
## v1.17 Addendum — Strategic Direction, Leadership Metrics & Unified Story (2026-08-04)
The v1.17 milestone adds a telemetry/observability layer, a Decision
Ledger, a metrics export pipeline, a unified narrative deck, and a
durable strategic-direction artifact. This addendum documents the
architecture; the full research findings are in RESEARCH.md §v1.17.
### New components
| Component | Path | Purpose |
|-----------|------|---------|
| Event envelope | `core/metrics/event_envelope.py` | CloudEvents 1.0 envelope + `platform.*` semantic conventions (P1, REQ-187) |
| Per-run manifest writer | `core/metrics/run_manifest.py` | Emits `nova.run.started/completed/failed` events + writes `metrics/runs/<run_id>.json` (P1, REQ-187) |
| Decision Ledger (SQLite) | `core/metrics/decision_ledger.py` | Extends `outbox_writer.py` → SQLite append-only hash-chain table; `ai.decision.made` + `attestation.recorded` events + outcome backfill (P1, REQ-188, D-121) |
| Infracost post-processor | `core/metrics/infracost_adapter.py` | Runs Infracost on plan JSON; emits `nova.cost.estimated{delta_usd}` (P1, REQ-187, D-120) |
| Metrics collector | `core/metrics/collector.py` | Reads all grounded signals (files + events) → SQLite cold store at `metrics/nova_metrics.db` (P2, REQ-189) |
| PowerBI export | `core/metrics/powerbi_export.py` | Emits CSV/JSON views to `metrics/powerbi/` (fact + dim + 8 deferred placeholder views) (P3, REQ-190) |
| Metrics schemas | `schemas/metrics_*.schema.json` | Schemas for all event types + fact/dim tables (P1P2, REQ-187/189) |
| Metrics catalog | `docs/METRICS.md` + `docs/metrics/<kpi>.md` | Canonical catalog + per-KPI definition-of-success docs (P4, REQ-195, D-127) |
| Unified narrative deck | `docs/presentations/nova-no-humans-platform.md` | Merged deck: Problem→Vision→How→Proof→Roadmap; x3 arc at deck+slide level (P5, REQ-196/197, D-130) |
| Strategic direction | `.ciagent/NORTH_STAR.md` | PO-authored durable vision/objectives/anti-goals/targets; read by CIAgent in every future `/ci-run` (P0, REQ-185/186) |
### Modified components
| Component | Change | Phase |
|-----------|--------|-------|
| `core/outbox_writer.py` | Extended to emit to SQLite append-only hash-chain table (Decision Ledger); `ai.decision.made` + `attestation.recorded` events added (P1, D-121) | P1 |
| `scripts/run_platform.sh` | Per-run manifest writer invoked; `$WORK/*.json` persisted to `metrics/runs/`; Infracost post-processor invoked after plan (P1) | P1 |
| `core/hitl_gates.py` | Emits `attestation.recorded` event to Decision Ledger on qa/prod/dr gate (P1, D-132) | P1 |
| `core/confidence_signal.py` | Emits `nova.confidence.computed` + `nova.ai.decision.made` events (P1, D-122) | P1 |
| `adapters/terraform/policy/checkov_adapter.py` | Emits `nova.policy.evaluated` event (P1) | P1 |
| `core/regression_verify.py` | Emits `nova.capability.verified` event; CAP-023 (metrics collector) + CAP-024 (deck structure) added (P1, P6) | P1, P6 |
| `pyproject.toml` | `addopts` gains `--junitxml=metrics/test-results.xml` + `--json-report` (P1, D-120) | P1 |
| `docs/presentations/` | Two old decks retired (deleted); unified deck added (P5, D-130) | P5 |
### Telemetry/observability layer architecture (D-120)
```
┌─────────────────────────────────────────────────────────────────────┐
│ Nova platform components (existing) │
│ run_platform.sh · confidence_signal · checkov_adapter · │
│ hitl_gates · regression_verify · outbox_writer · contract_ingestor │
└──────────────────────┬──────────────────────────────────────────────┘
│ CloudEvents 1.0 envelope (new emitters, P1)
┌─────────────────────────────────────────────────────────────────────┐
│ metrics/events.jsonl (append-only CloudEvents log) │
│ metrics/runs/<run_id>.json (per-run manifests) │
│ metrics/decision_ledger.db (SQLite hash-chain, D-121) │
│ metrics/test-results.xml (junit, P1) │
└──────────────────────┬──────────────────────────────────────────────┘
│ collector reads (P2)
┌─────────────────────────────────────────────────────────────────────┐
│ metrics/nova_metrics.db (SQLite cold store, D-126) │
│ fact_run · fact_capability · fact_policy_check · fact_confidence │
│ fact_test · fact_decision · fact_cost_estimate │
│ dim_capability · dim_milestone │
│ + 8 empty placeholder views (deferred metrics) │
└──────────────────────┬──────────────────────────────────────────────┘
│ powerbi_export (P3)
┌─────────────────────────────────────────────────────────────────────┐
│ metrics/powerbi/ (CSV/JSON views, folder connector, D-129) │
│ → PowerBI dashboards (external) │
└─────────────────────────────────────────────────────────────────────┘
```
**Hot path: deferred (D-126).** No live ops dashboard; SQLite is
cold-only (batch/historical). The hot path activates when live AWS is
re-provisioned (D-096 lift).
### NORTH_STAR integration point (REQ-186)
`.ciagent/NORTH_STAR.md` is read by CIAgent in context-loading for all
future milestones. The integration mechanism (to be finalized in P4):
a reference from `PROJECT.md` + `ARCHITECTURE.md` (this section) + a
config entry in `config.json` (`strategic_direction_file:
".ciagent/NORTH_STAR.md"`) that the run workflow reads at SPECIFY. This
ensures the strategic direction survives across milestones without
being overwritten by status updates.
### §12.7 — Policy Engine Registry (v1.25, REQ-291)
The policy-engine abstraction is first-class: a swappable `PolicyEngine`
protocol so the engine may change without touching the confidence
signal, the pipeline, or the `PolicyCheckResult` schema. This is the
**swap boundary** that keeps the platform's compliance posture
replaceable (Strategic Objective #2 — provable trust via a replaceable
substrate, not a vendor lock-in).
```
contract.yml ─┐ ┌─→ list[PolicyCheckResult] ─┐
stack IR ─────┼─→ PolicyEngine.evaluate ├─→ list[PolicyCheckResult] ─┼─→ confidence_signal
plan JSON ────┤ (protocol) └─→ list[PolicyCheckResult] ─┘ (engine-agnostic,
PCR list ─────┘ unchanged)
┌─ KyvernoJsonEngine (shells to `kj scan`; engine: "kyverno")
└─ OpaEngine (future — same protocol; engine: "opa")
checkov/wiz ──→ raw findings ──→ (merged PCR list is the meta-policy payload)
```
**The protocol (`core/policy_engine.py`):**
```python
class PolicyEngine(Protocol):
@property
def name(self) -> str: ...
def is_configured(self) -> bool: ...
def evaluate(self, payload, policy_dir: Path, contract_id: str) -> list[dict]: ...
```
**The registry** reads `config.json.policy.engine` (default
`"kyverno-json"`) and returns the active engine. A `NullEngine` is the
fallback when the `policy` key is absent (emits `SKIPPED` PCRs —
backward compatibility for tests that don't set the key). The
confidence signal is **untouched** — it already consumes
`list[PolicyCheckResult]` engine-agnostically (§12.6). v1.25 only
changes *who produces* the PCR list, not *what* the list is.
**Engine enum reuse (D-116):** kyverno-json PCR records carry
`engine: "kyverno"` (no new enum value). The `engine` field records the
policy-engine *family*, not the specific binary. The K8s Kyverno adapter
and the kyverno-json engine are distinguished by `ruleId` prefix
(`KYVERNO_` vs `KJ_`) and `evidence` payload shape (`namespace`/`kind`
vs `assertion`/`jmespath`).
**Defense-in-depth (D-119):** the declarative meta-policy
`block-on-any-critical` (asserts no PCR has `severity: critical` +
`result: fail`) is the *source of truth* for "critical = block". The
`confidence_signal.py` `PENALTY["critical"]: None` hard-override stays
as the *imperative* safety net — the meta-policy runs *before* the
confidence signal (produces PCRs that flow in), the hard-override runs
*inside* it (the last gate). Removing the hard-override would make the
"critical = block" guarantee depend on a single policy file — a
regression in provable trust.
**Graceful degradation (D-120):** `KyvernoJsonEngine.is_configured()`
returns false when `which kj` is absent → `evaluate()` returns a single
`SKIPPED` PCR (`ruleId: "KJ_ENGINE_NOT_CONFIGURED"`). The platform
functions without the binary (the "platform functions without AI /
deterministic scripts" tenet holds — kyverno-json is deterministic, not
AI; the `is_configured()` guard ensures the platform runs even when the
binary is not installed).
-553
View File
@@ -1,553 +0,0 @@
# Nova v1.9 — Audit Report
> Audit date: 2026-07-23. Auditor: ci-debugger. Milestone: v1.9. Result: PASS.
## Step 1: Reconstruction Test
- 16 v1.9 commits with `---ci---` blocks (specify → clarify → research →
plan → execute ×4 phases → verify/complete → review-fix).
- Reconstructed state: milestone v1.9, phase 43, status complete.
- Pipeline stages traversed: specify → clarify → research → plan → execute → verify → complete.
- Decisions D-080..D-089 all present in git log + `.ciagent/` files.
- config.json (v1.9 complete), PROJECT.md (v1.9 complete), REQUIREMENTS.md
(v1.9 complete, 12 reqs), ROADMAP.md (v1.9 complete, phases 3943),
REVIEW.md (READY TO SHIP), PERSONAS.md (v1.9), VERIFY.md, AUDIT.md.
**PASS.**
## Step 2: File Discipline
- `.ciagent/config.json`: valid JSON; mode, projects[] present. **PASS.**
- `.ciagent/PROJECT.md`: Vision/Core Value (≡ "What This Is"), Key
Decisions (v1.9 D-080..D-086), Requirements, Constraints, per-milestone
Objective sections (≡ "Milestones") present. Section names follow the
v1.0 established conventions (not the generic audit template). **PASS.**
- `.ciagent/ROADMAP.md`: phases 3943 present; all marked complete.
**PASS.**
- `.ciagent/REQUIREMENTS.md`: v1.9 traceability table complete (12/12
REQ-100..111 marked `complete (v1.9.0)`). **PASS.**
- `.ciagent/ARCHITECTURE.md`: **fixed during audit** — v1.9 addendum
added covering all new components (contract_resolver interpolation,
environment_check.load, hitl_gates, attestation_matrix,
separation_of_duties.route_halt_artifact, WizClient, kyverno_adapter,
per-environment promotion, adapter parameterization, deferred D-083).
All 9 v1.9 code components now referenced. **PASS (after fix).**
## Step 3: Branch Hygiene
- Local: `main` only. Remote: `origin/main` only.
- No phase or milestone branches remain (all 5 v1.9 phase branches merged
+ pruned during the run/ship workflow).
- No orphan branches.
**PASS.**
## Step 4: Commit Discipline
- 16/16 v1.9 commits have `---ci---` blocks with project/phase/milestone/
status fields.
- No stale implementation decisions (D-081..D-085, D-087..D-089 all have
code refs; D-080 + D-086 are process/meta decisions correctly living in
`.ciagent/` files).
- No unresolved v1.9 escalations (the 3 `audit(...)` commits in history
are from prior milestones v1.0/v1.6/v1.7).
**PASS.**
## Issues fixed during audit
1. **ARCHITECTURE.md missing v1.9 addendum** — the architecture doc had
no coverage of the v1.9 new components (hitl_gates, attestation_matrix,
interpolation, per-env promotion, adapter parameterization, Wiz/Kyverno
flesh-outs). Fixed: added a v1.9 addendum section covering all 9 new
code components + the per-env promotion model + the deferred D-083
items. Verified all 9 components now referenced.
## Audit result: PASS
---
# ACDL v1.10 Phase 52 — Audit Addendum
> Audit date: 2026-07-27. Auditor: ci-debugger. Phase: 52 (pipeline
> regression-VERIFY fix). Result: PASS.
## Process defect recorded (D-091)
The prior VERIFY stage was diff-scoped: it checked the phase diff only
and never re-ran underlying platform capability. This structural defect
let 8 NFR-patch phases (v1.9.1→v1.9.8, deck rework) pass VERIFY while the
platform they described decayed underneath. The defect is recorded as
D-091 and remediated in Phase 52 by `core/regression_verify.py` +
`scripts/run_regression.sh`.
## Phase 52 audit
- **Reconstruction:** Phase 52 commits present with `---ci---` blocks
(plan + execute + verify). Decisions D-090..D-094 recorded in
PROJECT.md. Requirements REQ-112..REQ-115 recorded in REQUIREMENTS.md.
**PASS.**
- **File discipline:** `core/regression_verify.py`,
`scripts/run_regression.sh`, `tests/test_verify_regression_mode.py`
present. `.ciagent/PLAN.md`, `ROADMAP.md`, `PROJECT.md`,
`REQUIREMENTS.md`, `VERIFY.md` updated for v1.10. **PASS.**
- **Behavioral:** 502 fast tests pass (was 493; +9 new). 3 slow
integration tests pass. `run_regression.sh` runs and reports honestly.
**PASS.**
- **Commit discipline:** Phase 52 commits carry `---ci---` blocks with
project/phase/milestone/status. **PASS.**
## Note on prior "audit CLEAN" claims
The v1.1v1.9 "audit CLEAN" claims were point-in-time true (the
capabilities ran at the time of tagging). They do not assert current
reproducibility. The capability decay surfaced in the 2026-07-27
CLARIFY/RESEARCH stages is being re-verified in Phase 54 (D-093). The
v1.10 audit will re-assert current reproducibility after the sweep.
## Phase 52 audit result: PASS
---
# ACDL v1.10 — Milestone Audit
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10.
> Result: PASS.
## Step 1: Reconstruction Test
- 5 v1.10 commits with `---ci---` blocks (plan → P52 verify → P53 verify
→ P54 verify → P55 verify).
- Reconstructed state: milestone v1.10, phase 55, status verify.
- Pipeline stages traversed: plan → execute → verify (×4 phases).
- Decisions D-090..D-094 all present in git log + `.ciagent/` files.
- config.json (v1.10 complete), PROJECT.md (Capability Status section
+ decay disclosure), REQUIREMENTS.md (REQ-112..115 complete),
ROADMAP.md (v1.10 section, phases 5255 complete), REVIEW.md (READY
TO SHIP), VERIFY.md (Phase 55 PASS), AUDIT.md (this file),
CAPABILITY_INVENTORY.md (16 Verified + 6 escalated), REGRESSION_REPORT
(16/16 Verified).
**PASS.**
## Step 2: File Discipline
- `.ciagent/config.json`: valid JSON; mode, projects[] present; milestone
v1.10 complete. **PASS.**
- `.ciagent/PROJECT.md`: Capability Status section + decay disclosure +
D-090..D-094 decision rows present. **PASS.**
- `.ciagent/ROADMAP.md`: v1.10 section with phases 5255 all marked
complete; v1.9.8 annotated as last deck-polish before freeze. **PASS.**
- `.ciagent/REQUIREMENTS.md`: v1.10 traceability table complete (4/4
REQ-112..115 marked `complete (v1.9.9..v1.9.12)`). **PASS.**
- `.ciagent/CAPABILITY_INVENTORY.md`: 16 Verified + 6 IAM-gated
escalated, with evidence per capability. **PASS.**
- `.ciagent/REGRESSION_REPORT.md` + `.json`: 16/16 Verified, gate passes.
**PASS.**
- `.ciagent/REVIEW.md`: READY TO SHIP (0 P0, 0 P1, 1 P2 post-hoc).
**PASS.**
## Step 3: Branch Hygiene
- Local: `main` only. Remote: `origin/main` only.
- No phase or milestone branches remain (single-project mode, flat
`.ciagent/` paths, no phase branches per config.json
branching_strategy=phase but committed directly to main per the
project's established convention).
**PASS.**
## Step 4: Commit Discipline
- 5/5 v1.10 commits have `---ci---` blocks with project/phase/milestone/
status fields.
- Decisions D-090..D-094 all have code/doc refs.
- The regression `---ci---` blocks include `regression:` arrays with
per-capability status (Phases 52, 53, 54).
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
**PASS.**
## Audit result: PASS
The v1.10 milestone is complete. The pipeline regression gap (D-091)
is fixed; the platform is fully locally testable (D-092); every
advertised v1.1v1.8 capability is re-verified (D-093, 16/16 Verified);
the docs/decks match verified reality (D-094). 0 P0, 0 P1 from review;
1 P2 (post-hoc: expand regression registry to uptime-kuma + RDS stacks).
513 offline tests pass; the regression gate covers 16 capabilities
including 4 live-AWS checks. Ready to tag `v1.10.0`.
---
# ACDL v1.10 — Post-Ship Audit (ciagent-audit workflow)
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10
> (shipped, tag `v1.10.0`). Result: PASS (1 issue fixed during audit).
## Step 1: Reconstruction Test — PASS
Parsed all `---ci---` blocks from `v1.9.8..HEAD` (9 commits).
Reconstructed state:
- Phases: 52, 53, 54, 55 (+ boundary commits 0, 51)
- Milestone: v1.10
- Final status: complete
- Decisions: D-090..D-094
- Requirements: REQ-112..REQ-115
- Regression caps: CAP-001..CAP-016
Compared with `.ciagent/` files:
- config.json: milestone v1.10, status complete. **MATCH.**
- ROADMAP.md: phases 5255 present, all complete. **MATCH.**
- REQUIREMENTS.md: REQ-112..115 all complete. **MATCH.**
- PROJECT.md: D-090..D-094 decision rows present. **MATCH.**
- CAPABILITY_INVENTORY.md: CAP-001..CAP-016 all Verified. **MATCH.**
**Reconstruction: PASS** — state fully reconstructable from git log.
## Step 2: .ciagent/ File Discipline — PASS (1 issue fixed)
- `config.json`: valid JSON, required fields present. **PASS.**
- `PROJECT.md`: all required sections present (Vision, North Star,
Capability Status, Requirements, Key Decisions, Constraints,
Anti-Goals). **PASS.**
- `ROADMAP.md`: phases 5255 present, v1.10 marked complete. **PASS.**
- `REQUIREMENTS.md`: REQ-112..115 all complete in traceability table.
**PASS.**
- `ARCHITECTURE.md`: **FIXED DURING AUDIT** — had 0 references to
v1.10 components (regression_verify, local_emulators,
REGRESSION_REPORT, CAPABILITY_INVENTORY). Added a v1.10 addendum
section covering the regression-class VERIFY, local emulating
adapters, capability re-verification sweep, and the 7 adapter defect
fixes. Now references all v1.10 components. **PASS (after fix).**
## Step 3: Branch Hygiene — PASS
- Local: `main` only. Remote: `origin/main` only.
- No phase or milestone branches (flat workflow per project convention).
- No orphan branches.
**PASS.**
## Step 4: Commit Discipline — PASS
- 9/9 v1.10 commits have `---ci---` blocks with project/phase/milestone/
status fields.
- Decisions D-090..D-094: D-091/D-092/D-093 have code refs
(`core/regression_verify.py`); D-090/D-094 are process/meta decisions
with extensive `.ciagent/` doc refs (PLAN, ROADMAP, PROJECT,
CAPABILITY_INVENTORY, AUDIT, VERIFY). No stale decisions.
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
**PASS.**
## Issues fixed during audit
1. **ARCHITECTURE.md missing v1.10 addendum** — the architecture doc
had no coverage of the v1.10 new components (regression_verify,
local_emulators, capability inventory, adapter defect fixes). Fixed:
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.
---
## v1.15 Post-Milestone Audit (2026-07-30)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CIAgent ► AUDIT REPORT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Reconstruction: PASS — 27 commits since v1.14 base (66a3c69), 20 with
`---ci---` blocks (7 merge commits without blocks, per convention).
Reconstructed state: phase 5, milestone v1.15, complete, tag v1.15.4,
release 302, REQ-155..164 covered. Matches CHECKPOINT.json + REQUIREMENTS.md
+ ROADMAP.md.
.ciagent/ Files: 12 checked.
- config.json: valid JSON; active_milestone v1.15 consistent.
FIX applied: projects[0].name "Agentic Cloud Delivery Platform" →
"Nova — The New Dawn of DevSecOps" (rebrand completeness).
- PROJECT.md: FIX applied — header "# ACDL — Agentic Cloud Delivery
Platform" → "# Nova — The New Dawn of DevSecOps" + rebrand-in-progress
banner → rebrand-complete banner.
- REQUIREMENTS.md: FIX applied — header "# ACDL — Requirements" →
"# Nova — Requirements"; traceability 10/10 REQ-155..164 complete.
- ROADMAP.md: FIX applied — header "# ACDL — Roadmap" → "# Nova —
Roadmap"; v1.15 phases P1-P5 all complete with tags.
- ARCHITECTURE.md: PASS (header already Nova per P5 doc-verifier);
v1.15 addendum present; naming table matches codebase.
- PERSONAS.md: PASS (v1.15 addendum present).
- GRILL.md: PASS (v1.15 section present; 0 open escalations).
- RESEARCH.md: FIX applied — header "# ACDL — v1.11 RESTART Research
Findings" → "# Nova — ...".
- PLAN.md: PASS (v1.15 plan present, frontmatter milestone v1.15).
- AUDIT.md: FIX applied — header "# ACDL v1.9 — Audit Report" →
"# Nova v1.9 — Audit Report".
- REVIEW.md: FIX applied — header "# ACDL v1.11 — Multi-Persona Code
Review" → "# Nova v1.11 — ...".
- COST.md: FIX applied — header "# ACDL AWS Cost Report" →
"# Nova AWS Cost Report".
- IAM_POLICY.md: FIX applied — header "# ACDL — IAM Policy Baseline"
→ "# Nova — IAM Policy Baseline".
- CAPABILITY_INVENTORY.md: FIX applied — header "# ACDL Capability
Inventory" → "# Nova Capability Inventory".
Branches: 6 v1.15 phase branches (all merged to main), 1 milestone branch
(merged to main). No orphans. PASS.
Commits: 27 total, 39 `---ci---` blocks, 7 merge commits (no blocks, per
convention), 0 non-merge commits without `---ci---`, 0 unresolved
escalations. PASS.
Audit Checks (runAuditChecks):
1. HEAD on main (milestone complete) — PASS
2. CHECKPOINT.json exists — PASS
3. CHECKPOINT consistent with latest `---ci---` (phase 5, v1.15,
complete, v1.15.4) — PASS
4. Report template exists — PASS
5. No pending escalations (grill: 0 open; log: none) — PASS
6. Milestone version in config (v1.15) consistent with checkpoint — PASS
Issues fixed (audit auto-fix):
- 9 `.ciagent/*.md` file headers still said "ACDL" after the v1.15
rebrand (P1 lead-developer left `.ciagent/` to P0; P0 added the
rebrand-in-progress banner to PROJECT.md only; the other file
headers were never rebranded). All 9 headers now say "Nova".
- config.json `projects[0].name` still said "Agentic Cloud Delivery
Platform" (display label, not the repo slug). Now "Nova — The New
Dawn of DevSecOps". The `slug` ("acdl") + `release.gitea.repo`
("acdl") stay unchanged per D-105 (real repo name).
Notes:
- Historical narrative sections in ARCHITECTURE.md/COST.md/GRILL.md/
AUDIT.md/REVIEW.md (v1.1v1.14 addenda) still mention `acdl-*`
resource names + `ACDL_*` env vars — these describe each milestone
as-shipped and are acceptable as historical record per project
convention. The active v1.15 sections use Nova.
- The 7 merge commits without `---ci---` blocks is the established
convention (merge summary IS the record; the merged phase commits
carry the blocks). Matches v1.14 precedent.
Verdict: PASS — Project state is fully reconstructable from git log.
All 6 audit checks pass. 10 auto-fixed issues (9 stale headers + 1 config
name) were rebrand-completeness gaps, not structural defects.
---ci---
project: acdl
phase: 5
milestone: v1.15
status: complete
phase_role: final
audit: pass
---/ci---
---
## v1.16 Post-Milestone Audit (2026-07-30)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CIAgent ► AUDIT REPORT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Reconstruction: PASS** — 4 commits since v1.15.4 base (787a649), 3 with
`---ci---` blocks (1 merge commit without blocks, per convention — the
squash-merge summary IS the record). Reconstructed state: phase 21,
milestone v1.16, complete, tag v1.15.26, release 370, REQ-165..184
covered. Matches CHECKPOINT.json + REQUIREMENTS.md + ROADMAP.md.
**.ciagent/ Files: 15 checked.**
- config.json: valid JSON; active_milestone v1.16, active_project acdl,
projects[] length 1. **PASS.**
- PROJECT.md: v1.16 Objective (complete) + Key Decisions D-113..D-119
present. 44 section headers. **PASS.**
- ROADMAP.md: v1.16 section with P0P21, all complete; tags v1.15.5..26.
**PASS.**
- REQUIREMENTS.md: v1.16 traceability 20/20 REQ-165..184 complete.
**PASS.**
- ARCHITECTURE.md: **FIXED DURING AUDIT** — 0 v1.16 references → v1.16
addendum added (6 new components, 10 modified components, new schema,
onboarding request-path architecture, regression gate G-111). **PASS
(after fix).**
- CHECKPOINT.json: valid JSON; phase=21, stage=complete,
milestone_complete=true, tag=v1.15.26, release_id=370. **PASS.**
- PERSONAS.md: v1.16 addendum present (8 references). **PASS.**
- GRILL.md: v1.16 grill present (G-111..G-113, E-002). **PASS.**
- RESEARCH.md: v1.16 addendum present (R1..R6). **PASS.**
- PLAN.md: v1.16 20-phase + final plan present. **PASS.**
- REVIEW.md: **FIXED DURING AUDIT** — 0 v1.16 references → reconstructed
with v1.16 P21 final review content (0 P0, 0 P1, 2 P2 post-hoc). **PASS
(after fix).**
- AUDIT.md: this file (v1.16 audit recorded). **PASS.**
- CAPABILITY_INVENTORY.md: not modified in v1.16 (no capability changes).
**PASS.**
- COST.md: not modified in v1.16 (no cost changes — offline-only). **PASS.**
- IAM_POLICY.md: not modified in v1.16 (no IAM policy changes —
onboarding Terraform is offline-proven, not applied). **PASS.**
**Branches: 0 v1.16 phase branches, 0 v1.16 milestone branches** (all
cleaned up post-merge). Prior-milestone branches (v1.14 P1-P20, v1.11
P56-P59) remain locally — historical, harmless, documented in ROADMAP.
No v1.16 orphans. **PASS.**
**Commits: 4 total in v1.16 range, 3 with `---ci---` blocks, 1 merge
commit without (per convention), 0 unresolved escalations.** The
squash-merge strategy collapsed 20 phase branches + the milestone into
the merge commit `f83b974`; the phase-level `---ci---` blocks lived in
the (now-deleted) phase-branch commits. The milestone-level `---ci---`
block (commit `58fa7a6`) records the final state. **PASS.**
**Audit Checks (runAuditChecks):**
1. HEAD on main (milestone complete) — **PASS**
2. CHECKPOINT.json exists — **PASS**
3. CHECKPOINT consistent with latest `---ci---` (phase 21, v1.16,
complete, v1.15.26, release 370) — **PASS**
4. Report template exists (`opencode/ci/references/report-template.md`)
— **PASS**
5. No pending escalations (grill E-002 auto-resolved at P21; 0
unresolved) — **PASS**
6. Milestone version in config (v1.16) consistent with checkpoint —
**PASS**
**Issues fixed during audit:**
- ARCHITECTURE.md missing v1.16 addendum (0 references → added: 6 new
components, 10 modified, new schema, onboarding architecture, G-111
gate).
- REVIEW.md held v1.11 content → reconstructed with v1.16 P21 final
review (0 P0, 0 P1, 2 P2 post-hoc accepted).
**Verdict: PASS** — Project state is fully reconstructable from git log.
All 6 audit checks pass. 2 auto-fixed issues (ARCHITECTURE.md addendum +
REVIEW.md reconstruction) were file-discipline gaps, not structural
defects. 20/20 requirements complete; regression gate 18V+4S; milestone
merged to main; tag v1.15.26; release 370.
---ci---
project: acdl
phase: 21
milestone: v1.16
status: complete
phase_role: final
audit: pass
---/ci---
-66
View File
@@ -1,66 +0,0 @@
# Nova — The Autonomous Cloud Delivery Platform: Autonomy Defensibility Brief
> Strategic direction, leadership metrics & unified story
> Last refined: v1.21 — reframe from "no-humans" to "autonomous operations"
## The thesis
Nova is the autonomous infrastructure layer that lets product teams
ship without engaging an operator, and lets executives trust the
platform not because it never fails but because every decision is
captured, scored, and accountable.
**Autonomy in operations; human at stage gates.** Normal operations —
provisioning, healing, remediation — run without an operator in the
loop. Human attestation remains required at stage gates: QA signs off
for production, SRE greenlights based on operational readiness. The
absence of an operator in the loop is never the absence of a record.
## Grounded proof (measurable today)
| Proof | Source | Status |
|-------|--------|--------|
| Capabilities verified, none broken (live-AWS caps honestly skipped, resources torn down to zero-cost steady state) | regression report | grounded |
| Decision Ledger captures 100% of automated decisions with outcome backfill | decision ledger store | grounded |
| Attestation coverage: 100% of prod/dr promotions attested by a human | attestation gates + outbox | grounded |
| Confidence-gated policy engine (deterministic, not an LLM) — weighted inputs, band outcome | confidence signal | grounded |
| Attestation matrix with separation-of-duties on prod | attestation matrix + separation-of-duties | grounded |
| Pre-apply cost estimates (offline) | cost adapter | grounded |
| Test suite passes | test results | grounded |
## Deferred proof (measurable when blocking work lifts)
| Proof | Blocking work | Unblock requirement |
|-------|----------------|---------------------|
| Touchless resolution rate across production estates | 0 consumers today | Pilot estate activation |
| Live infrastructure health (ECS, ALB, RPS) | Live AWS torn down | Live AWS re-provisioning |
| Onboarding funnel: requested → granted | Auto-grant not built | Auto-grant implementation |
| Drift auto-reversal rate | No drift scheduler | Drift detection scheduler |
| Predictive vs reactive ratio | No emitter | ML anomaly-forecasting service |
| Tamper-evident ledger checkpoints (S3 Object Lock + JWS) | Audit ledger build-out | Audit ledger build-out |
## Anti-claims (what Nova is NOT)
1. **Nova's decisions are NOT made by an LLM.** They are made by a
confidence-gated policy engine: deterministic scripts calculate a
score, and a band outcome gates the action. The platform functions
without AI. The Decision Ledger captures this real decision path —
not a fabricated "AI agent." When an LLM planner is added, it will
emit richer `alternatives_considered` without schema breakage.
2. **Nova does NOT remove humans from accountability.** Only from
normal operations. Every stage-gate promotion (qa/prod/dr) requires
a human attestation recorded with approver identity,
separation-of-duties check, and the evidence matrix.
3. **Nova is NOT for legacy, untagged, or freeform infrastructure.** It
requires Terraform-managed, policy-aligned, fully-tagged inputs.
4. **Nova does NOT fabricate metrics.** Every metric is grounded (cites
a source), derived (documented formula), or deferred (cites the
blocking work). No fabricated numbers in any deck slide or metrics
entry (the "no fabrication" hard constraint).
## What "won" looks like
By month 18, Nova is the layer enterprise leadership points to when
they say *"we don't have an infrastructure ops team anymore, and the
audit trail is stronger than it ever was"* — and it is the layer their
AI engineering teams reach for first when an agent needs to deploy.
@@ -1,46 +0,0 @@
# P4 — Live Pilot Run Evidence (v1.26, v0.2 re-run)
> The live `terraform apply` against AWS `581513795199` succeeded. The
> Decision Ledger + outcome backfill are complete. SPEC §5.8 evidence
> stream verified.
## Apply result (account 581513795199, dev, autonomous)
- **ALB DNS**: `app-254671247.us-east-1.elb.amazonaws.com`
- **ECS service**: `arn:aws:ecs:us-east-1:581513795199:service/nova-cluster/nova-microservice`
- **DynamoDB table**: `nova-blkex-ledger-dev` (PK `block_index`, PAY_PER_REQUEST)
- **S3 bucket**: `nova-blkex-blocks-dev-581513795199-us-east-1` (versioning + SSE)
- **ECS cluster**: `arn:aws:ecs:us-east-1:581513795199:cluster/nova-cluster`
- **ECR repo**: `581513795199.dkr.ecr.us-east-1.amazonaws.com/app-repo`
- **IAM role**: `arn:aws:iam::581513795199:role/nova-app-role`
- **KMS key**: `arn:aws:kms:us-east-1:581513795199:key/e9a7ba15-d5cb-4f4d-ab20-bfac5cb62bcf`
- **Platform VPC** (prerequisite): `vpc-0d7c8867e6cc080f1` + 6 subnets + ECS SG `sg-0c95704b16859e86f`
## Confidence signal
- score: **0.800**, band: **pass** (dev autonomous, ≥0.50, no HITL)
- human_override: false
- escalation_reason: absent (clean apply — REQ-318)
## Decision Ledger (SQLite hash-chain, /root/metrics/decision_ledger.db)
- `nova.ai.decision.made` — decision_id `blkex-pilot-apply-v0.2`, chosen_action `pass`, human_override false
- `nova.outcome.backfilled` — outcome `pending → succeeded`, backfilled_at `2026-08-19T03:05:04Z`
- chain valid: true (0 breaks)
## Outcome backfill (REQ-317)
- fact_decision.outcome: `pending``succeeded` (NOT stuck pending)
- backfilled_at: `2026-08-19T03:05:04Z`
## Module-completeness gaps fixed (uncovered by the live apply)
- ecs-service L1: added `execution_role_arn` + `task_role_arn` (Fargate requires execution role for ECR pull)
- microservice L2 composition: wired `roles.outputs.role_arn``service.inputs.{execution,task}_role_arn`
- microservice L2 composition: wired `platform_vpc.outputs.ecs_security_group_id``alb.inputs.security_group` (ALB requires a SG)
## Run id
- NOVA_RUN_ID: `blkex-pilot-apply-v0.2`
---ci---
project: acdl
phase: 4
milestone: v1.26
status: execute
wave: W1
---
File diff suppressed because it is too large Load Diff
-127
View File
@@ -1,127 +0,0 @@
# `.ciagent/archive/` — Completed-Milestone History
This directory holds byte-identical snapshots of `.ciagent/` files that
were compressed out of the active agent context. Compression is **lossless
via relocation**: every original byte is reachable here, and the git
history at the commit prior to compression preserves the authoritative
state for offline agent loading.
## Why archive
The active milestone is v1.27 (PO State Catalog & Ciagent Compression).
The `.ciagent/` root was compressed twice:
1. **v1.26 P2 compression** (~11,164 lines → ~5,232): the
completed-milestone narratives (v1.0v1.24) were relocated. Per the
run.md context-loading model, agents read `.ciagent/` every
`/ci-run`; the historical narrative was not load-bearing for v1.26
execution and was relocated to keep the working context lean.
2. **v1.27 P1 compression** (~5,232 → ~3,882): the v1.26 phase
verifications + review + evidence + the dated CAPABILITY_INVENTORY
(superseded by STATE.md) + AUTONOMY_THESIS (folded into NORTH_STAR)
+ COST (predates v1.26 pilot) were relocated. The 4 pre-execution
files (CLARIFY/GRILL/IDEATE/RESEARCH) were rewritten by v1.27 P0
and stay active through v1.27.
## Contents
### Snapshots of slimmed files (full content before compression)
| File | Original (lines) | Replaces | Status at time of snapshot |
|---|---|---|---|
| `PROJECT-v1.0-v1.24.md` | 1784 | `.ciagent/PROJECT.md` | v1.0v1.24 milestone-by-milestone narrative + active milestone v1.26 sections |
| `REQUIREMENTS-v1.0-v1.24.md` | 2490 | `.ciagent/REQUIREMENTS.md` | All requirements v1.0 (REQ-01) through v1.26 (REQ-322) |
| `ROADMAP-v1.0-v1.24.md` | 2341 | `.ciagent/ROADMAP.md` | All phase breakdowns v1.0 through v1.26 |
| `ARCHITECTURE-v1.0-v1.24.md` | 945 | `.ciagent/ARCHITECTURE.md` | Full architecture reference + historical "how we got here" narrative |
The slimmed in-place files retain: active milestone v1.26 context, the
v1.25 milestone (since v1.26 tags ride the v1.25.x line), the durable
vision/tenets/RACI/capability-status sections, and the current-state
architecture reference.
### Completed-phase artifacts (relocated verbatim)
| File | Original (lines) | Phase(s) documented |
|---|---|---|
| `REVIEW.md` | 111 | Multi-persona code review records from completed phases |
| `AUDIT.md` | 553 | Project health audit records (reconstruction tests, branch hygiene) |
| `VERIFY.md` | 86 | Per-phase verification records |
| `PRE_MORTEM.md` | 228 | Pre-mortem analyses for completed milestones |
### v1.27 compression — archived files (8 files, lossless `git mv`)
> The v1.27 NFR milestone (PO State Catalog & Ciagent Compression) archived
> 7 platform-root files + 1 consumer file. All are byte-identical
> relocations; git history at the pre-v1.27 commits preserves the
> authoritative state.
#### Snapshots of superseded durable references (3 files)
| File | Original (lines) | Superseded by | Status at time of snapshot |
|---|---|---|---|
| `CAPABILITY_INVENTORY-v1.10.md` | 120 | `.ciagent/STATE.md` (v1.27) | The 2026-07-27 re-verification sweep (v1.1→v1.8 capabilities). Predates v1.26 pilot (CAP-025 absent; blockchain capabilities absent). |
| `AUTONOMY_THESIS-v1.21.md` | 65 | `NORTH_STAR.md` Vision + Anti-Goals #2 | "Last refined: v1.21" — the autonomy-in-operations thesis, fully folded into NORTH_STAR.md. |
| `COST-v1.14.md` | 106 | (future cost milestone) | AWS cost report dated 2026-07-29, framed "v1.0 → v1.14". Predates v1.26 live pilot (ECS + ALB + DynamoDB + S3 costs not reflected). |
#### v1.26 phase verifications + review + evidence (4 files)
| File | Original (lines) | Phase(s) documented |
|---|---|---|
| `VERIFY-P03.md` | 39 | v1.26 P3 verification — PASS (shipped `v1.25.3`) |
| `VERIFY-P04.md` | 31 | v1.26 P4 verification — PASS (shipped `v1.25.4`) |
| `REVIEW-AUDIT-P05.md` | 218 | v1.26 P5 final review + audit — PROCEED (shipped `v1.25.5`; 0 P0 remain; audit CLEAN) |
| `P4-PILOT-RUN-EVIDENCE-v1.26.md` | 46 | v1.26 live apply evidence (`blkex-pilot-apply-v0.2`; confidence 0.800 pass; outcome backfilled; hash chain valid) |
#### Consumer subproject archive (1 file)
| File | Original (lines) | Phase(s) documented |
|---|---|---|
| `nova-blockchain-exchange/archive/ROADMAP-v1.26.md` | 57 | v1.26 consumer roadmap (P3/P4/P5 marked "planned" at archive time; v1.26 shipped `v1.25.5`). Phase narrative preserved in the platform `.ciagent/ROADMAP.md` §v1.26. |
#### v1.26 pre-execution artifacts (in git history, not archived to disk)
The v1.26 pre-execution files (CLARIFY, GRILL, IDEATE, RESEARCH) were
overwritten by the v1.27 P0 pre-execution cycle. The v1.26-era content
is preserved in git history at the pre-v1.27-P0 commits (search the
log for `docs(P00):` commits on the `milestone/v1.26-pilot-activation`
line). The v1.27 P0 versions stay active through v1.27; they archive at
v1.28 P1 if v1.28 happens. Decisions D-200..D-213 (v1.26) are folded
into `PROJECT.md` load-bearing decisions; D-214..D-225 (v1.27) live in
the active `CLARIFY.md`.
### Live operational files NOT archived
These files remain at their canonical `.ciagent/` paths because they are
read/write targets of live code paths and must not be relocated:
- `REGRESSION_REPORT.json` — written by `core/regression_verify.py:705`,
read by `core/metrics/collector.py:27` + `core/metrics/trust_snapshot.py:21`
+ `metrics/` views.
- `REGRESSION_REPORT.md` — written by `core/regression_verify.py:704`,
referenced by `scripts/run_regression.sh`.
- `CHECKPOINT.json` — the authoritative resume point for `/ci-run`.
- `config.json` — operational configuration (no historical content).
## How to load archived content
Agents that need completed-milestone history can read these files
directly (they live inside `.ciagent/`, so the path convention holds):
```
.ciagent/archive/PROJECT-v1.0-v1.24.md
.ciagent/archive/REQUIREMENTS-v1.0-v1.24.md
.ciagent/archive/ROADMAP-v1.0-v1.24.md
.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md
.ciagent/archive/{REVIEW,AUDIT,VERIFY,PRE_MORTEM}.md
```
For the authoritative pre-compression state of any `.ciagent/` file,
use git history at the commit immediately preceding the compression
commit (search the log for `chore(P02): compress .ciagent/ files`).
## `completed-milestones/`
Reserved for future per-milestone summary files if a milestone's
narrative is too large for the slimmed in-place ROADMAP/PROJECT. Currently
empty; v1.0v1.24 narrative is fully preserved in the four snapshot
files above.
File diff suppressed because it is too large Load Diff
-219
View File
@@ -1,219 +0,0 @@
# P05 Final Review + Audit — v1.26 Live Pilot Estate Activation
> **Phase:** 5 (final review + audit + ship) — review + audit only; the
> milestone ship (merge to main / tag v1.25.5 / branch deletion) is the
> orchestrator's next step, deliberately out of scope here.
> **Branch:** `phase/05-final-review-ship`
> **Milestone:** `milestone/v1.26-pilot-activation`
> **Tags so far:** v1.25.0 (P0) → v1.25.1 (P1) → v1.25.2 (P2) →
> v1.25.3 (P3) → v1.25.4 (P4). P5 ships v1.25.5 (= the v1.26 release).
> **Date:** 2026-08-19
---
## 1. Review (ciagent-review equivalent)
Multi-persona review across P1..P4 (lead-developer coordination;
correctness / testing / security / maintainability axes). The spot-checks
below confirm the P3/P4 commits deliver what their messages claim.
### Correctness spot-checks (all PASS)
- **kyverno-json substrate fix (59d837f):** the engine `_translate` parses
the real `kj` v0.0.3 bare-list output (not the v1.25-assumed
`{"results":[...]}` dict); `_materialize_yaml_policy_dir` mirrors `.json`
policies to `.yaml` twins (kj v0.0.3 ignores `.json`); the `validate`
wrapper was removed from all 16 policies + the check syntax fixed
(`expression: expected_value`). All 36 kj-dependent tests pass against
real `kj` (0 skips). The install script fixed
(`go install .../kyverno-json@latest` + symlink, not the broken
`cmd/kj@latest`).
- **outcome backfill (51b886f, REQ-317):** `core/metrics/outcome_backfill.py`
updates `fact_decision.outcome` pending → succeeded/failed; idempotent +
terminal (no overwrite of a non-pending outcome); wired into the
collector. The P4 run evidence (6ced8ed) confirms
`nova.outcome.backfilled (pending->succeeded)`.
- **Gitea adapter (P3 W0):** the consumer `deploy.yml` has no cross-repo
`uses:` — inline `actions/checkout@v4` of `acdl/acdl @ ref: v1.25` into
`platform/` then `bash platform/scripts/run_platform.sh`. SPEC §10 Q1
resolved by evidence.
- **env-JSON state_backend (3300ed2, REQ-319):** the adapter reads
`env.state_backend.bucket` when present (fallback to the computed
`nova-tfstate-{account_id}-{region}` for backwards compat). `dev.json`
bound to `581513795199` + `nova-tfstate-581513795199-us-east-1`;
qa/prod/dr stay placeholder (account `000000000000` — the pilot-readiness
policy blocks apply, D-208).
- **pilot policies (e22661a, REQ-315/320):** `no-placeholder-account.json`
passes on dev (581513795199), fails on placeholder;
`all-matches-committed.json` asserts `all_committed == true`. Both run
against real `kj` (not skipped).
### Testing
- 844 tests collected; **844 pass** (839 fast + 5 slow individually
re-run: 2 `test_run_local_e2e_*` + 3 `test_verify_regression_mode::*`).
0 failures, 0 skips that shouldn't skip.
- New feature coverage confirmed: REQ-317 backfill test
(`test_outcome_backfill.py`), REQ-318 escalation_reason test
(`test_confidence_escalation_reason.py`), REQ-315/320 policy tests
(`test_settlement_finality_policy.py`, `test_pilot_readiness_policy.py`
— both real-kj), REQ-316 CAP-025 test (`test_regression_pilot.py`), Gitea
adapter tests (`test_deploy_workflow_invocation.py` +
`test_deploy_gitea_invocation.py` — assert no cross-repo `uses:`,
`ref: v1.25`, `secrets: inherit`), rotation workflow test
(`test_rotate_key_workflow.py`), CAP-025 test
(`test_deploy_workflow_env_input.py`).
- The v1.25 `pytest.skip("kj not installed")` skips are gone — `_require_kj`
no longer skips (kj v0.0.3 installed). All kj-dependent tests exercise
the real engine.
### Security
- **No `NOVA_AWS_*` secrets in committed files.** `.env.secrets` is
gitignored and NOT tracked (`git ls-files` confirms). All `NOVA_AWS_*`
references in committed workflow files are `${{ secrets.* }}` placeholder
references — the correct pattern. The W6 fix (b237b3e) removed raw
`NOVA_AWS_*` from the shell env in `run_platform.sh`'s local fallback.
- **No forge mentions in synced files.** `test_no_forge_mentions` PASS
(the REQ-230 guard). The W6/W7 fix (03edd82) renamed `NOVA_GITEA_TOKEN`
`NOVA_FORGE_TOKEN` (forge-agnostic) after the guard tripped.
### Maintainability
- **No stale `TYPE_MAP` refs in active docs.** The P4 W2 fix (a0799f1)
fixed the stale `TYPE_MAP`/`INPUT_MAP` references in `adapters/README.md`
(IDEATE I8). Remaining `TYPE_MAP` mentions are in `.ciagent/archive/`
(historical, correct) + `.ciagent/{CLARIFY,IDEATE,RESEARCH}.md`
(decision records, correct context).
- **No new TODOs/FIXMEs in P3/P4.** `grep` over `core/` for
`TODO|FIXME|XXX|HACK` returns 0 matches.
- The P3 W0.5 fix (3735330) resolved pre-existing P2 drift (dynamodb
`simple.yaml``simple.yml`, sync_workflows re-sync, CAP-024 deck path
`nova-autonomous-cloud-delivery-marp.md`).
### Review verdict
**0 P0 issues remain** after the one P0 fix applied this phase (see §3).
**P1+ issues for post-hoc review (none blocking ship):**
| # | Severity | Issue | Disposition |
|---|----------|-------|-------------|
| R-1 | P2 (cosmetic) | `CHECKPOINT.json` `phase_branch` field is stale (`phase/03-pilot-metrics-and-policies`) — should be `phase/04-pilot-run-and-docs` or cleared. | Post-hoc. The orchestrator's ship step overwrites CHECKPOINT entirely (`stage: complete, phase: 5, phase_role: final`), so this field is transient. Not fixed here to avoid touching CHECKPOINT outside the ship step. |
| R-2 | P3 (historical) | The v1.26 consumer-repo merge commit (78da051) + the P0 merge (d391cdf) use `---/ci---` close markers; the v1.26 platform-repo commits (P3/P4) use `---ci---` only. Minor format inconsistency from the multi-project boundary. | Post-hoc. Cosmetic; both markers are recognized by the audit tooling. |
| R-3 | P3 (future-hardening) | Single `NOVA_AWS_*` root-equivalent key (D-207). Documented in PLAN.md §Future Hardening — a future milestone should split into `NOVA_BOOTSTRAP_AWS_*` + least-privilege `NOVA_AWS_*` runner key. | Post-hoc. Out of v1.26 scope by design (D-207, G-Q9). |
---
## 2. Audit (ciagent-audit equivalent)
### 2.1 Reconstruction test — **PASS**
The git log `---ci---` blocks are consistent with the `.ciagent/` file
states. The last 20 commits on `milestone/v1.26-pilot-activation` show the
expected phase progression:
- P0 (`d391cdf`, status: complete) → P1 ship (`2ee541f`) →
P2 reconcile (`d022ddc`) → P2 complete (`6a3d47e`) →
P3 W0.5 → W2 → W3 → W4 → W5 → W6 → W6/W7 → verify (`5d1a985`) →
docs (`732998b`) → merge+complete (`268f695`, `6b60c0c`) →
P4 W1 (`cec34ab`, `6ced8ed`) → W2 (`a0799f1`) → verify (`074ee05`) →
merge+complete (`6eb7af2`, `f266dcf`).
Each phase follows the `execute → verify → complete` lifecycle. The
CHECKPOINT `current_phase` (phase 4, status complete, tag v1.25.4) matches
the latest commit (`f266dcf docs(ship): P4 complete → v1.25.4`). The
`previous_phase` (phase 3, tag v1.25.3, complete) is consistent.
All 4 merge commits on the milestone branch (d391cdf, 78da051, 268f695,
6eb7af2) carry `---ci---` blocks with project/phase/milestone/status.
### 2.2 `.ciagent/` file discipline — **CLEAN** (after the one P0 fix)
- **CHECKPOINT.json:** `current_phase` (4/complete/v1.25.4) + `previous_phase`
(3/complete/v1.25.3) consistent with the git log. `waves` map + `pre_run`
map + `notes` accurately describe the P4 live apply + outcome backfill.
One stale field: `phase_branch` (R-1, post-hoc).
- **REQUIREMENTS.md:** v1.26 traceability table now shows all 13 REQs
(310..322) complete. **One P0 fix applied:** REQ-316 row corrected from
"P4 live-verify pending" → "v1.25.4 — live-verify complete" (P4 is
complete; v1.25.4 tagged; the live apply against 581513795199 succeeded
per commit 6ced8ed + verify 074ee05). The v1.25 table (REQ-291..309) is
all-complete + consistent with ROADMAP.
- **ROADMAP.md:** v1.26 phases P0..P4 marked complete; P5 marked "planned"
(correct — this phase is in progress, ship is next). v1.25 marked
complete. The phase descriptions match the commits.
- **PLAN.md:** the active phase plan covers P0..P5 with wave ordering,
persona assignment, + the REQ-322→P2 W0 revision. Consistent with what
shipped.
- **ARCHITECTURE.md:** §12.8 (Pilot Estate) + §12.9 (rotation) present
(P4 W2 docs).
- **PROJECT.md:** v1.26 active milestone noted; multi-project mode
(`nova-blockchain-exchange`) reflected.
### 2.3 Branch hygiene — **CLEAN**
`git branch -a` (local):
- `main`
- `milestone/v1.26-pilot-activation`
- `phase/05-final-review-ship` (current)
P1..P4 phase branches are deleted (only milestone + P5 remain, as
required). Remote: `origin/main` + `origin/milestone/v1.26-pilot-activation`
mirror the local state.
Tags: `v1.25` (floating) + `v1.25.0` + `v1.25.1` + `v1.25.2` + `v1.25.3` +
`v1.25.4` all exist. `v1.25.5` is not yet present (correct — it's the
orchestrator's ship step).
### 2.4 Commit discipline — **CLEAN**
Every v1.26-scope commit on the milestone branch carries a `---ci---`
block with `project` + `phase` + `milestone` + `status` (and most carry
`wave`). The 4 merge commits (d391cdf, 78da051, 268f695, 6eb7af2) all
carry `---ci---` blocks. (Historical commits from v1.0-v1.18 predate the
block convention — out of scope for this audit.)
The consumer-repo merge (78da051) correctly carries
`project: nova-blockchain-exchange` (multi-project boundary respected);
the platform commits carry `project: acdl`.
### Audit verdict
| Check | Result | Detail |
|-------|--------|--------|
| Reconstruction test | **PASS** | git-log `---ci---` blocks ↔ `.ciagent/` consistent; phase 4/complete/v1.25.4 matches HEAD. |
| File discipline | **CLEAN** | All 6 `.ciagent/` files consistent after the REQ-316 P0 fix. One stale `phase_branch` field (R-1, post-hoc). |
| Branch hygiene | **CLEAN** | Only main + milestone + P5; P1-P4 deleted; v1.25.0..v1.25.4 tagged. |
| Commit discipline | **CLEAN** | All v1.26 commits carry `---ci---` blocks; merge commits included. |
---
## 3. P0 fixes applied this phase
| # | File | Fix |
|---|------|-----|
| P0-1 | `.ciagent/REQUIREMENTS.md` | REQ-316 traceability row: "P4 live-verify pending" → "v1.25.4 — live-verify complete". P4 is complete (v1.25.4 tagged, live apply against 581513795199 succeeded per commits 6ced8ed + 074ee05); the "pending" text was stale documentation drift that misstated the milestone state. |
No code-level P0 issues found — the P3/P4 feat/fix commits deliver what
they claim; the test suite is green; no secrets leaked; no forge mentions;
no stale active-doc references.
---
## 4. Overall verdict — **PROCEED to milestone ship**
- **Review:** 0 P0 issues remain (1 P0 fix applied: REQ-316 doc drift).
3 P1+ items flagged for post-hoc (R-1 stale CHECKPOINT field, R-2 close-
marker inconsistency, R-3 future key-split — none block ship).
- **Audit:** reconstruction PASS; file discipline CLEAN; branch hygiene
CLEAN; commit discipline CLEAN.
- **Tests:** 844 passed, 0 failed, 0 unexpected skips (5 slow tests
individually confirmed green: 2 local-e2e + 3 regression-mode).
**Decision: PROCEED.** The orchestrator's next step (Wave 3 milestone
ship: merge `phase/05-final-review-ship` → `milestone/v1.26-pilot-
activation` → `main`; tag `v1.25.5`; Gitea release; delete milestone
branches; final CHECKPOINT clear) is unblocked. Per the full-autonomy
"never halt" directive, even if a P0 had been critical, the ship step
would still proceed with the issue documented — but here the single P0
was a cosmetic doc-drift, now fixed.
-112
View File
@@ -1,112 +0,0 @@
# Nova v1.16 — Multi-Persona Code Review (final phase P21)
**Reviewer:** lead-developer (model: glm-5.2)
**Scope:** v1.16 milestone — 22 tags (v1.15.5..v1.15.26), 20 execution
phases + final. Squash-merged to main via `milestone/v1.16-nova-simplification`.
**Date:** 2026-07-30
> **Historical note:** REVIEW.md was reconstructed at v1.16 P21 (the
> v1.3v1.15 reviews were not persisted or were overwritten per the
> established convention). The v1.16 review overwrites prior content.
## Review approach
The v1.16 milestone is an NFR sweep (no new features). Each of the 20
execution phases shipped with a 4-layer verify (structural/behavioral/
security/quality) + `run_ci.sh` 3-stage PASS at every phase boundary.
The final-phase review (P21) is a milestone-level cross-phase check,
not a per-phase re-review (the per-phase verify already ran).
## P0 issues (0)
No blocking issues found. The 4-layer verify at each phase boundary +
the regression gate (D-118, 18V+4S at P9 + P21) are the structural
controls. No P0 was auto-applied at P21.
## P1 issues (0)
No P1 issues flagged. The grill binding decisions (G-111..G-113) were
incorporated into the plan before execution; the regression gate (G-111)
passed at both checkpoints (P9 + P21).
## P2 issues (2 — post-hoc, non-blocking)
### P2-1: Onboarding framing (E-002, deferred from grill)
[scope] `.ciagent/PROJECT.md`, `.ciagent/ROADMAP.md`
The grill escalation E-002 (confidence 0.55) flagged that the PROJECT.md
framing "first self-service onboarding request path" may over-promise
relative to a request-*acceptance* path that writes a pending row +
generates an env-file + proves the role Terraform offline but never
fulfills (no live role grant). The milestone is internally consistent
with D-113 (request-path only) — the wording is the only risk. The
ROADMAP/PROJECT use "request path" (not "request-fulfillment"), and the
Out-of-Scope section explicitly defers real AWS provisioning. **Accepted
as-is** — the framing is accurate for what was delivered (a request path,
not a fulfillment path).
### P2-2: REVIEW.md + AUDIT.md not updated during the run
[maintainability] `.ciagent/REVIEW.md`, `.ciagent/AUDIT.md`
REVIEW.md still held v1.11 content during the v1.16 run (the per-phase
verify ran but wasn't persisted to REVIEW.md until P21). AUDIT.md held
v1.15 content. Both are reconstructed at P21 (this review + the audit
running now). This matches the established convention (REVIEW.md is
overwritten at milestone complete; the per-phase verify commits are the
record). Not a defect.
## What is correct
- **State-bucket drift fix (P1):** `adapter.py:117` now emits
`nova-tfstate-*` (matching the live bucket renamed in v1.15 P4). The
new `test_adapt_emits_nova_state_bucket` regression guard asserts this.
- **Kyverno label fix (P1):** `require-resource-labels.yml` enforces
`nova:*` labels (consistent with `nova_tagging.py` hard-fail on
`acdl:*`). No policy contradiction.
- **Ingestor defense-in-depth (P10):** fail-closed on missing IAM
identity (401, not silent pass); env enum derived from
`core/environments/` (not hardcoded). The `NOVA_LAMBDA_LOCAL_BYPASS`
env allows local/stub testing without blocking the fail-closed path.
- **Payload validation (P11):** 256 KB size cap + contract.schema.json
validation before the DynamoDB write; aligned error/stackTrace caps
(both 10000).
- **Regression gate (G-111):** CAP-013..016 return `Skipped` (not
`Decayed`/`Broken`) for the post-teardown steady state (D-096).
`passed` accepts Skipped. Gate passes at 18V+4S.
- **Workflow generator (P8):** `sync_workflows.py` + `workflows-src/`
single source; the byte-identity test is replaced with a generator-
output test (`--check` exits 0). The 3 pairs are no longer hand-synced.
- **Onboarding request path (P18-P20):** schema + Lambda action (pending
CMDB row, no AWS resources) + env-file autogen + offline-proven
cross-account Terraform. Self-service message (no "contact the platform
team"). Real AWS provisioning explicitly deferred (D-113/D-114).
- **Splits (P12/P13):** `contract_resolver` + `regression_verify` split
with re-export shims; G-113 one-way import direction documented. All
tests pass without modification (backwards compat preserved).
- **DX (P15-P17):** `--help` works + documents all 9 flags; workflows
README catalogs all 7 workflows; getting-started is offline-first.
- **Regression gate:** 18 Verified + 4 Skipped at P9 + P21 (0 Decayed/
Broken). The 4 Skipped are the post-v1.11-teardown live-AWS caps.
## Test coverage assessment
~635 tests pass (was ~620 at v1.15.4). New test files:
- `tests/test_onboarding.py` (3 tests — env-file generation)
- `tests/test_onboarding_terraform.py` (3 tests — terraform validate + tags)
- `tests/test_docs_coverage.py` (expanded — workflows README catalog)
New tests in existing files: `test_adapt_emits_nova_state_bucket`,
`test_onboarding_message_says_nova_not_acdl`, `test_no_identity_fails_closed`,
`test_no_identity_passes_with_local_bypass`, `test_oversized_contract_rejected`,
`test_schema_invalid_contract_rejected`, `TestNarrowedException` (2 tests),
`TestOnboardConsumer` (3 tests), `TestOnboardingMessageSelfService` (2 tests),
`test_sync_workflows_check_passes`.
## Verdict
**PASS — 0 P0, 0 P1, 2 P2 (post-hoc, accepted).** The v1.16 NFR milestone
is complete. All 20 requirements (REQ-165..184) satisfied; regression
gate 18V+4S; CI 3-stage PASS at every phase boundary. The onboarding
request path is self-service; real AWS provisioning deferred. The
state-bucket drift + Kyverno label contradiction (the two correctness
regressions from the v1.15 rebrand) are fixed with regression guards.
File diff suppressed because it is too large Load Diff
-39
View File
@@ -1,39 +0,0 @@
# VERIFY — v1.26 P3 (pilot-metrics-and-policies) PASS
> Four-layer verification. All gates green.
## Structural
- pilot-readiness/no-placeholder-account.json + settlement-finality/all-matches-committed.json exist (REQ-315/320)
- core/metrics/outcome_backfill.py + tests exist (REQ-317)
- escalation_reason emitted on block band (REQ-318) — test_confidence_escalation_reason.py
- adapters/terraform/adapter.py reads env.state_backend.bucket (REQ-319) — test_adapter_state_backend.py
- core/environments/dev.json bound to 581513795199 (D-203); qa/prod/dr placeholder (D-208)
- CAP-025 in CAPABILITY_REGISTRY (REQ-316) — test_regression_pilot.py
- workflows-src/rotate-aws-key.yml + synced copies (SPEC §5.9)
- consumer deploy.yml: no cross-repo uses: (SPEC §10 Q1 — inline adapter, option c)
- kj installed (v0.0.3); kyverno-json policy tests run (not skipped)
## Behavioral
- platform: 844 passed (full suite, including @pytest.mark.slow live-AWS CAPs)
- consumer: 90 passed, 6 skipped (pre-existing unrelated skips)
- kj substrate: 69 targeted policy/engine tests pass against real kj (zero skips)
- pilot policies: pass on valid fixtures, fail on invalid (verified via kj scan violations)
## Security
- no raw NOVA_AWS_* export in scripts/run_platform.sh shell env (SPEC §5.2 — blocked_env_vars guard)
- forge-agnostic synced files (REQ-230 — test_no_forge_mentions pass)
- no secrets tracked in git (test_no_secrets_tracked pass)
- NOVA_AWS_* redacted on emit (existing outbox_writer + confidence_signal redaction)
## Quality
- 7 pre-existing P2 failures (uncovered by W0.5 full-suite run with kj installed) all fixed:
dynamodb examples (.yml), sync_workflows drift, CAP-024 deck path (-marp.md), 3 disk-space environmental
- zero regressions vs baseline
- territory enforcement (warn mode) respected across waves
---ci---
project: acdl
phase: 3
milestone: v1.26
status: verify
---
-31
View File
@@ -1,31 +0,0 @@
# VERIFY — v1.26 P4 (pilot-run-and-docs) PASS
## Structural
- Live apply: AWS resources exist (ALB, ECS, DynamoDB, S3, KMS, ECR, IAM) — account 581513795199
- ecs-service L1: execution_role_arn + task_role_arn wired (module-completeness gap fixed)
- microservice L2 composition: roles→service wires + ALB SG wire
- Decision Ledger: ai.decision.made + nova.outcome.backfilled (hash chain valid)
- fact_decision.outcome: pending→succeeded (REQ-317 outcome backfill verified)
- Docs: adapters/README, docs/METRICS, ARCHITECTURE §12.8, consumer onboarding README
## Behavioral
- platform: 844 passed (full suite)
- consumer: 90 passed, 6 skipped (deploy invocation tests pass on the inline adapter)
- live terraform apply: exit 0 (Apply complete! Resources created)
## Security
- NOVA_AWS_* not in shell env (run_platform.sh unset after sourcing .env.secrets)
- Decision Ledger events redact secrets (no NOVA_AWS_* values in payloads)
- forge-agnostic synced files (test_no_forge_mentions pass)
## Quality
- No regressions (844 baseline holds)
- The live apply uncovered + fixed 2 module-completeness gaps (ecs-service role, ALB SG)
- The Post-Pilot metrics now have non-zero denominators (n=1 real run)
---ci---
project: acdl
phase: 4
milestone: v1.26
status: verify
---
-87
View File
@@ -1,87 +0,0 @@
# VERIFY — P1 engine-core (v1.25)
> 4-layer verify gate: structural, behavioral, security, quality.
> Phase: P1. Requirements: REQ-291..294, 308, 309. Result: PASS.
## Structural
- `core/policy_engine.py` exists, implements `PolicyEngine` Protocol
(PEP 544, `@runtime_checkable`), `PolicyEngineRegistry` with
`register()` + `get_engine()`, `NullEngine` fallback.
- `adapters/kyverno-json/kyverno_json_engine.py` exists, exports
`KyvernoJsonEngine` with `name`, `is_configured()`, `evaluate()`.
- `adapters/kyverno-json/__init__.py` loads the engine by file path
(the dir name has a hyphen — not a valid Python package name).
- `adapters/kyverno-json/policies/_smoke.json` exists (trivial policy
for round-trip validation).
- `scripts/install-kyverno-json.sh` exists (go install kj@latest).
- `.ciagent/config.json` has the `policy` object
(`engine: kyverno-json`, `policy_root`).
- `.gitea/workflows/ci.yml` + `.github/workflows/ci.yml` have the
Go + kj install step (best-effort, tests skip when kj absent).
- `tests/test_policy_engine.py` (10 tests) +
`tests/test_kyverno_json_engine.py` (16 tests) exist.
## Behavioral
- `pytest tests/test_policy_engine.py tests/test_kyverno_json_engine.py`:
**24 passed, 2 skipped** (kj not installed — expected;
`pytest.skip("kj not installed")`).
- `NullEngine` satisfies the `PolicyEngine` Protocol (G-Q8a —
`isinstance(NullEngine(), PolicyEngine)` is True). Proves the swap
boundary is real without implementing OPA.
- `KyvernoJsonEngine.is_configured()` returns `False` when
`which kj` is absent → `evaluate()` returns a single
`KJ_ENGINE_NOT_CONFIGURED` SKIPPED PCR (distinct `ruleId` from
NullEngine's `NULL_ENGINE_INACTIVE` — G-Q4).
- PCR records validate against `schemas/policy_check_result.schema.json`
(via `jsonschema.validate` in tests).
- Defensive parsing: malformed kyverno-json output → `error` PCR
(`KJ_ENGINE_ERROR`), never an exception.
- Severity annotation reading (G-Q10a): policies with
`nova.cloudinit.dev/severity: high` produce PCRs with `severity: high`;
policies without the annotation default to `info`.
- Registry: `get_engine()` returns the configured engine; unknown
engine name raises `KeyError`; `policy` key absent → `NullEngine`.
- No regression: `pytest tests/test_confidence_signal.py
tests/test_adapter.py tests/test_checkov_adapter.py
tests/test_kyverno_adapter.py tests/test_contract_resolver.py` —
**132 passed** (unchanged).
## Security
- No new secrets, no new network calls in the engine core (the engine
shells to a local binary; the binary makes no network calls for
`scan`).
- `is_configured()` guard ensures the platform runs without the binary
(no hard dependency that could be exploited as a DoS vector).
- The engine writes the payload to a temp file (`tempfile.NamedTemporaryFile`)
and unlinks it in a `finally` block (no leftover payload on disk).
- No `shell=True` in the `subprocess.run` call (command is a list —
no shell injection surface).
## Quality
- `python3 -m py_compile` passes on all new Python files.
- The `PolicyEngine` Protocol is minimal (3 members) — the swap
boundary is the moat (NORTH_STAR Strategic Objective #2).
- The `NullEngine` proves a second implementation exists (structural
conformance) — the OPA swap is a known quantity (RESEARCH §4.2).
- Tests use `pytest.skip` when `which kj` is absent, so the CI matrix
passes with or without the binary (the suite is green in both cases).
## Must-have checklist
- [x] `PolicyEngine` Protocol + `PolicyEngineRegistry` + `NullEngine`
(REQ-291)
- [x] `config.json.policy` object (REQ-292)
- [x] `KyvernoJsonEngine` adapter (REQ-293)
- [x] `__init__.py` + `_smoke.json` + `install-kyverno-json.sh` + CI
install (REQ-294)
- [x] `test_policy_engine.py` — protocol conformance, registry,
NullEngine fallback (REQ-308)
- [x] `test_kyverno_json_engine.py` — PCR schema validity, defensive
parsing, skip-without-kj (REQ-309)
**Verdict: PASS** — all P1 must-haves met, no regressions, 24 new
tests pass (2 skip-without-kj), 132 existing tests unchanged.
+5 -16
View File
@@ -2,18 +2,13 @@
"projects": [
{
"slug": "acdl",
"name": "Nova — The New Dawn of DevSecOps",
"name": "Agentic Cloud Delivery Platform",
"default": true
},
{
"slug": "nova-blockchain-exchange",
"name": "Nova Pilot Consumer — Blockchain Stock Exchange",
"default": false
}
],
"active_project": "acdl",
"active_projects": ["acdl", "nova-blockchain-exchange"],
"active_milestone": "v1.28",
"active_projects": ["acdl"],
"active_milestone": "v1.14",
"autonomy": {
"level": "full",
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
@@ -64,7 +59,7 @@
},
"git": {
"branching_strategy": "flat",
"_branching_strategy_note": "Nova uses flat workflow (committed directly to main per established convention since v1.0; renamed ACDL→Nova in v1.15). The 'phase' strategy is advisory; CIAgent uses milestone/phase branches for v1.14 but the project convention is flat.",
"_branching_strategy_note": "ACDL uses flat workflow (committed directly to main per established convention since v1.0). The 'phase' strategy is advisory; CIAgent uses milestone/phase branches for v1.14 but the project convention is flat.",
"auto_commit": true,
"auto_push": true
},
@@ -72,8 +67,7 @@
"sources": [".env", ".env.secrets", ".env.*"],
"disallow": ["shell_env", "netrc", "keychain", "rc_files", "global_config"],
"scopes": {
"forge": "NOVA_FORGE_TOKEN",
"gitea": "NOVA_FORGE_TOKEN",
"gitea": "ACDL_GITEA_TOKEN",
"github": "GITHUB_TOKEN",
"gitlab": "GITLAB_TOKEN",
"openai": "OPENAI_API_KEY",
@@ -214,10 +208,5 @@
"telemetry": {
"enabled": true,
"persist": true
},
"strategic_direction_file": ".ciagent/NORTH_STAR.md",
"policy": {
"engine": "kyverno-json",
"policy_root": "adapters/kyverno-json/policies"
}
}
@@ -1,97 +0,0 @@
# Nova Pilot Consumer — Blockchain Stock Exchange
> **Milestone:** v1.26 — Live Pilot Estate Activation
> **Git:** https://git.cloudinit.dev/continuous-intelligence/nova-blockchain-exchange
> **Local clone:** /root/nova-blockchain-exchange
> **Role:** The first real consumer estate. A stock exchange built on a
> homegrown blockchain, offering equities trading (pilot scope). The
> consumer repo owns the app code + `contract.yaml`; the Nova platform
> (`acdl` repo) provides the deploy workflow, policy engine, and
> attestation gates.
---
## Vision / Core Value
A self-contained securities-trading exchange where every order, match,
and settlement is recorded as an immutable transaction on a homegrown
Proof-of-Authority (PoA) blockchain. The pilot demonstrates that Nova's
autonomous infrastructure can take a real consumer estate from contract
to production — apply, attest, record — without an operator in the loop
of normal operations.
## North Star Alignment
- **Strategic Objective #1** (production-grade zero-touch operations):
this estate is the first real consumer; the pilot activates the
autonomy claim beyond internal demos.
- **Strategic Objective #2** (provable trust): every apply decision +
attestation lands in the Decision Ledger; the settlement-finality
kyverno-json policy (IDEATE) makes trust a policy artifact.
- **Strategic Objective #3** (compounding ROI): unblocks the three
Post-Pilot targets (Touchless Resolution ≥99%, Human Escalation
<0.1%, AI Decision Accuracy ≥99.5%) — the denominators activate when
this estate runs.
## Domain Boundaries
- **This repo owns:** the blockchain (consensus, blocks, transactions),
the order-matching engine, the settlement service, the `contract.yaml`
that declares the infrastructure, and the consumer-side deploy workflow
invocation (`uses: acdl/.github/workflows/deploy.yml@v1.25`).
- **The platform (`acdl`) repo owns:** the deploy workflow, the policy
engine (kyverno-json), the contract resolver, the adapter, the
confidence signal, the HITL gates, and the Decision Ledger.
## Scope: v1.26 Pilot
- **Equities only** (bonds, derivatives, options deferred to future
milestones — different settlement models).
- **Minimal PoA ledger** — append-only blocks, single validator (pilot),
T+1 settlement finality = block commit. No multi-validator BFT.
- **Homegrown chain** — authored as part of this repo, not deployed on
Ethereum/Solana/Hyperledger.
## Anti-Goals (v1.26)
1. Not a general-purpose blockchain platform — purpose-built for
securities settlement in the pilot.
2. Not multi-validator consensus — single validator for the pilot.
3. Not bonds/derivatives/options — equities only this milestone.
4. Not a replacement for the Nova platform — this is a *consumer* of
Nova, not a fork.
## Key Decisions (v1.26 — established in SPECIFY, refined in CLARIFY)
| ID | Decision | Rationale | Affects |
|---|---|---|---|
| D-200 | Pilot scope = equities only | Bonds/derivatives/options have very different settlement models; equities (T+1) is the simplest to demonstrate the Nova platform's policy gates over a real estate. | Phase count; requirement scope. |
| D-201 | Homegrown PoA ledger (single validator) | Minimal viable chain for a pilot; settlement finality = block commit. Multi-validator BFT is a future milestone. | Blockchain core design. |
| D-202 | Consumer repo = `nova-blockchain-exchange` (Gitea) | New repo under `continuous-intelligence` org; tracked as 2nd CIAgent project. | Multi-project config. |
| D-203 | AWS account = 581513795199 (existing) | Reuse the bootstrapped account; state bucket + outbox table created in pre-run Workstream A3. | Env JSON binding. |
| D-204 | D-083 (S3 Object Lock/JWS) stays deferred | The SQLite hash-chain + DynamoDB outbox is the pilot's audit record. Tamper-evidence is a future milestone. | Audit ledger scope. |
| D-205 | Cold-only metrics sufficient (D-126) | No hot ops dashboard in the pilot; cold SQLite store + PowerBI export. | Metrics pipeline. |
## Constraints
- The consumer repo's deploy MUST go through `deploy.yml@v1.25` (the
reusable workflow) — no direct `terraform apply` bypassing the
platform's policy + attestation gates.
- The `contract.yaml` MUST validate against
`schemas/contract.schema.json`.
- The homegrown blockchain MUST be deterministic (same inputs → same
block) — it is automation, not AI (NORTH_STAR Objective #2 tenet).
## Context
- The Nova platform (`acdl` repo) completed v1.25 (kyverno-json Unified
Policy Engine). The swappable `PolicyEngine` adapter is in place.
- The AWS bootstrap (S3 state bucket + DynamoDB outbox) was re-run in
the pre-run (Workstream A3) — the platform components exist.
- The consumer repo was created on Gitea (Workstream A4) and cloned to
`/root/nova-blockchain-exchange`.
- **Phase-by-phase history:** `.ciagent/ROADMAP.md` §v1.26 (the
consumer ROADMAP is archived at
`.ciagent/nova-blockchain-exchange/archive/ROADMAP-v1.26.md` since
v1.27 — the platform ROADMAP is the source of truth for milestone
phase narrative).
-180
View File
@@ -1,180 +0,0 @@
# nova-blockchain-exchange — Consumer Onboarding Guide
> **Milestone:** v1.26 — the first real Nova consumer estate. This
> guide is for the consumer side: how to invoke the deploy, what
> secrets to set, what the contract looks like, and how to verify the
> result. The platform side is documented in
> `.ciagent/ARCHITECTURE.md` §12.8; the live-pilot evidence is in
> `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` (archived v1.27).
This is a **consumer** of the Nova platform, not a fork. The consumer
repo owns the app code (the blockchain, the order-matching engine, the
settlement service) and the `contract.yaml` that declares the
infrastructure. The Nova platform (`acdl` repo) owns the deploy
workflow, the policy engine, the contract resolver, the Terraform
adapter, the confidence signal, the HITL gates, and the Decision
Ledger. The consumer never clones the platform repo and never runs
`terraform apply` directly.
---
## 1. Invoke the deploy
The consumer's `.github/workflows/deploy.yml` (and its byte-identical
`.gitea/workflows/deploy.yml` mirror) is a `workflow_dispatch` workflow.
It does **not** use cross-repo `uses:` (SPEC §10 Q1 — the Gitea forge
rejects it). Instead it is an **inline adapter**: it checks out the
consumer repo, then checks out `acdl/acdl` @ `ref: v1.25` into
`platform/`, then runs `bash platform/scripts/run_platform.sh`.
To run a deploy:
1. In the consumer repo's Actions UI, pick the **Deploy** workflow.
2. Click **Run workflow**.
3. Inputs:
- `mode` = `full` (the default — applies the Terraform). Other
values: `plan-only` (no apply), `check-only` (policy + confidence
only), `decommission` (requires a `changeRequestId`).
- `environment` = `dev` (the pilot scope — equities only, dev only,
D-020/D-200). Leave empty to use the contract's `environment`
field.
4. The workflow runs the platform pipeline end-to-end: contract
resolve → adapter compile → terraform plan → policy (kyverno-json)
→ confidence signal → (dev: autonomous apply) → Decision Ledger
events.
For the pilot, the documented invocation is `mode=full,
environment=dev`. The first live run was `blkex-pilot-apply-v0.2`
(2026-08-19).
---
## 2. Secrets to set
Set these in the forge's Actions secret store (the consumer repo's
"Secrets and variables → Actions" page). The platform-managed
scheduled workflow `rotate-aws-key.yml` rotates the `NOVA_AWS_*` key
daily (SPEC §5.9 — the v0.2 deploy uses the currently-active key).
| Secret | Purpose |
| --- | --- |
| `NOVA_AWS_ACCESS_KEY_ID` | The static AWS access key for the deploy IAM principal. Used by `aws-actions/configure-aws-credentials` when OIDC is unavailable (the Gitea path — no OIDC token is minted). |
| `NOVA_AWS_SECRET_ACCESS_KEY` | The matching secret key. Rotated by `workflows-src/rotate-aws-key.yml`. |
| `AWS_DEFAULT_REGION` | The target region (`us-east-1` for the pilot). |
The platform's `.github/workflows/deploy.yml` (GitHub Actions reference
impl) supports an OIDC path instead of the static key — set
`NOVA_AWS_ACCOUNT_ID` and leave the `NOVA_AWS_*` key secrets empty.
The Gitea inline adapter uses the static-key path.
---
## 3. The contract shape
The consumer declares its infrastructure in `contract.yaml` at the
repo root, validated against the platform's
`schemas/contract.schema.json`. The pilot contract has the shape:
```yaml
id: blkex
name: blockchain-exchange
environment: dev
infrastructure:
microservice: # the L2 composition (ECS Fargate + ALB + roles)
...
dynamodb: # the L1 DynamoDB table (the ledger)
...
s3: # the L1 S3 bucket (block storage)
...
```
Three `infrastructure.*` blocks: `microservice` (the L2 composition
that wires the ECS service, the ALB, and the IAM roles together), and
the two L1 primitives (`dynamodb` for the ledger, `s3` for block
storage). Per-environment variants live in
`contracts/blockchain-exchange.{dev,qa,prod}.yml` (the per-env
promotion model, REQ-105). The pilot runs the `dev` variant.
The contract is the **only** consumer-facing artifact that describes
infrastructure. It is IR-typed (engine-agnostic); the platform
resolves it to a target stack, the Terraform adapter compiles the
stack to HCL, and `terraform apply` runs in the central pipeline —
never on the consumer's workstation.
---
## 4. What the platform does
When `run_platform.sh` runs against `contract.yaml`:
1. **Resolve** the contract to a target stack (a list of L1 instances +
inputs + relationships), reading `modules/registry.json` for each
L1's `terraform_dir`.
2. **Compile** the stack to Terraform HCL via the stateless adapter
(`adapters/terraform/adapter.py`) — emits `module "<rid>" { source }
` blocks + wired `ref:` refs. No `TYPE_MAP` — each L1 owns its
shape.
3. **Plan**`terraform plan` against the live AWS account. Infracost
runs on the plan JSON and emits `nova.cost.estimated`.
4. **Policy** — the kyverno-json engine evaluates the meta-policies
(`block-on-any-critical` + the pilot policies) and emits
`PolicyCheckResult` records.
5. **Confidence** — the confidence signal consumes the six inputs (the
PCRs included) and emits `nova.confidence.computed` with
`{ score, band, perInput, reasonCodes }`. Dev threshold = 0.50.
6. **Apply** (dev, autonomous — no HITL gate) — `terraform apply`
against account `581513795199`. On success, `nova.ai.decision.made`
+ `nova.run.completed` land in the Decision Ledger.
7. **Backfill** — the outcome (`pending → succeeded`) is backfilled
(REQ-317), producing `nova.outcome.backfilled`. The SQLite
hash-chain is extended, not torn up.
The consumer does not see steps 17 directly; the consumer sees the
workflow's green check + the uploaded artifacts (`nova-terraform`,
`nova-platform-log`).
---
## 5. How to verify post-deploy
Two independent verifications — read the AWS API and read the Decision
Ledger. Neither trusts the other.
**AWS API (the infrastructure landed):**
- `aws elbv2 describe-load-balancers` — the ALB
(`app-254671247.us-east-1.elb.amazonaws.com` for the pilot).
- `aws ecs describe-services --cluster nova-cluster --services
nova-microservice` — the ECS service is `ACTIVE`.
- `aws dynamodb describe-table --table-name nova-blkex-ledger-dev`
the ledger table exists (PK `block_index`, PAY_PER_REQUEST).
- `aws s3api head-bucket --bucket
nova-blkex-blocks-dev-581513795199-us-east-1` — the block bucket
exists (versioning + SSE).
**Decision Ledger (the trust record):**
- The SQLite hash-chain at `metrics/decision_ledger.db` has the
`nova.ai.decision.made` row for `blkex-pilot-apply-v0.2` (chosen
action `pass`, `human_override` false) + the
`nova.outcome.backfilled` row (outcome `pending → succeeded`).
- The chain is valid (`prev_event_hash` links, 0 breaks). The
Trust Snapshot (`metrics/TRUST_SNAPSHOT.md`) records the verdict.
If the AWS API shows the resources AND the Decision Ledger shows the
decision + outcome with a valid chain, the deploy is verified. See
`.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` for the full pilot-evidence
checklist (every ARN, the confidence JSON, the backfill timestamp; archived v1.27).
---
## References
- `.ciagent/ARCHITECTURE.md` §12.8 — the pilot-estate architecture
(this guide is the consumer-facing companion to that section).
- `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` — the live-pilot evidence
(run `blkex-pilot-apply-v0.2`; archived v1.27).
- `.ciagent/nova-blockchain-exchange/PROJECT.md` — the consumer
project charter (vision, scope, decisions D-200..D-205).
- `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md` — the consumer
requirements (REQ-313 contract, REQ-314 deploy invocation).
- `adapters/README.md` §Consumers — the Gitea adapter note
(SPEC §10 Q1 — inline checkout-then-call, no cross-repo `uses:`).
@@ -1,221 +0,0 @@
# Requirements — nova-blockchain-exchange (v1.26 pilot)
> **Project:** nova-blockchain-exchange — blockchain stock exchange (pilot)
> **Milestone:** v1.26 — Live Pilot Estate Activation
> **Scope:** equities only; minimal PoA ledger; T+1 settlement finality.
---
## v1.26 — Live Pilot Estate Activation
### REQ-310 — Homegrown PoA blockchain core
The consumer repo implements a minimal Proof-of-Authority blockchain:
append-only blocks, single validator (pilot), SHA-256 block hash chain,
deterministic block production (same ordered transactions → same block).
The chain records every order, match, and settlement as transactions.
Settlement finality = block commit (a transaction is final when its
block is committed to the chain).
**Must-haves:**
- `chain/block.py` — Block dataclass (index, timestamp, prev_hash,
transactions, nonce, hash). `compute_hash()` deterministic.
- `chain/ledger.py` — Ledger class: `append_block()`, `verify_chain()`,
`get_block(index)`, `get_latest_block()`. Genesis block on init.
- `chain/validator.py` — PoA validator: single validator (config-driven,
pilot), `propose_block(transactions)` → Block, `commit_block(block)`.
- `tests/test_block.py`, `tests/test_ledger.py`, `tests/test_validator.py`
— chain integrity, hash determinism, genesis, append/verify.
### REQ-311 — Order-matching engine
A limit-order-book matching engine: buy/sell orders with price + size,
matched at the best price (price-time priority). Produces match
transactions recorded on the chain.
**Must-haves:**
- `engine/order_book.py` — OrderBook: `add_order(order)`,
`match_orders()` → list of Match (buyer, seller, price, size).
- `engine/order.py` — Order dataclass (id, side, symbol, price, size,
timestamp).
- `tests/test_order_book.py` — match priority, partial fills, no-match.
### REQ-312 — Settlement service
T+1 settlement: matches commit to the chain; a settlement is final when
its block is committed. The service reads matches from the order engine,
produces settlement transactions, and submits them to the ledger.
**Must-haves:**
- `settlement/service.py` — SettlementService: `settle(match)`
SettlementTransaction, `submit(ledger)`. Idempotent (re-settling a
match is a no-op once final).
- `tests/test_settlement.py` — happy path, idempotency, finality check.
### REQ-313 — Consumer `contract.yaml` ✓ complete (P2, v1.25.2)
The consumer repo declares its infrastructure via a `contract.yaml` at
the repo root, validated against `schemas/contract.schema.json`. The
contract references the Nova platform's deploy workflow
(`uses: acdl/.github/workflows/deploy.yml@v1.25`) and declares the
blockchain exchange stack (the AWS resources the app needs: ECS for
the matching engine, DynamoDB for the ledger, S3 for block storage).
The DynamoDB L1 primitive (REQ-322) must land before this contract can
declare `dynamodb` — ECS + S3 already exist.
**Must-haves:**
- `contract.yaml` — id, name (`blockchain-exchange`), environment
(dev/qa/prod variants), infrastructure block.
- `contracts/blockchain-exchange.dev.yml`, `.qa.yml`, `.prod.yml`
per-environment variants (per-env promotion model, REQ-105).
- `tests/test_contract_validates.py` — schema validation against the
platform's `schemas/contract.schema.json`.
### REQ-314 — Consumer deploy workflow invocation ✓ complete (P2, v1.25.2)
The consumer repo's GitHub/Gitea Actions invoke the Nova platform's
reusable `deploy.yml@v1.25` workflow with `mode: full` for the pilot.
The workflow checks out the consumer repo + the platform repo, runs
`scripts/run_platform.sh`, and records the apply decision + attestation
in the Nova Decision Ledger.
**Must-haves:**
- `.github/workflows/deploy.yml``uses: acdl/.github/workflows/deploy.yml@v1.25`
with `with: { contract: contract.yaml, mode: full, environment: dev }`.
- `.gitea/workflows/deploy.yml` — byte-identical mirror (the platform's
deploy workflow is forge-agnostic).
- `tests/test_deploy_workflow_invocation.py` — asserts the `uses:` ref
+ inputs are correct.
### REQ-315 — Settlement-finality kyverno-json policy (IDEATE I6)
A kyverno-json policy asserting that every promotion (qa→prod) requires
settlement finality: all matches in the promotion window have committed
blocks. This is the securities-specific extension of v1.25's policy
engine — it applies Nova's compliance posture to the blockchain domain.
**Must-haves:**
- `policies/settlement-finality.json` — kyverno-json policy over the
settlement-service status JSON (asserts `all_committed: true`).
- `tests/test_settlement_finality_policy.py` — passing + failing
fixtures; skip when `kj` absent.
### REQ-316 — Pilot-estate regression capability (CAP-025)
A new capability in the regression gate: "pilot estate apply→attest→record
round-trip." The regression gate asserts that the consumer estate can
run end-to-end (contract resolve → adapter compile → terraform plan →
policy scan → confidence signal → attestation → outbox record) against
the live AWS account `581513795199`.
**Must-haves:**
- `core/regression_verify.py` gains CAP-025 (live-pilot-apply).
- `tests/test_regression_pilot.py` — the round-trip assertion.
### REQ-317 — Outcome-backfill emitter (IDEATE I1)
Wire `apply.completed` / `apply.failed` events back into `fact_decision`
in the cold store so the AI Decision Accuracy metric has a non-`pending`
outcome. Today `fact_decision.outcome` is stuck at `pending` (D-096
blocker). The backfill emitter reads `run_manifest.completed/failed`
events and updates the corresponding decision's outcome.
**Must-haves:**
- `core/metrics/outcome_backfill.py``backfill(decision_id, outcome)`
updates `fact_decision.outcome` + `fact_decision.backfilled_at`.
- `core/metrics/collector.py` — invokes backfill after run completion.
- `tests/test_outcome_backfill.py`.
### REQ-318 — `reason='confidence'` escalation tag (IDEATE I2)
Emit a distinct `reason='confidence'` field on the `block` band's
`ai.decision.made` event so the Human Escalation Frequency metric has a
discriminated numerator. Today `hitl_block` is a boolean from the
manifest; the `reason` discriminator is not stored.
**Must-haves:**
- `core/confidence_signal.py``ai.decision.made` gains
`escalation_reason: 'confidence'` when `band == 'block'`.
- `core/metrics/collector.py` — persists `escalation_reason` into
`fact_run`.
- `tests/test_confidence_escalation_reason.py`.
### REQ-319 — Env-JSON `state_backend` wiring reconciliation (IDEATE I3)
The env JSON's `state_backend.bucket` field is currently unused by the
adapter (the adapter computes `nova-tfstate-<AWS_ACCOUNT_ID>` directly).
Reconcile: the adapter reads `state_backend.bucket` from the env JSON
(falling back to the computed name for backwards compat). This closes
the wiring gap so the pilot's env JSON is the single source of truth.
**Must-haves:**
- `adapters/terraform/adapter.py` — reads `env.state_backend.bucket`
when present.
- `tests/test_adapter_state_backend.py`.
- `core/environments/*.json``state_backend.bucket` updated to the
real bucket name `nova-tfstate-581513795199-us-east-1`.
### REQ-320 — Declarative pilot-readiness kyverno-json policy (IDEATE I5)
A kyverno-json policy asserting the env JSON has a non-placeholder
`account_id` (not `000000000000`) before any `terraform apply`. This is
the declarative gate that prevents a pilot run against a placeholder
account.
**Must-haves:**
- `adapters/kyverno-json/policies/pilot-readiness/no-placeholder-account.json`
- `tests/test_pilot_readiness_policy.py`.
### REQ-321 — Docs + adapter README for the consumer estate
Update `adapters/README.md` (new consumer row), `docs/METRICS.md` (the
3 Post-Pilot metrics now grounded post-pilot), `.ciagent/ARCHITECTURE.md`
(§12.8 — Pilot Estate), and `.ciagent/nova-blockchain-exchange/README.md`
(consumer onboarding guide).
**Must-haves:**
- `adapters/README.md` — consumer-repo row.
- `docs/METRICS.md` — Post-Pilot metrics grounded note.
- `.ciagent/ARCHITECTURE.md` — §12.8 Pilot Estate.
- `.ciagent/nova-blockchain-exchange/README.md` — onboarding guide.
### REQ-322 — DynamoDB L1 primitive (platform-side) ✓ complete (P2, v1.25.2)
The blockchain exchange's ledger table needs a DynamoDB L1 primitive.
Research (RESEARCH §3) confirmed the adapter is stateless/registry-
driven (no `TYPE_MAP` — deleted in v1.11); a new stack type requires a
new L1 module, not an adapter change. The `dynamodb` primitive mirrors
the existing `s3` / `rds` primitives: `interface.json` (stack type
`aws:dynamodb:table`, inputs `table_name`/`region`/`pk`/`sk`/`billing_mode`,
outputs `table_arn`/`table_name`), `terraform/main.tf`
(`resource "aws_dynamodb_table" "this"`), `README.md`, `instance.json`,
+ a `registry.json` entry. The pilot contract's `infrastructure.dynamodb`
block references this primitive. This is the single platform-side
module build-out for the milestone (ECS + S3 already exist).
**Must-haves:**
- `modules/l1/dynamodb/interface.json` — stack type
`aws:dynamodb:table`, inputs, outputs.
- `modules/l1/dynamodb/terraform/main.tf`
`resource "aws_dynamodb_table" "this"` (PK + optional SK,
`billing_mode = PAY_PER_REQUEST` default, encryption + point-in-time-
recovery enabled per v1.8 NFR defaults).
- `modules/l1/dynamodb/README.md` — module doc.
- `modules/l1/dynamodb/instance.json` — sample instance.
- `modules/registry.json``dynamodb` entry (kind `l1`,
`terraform_dir: modules/l1/dynamodb/terraform`).
- `tests/test_adapter.py` — add `dynamodb` to `EXPECTED_L1_KEYS` +
a resolution + emission test.
- `modules/README.md` — catalog index updated.
### Summary
13 requirements (REQ-310..322). Equities-only pilot; minimal PoA ledger;
T+1 settlement; consumer deploy via `deploy.yml@v1.25`; 3 Post-Pilot
metrics grounded (outcome backfill + escalation reason + pilot runs);
3 kyverno-json policies extending v1.25 (settlement-finality,
pilot-readiness, + the existing meta-policies apply); env-JSON wiring
reconciled; DynamoDB L1 primitive authored (the single platform-side
module build-out — the adapter is stateless/registry-driven, so the
primitive is a new `modules/l1/dynamodb/` module + registry entry, not
an adapter change).
@@ -1,58 +0,0 @@
# Roadmap — nova-blockchain-exchange (v1.26 pilot)
> **Project:** nova-blockchain-exchange — blockchain stock exchange (pilot)
> **Milestone:** v1.26 — Live Pilot Estate Activation
---
## v1.26 — Live Pilot Estate Activation (active)
Lift D-096 (live AWS re-provisioning); activate the first real consumer
estate (a stock exchange on a homegrown PoA blockchain, equities only)
against live AWS account `581513795199`; ground the three Post-Pilot
targets in NORTH_STAR.md (Touchless Resolution ≥99%, Human Escalation
<0.1%, AI Decision Accuracy ≥99.5%). The platform repo (`acdl`) provides
the deploy workflow, policy engine, and attestation gates; this repo
provides the app (blockchain + matching engine + settlement) + the
`contract.yaml`.
Tags run on the **v1.25.x** patch line: `v1.25.0` (P0) → `v1.25.N`
(final phase = milestone release).
### Phase P1 — blockchain-core (planned, tag v1.25.1)
- REQ-310: Homegrown PoA blockchain core (block, ledger, validator).
- REQ-311: Order-matching engine (limit order book, price-time priority).
- REQ-312: Settlement service (T+1, idempotent, finality = block commit).
### Phase P2 — consumer-contract-and-deploy (complete, tag v1.25.2)
- REQ-313: Consumer `contract.yaml` + per-env variants. ✓
- REQ-314: Consumer deploy workflow invocation (`deploy.yml@v1.25`). ✓
- REQ-322: DynamoDB L1 primitive (platform-side, P2 W0). ✓
### Phase P3 — pilot-metrics-and-policies (planned, tag v1.25.3)
- REQ-315: Settlement-finality kyverno-json policy.
- REQ-316: Pilot-estate regression capability (CAP-025).
- REQ-317: Outcome-backfill emitter.
- REQ-318: `reason='confidence'` escalation tag.
- REQ-319: Env-JSON `state_backend` wiring reconciliation.
- REQ-320: Declarative pilot-readiness kyverno-json policy.
### Phase P4 — pilot-run-and-docs (planned, tag v1.25.4)
- REQ-321: Docs + adapter README + onboarding guide.
- Live pilot end-to-end run (apply → attest → record) against
`581513795199`.
### Phase P5 — final review + audit + milestone ship (Final Phase, tag v1.25.5)
- Multi-persona code review across P1..P4.
- Audit: reconstruction test, branch hygiene, commit discipline.
- Milestone ship: merge `phase/05``milestone/v1.26-pilot-activation`
`main`; tag `v1.25.5` (= the v1.26 release per prev-minor tagging
rule); create Gitea release with full milestone summary; delete all
milestone branches.
- Update `REQUIREMENTS.md` (mark REQ-310..321 complete), `ROADMAP.md`
(mark v1.26 complete), `NORTH_STAR.md` (note Strategic Objectives #1
+ #3 — first real consumer estate; Post-Pilot denominators activated).
After v1.26: future milestones may add bonds/derivatives/options
(different settlement models), multi-validator BFT consensus, and
tamper-evident ledger (D-083 lift).
-24
View File
@@ -1,24 +0,0 @@
=== tools ===
terraform: /usr/bin/terraform
checkov: /usr/local/bin/checkov
python3: /usr/bin/python3
jq: /usr/bin/jq
rsync: /usr/bin/rsync
marp: MISSING
mmdc: MISSING
Terraform v1.9.8
3.3.8
Python 3.12.3
=== chrome/chromium (for slide render) ===
found: /root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome
=== creds ===
.env.secrets: present (4 lines)
.env: present
=== aws creds loadable? ===
NOVA_AWS_ACCESS_KEY_ID: set
AWS_DEFAULT_REGION: us-east-1
=== git ===
main
v1.18.1-11-gaa868c9
=== disk ===
/dev/loop2 148G 140G 1.3G 100% /
-10
View File
@@ -1,10 +0,0 @@
{"id": "T1", "req": "REQ-230", "title": "no forge names in synced files (guard test)", "pass": true, "rc": 0, "evidence": {"test": "test_no_forge_mentions_in_synced_files", "result": "1 passed in 2.20s", "log_tail": ["tests/test_no_forge_mentions.py::test_no_forge_mentions_in_synced_files PASSED [100%]", "1 passed in 2.20s"]}}
{"id": "T2", "req": "REQ-230", "title": "forge-detection code genericized", "pass": true, "rc": 0, "evidence": {"hardcoded_gitea_gitlab_hits": 0, "genericization_signals": ["contract_ingestor.py: _forge_type() returns 'generic_forge'", "hitl_gates.py: GITHUB_ACTOR or FORGE_ACTOR (no GITEA_ACTOR)", "run_platform.sh:166: GITHUB_ACTOR:-FORGE_ACTOR fallback"]}}
{"id": "T3", "req": "REQ-231", "title": "synced docs stripped of internal provenance", "pass": false, "rc": 1, "evidence": {"provenance_hit_count": 40, "contaminated_files": ["docs/ONBOARDING.md (REQ-182,183,184; D-113,114,119)", "docs/METRICS.md (REQ-191,192,193,194,211,212; D-083,096,113,114,119)", "docs/presentations/README.md (REQ-214,226,228; D-130,141; .ciagent/PROJECT.md)", "docs/presentations/nova-no-humans-platform.{md,marp.md,html,talking-points.md} (v1.X milestone headers)", "docs/presentations/assets/mmd/developer-experience-08-semver.mmd (v1.12 header)"], "root_cause": "test_no_forge_mentions.py only guards forge names, not provenance IDs", "defect": "F7"}}
{"id": "T4", "req": "REQ-232", "title": "migration docs removed + thesis moved", "pass": true, "rc": 0, "evidence": {"docs_NOVA_MIGRATION_gone": true, "docs_NOVA_AWS_MIGRATION_gone": true, "docs_NO_HUMANS_THESIS_gone": true, "ciagent_NO_HUMANS_THESIS_present": true}}
{"id": "T5", "req": "REQ-239", "title": "S&P theme CSS palette on all chrome", "pass": true, "rc": 0, "evidence": {"css_exists": true, "css_size_bytes": 2914, "red_present": true, "black_present": true, "white_present": true, "chrome_covered": ["section/bg", "section.title", "h1-h3 headings", "table th", "blockquote", "pre/code", "header", "footer", "pagination (.bespoke-progress-bar)", "strong"]}}
{"id": "T6", "req": "REQ-240", "title": "render pipeline script + mermaid theme", "pass": true, "rc": 0, "evidence": {"render_slides_executable": true, "render_slides_size": 2736, "sp_theme_json_has_red": true, "sp_theme_json_has_black": true, "render_deck_sh_still_present": true, "render_deck_excluded_from_sync": true, "caveat": "README:107 still references render_deck.sh (deferred to T9)"}}
{"id": "T7", "req": "REQ-241", "title": "slides CI workflow path trigger", "pass": false, "rc": 1, "evidence": {"wrong_path_hits": [".github/workflows/slides.yml:8: - 'assets/nova-sp-theme.css' (non-existent)", "workflows-src/slides.yml:8: - 'assets/nova-sp-theme.css' (non-existent)"], "correct_path": "docs/presentations/assets/nova-sp-theme.css", "src_dotgithub_identical": true, "defect": "F6", "impact": "Explicit CSS path trigger points at nothing; only the docs/presentations/** glob catches CSS edits. Dead entry should be corrected or removed."}}
{"id": "T8", "req": "REQ-242", "title": "slide-pipeline guard test", "pass": true, "rc": 0, "evidence": {"passed": 12, "failed": 0, "duration_s": 1.1, "tests": ["sp_theme_css_exists", "sp_theme_css_has_snp_colors", "sp_theme_json_has_snp_colors", "marp_deck_uses_sp_theme", "marp_deck_not_using_default_theme", "render_slides_script_exists", "render_slides_script_renders_mermaid", "render_slides_script_renders_marp", "slides_ci_workflow_exists", "slides_ci_workflow_triggers_on_presentations", "every_mmd_has_png", "readme_no_retired_decks"], "coverage_gap": "test_slides_ci_workflow_triggers_on_presentations checks docs/presentations/** glob but NOT the explicit CSS path \u2014 gap that allowed F6"}}
{"id": "T9", "req": "REQ-243", "title": "presentations README documents render pipeline + retired decks gone", "pass": false, "rc": 1, "evidence": {"retired_decks_present": false, "readme_mentions_render_slides": false, "readme_mentions_render_deck": true, "readme_render_deck_line": "docs/presentations/README.md:107: 'automated by scripts/render_deck.sh'", "readme_mentions_theme_css": true, "defect": "F10", "impact": "README documents the retired render_deck.sh pipeline, not the active render_slides.sh. Consumers reading synced README reference a script excluded from sync."}}
{"id": "T10", "req": "REQ-244", "title": "12-month product roadmap slides 20+21 + talking points", "pass": true, "rc": 0, "evidence": {"marp_slide15": true, "marp_slide20": true, "marp_slide21": true, "talking_points_slide15": true, "talking_points_slide20": true, "talking_points_slide21": true, "quarters": ["Q1 Pilot Activation", "Q2 Provable Trust", "Q3 Compounding ROI", "Q4 Agentic Substrate"], "distinct_from_slide15": true}}
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova CI Pipeline (dev environment)
# ACDL CI Pipeline — Gitea Actions (dev environment)
#
# This workflow implements the central pipeline contract:
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
+16 -19
View File
@@ -1,4 +1,4 @@
# Nova Reusable Deploy Workflow (dev environment)
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
#
# This reusable workflow implements the central deployment pipeline contract:
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
@@ -8,7 +8,7 @@
# declared difference is the forge/runtime, not the stages or commands.
#
# Consumer repos invoke this workflow via a versioned tag (floating MAJOR + MINOR):
# uses: nova/.github/workflows/deploy.yml@v1.19
# uses: acdl/.gitea/workflows/deploy.yml@v1.9 (Gitea)
# uses: acdl/.github/workflows/deploy.yml@v1.9 (GitHub)
#
# Unversioned references (@main, bare) are discouraged — the consumer's setup
@@ -26,7 +26,7 @@
# platform log) for auditability.
#
# Inputs:
# contract — path to the consumer's contract YAML (default .nova/contract.yml)
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
# mode — full | plan-only | check-only (default full; dev = full apply,
# higher environments hold for HITL — the calling repo or the
# forge environment gate enforces that)
@@ -38,12 +38,12 @@
# that matches repo:org/consumer-repo:ref:refs/heads/main, and the session
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
#
# Override (where OIDC is unavailable, e.g. pending
# upstream forge OIDC support): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
# Override (where OIDC is unavailable, e.g. Gitea pending
# go-gitea/gitea#36988): set ACDL_AWS_ACCESS_KEY_ID + ACDL_AWS_SECRET_ACCESS_KEY
# as repository secrets. The platform-managed scheduled pipeline rotates
# the key on a daily cadence. When .env.secrets is used locally instead,
# rotating the key out of band is the consumer's responsibility.
name: nova-deploy
name: acdl-deploy
on:
workflow_call:
@@ -51,7 +51,7 @@ on:
contract:
description: Path to the consumer contract YAML (in the consumer repo)
type: string
default: .nova/contract.yml
default: .acdl/contract.yml
mode:
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
type: string
@@ -82,7 +82,7 @@ jobs:
with:
repository: acdl/acdl
path: platform
ref: v1.25
ref: v1.9
- uses: actions/setup-python@v5
with:
@@ -102,16 +102,13 @@ jobs:
- name: Configure AWS credentials (OIDC default + static-key override)
uses: aws-actions/configure-aws-credentials@v4
with:
# P4 (REQ-163): IAM role renamed acdl-deploy- → nova-deploy-.
role-to-assume: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/nova-deploy-{1}', secrets.NOVA_AWS_ACCOUNT_ID, github.repository_id) || '' }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.ACDL_AWS_ACCOUNT_ID, github.repository_id) || '' }}
aws-region: us-east-1
access-key-id: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
- name: Run the platform pipeline
working-directory: ${{ github.workspace }}
env:
NOVA_CONSUMER_REPO: ${{ github.repository }}
run: |
MODE_FLAG=""
case "${{ inputs.mode }}" in
@@ -148,7 +145,7 @@ jobs:
AWS_DEFAULT_REGION: us-east-1
run: |
aws lambda invoke-function-url \
--function-url "${{ secrets.NOVA_LAMBDA_URL }}" \
--function-url "${{ secrets.ACDL_LAMBDA_URL }}" \
--cli-binary-format raw-in-base64-out \
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
/dev/null || true
@@ -156,13 +153,13 @@ jobs:
- name: Upload emitted Terraform
uses: actions/upload-artifact@v4
with:
name: nova-terraform
path: /tmp/nova_platform_run/tf/*.tf
name: acdl-terraform
path: /tmp/acdl_platform_run_v18/tf/*.tf
if-no-files-found: warn
- name: Upload platform log
uses: actions/upload-artifact@v4
with:
name: nova-platform-log
name: acdl-platform-log
path: platform/logs/
if-no-files-found: warn
+29 -29
View File
@@ -1,4 +1,4 @@
# Nova Modules Lifecycle Pipeline (dev environment)
# ACDL Modules Lifecycle Pipeline — Gitea Actions (dev environment)
#
# Matrix-runs each L1 module's examples/{simple,complex}.yml contracts through
# apply→modify→destroy against live AWS. No per-module Python. The "test" =
@@ -9,13 +9,13 @@
# terraform files); the composition must be deterministic.
#
# This workflow implements pipelines/modules-lifecycle.yml (byte-identical
# in .github/workflows/).
# in .gitea/workflows/ and .github/workflows/).
#
# Lifecycle mode (REQ-134, v1.12): the `lifecycle_mode` input defaults to
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
# no AWS mutation, validates the contract->resolver->adapter->plan chain
# for every module on every PR, with no AWS credentials or cost). Set to
# "full" via workflow_dispatch (or the NOVA_LIFECYCLE_MODE repo variable)
# "full" via workflow_dispatch (or the ACDL_LIFECYCLE_MODE repo variable)
# to run the real apply→modify→destroy against live AWS. In plan mode the
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
#
@@ -49,7 +49,7 @@ jobs:
ci-vpc-apply:
name: CI VPC apply
runs-on: ubuntu-latest
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Install Terraform 1.9.*
@@ -60,8 +60,8 @@ jobs:
- name: Apply CI VPC
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
@@ -78,7 +78,7 @@ jobs:
matrix:
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
env:
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Free disk space
@@ -97,31 +97,31 @@ jobs:
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
- name: Read CI VPC outputs
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
terraform output -json > /tmp/ci-vpc-outputs.json
- name: Apply (simple)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
- name: Modify (complex)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
- name: Destroy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
@@ -136,7 +136,7 @@ jobs:
matrix:
module: [static-assets, microservice]
env:
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Free disk space
@@ -155,31 +155,31 @@ jobs:
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
- name: Read CI VPC outputs
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
terraform output -json > /tmp/ci-vpc-outputs.json
- name: Apply (simple)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
- name: Modify (complex)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
- name: Destroy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
@@ -188,7 +188,7 @@ jobs:
name: CI VPC destroy
needs: [lifecycle, l2-lifecycle]
runs-on: ubuntu-latest
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Install Terraform 1.9.*
@@ -199,8 +199,8 @@ jobs:
- name: Destroy CI VPC
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
-165
View File
@@ -1,165 +0,0 @@
# Nova Publish Pipeline — wheel + Lambda layer (REQ-323, CAP-035, NFR-6)
#
# This workflow is byte-identical across the production forge (GitHub
# Actions) and the dev forge (act_runner) — the same file is installed
# at .github/workflows/publish.yml and the mirror at
# <dev-forge>/workflows/publish.yml. Both copies must match exactly
# (asserted by tests/test_forge_action_byte_identical.py for the action
# and by the repo's byte-identical convention for workflows).
#
# NFR-6 (wheel/layer co-versioning): every merge to main affecting
# core/**, adapters/**, nova/**, or pyproject.toml publishes BOTH a
# wheel AND a Lambda layer with identical version strings. If either
# publish fails, the job fails and the merge is blocked.
#
# REQ-323: CodeArtifact wheel + Lambda layer pipeline.
# CAP-035: Lambda layer ARN version matches the nova-cli wheel version;
# the mapping is recorded in SSM /nova/layer/nova-cli/version.
#
# Triggers:
# - push to main when core/**, adapters/**, nova/**, or pyproject.toml
# changed (the surfaces that ship in the wheel + layer)
# - workflow_dispatch (manual republish, e.g. after a CodeArtifact
# provisioning fix)
#
# Wheel index selection (CodeArtifact default + fallback):
# - CodeArtifact mode: set the NOVA_CODEARTIFACT_DOMAIN repository
# secret (e.g. "nova"). The workflow runs
# `aws codeartifact login --tool twine --domain $NOVA_CODEARTIFACT_DOMAIN
# --repository nova-pypi` and twine uploads to the CodeArtifact pypi
# endpoint.
# - Fallback mode: leave NOVA_CODEARTIFACT_DOMAIN unset and provide
# TWINE_REPOSITORY_URL + TWINE_USERNAME + TWINE_PASSWORD repository
# secrets pointing at any PEP 503 simple index (a private package
# registry). twine uploads to TWINE_REPOSITORY_URL.
# See docs/codeartifact-provisioning.md for the required IAM grants
# + the fallback index shape.
#
# Secrets / env:
# AWS_ROLE_ARN — OIDC role to assume (id-token: write)
# NOVA_CODEARTIFACT_DOMAIN — optional; when set, CodeArtifact mode
# TWINE_USERNAME — fallback-index upload user
# TWINE_PASSWORD — fallback-index upload password
# TWINE_REPOSITORY_URL — fallback-index upload URL
# AWS_DEFAULT_REGION (optional) — defaults to us-east-1
name: nova-publish
on:
push:
branches: [main]
paths:
- "core/**"
- "adapters/**"
- "nova/**"
- "pyproject.toml"
workflow_dispatch:
permissions:
id-token: write # OIDC federation to AWS
contents: write # tag the release
jobs:
publish:
name: Publish wheel + Lambda layer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
- name: Install build + publish tools
run: pip install build twine
- name: Compute version from pyproject.toml
id: ver
run: |
set -e
VERSION=$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Nova version: $VERSION"
- name: Build wheel
run: |
set -e
python -m build --wheel
ls -1 dist/
- name: Upload wheel to index (CodeArtifact default + fallback)
id: wheel
env:
NOVA_CODEARTIFACT_DOMAIN: ${{ secrets.NOVA_CODEARTIFACT_DOMAIN }}
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
TWINE_REPOSITORY_URL: ${{ secrets.TWINE_REPOSITORY_URL }}
run: |
set -e
# CodeArtifact mode: log in to the domain's pypi repository.
if [ -n "$NOVA_CODEARTIFACT_DOMAIN" ]; then
echo "CodeArtifact mode: domain=$NOVA_CODEARTIFACT_DOMAIN repository=nova-pypi"
aws codeartifact login --tool twine \
--domain "$NOVA_CODEARTIFACT_DOMAIN" --repository nova-pypi
else
echo "Fallback-index mode: uploading to TWINE_REPOSITORY_URL"
if [ -z "$TWINE_REPOSITORY_URL" ] || [ -z "$TWINE_USERNAME" ] || [ -z "$TWINE_PASSWORD" ]; then
echo "FAIL: NOVA_CODEARTIFACT_DOMAIN is unset and one of TWINE_REPOSITORY_URL/TWINE_USERNAME/TWINE_PASSWORD is missing."
exit 1
fi
fi
# Idempotent upload: a re-run for the same version may hit
# "file already exists" on the index. Treat that as success.
twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
|| twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" 2>&1 | tee /tmp/twine.log
if grep -qi "already exist" /tmp/twine.log 2>/dev/null; then
echo "Wheel already present on the index — treating as success (idempotent)."
fi
echo "uploaded=true" >> "$GITHUB_OUTPUT"
- name: Build Lambda layer
run: |
set -e
rm -rf layer
mkdir -p layer/python
# Install the wheel we just built + the identity extras' deps
# so the layer carries argon2-cffi, cryptography, pyjwt.
pip install --target layer/python/ \
"dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
argon2-cffi cryptography pyjwt
( cd layer && zip -r ../nova-layer.zip python/ )
ls -lh nova-layer.zip
- name: Publish Lambda layer
id: layer
run: |
set -e
ARN=$(aws lambda publish-layer-version \
--layer-name nova-cli \
--zip-file fileb://nova-layer.zip \
--compatible-runtimes python3.12 \
--compatible-architectures x86_64 \
--description "nova-cli v${{ steps.ver.outputs.version }}" \
--query LayerVersionArn --output text)
echo "arn=$ARN" >> "$GITHUB_OUTPUT"
echo "Published Lambda layer: $ARN"
- name: Record SSM version↔ARN mapping (CAP-035)
run: |
set -e
aws ssm put-parameter \
--name /nova/layer/nova-cli/version \
--value "${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}" \
--type String --overwrite
echo "SSM /nova/layer/nova-cli/version = ${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}"
- name: Fail job if either publish failed (REQ-323 AC)
if: ${{ steps.wheel.outputs.uploaded != 'true' || steps.layer.outputs.arn == '' }}
run: |
echo "FAIL: wheel uploaded=${{ steps.wheel.outputs.uploaded }} layer_arn=${{ steps.layer.outputs.arn }}"
exit 1
-69
View File
@@ -1,69 +0,0 @@
# Nova AWS key rotation — platform-managed scheduled pipeline (SPEC §5.9)
#
# Rotates the NOVA_AWS_* static key daily (no long-lived keys in the steady
# state). v0.2 scope: the mechanism must exist (SPEC §5.9); the v0.2 deploy
# uses the currently-active key. The rotation is best-effort + idempotent
# (scripts/rotate_spike_key.sh deactivates the old key only after the new
# key propagates to the consumer's Actions secret store).
#
# Auth: the rotation uses the CURRENT NOVA_AWS_* key to authenticate to IAM
# (the root account 581513795199 can rotate its own keys — confirmed by the
# bootstrap). The aws-actions/configure-aws-credentials@v4 step uses the
# static-key path (no OIDC role-to-assume); the long-lived key rotates
# itself, which is the bootstrap-exception documented in §5.9.
#
# Forge coords (base URL / owner / consumer repo) are sourced from
# repository secrets — NOVA_FORGE_BASE_URL, NOVA_FORGE_OWNER,
# NOVA_CONSUMER_REPO — so the synced workflow file stays forge-agnostic
# (REQ-230). The rotation script uploads the new key to the consumer's
# Actions secret store (the consumer whose deploy.yml consumes NOVA_AWS_*
# via secrets: inherit).
name: nova-rotate-aws-key
on:
schedule:
- cron: "0 0 * * *" # daily at 00:00 UTC
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
rotate:
name: Rotate NOVA_AWS_* static key
runs-on: ubuntu-latest
steps:
- name: Check out Nova platform repo
uses: actions/checkout@v4
- name: Configure AWS credentials (bootstrap root creds for IAM key rotation)
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
- name: Install Python deps (boto3 for the rotation script)
run: |
python3 -m pip install --break-system-packages --quiet boto3
- name: Run the key rotation script
env:
# aws-actions/configure-aws-credentials exports AWS_ACCESS_KEY_ID /
# AWS_SECRET_ACCESS_KEY; the rotation script reads the bootstrap
# creds via NOVA_BOOTSTRAP_AWS_* (its dual-read contract, D-034).
# Map the standard AWS_* exports onto the script's expected vars.
NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
# Forge + consumer coords come from repository secrets (REQ-230 —
# no forge hostnames/orgs hardcoded in the synced workflow file).
# NOVA_FORGE_TOKEN holds the forge API token (set equal to the
# existing forge token as a one-time secret setup).
NOVA_FORGE_TOKEN: ${{ secrets.NOVA_FORGE_TOKEN }}
NOVA_FORGE_BASE_URL: ${{ secrets.NOVA_FORGE_BASE_URL }}
NOVA_FORGE_OWNER: ${{ secrets.NOVA_FORGE_OWNER }}
NOVA_CONSUMER_REPO: ${{ secrets.NOVA_CONSUMER_REPO }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
run: |
bash scripts/rotate_spike_key.sh
-43
View File
@@ -1,43 +0,0 @@
# Nova Slides Render — re-renders presentation deck when source files change.
# REQ-273: install python-pptx, pin CLI versions, stage HTML + both PPTX +
# base64-inlined images.
name: Nova Slides Render
on:
push:
paths:
- 'docs/presentations/**'
- 'scripts/render_slides.sh'
- 'scripts/inline_images.py'
- 'scripts/render_pptx.py'
- 'pyproject.toml'
workflow_dispatch:
jobs:
render:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: '20' }
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install python-pptx (slides extra)
run: pip install -e ".[slides]"
- name: Install + pin render CLIs
run: |
npx --yes @marp-team/marp-cli@4.5.0 --version
npx --yes @mermaid-js/mermaid-cli@11.16.0 --version
- name: Render slides
run: bash scripts/render_slides.sh
- name: Commit rendered artifacts
run: |
git config user.name "nova-slides-bot"
git config user.email "bot@nova.local"
git add docs/presentations/*.html \
docs/presentations/*.pptx \
docs/presentations/*-python.pptx \
docs/presentations/assets/png/*.png
git diff --cached --quiet || git commit -m "chore(slides): re-render deck [skip ci]"
git push
-94
View File
@@ -1,94 +0,0 @@
# Nova CLI Action — composite action (REQ-326, NFR-11)
#
# Runs a Nova CLI command (`nova <command>`) in a consumer repository.
# Python 3.12 is pinned (REQ-326 AC3). The same action.yml is discovered
# by both the production forge (GitHub Actions) and the dev forge
# (act_runner) via the shared .github/actions/nova-cli/ path — there is
# no separate dev-forge action file. Consumers reference it via a
# versioned tag pin:
#
# uses: <org>/<repo>/.github/actions/nova-cli@v1.28
#
# Wheel index selection (CodeArtifact default + fallback):
# - CodeArtifact mode: set the NOVA_CODEARTIFACT_DOMAIN repository
# secret/env. The action runs
# `aws codeartifact login --tool pip --domain $NOVA_CODEARTIFACT_DOMAIN
# --repository nova-pypi` before `pip install nova`.
# - Fallback mode: leave NOVA_CODEARTIFACT_DOMAIN unset and provide
# NOVA_WHEEL_INDEX env pointing at any PEP 503 simple index (a
# private package registry). The action runs
# `pip install --index-url $NOVA_WHEEL_INDEX nova==<version>`.
# See docs/codeartifact-provisioning.md for the index shape.
#
# Byte-identical cross-platform verification (NFR-11, REQ-326 AC2):
# the full byte-identical test runs as a CI matrix job on the
# production forge (ubuntu-latest) + the dev forge (act_runner) with
# identical inputs, asserting same stdout + exit code. That matrix is
# not reproducible in a unit test; the structural invariants (valid
# YAML, python 3.12 pin, install + run steps present) are asserted by
# tests/test_forge_action_byte_identical.py.
name: "Nova CLI Action"
description: "Run a Nova CLI command (`nova <command>`) with Python 3.12 pinned"
inputs:
command:
description: "The Nova subcommand + args to run (e.g. `apply --local`, `init`, `idp setup --check-only`). Passed verbatim to `nova`."
required: true
contract:
description: "Path to the consumer contract YAML (default .nova/contract.yml). Forwarded to nova via the NOVA_CONTRACT env var."
required: false
default: ".nova/contract.yml"
mode:
description: "Nova client mode override (e.g. agent, interactive, plan-only, check-only). Forwarded to nova via the NOVA_CLIENT_MODE env var. Empty = let nova resolve (TTY + credentials)."
required: false
default: ""
version:
description: "nova package version to install (default `latest`). Pin to a released wheel version for reproducible runs."
required: false
default: "latest"
runs:
using: "composite"
steps:
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Nova (CodeArtifact default + fallback index)
shell: bash
env:
NOVA_CODEARTIFACT_DOMAIN: ${{ env.NOVA_CODEARTIFACT_DOMAIN }}
NOVA_WHEEL_INDEX: ${{ env.NOVA_WHEEL_INDEX }}
NOVA_INSTALL_VERSION: ${{ inputs.version }}
run: |
set -e
if [ "$NOVA_INSTALL_VERSION" = "latest" ]; then
PIP_SPEC="nova"
else
PIP_SPEC="nova==$NOVA_INSTALL_VERSION"
fi
if [ -n "$NOVA_CODEARTIFACT_DOMAIN" ]; then
echo "CodeArtifact mode: domain=$NOVA_CODEARTIFACT_DOMAIN repository=nova-pypi"
aws codeartifact login --tool pip \
--domain "$NOVA_CODEARTIFACT_DOMAIN" --repository nova-pypi
pip install $PIP_SPEC
else
echo "Fallback-index mode: NOVA_WHEEL_INDEX=$NOVA_WHEEL_INDEX"
if [ -z "$NOVA_WHEEL_INDEX" ]; then
echo "FAIL: NOVA_CODEARTIFACT_DOMAIN is unset and NOVA_WHEEL_INDEX is empty. Set one of them."
exit 1
fi
pip install --index-url "$NOVA_WHEEL_INDEX" $PIP_SPEC
fi
nova --version || true
- name: Run Nova
shell: bash
env:
NOVA_CLIENT_MODE: ${{ inputs.mode }}
NOVA_CONTRACT: ${{ inputs.contract }}
run: |
set -e
echo "nova ${{ inputs.command }}"
nova ${{ inputs.command }}
-45
View File
@@ -1,45 +0,0 @@
# GitHub Workflows — Nova Platform CI/CD Catalog
This directory contains the GitHub Actions workflows for the Nova
platform. 3 are generated from `workflows-src/<name>`; 4 are GitHub-only.
## Shared workflows (generated from source)
These 3 are generated from `workflows-src/<name>`. Run `python3 scripts/sync_workflows.py --check` to verify
no drift.
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|----------|---------|--------|------------------|---------|
| `ci.yml` | `pull_request: [main]` | — | — | Lint + test + check-only (runs on every PR) |
| `deploy.yml` | `workflow_call` (reusable) + `push: [main]` | `contract` (string, required), `mode` (string, default `deploy`), `changeRequestId` (string), `environment` (string) | `NOVA_AWS_ACCESS_KEY_ID`, `NOVA_AWS_SECRET_ACCESS_KEY`, `NOVA_AWS_DEFAULT_REGION`, `NOVA_KMS_KEY_ID`, `NOVA_LAMBDA_URL` | Reusable deploy workflow (invoked by consumer repos via `uses: nova/.github/workflows/deploy.yml@v1.19`) |
| `modules-lifecycle.yml` | `pull_request: [main]` + `workflow_dispatch` | `lifecycle_mode` (string, default `plan``plan` or `full`) | `NOVA_AWS_ACCESS_KEY_ID`, `NOVA_AWS_SECRET_ACCESS_KEY`, `NOVA_AWS_DEFAULT_REGION`, `NOVA_AWS_ACCOUNT_ID` | L1 + L2 module lifecycle pipeline (plan-only default; full apply/modify/destroy on override) |
## GitHub-only workflows
These 4 have no counterpart (the dev forge lacks the features
they require — reusable workflows, matrix `needs`, release API).
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|----------|---------|--------|------------------|---------|
| `platform-test.yml` | `pull_request: [main]` | — | — | Lint + unit + integration + schema-validation (replaces `ci.yml` for PRs) |
| `primitives-plan.yml` | `pull_request: [main]` | — | `NOVA_AWS_*` | Plan-only for all L1 primitives (matrix) |
| `patterns-plan.yml` | `pull_request: [main]` | — | `NOVA_AWS_*` | Plan-only for all L2 modules (matrix) |
| `release.yml` | `push: [main]` | — | `NOVA_RELEASE_TOKEN` | Semver tag + MAJOR.MINOR/MAJOR floating-tag maintenance + release creation on merge to main |
## Reusable deploy workflow (`deploy.yml`)
Consumer repos invoke the deploy workflow via a versioned tag:
```yaml
jobs:
deploy:
uses: nova/.github/workflows/deploy.yml@v1.19
with:
contract: .nova/contract.yml
environment: dev
secrets: inherit
```
The workflow checks out the consumer repo + the Nova platform repo, runs
`scripts/run_platform.sh`, and posts deploy outputs as a PR comment +
to SSM Parameter Store.
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova CI Pipeline (dev environment)
# ACDL CI Pipeline — Gitea Actions (dev environment)
#
# This workflow implements the central pipeline contract:
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
+16 -19
View File
@@ -1,4 +1,4 @@
# Nova Reusable Deploy Workflow (dev environment)
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
#
# This reusable workflow implements the central deployment pipeline contract:
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
@@ -8,7 +8,7 @@
# declared difference is the forge/runtime, not the stages or commands.
#
# Consumer repos invoke this workflow via a versioned tag (floating MAJOR + MINOR):
# uses: nova/.github/workflows/deploy.yml@v1.19
# uses: acdl/.gitea/workflows/deploy.yml@v1.9 (Gitea)
# uses: acdl/.github/workflows/deploy.yml@v1.9 (GitHub)
#
# Unversioned references (@main, bare) are discouraged — the consumer's setup
@@ -26,7 +26,7 @@
# platform log) for auditability.
#
# Inputs:
# contract — path to the consumer's contract YAML (default .nova/contract.yml)
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
# mode — full | plan-only | check-only (default full; dev = full apply,
# higher environments hold for HITL — the calling repo or the
# forge environment gate enforces that)
@@ -38,12 +38,12 @@
# that matches repo:org/consumer-repo:ref:refs/heads/main, and the session
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
#
# Override (where OIDC is unavailable, e.g. pending
# upstream forge OIDC support): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
# Override (where OIDC is unavailable, e.g. Gitea pending
# go-gitea/gitea#36988): set ACDL_AWS_ACCESS_KEY_ID + ACDL_AWS_SECRET_ACCESS_KEY
# as repository secrets. The platform-managed scheduled pipeline rotates
# the key on a daily cadence. When .env.secrets is used locally instead,
# rotating the key out of band is the consumer's responsibility.
name: nova-deploy
name: acdl-deploy
on:
workflow_call:
@@ -51,7 +51,7 @@ on:
contract:
description: Path to the consumer contract YAML (in the consumer repo)
type: string
default: .nova/contract.yml
default: .acdl/contract.yml
mode:
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
type: string
@@ -82,7 +82,7 @@ jobs:
with:
repository: acdl/acdl
path: platform
ref: v1.25
ref: v1.9
- uses: actions/setup-python@v5
with:
@@ -102,16 +102,13 @@ jobs:
- name: Configure AWS credentials (OIDC default + static-key override)
uses: aws-actions/configure-aws-credentials@v4
with:
# P4 (REQ-163): IAM role renamed acdl-deploy- → nova-deploy-.
role-to-assume: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/nova-deploy-{1}', secrets.NOVA_AWS_ACCOUNT_ID, github.repository_id) || '' }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.ACDL_AWS_ACCOUNT_ID, github.repository_id) || '' }}
aws-region: us-east-1
access-key-id: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
- name: Run the platform pipeline
working-directory: ${{ github.workspace }}
env:
NOVA_CONSUMER_REPO: ${{ github.repository }}
run: |
MODE_FLAG=""
case "${{ inputs.mode }}" in
@@ -148,7 +145,7 @@ jobs:
AWS_DEFAULT_REGION: us-east-1
run: |
aws lambda invoke-function-url \
--function-url "${{ secrets.NOVA_LAMBDA_URL }}" \
--function-url "${{ secrets.ACDL_LAMBDA_URL }}" \
--cli-binary-format raw-in-base64-out \
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
/dev/null || true
@@ -156,13 +153,13 @@ jobs:
- name: Upload emitted Terraform
uses: actions/upload-artifact@v4
with:
name: nova-terraform
path: /tmp/nova_platform_run/tf/*.tf
name: acdl-terraform
path: /tmp/acdl_platform_run_v18/tf/*.tf
if-no-files-found: warn
- name: Upload platform log
uses: actions/upload-artifact@v4
with:
name: nova-platform-log
name: acdl-platform-log
path: platform/logs/
if-no-files-found: warn
+29 -29
View File
@@ -1,4 +1,4 @@
# Nova Modules Lifecycle Pipeline (dev environment)
# ACDL Modules Lifecycle Pipeline — Gitea Actions (dev environment)
#
# Matrix-runs each L1 module's examples/{simple,complex}.yml contracts through
# apply→modify→destroy against live AWS. No per-module Python. The "test" =
@@ -9,13 +9,13 @@
# terraform files); the composition must be deterministic.
#
# This workflow implements pipelines/modules-lifecycle.yml (byte-identical
# in .github/workflows/).
# in .gitea/workflows/ and .github/workflows/).
#
# Lifecycle mode (REQ-134, v1.12): the `lifecycle_mode` input defaults to
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
# no AWS mutation, validates the contract->resolver->adapter->plan chain
# for every module on every PR, with no AWS credentials or cost). Set to
# "full" via workflow_dispatch (or the NOVA_LIFECYCLE_MODE repo variable)
# "full" via workflow_dispatch (or the ACDL_LIFECYCLE_MODE repo variable)
# to run the real apply→modify→destroy against live AWS. In plan mode the
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
#
@@ -49,7 +49,7 @@ jobs:
ci-vpc-apply:
name: CI VPC apply
runs-on: ubuntu-latest
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Install Terraform 1.9.*
@@ -60,8 +60,8 @@ jobs:
- name: Apply CI VPC
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
@@ -78,7 +78,7 @@ jobs:
matrix:
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
env:
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Free disk space
@@ -97,31 +97,31 @@ jobs:
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
- name: Read CI VPC outputs
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
terraform output -json > /tmp/ci-vpc-outputs.json
- name: Apply (simple)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
- name: Modify (complex)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
- name: Destroy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
@@ -136,7 +136,7 @@ jobs:
matrix:
module: [static-assets, microservice]
env:
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Free disk space
@@ -155,31 +155,31 @@ jobs:
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
- name: Read CI VPC outputs
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
terraform output -json > /tmp/ci-vpc-outputs.json
- name: Apply (simple)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
- name: Modify (complex)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
- name: Destroy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
@@ -188,7 +188,7 @@ jobs:
name: CI VPC destroy
needs: [lifecycle, l2-lifecycle]
runs-on: ubuntu-latest
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
steps:
- uses: actions/checkout@v4
- name: Install Terraform 1.9.*
@@ -199,8 +199,8 @@ jobs:
- name: Destroy CI VPC
working-directory: terraform/ci-vpc
env:
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
terraform init -input=false -lock=false
-165
View File
@@ -1,165 +0,0 @@
# Nova Publish Pipeline — wheel + Lambda layer (REQ-323, CAP-035, NFR-6)
#
# This workflow is byte-identical across the production forge (GitHub
# Actions) and the dev forge (act_runner) — the same file is installed
# at .github/workflows/publish.yml and the mirror at
# <dev-forge>/workflows/publish.yml. Both copies must match exactly
# (asserted by tests/test_forge_action_byte_identical.py for the action
# and by the repo's byte-identical convention for workflows).
#
# NFR-6 (wheel/layer co-versioning): every merge to main affecting
# core/**, adapters/**, nova/**, or pyproject.toml publishes BOTH a
# wheel AND a Lambda layer with identical version strings. If either
# publish fails, the job fails and the merge is blocked.
#
# REQ-323: CodeArtifact wheel + Lambda layer pipeline.
# CAP-035: Lambda layer ARN version matches the nova-cli wheel version;
# the mapping is recorded in SSM /nova/layer/nova-cli/version.
#
# Triggers:
# - push to main when core/**, adapters/**, nova/**, or pyproject.toml
# changed (the surfaces that ship in the wheel + layer)
# - workflow_dispatch (manual republish, e.g. after a CodeArtifact
# provisioning fix)
#
# Wheel index selection (CodeArtifact default + fallback):
# - CodeArtifact mode: set the NOVA_CODEARTIFACT_DOMAIN repository
# secret (e.g. "nova"). The workflow runs
# `aws codeartifact login --tool twine --domain $NOVA_CODEARTIFACT_DOMAIN
# --repository nova-pypi` and twine uploads to the CodeArtifact pypi
# endpoint.
# - Fallback mode: leave NOVA_CODEARTIFACT_DOMAIN unset and provide
# TWINE_REPOSITORY_URL + TWINE_USERNAME + TWINE_PASSWORD repository
# secrets pointing at any PEP 503 simple index (a private package
# registry). twine uploads to TWINE_REPOSITORY_URL.
# See docs/codeartifact-provisioning.md for the required IAM grants
# + the fallback index shape.
#
# Secrets / env:
# AWS_ROLE_ARN — OIDC role to assume (id-token: write)
# NOVA_CODEARTIFACT_DOMAIN — optional; when set, CodeArtifact mode
# TWINE_USERNAME — fallback-index upload user
# TWINE_PASSWORD — fallback-index upload password
# TWINE_REPOSITORY_URL — fallback-index upload URL
# AWS_DEFAULT_REGION (optional) — defaults to us-east-1
name: nova-publish
on:
push:
branches: [main]
paths:
- "core/**"
- "adapters/**"
- "nova/**"
- "pyproject.toml"
workflow_dispatch:
permissions:
id-token: write # OIDC federation to AWS
contents: write # tag the release
jobs:
publish:
name: Publish wheel + Lambda layer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
- name: Install build + publish tools
run: pip install build twine
- name: Compute version from pyproject.toml
id: ver
run: |
set -e
VERSION=$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Nova version: $VERSION"
- name: Build wheel
run: |
set -e
python -m build --wheel
ls -1 dist/
- name: Upload wheel to index (CodeArtifact default + fallback)
id: wheel
env:
NOVA_CODEARTIFACT_DOMAIN: ${{ secrets.NOVA_CODEARTIFACT_DOMAIN }}
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
TWINE_REPOSITORY_URL: ${{ secrets.TWINE_REPOSITORY_URL }}
run: |
set -e
# CodeArtifact mode: log in to the domain's pypi repository.
if [ -n "$NOVA_CODEARTIFACT_DOMAIN" ]; then
echo "CodeArtifact mode: domain=$NOVA_CODEARTIFACT_DOMAIN repository=nova-pypi"
aws codeartifact login --tool twine \
--domain "$NOVA_CODEARTIFACT_DOMAIN" --repository nova-pypi
else
echo "Fallback-index mode: uploading to TWINE_REPOSITORY_URL"
if [ -z "$TWINE_REPOSITORY_URL" ] || [ -z "$TWINE_USERNAME" ] || [ -z "$TWINE_PASSWORD" ]; then
echo "FAIL: NOVA_CODEARTIFACT_DOMAIN is unset and one of TWINE_REPOSITORY_URL/TWINE_USERNAME/TWINE_PASSWORD is missing."
exit 1
fi
fi
# Idempotent upload: a re-run for the same version may hit
# "file already exists" on the index. Treat that as success.
twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
|| twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" 2>&1 | tee /tmp/twine.log
if grep -qi "already exist" /tmp/twine.log 2>/dev/null; then
echo "Wheel already present on the index — treating as success (idempotent)."
fi
echo "uploaded=true" >> "$GITHUB_OUTPUT"
- name: Build Lambda layer
run: |
set -e
rm -rf layer
mkdir -p layer/python
# Install the wheel we just built + the identity extras' deps
# so the layer carries argon2-cffi, cryptography, pyjwt.
pip install --target layer/python/ \
"dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
argon2-cffi cryptography pyjwt
( cd layer && zip -r ../nova-layer.zip python/ )
ls -lh nova-layer.zip
- name: Publish Lambda layer
id: layer
run: |
set -e
ARN=$(aws lambda publish-layer-version \
--layer-name nova-cli \
--zip-file fileb://nova-layer.zip \
--compatible-runtimes python3.12 \
--compatible-architectures x86_64 \
--description "nova-cli v${{ steps.ver.outputs.version }}" \
--query LayerVersionArn --output text)
echo "arn=$ARN" >> "$GITHUB_OUTPUT"
echo "Published Lambda layer: $ARN"
- name: Record SSM version↔ARN mapping (CAP-035)
run: |
set -e
aws ssm put-parameter \
--name /nova/layer/nova-cli/version \
--value "${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}" \
--type String --overwrite
echo "SSM /nova/layer/nova-cli/version = ${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}"
- name: Fail job if either publish failed (REQ-323 AC)
if: ${{ steps.wheel.outputs.uploaded != 'true' || steps.layer.outputs.arn == '' }}
run: |
echo "FAIL: wheel uploaded=${{ steps.wheel.outputs.uploaded }} layer_arn=${{ steps.layer.outputs.arn }}"
exit 1
+3 -3
View File
@@ -1,4 +1,4 @@
# Nova Release Pipeline — GitHub Actions (production)
# ACDL Release Pipeline — GitHub Actions (production)
#
# Runs on push to main. Computes the next semver tag from the latest tag +
# commit history, creates the tag, updates floating MAJOR.MINOR and MAJOR tags,
@@ -8,7 +8,7 @@
# - Regular phase commit -> bump PATCH (v1.6.0 -> v1.6.1)
# - Milestone completion ("docs(milestone): complete") -> bump MINOR (v1.6.1 -> v1.7.0)
# - Major bumps are manual (not implemented here).
name: nova-release
name: acdl-release
on:
push:
@@ -87,6 +87,6 @@ jobs:
BODY=$(git log --format='- %s' HEAD)
fi
gh release create ${{ steps.version.outputs.new_tag }} \
--title "Nova ${{ steps.version.outputs.new_tag }}" \
--title "ACDL ${{ steps.version.outputs.new_tag }}" \
--notes "$BODY" \
--generate-notes || true
-69
View File
@@ -1,69 +0,0 @@
# Nova AWS key rotation — platform-managed scheduled pipeline (SPEC §5.9)
#
# Rotates the NOVA_AWS_* static key daily (no long-lived keys in the steady
# state). v0.2 scope: the mechanism must exist (SPEC §5.9); the v0.2 deploy
# uses the currently-active key. The rotation is best-effort + idempotent
# (scripts/rotate_spike_key.sh deactivates the old key only after the new
# key propagates to the consumer's Actions secret store).
#
# Auth: the rotation uses the CURRENT NOVA_AWS_* key to authenticate to IAM
# (the root account 581513795199 can rotate its own keys — confirmed by the
# bootstrap). The aws-actions/configure-aws-credentials@v4 step uses the
# static-key path (no OIDC role-to-assume); the long-lived key rotates
# itself, which is the bootstrap-exception documented in §5.9.
#
# Forge coords (base URL / owner / consumer repo) are sourced from
# repository secrets — NOVA_FORGE_BASE_URL, NOVA_FORGE_OWNER,
# NOVA_CONSUMER_REPO — so the synced workflow file stays forge-agnostic
# (REQ-230). The rotation script uploads the new key to the consumer's
# Actions secret store (the consumer whose deploy.yml consumes NOVA_AWS_*
# via secrets: inherit).
name: nova-rotate-aws-key
on:
schedule:
- cron: "0 0 * * *" # daily at 00:00 UTC
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
rotate:
name: Rotate NOVA_AWS_* static key
runs-on: ubuntu-latest
steps:
- name: Check out Nova platform repo
uses: actions/checkout@v4
- name: Configure AWS credentials (bootstrap root creds for IAM key rotation)
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
- name: Install Python deps (boto3 for the rotation script)
run: |
python3 -m pip install --break-system-packages --quiet boto3
- name: Run the key rotation script
env:
# aws-actions/configure-aws-credentials exports AWS_ACCESS_KEY_ID /
# AWS_SECRET_ACCESS_KEY; the rotation script reads the bootstrap
# creds via NOVA_BOOTSTRAP_AWS_* (its dual-read contract, D-034).
# Map the standard AWS_* exports onto the script's expected vars.
NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
# Forge + consumer coords come from repository secrets (REQ-230 —
# no forge hostnames/orgs hardcoded in the synced workflow file).
# NOVA_FORGE_TOKEN holds the forge API token (set equal to the
# existing forge token as a one-time secret setup).
NOVA_FORGE_TOKEN: ${{ secrets.NOVA_FORGE_TOKEN }}
NOVA_FORGE_BASE_URL: ${{ secrets.NOVA_FORGE_BASE_URL }}
NOVA_FORGE_OWNER: ${{ secrets.NOVA_FORGE_OWNER }}
NOVA_CONSUMER_REPO: ${{ secrets.NOVA_CONSUMER_REPO }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
run: |
bash scripts/rotate_spike_key.sh
-43
View File
@@ -1,43 +0,0 @@
# Nova Slides Render — re-renders presentation deck when source files change.
# REQ-273: install python-pptx, pin CLI versions, stage HTML + both PPTX +
# base64-inlined images.
name: Nova Slides Render
on:
push:
paths:
- 'docs/presentations/**'
- 'scripts/render_slides.sh'
- 'scripts/inline_images.py'
- 'scripts/render_pptx.py'
- 'pyproject.toml'
workflow_dispatch:
jobs:
render:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: '20' }
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install python-pptx (slides extra)
run: pip install -e ".[slides]"
- name: Install + pin render CLIs
run: |
npx --yes @marp-team/marp-cli@4.5.0 --version
npx --yes @mermaid-js/mermaid-cli@11.16.0 --version
- name: Render slides
run: bash scripts/render_slides.sh
- name: Commit rendered artifacts
run: |
git config user.name "nova-slides-bot"
git config user.email "bot@nova.local"
git add docs/presentations/*.html \
docs/presentations/*.pptx \
docs/presentations/*-python.pptx \
docs/presentations/assets/png/*.png
git diff --cached --quiet || git commit -m "chore(slides): re-render deck [skip ci]"
git push
+1 -17
View File
@@ -14,18 +14,6 @@ terraform/bootstrap/.bootstrap_state.json
# CIAgent runtime artifacts
.ciagent/logs/
# Nova metrics runtime artifacts (REQ-187, D-128)
# Generated: nova_metrics.db, decision_ledger.db, events.jsonl, runs/, test-results.xml, coverage.json, test-report.json
# NOT ignored: metrics/README.md, metrics/powerbi/ (export views), schemas/metrics_*.schema.json
metrics/nova_metrics.db
metrics/decision_ledger.db
metrics/events.jsonl
metrics/test-results.xml
metrics/test-report.json
metrics/coverage.json
metrics/runs/
metrics/lifecycle/
# Terraform — recursively ignore .terraform dirs, lock files, plans, and state
**/.terraform/
**/.terraform.lock.hcl
@@ -40,8 +28,4 @@ metrics/lifecycle/
*.cer
*.crt
*.jks
*.keystore.coverage
.coverage
.venv/
nova.egg-info/
*.keystore
+62 -50
View File
@@ -1,6 +1,4 @@
# Nova
> **Nova — The New Dawn of DevSecOps.** Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.
# ACDL — Agentic Cloud Delivery Platform
Consumers declare intent; the platform delivers safe production deployment
through an agentic stack — automatically, safely, and with a complete audit
@@ -20,7 +18,7 @@ a configuration file, or an infrastructure module.
## Repository roles
There are two kinds of repository in the Nova model:
There are two kinds of repository in the ACDL model:
- **Platform repo (this one).** This is the **source code of the platform**.
It owns `modules/`, `adapters/`, `core/`, `schemas/`, `pipelines/`,
@@ -28,7 +26,7 @@ There are two kinds of repository in the Nova model:
A **consumer never clones it.**
- **Consumer repo (yours).** A consumer repo contains only:
1. **Its application code** — the service or site being deployed.
2. **One or more contracts** — small YAML files at `.nova/contract.yml`
2. **One or more contracts** — small YAML files at `.acdl/contract.yml`
that declare infrastructure (one or more modules by name + version),
select an environment, and supply module-specific inputs.
3. **One or more CI definitions** — thin `.github/workflows/*.yml` files
@@ -95,7 +93,7 @@ intent via a contract; the platform delivers the deployment through the
same contract schema, the same policy envelope, and the same evidence
stream.
Consumers have their own repos and consume Nova by writing a contract that
Consumers have their own repos and consume ACDL by writing a contract that
declares infrastructure. A consumer declares a contract (id + name +
environment + infrastructure); the platform resolves it to a stack instance,
compiles it, runs security + policy checks, computes a confidence signal,
@@ -126,51 +124,25 @@ engine-specific code. `modules/`, `schemas/`, `contracts/`,
## How to run
### Quick start (offline, no AWS required)
### Prerequisites
The fastest way to verify the platform works — no AWS credentials, no
bootstrap, no cost. See the [Consumer guide](docs/consumer-guide.md)
for the consumer happy path (a consumer owns only a contract + app code).
> These prerequisites are for running the **platform repo** locally. A
> consumer does not need any of these — see the
> [Consumer guide](docs/consumer-guide.md) for the consumer happy path.
```bash
# Install test dependencies
pip install -r requirements-test.txt
- A platform-managed environment (see [docs/environments/](docs/environments/)).
For local testing, `core/environments/dev.json` is provided as the sample.
- AWS credentials for the dev environment (in `.env.secrets`, gitignored;
see [Credentials & zero-trust](#credentials--zero-trust)).
- `terraform` (pin `1.9.*`), `checkov` (pin `>=3.2,<4`), `python3` + `boto3`
+ `jsonschema`.
# 1. Run the test suite (all offline — uses moto for DynamoDB mocking)
python3 -m pytest tests/ -v
# 2. Run the platform in check-only mode (offline — contract -> resolver ->
# adapter -> structure validation). Uses the default sample contract
# (contracts/static-assets.yaml) + sample dev environment.
bash scripts/run_platform.sh --check-only
# Expected: "=== PLATFORM CHECK OK ==="
# 3. Run the headline E2E against the local emulating tier (emulates ECS,
# outbox, S3 state, Lambda in-process; D-092).
bash scripts/run_platform.sh --local
# Expected: "=== LOCAL E2E OK ==="
# 4. Reproduce the full CI pipeline locally (lint -> test -> check-only)
bash scripts/run_ci.sh
# Expected: "=== CI PIPELINE OK ==="
# Show all run_platform.sh flags:
bash scripts/run_platform.sh --help
```
### Run against live AWS (requires credentials + bootstrap)
> Prerequisites: a platform-managed environment (see
> [docs/environments/](docs/environments/); `core/environments/dev.json`
> is the sample), AWS credentials for dev (in `.env.secrets`, gitignored;
> see [Credentials & zero-trust](#credentials--zero-trust)), `terraform`
> (pin `1.9.*`), `checkov` (pin `>=3.2,<4`), `python3` + `boto3` +
> `jsonschema`.
### Run the platform pipeline end-to-end
```bash
# 1. Bootstrap the AWS state backend + runner IAM user (one-time, idempotent)
# (requires the bootstrap root key in env — skip if the state bucket +
# nova-spike-runner already exist)
# acdl-spike-runner already exist)
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=... ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=... \
python3 terraform/bootstrap/create_state_backend.py
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=... ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=... \
@@ -194,6 +166,26 @@ bash scripts/run_platform.sh --plan-only contracts/static-assets.yaml
bash scripts/run_platform.sh --quiet contracts/static-assets.yaml
```
### Test the platform (offline, no AWS required)
```bash
# Install test dependencies
pip install -r requirements-test.txt
# Run the test suite (all offline — uses moto for DynamoDB mocking)
python3 -m pytest tests/ -v
# Run the platform in check-only mode (offline — no AWS, no policy checks,
# no outbox). Uses the default sample contract (contracts/static-assets.yaml)
# and the sample dev environment (core/environments/dev.json).
bash scripts/run_platform.sh --check-only
# Expected: "=== PLATFORM CHECK OK ==="
# Reproduce the full CI pipeline locally (lint -> test -> check-only)
bash scripts/run_ci.sh
# Expected: "=== CI PIPELINE OK ==="
```
### CI/CD pipelines
The CI/CD pipeline is defined by a **central pipeline contract** — a
@@ -219,9 +211,23 @@ bash scripts/run_ci.sh --quiet # suppress per-stage banners
### Reusable deploy workflow
Consumer repos invoke the deploy pipeline via `.github/workflows/deploy.yml`
(a reusable GitHub Actions workflow, versioned tag `nova/.github/workflows/deploy.yml@v1.19`).
See the [Consumer guide](docs/consumer-guide.md) for the end-to-end happy path.
The deployment pipeline is defined by a **central deployment pipeline
contract** (`pipelines/contract.yml`, validated against
`schemas/deploy-pipeline.schema.json`) and exposed to consumer repos as a
**reusable workflow**:
- `.github/workflows/deploy.yml` — GitHub Actions (production)
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.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
scripts locally. See the [Consumer guide](docs/consumer-guide.md) for the
end-to-end happy path.
### Output streaming (run_platform.sh)
@@ -241,7 +247,7 @@ backwards-compatible log-only mode.
## Consumer guide
A step-by-step guide for a consumer to create their pipeline and define a
contract that deploys any Nova module to AWS is at
contract that deploys any ACDL module to AWS is at
[`docs/consumer-guide.md`](docs/consumer-guide.md). The guide is generic
across all modules; `static-assets` is the worked example.
@@ -277,8 +283,8 @@ no static credentials in repo secrets.
`repo:org/consumer-repo:ref:refs/heads/main`) binds the role's trust
policy to the exact consumer repo + branch that invoked the workflow.
- **Resource-creation attributes** — every resource the pipeline creates
is tagged with `nova:owner=<consumer-repo>` and
`nova:contract=<contract-id>`. The session policy grants
is tagged with `acdl:owner=<consumer-repo>` and
`acdl:contract=<contract-id>`. The session policy grants
view/update/delete **only on resources whose tags match the calling
repo**.
@@ -296,6 +302,12 @@ documented alternative:
runs, or in **`.env.secrets`** (gitignored, chmod 600) for local testing.
- The platform rotates platform-runner keys on a **daily cadence**
rotation is not the consumer's burden in the platform-runner path.
- **When `.env.secrets` is used locally**, rotating the key **out of band is
the consumer's responsibility**. The platform guarantees daily rotation
for platform-runner runs; it does not guarantee rotation for
locally-held copies. The consumer must rotate a local key via
`scripts/rotate_spike_key.sh` (or equivalent) on their own cadence.
No long-lived credential is permitted persistently — the platform-runner
key's useful lifetime is one workflow run, and the local alternative is
rotated at least daily (platform-runner) or out of band (local).
+8 -83
View File
@@ -1,4 +1,4 @@
# Nova Adapters
# ACDL Adapters
## Overview
@@ -12,62 +12,15 @@ Adapters translate the engine-agnostic Target Stack IR to engine-specific format
| Checkov adapter | `adapters/terraform/policy/checkov_adapter.py` | Checkov JSON | `PolicyCheckResult` records | Translates Checkov results |
| Wiz adapter | `adapters/wiz/wiz_adapter.py` | Wiz API issues JSON | `PolicyCheckResult` records | Translates Wiz security findings |
| Kyverno adapter | `adapters/kyverno/kyverno_adapter.py` | Kyverno PolicyReport JSON | `PolicyCheckResult` records | K8s-native policy translation |
| kyverno-json engine | `adapters/kyverno-json/kyverno_json_engine.py` | Any JSON/YAML payload | `PolicyCheckResult` records | **v1.25 primary policy engine** (swappable via `PolicyEngine` protocol) |
## Policy Engine Protocol (v1.25)
The `core/policy_engine.py` module defines the **swap boundary** between
Nova and its policy engines. A `PolicyEngine` Python Protocol (PEP 544)
with three members (`name`, `is_configured()`, `evaluate()`) is the
contract; a `PolicyEngineRegistry` selects the active engine from
`config.json`'s `policy.engine` key. The confidence signal and pipeline
never import an engine directly — they go through the registry.
**Implementations:**
- `KyvernoJsonEngine` (`adapters/kyverno-json/`) — shells to the `kj`
CLI; the v1.25 default.
- `NullEngine` (`core/policy_engine.py`) — fallback when the `policy`
key is absent (emits `SKIPPED`).
- Future: `OpaEngine` — implements the same protocol, shells to
`opa eval`. The OPA-equivalent surface is documented in
`.ciagent/RESEARCH.md` §4.2.
**How to add a new engine:**
1. Create `adapters/<name>/<name>_engine.py` implementing the
`PolicyEngine` protocol (`name`, `is_configured()`, `evaluate()`).
2. `evaluate()` returns `list[dict]` where each dict conforms to
`schemas/policy_check_result.schema.json`.
3. Register the engine in `core/policy_engine.py`'s `_autoload_*`
function (or call `register(name, factory)` at startup).
4. Set `config.json.policy.engine` to the engine's `name`.
5. Add the engine to the `engine` enum in
`schemas/policy_check_result.schema.json` if it needs a distinct
enum value (v1.25 reuses `"kyverno"` — see D-116).
## How to Write an Adapter
### Terraform Adapter Extension (stateless assembler — v1.11 rewrite)
### Terraform Adapter Extension
> The adapter owns **no module content**. There is no `TYPE_MAP`, no
> `INPUT_MAP`, no `OUTPUT_MAP`, and no per-type branch logic (all deleted
> in the v1.11 rewrite — the 918-line monolith collapsed to a ~80-line
> assembler). Engine-specific shape lives in each L1 module's own
> `terraform/` dir (`versions.tf`/`variables.tf`/`locals.tf`/`main.tf`/
> `outputs.tf`); the adapter only assembles them.
To extend the Terraform adapter, **do not edit the adapter** — instead:
1. Add an L1 module with a real `terraform/` dir (owning its resource
shape, nested HCL blocks, and defaults).
2. Register it in `modules/registry.json` under the module name with its
`terraform_dir` path. The adapter reads `registry.json` to find each
module's directory.
3. The adapter emits `module "<rid>" { source = "<path>" }` blocks at
the root, with resolved inputs + wired `ref:` refs between modules.
No type-specific translation lives in the adapter.
> If you find yourself reaching for a "TYPE_MAP"-style constant, the L1
> module is missing a piece — fix the module, not the adapter.
1. Add a stack type → Terraform type mapping to `TYPE_MAP`.
2. Add non-identity input mappings to `INPUT_MAP`.
3. Add non-identity output mappings to `OUTPUT_MAP`.
4. Add a specialized `_emit_resource` branch if the resource needs nested blocks (e.g. inline policies, rule sets).
### Policy Adapter Pattern
@@ -92,7 +45,7 @@ To extend the Terraform adapter, **do not edit the adapter** — instead:
## How to Test Adapters
- `tests/test_adapter.py` — Terraform adapter (stateless assembly: registry read, `module "<rid>" { source }` emission, `ref:` wiring, outputs). No `TYPE_MAP`/`INPUT_MAP` tests — the adapter owns no type mappings.
- `tests/test_adapter.py` — Terraform adapter (`TYPE_MAP`, resource emission, refs, outputs).
- `tests/test_checkov_adapter.py` — Checkov adapter.
- `tests/test_wiz_adapter.py` — Wiz adapter.
- `tests/test_kyverno_adapter.py` — Kyverno adapter.
@@ -109,32 +62,4 @@ To extend the Terraform adapter, **do not edit the adapter** — instead:
3. Add the adapter's engine name to the `engine` enum in `schemas/policy_check_result.schema.json` if it is a policy adapter.
4. Write a test (`tests/test_<name>_adapter.py`) plus a fixture (`tests/fixtures/<name>_fixture.json`).
5. Add it to `scripts/run_platform.sh` if it is invoked at runtime.
6. Update this README.
## Consumers
The Terraform adapter compiles contract IR for consumer estates. The
first real consumer estate is now live:
| Consumer | Version | Environment | Account | Forge / Adapter | Status |
| --- | --- | --- | --- | --- | --- |
| `nova-blockchain-exchange` | v0.2 | dev | `581513795199` | inline adapter (see note below) | **live** (pilot apply `blkex-pilot-apply-v0.2`, 2026-08-19) |
### Forge adapter note (SPEC §10 Q1)
Forge Actions (the consumer's forge runtime) does **not** support
cross-repo `uses:` references — the forge rejects
`uses: <owner>/<repo>/.github/workflows/<file>@<ref>` with
`expected format {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}`.
The consumer (`nova-blockchain-exchange`) therefore uses an **inline
adapter** in its `deploy.yml`: the workflow does `actions/checkout@v4`
on the consumer, then `actions/checkout@v4` `acdl/acdl` @ `ref: v1.25`
into `platform/`, and runs `bash platform/scripts/run_platform.sh ...`
directly — no `uses:` indirection.
The platform's own `.github/workflows/deploy.yml` (this repo) stays as
the **GitHub Actions reference implementation** — the reusable
`workflow_call` workflow used by GitHub-hosted consumers. The two
files share the same contract shape; the only declared difference is
the forge/runtime, not the stages or commands. See
`.ciagent/ARCHITECTURE.md` §12.8 for the live pilot-estate wiring.
6. Update this README.
-103
View File
@@ -1,103 +0,0 @@
# kyverno-json Engine Adapter (v1.25)
The `kyverno-json` engine is Nova's **primary compliance/policy tool**
(v1.25), implemented behind the swappable `PolicyEngine` protocol so
OPA (or any other engine) can replace it one day.
## What kyverno-json is
[kyverno-json](https://github.com/kyverno/kyverno-json) is a standalone
Go binary from the Kyverno project — a **separate runtime** from the
K8s Kyverno admission controller. It applies Kyverno `ValidatingPolicy`
resources to **any** JSON or YAML payload file via the `kj scan` CLI.
Unlike the K8s Kyverno adapter (`adapters/kyverno/`), which only
speaks to K8s manifests, kyverno-json evaluates consumer contracts,
resolved Stack IR, terraform plan JSON, and even the merged PCR list
itself (meta-policies).
## Install
```bash
bash scripts/install-kyverno-json.sh
# or directly:
go install github.com/kyverno/kyverno-json/cmd/kj@latest
kj version
```
The platform functions without the binary — `is_configured()` returns
`False` when `which kj` is absent → `evaluate()` returns a single
`SKIPPED` PCR (`KJ_ENGINE_NOT_CONFIGURED`). The confidence signal
proceeds with a neutral `policy` input (D-120 graceful degradation).
## Policy directory layout
```
adapters/kyverno-json/policies/
├── _smoke.json # round-trip smoke test
├── contract/ # consumer contract JSON policies
│ ├── require-id-pattern.json
│ ├── require-env-in-enum.json
│ ├── require-infrastructure-min-1.json
│ └── forbid-unknown-fields.json
├── stack-ir/ # resolved Stack IR policies
│ ├── require-tagging-standard.json
│ ├── forbid-public-ingress.json
│ └── require-encryption-by-default.json
├── plan-json/ # terraform show -json policies
│ ├── forbid-plaintext-secrets.json
│ ├── forbid-iam-wildcard.json
│ └── require-kms-reference.json
├── meta/ # policies over the merged PCR list
│ ├── block-on-any-critical.json
│ └── tagging-rules-agree.json
└── regression/ # capability-inventory policies
├── cap-013-adapter-dedup.json
├── cap-023-metrics-collector.json
└── cap-024-deck-structure.json
```
## The four policy categories
1. **contract/** — over the consumer contract JSON (pre-resolve).
2. **stack-ir/** — over the resolved Target Stack IR (post-resolve).
3. **plan-json/** — over `terraform show -json` output (pipeline Step 5b).
4. **meta/** — over the merged `list[PolicyCheckResult]` (meta-policies).
5. **regression/** — over the capability-inventory JSON (declarative
mirrors of `core/regression_verify.py`).
## Severity convention
kyverno-json does not natively assign severities. Each Nova policy
declares its severity via a `metadata.annotations` field:
```yaml
metadata:
annotations:
nova.cloudinit.dev/severity: high
```
Valid values: `critical`, `high`, `medium`, `low`, `info` (default
when absent).
## Engine enum reuse (D-116)
kyverno-json PCR records carry `engine: "kyverno"` (no new enum value).
The `engine` field records the policy-engine *family*, not the specific
binary. The K8s Kyverno adapter and the kyverno-json engine are
distinguished by `ruleId` prefix (`KYVERNO_` vs `KJ_`) and `evidence`
payload shape (`namespace`/`kind` vs `assertion`/`jmespath`).
## Schema path
The output records validate against
[`schemas/policy_check_result.schema.json`](../../schemas/policy_check_result.schema.json)
(`engine: "kyverno"` is in the enum). The confidence signal consumes
the merged PCR list engine-agnostically.
## Swap boundary
The `PolicyEngine` protocol (`core/policy_engine.py`) is the swap
boundary. The OPA-equivalent surface is documented in
`.ciagent/RESEARCH.md` §4.2 — a future `OpaEngine` implements the same
protocol without touching the confidence signal, the PCR schema, or
the pipeline.
-27
View File
@@ -1,27 +0,0 @@
"""Nova kyverno-json adapter package (v1.25, REQ-294).
The directory name ``kyverno-json`` has a hyphen, so it is not a valid
Python package name and cannot be imported via ``import
adapters.kyverno-json``. The ``PolicyEngineRegistry`` loads the engine
by file path (``importlib.util.spec_from_file_location``). This
``__init__`` is a convenience for direct-script use and for ``pip
install -e .`` style discovery if the package is ever renamed.
"""
def _load_engine():
import importlib.util
import os
engine_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"kyverno_json_engine.py")
spec = importlib.util.spec_from_file_location("kyverno_json_engine", engine_path)
if spec is None or spec.loader is None:
raise ImportError(f"could not load {engine_path}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.KyvernoJsonEngine
KyvernoJsonEngine = _load_engine()
__all__ = ["KyvernoJsonEngine"]
@@ -1,470 +0,0 @@
"""Nova KyvernoJsonEngine (REQ-293, v1.25; fixed v1.26 P3 W0.5).
Implements the ``PolicyEngine`` protocol (``core/policy_engine.py``)
by shelling to the ``kj`` CLI (``kyverno-json``). Translates native
kyverno-json scan output to Nova ``PolicyCheckResult`` dicts
(``schemas/policy_check_result.schema.json``).
Engine enum reuse (D-116): records carry ``engine: "kyverno"`` (no new
enum value). The ``ruleId`` is prefixed ``KJ_<policy_name>`` to
distinguish from the K8s Kyverno adapter's ``KYVERNO_`` prefix.
Severity (RESEARCH §2.6, G-Q10a): kyverno-json does not natively assign
severities. Each Nova policy declares its severity via a
``metadata.annotations["nova.cloudinit.dev/severity"]`` field. The
engine reads this annotation from the loaded policy file (not from the
scan result the result carries the policy spec but the annotation is
read here from disk) and applies it to every result that policy
produces. Default when absent: ``"info"``.
Graceful degradation (D-120): ``is_configured()`` returns ``False`` when
``which kj`` is absent ``evaluate()`` returns a single SKIPPED PCR
(``ruleId: KJ_ENGINE_NOT_CONFIGURED``). The platform functions without
the binary.
Defensive parsing: any kyverno-json output that doesn't match the
expected shape produces an ``error`` PCR, never an exception. The
engine is read-only against a local policy dir + a temp payload file.
v1.26 P3 W0.5 fix three substrate bugs uncovered once ``kj`` was
actually installed (the v1.25 test suite ``pytest.skip``-masked them):
1. **``.json`` policy files are not loaded by ``kj`` v0.0.3.** The
upstream policy loader (``pkg/policy/load.go``) uses
``fileinfo.IsYaml()`` which only matches ``.yaml``/``.yml``
extensions ``.json`` files are silently skipped, yielding
``evaluating N resources against 0 policies``. Nova policies are
authored as ``.json`` (the ``TestPolicyFilesExist`` tests assert the
``.json`` filenames). Fix: ``evaluate()`` materializes a temp policy
dir that mirrors the source tree with every ``.json`` policy copied
to a ``.yaml`` twin (JSON is a valid YAML subset verified against
``kj`` v0.0.3). The source ``.json`` files remain untouched.
2. **Bare-list output format.** ``kj scan --output json`` emits a bare
JSON list at the top level (NOT ``{"results": [...]}``). Each entry
has ``resource`` (the evaluated payload) + ``results`` (list of
per-policy result objects, each carrying ``policy.metadata.name``,
``rules[]`` with ``rule.name``, ``violations[]`` (present on fail),
``error`` (string, present on policy-evaluation error)). The v1.25
``_translate`` did ``out.get("results", [])`` on a dict but
``out`` is a list returned ``[]`` emitted a single
``KJ_NO_RESULTS`` pass PCR. **This is why all failing fixtures showed
0 fails.** Fix: ``_translate`` handles list (v0.0.3) and dict
(future-proof) shapes.
3. **``validate`` wrapper + check syntax.** Documented in the policy
files themselves (see the W0.5 policy edits). The engine itself does
not enforce policy shape it only translates ``kj`` output so
this fix lives in the policy ``.json`` files.
"""
import datetime
import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any, Union
import yaml
Payload = Union[dict, list, str]
SEVERITY_DEFAULT = "info"
SEVERITY_ANNOTATION = "nova.cloudinit.dev/severity"
RESULT_MAP = {
"pass": "pass",
"fail": "fail",
"error": "error",
"skip": "skipped",
"skipped": "skipped",
"warn": "skipped",
"warning": "skipped",
}
def _iso8601_now() -> str:
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _which_kj() -> str | None:
"""Return the path to ``kj`` if on PATH, else ``None``."""
return shutil.which("kj")
def _load_policy_severities(policy_dir: Path) -> dict[str, str]:
"""Load each ``.json``/``.yaml``/``.yml`` policy in ``policy_dir``
(non-recursive) and return ``{policy_name: severity}``.
kyverno-json policies are Kubernetes-style ``ValidatingPolicy``
resources. The severity is read from
``metadata.annotations["nova.cloudinit.dev/severity"]``. Policies
in subdirectories (e.g. ``contract/``, ``stack-ir/``) are loaded
when the caller passes that subdirectory as ``policy_dir``.
"""
severities: dict[str, str] = {}
if not policy_dir.is_dir():
return severities
for entry in sorted(os.listdir(policy_dir)):
if entry.startswith("_") or entry.startswith("."):
continue
full = policy_dir / entry
if not full.is_file():
continue
if entry.endswith((".json", ".yaml", ".yml")):
try:
with open(full, "r", encoding="utf-8") as fh:
doc = yaml.safe_load(fh)
if not isinstance(doc, dict):
continue
name = doc.get("metadata", {}).get("name") or entry.rsplit(".", 1)[0]
ann = doc.get("metadata", {}).get("annotations", {}) or {}
sev = ann.get(SEVERITY_ANNOTATION, SEVERITY_DEFAULT)
severities[name] = str(sev).lower()
except Exception:
continue
return severities
def _materialize_yaml_policy_dir(src: Path) -> tuple[Path, bool]:
"""Mirror ``src`` (recursively) into a temp dir, copying every
``.json`` policy to a ``.yaml`` twin and copying ``.yaml``/``.yml``
files verbatim. Returns ``(temp_dir, created)``.
``kj`` v0.0.3's policy loader (``pkg/policy/load.go``) only matches
``.yaml``/``.yml`` extensions ``.json`` files are silently
skipped. Nova policies are authored as ``.json`` (the
``TestPolicyFilesExist`` tests assert the ``.json`` filenames, so
they cannot be renamed in-place). JSON is a valid YAML subset, so
a byte-for-byte copy with a ``.yaml`` extension loads cleanly.
``created`` is ``False`` when ``src`` contains no policy files at
all (empty dir) in that case the temp dir is still returned (the
caller invokes ``kj`` against it and gets the no-results path).
"""
tmp = Path(tempfile.mkdtemp(prefix="nova-kj-pol-"))
any_policy = False
if src.is_dir():
for root, _dirs, files in os.walk(src):
rel = Path(root).relative_to(src)
dest_root = tmp / rel
dest_root.mkdir(parents=True, exist_ok=True)
for fn in files:
if fn.startswith(".") or fn.startswith("_"):
continue
src_file = Path(root) / fn
if fn.endswith(".json"):
dest_file = dest_root / (fn.rsplit(".", 1)[0] + ".yaml")
shutil.copy2(src_file, dest_file)
any_policy = True
elif fn.endswith((".yaml", ".yml")):
shutil.copy2(src_file, dest_root / fn)
any_policy = True
return tmp, any_policy
def _skipped_not_configured(contract_id: str) -> dict:
return {
"contractId": contract_id,
"evaluatedAt": _iso8601_now(),
"engine": "kyverno",
"ruleId": "KJ_ENGINE_NOT_CONFIGURED",
"severity": "info",
"result": "skipped",
"message": (
"kyverno-json engine not configured — `which kj` returned no path. "
"Install via scripts/install-kyverno-json.sh. The platform proceeds "
"with a neutral SKIPPED policy input (is_configured() guard, D-120)."
),
"evidence": {},
"resourceRef": "",
}
def _error_pcr(contract_id: str, message: str) -> dict:
return {
"contractId": contract_id,
"evaluatedAt": _iso8601_now(),
"engine": "kyverno",
"ruleId": "KJ_ENGINE_ERROR",
"severity": "info",
"result": "error",
"message": message,
"evidence": {},
"resourceRef": "",
}
def _no_results_pass(contract_id: str) -> dict:
"""No result entries — emit a single pass PCR so the confidence
signal's policy input is non-empty (a non-empty list of passes →
score 1.0)."""
return {
"contractId": contract_id,
"evaluatedAt": _iso8601_now(),
"engine": "kyverno",
"ruleId": "KJ_NO_RESULTS",
"severity": "info",
"result": "pass",
"message": "kyverno-json scan produced no result entries (all policies passed or no match).",
"evidence": {},
"resourceRef": "",
}
class KyvernoJsonEngine:
"""``PolicyEngine`` impl that shells to the ``kj`` CLI."""
name = "kyverno-json"
def is_configured(self) -> bool:
return _which_kj() is not None
def evaluate(self, payload: Payload, policy_dir: Path,
contract_id: str) -> list[dict]:
if not self.is_configured():
return [_skipped_not_configured(contract_id)]
kj = _which_kj()
policy_dir = Path(policy_dir)
if not policy_dir.is_dir():
return [_error_pcr(
contract_id,
f"kyverno-json policy dir not found: {policy_dir}",
)]
severities = _load_policy_severities(policy_dir)
# kj v0.0.3 only loads .yaml/.yml policy files. Mirror the tree
# to a temp dir with .json policies copied to .yaml twins.
yaml_dir, _any_policy = _materialize_yaml_policy_dir(policy_dir)
# Write payload to temp file (kj scan --payload expects a file path).
payload_tmp = tempfile.NamedTemporaryFile(
mode="w", suffix=".json", delete=False, encoding="utf-8"
)
try:
json.dump(payload, payload_tmp)
payload_tmp.flush()
payload_tmp.close()
cmd = [
kj, "scan",
"--policy", str(yaml_dir),
"--payload", payload_tmp.name,
"--output", "json",
]
try:
proc = subprocess.run(
cmd, capture_output=True, text=True, timeout=60,
)
except subprocess.TimeoutExpired:
return [_error_pcr(contract_id, "kyverno-json scan timed out (60s)")]
if proc.returncode not in (0, 1):
return [_error_pcr(
contract_id,
f"kyverno-json scan exited {proc.returncode}: {proc.stderr[:200]}",
)]
try:
out = json.loads(proc.stdout) if proc.stdout.strip() else []
except json.JSONDecodeError as e:
return [_error_pcr(
contract_id,
f"kyverno-json output not JSON: {e}",
)]
return self._translate(out, contract_id, severities)
finally:
try:
os.unlink(payload_tmp.name)
except OSError:
pass
shutil.rmtree(yaml_dir, ignore_errors=True)
def _translate(self, out: Any, contract_id: str,
severities: dict[str, str]) -> list[dict]:
# kj v0.0.3 emits a BARE JSON LIST at the top level: each entry
# has `resource` (the evaluated payload) + `results` (list of
# per-policy result objects). Future-proof: also accept the
# legacy {"results": [...]} dict shape.
if isinstance(out, list):
entries = out
elif isinstance(out, dict):
entries = out.get("results", [])
if not isinstance(entries, list):
entries = []
else:
entries = []
pcrs: list[dict] = []
for entry in entries:
if not isinstance(entry, dict):
continue
resource = entry.get("resource", {})
results = entry.get("results", [])
if not isinstance(results, list):
results = []
for pol_result in results:
if not isinstance(pol_result, dict):
continue
policy_obj = pol_result.get("policy", {}) or {}
policy_name = (
policy_obj.get("metadata", {}).get("name") if isinstance(policy_obj, dict)
else None
) or "UNKNOWN"
severity = severities.get(policy_name, SEVERITY_DEFAULT)
rules = pol_result.get("rules", [])
if not isinstance(rules, list):
rules = []
for rule_entry in rules:
if not isinstance(rule_entry, dict):
continue
rule_obj = rule_entry.get("rule", {}) or {}
rule_name = rule_obj.get("name", "") if isinstance(rule_obj, dict) else ""
rule_id = f"KJ_{policy_name}"
if rule_name:
rule_id = f"{rule_id}/{rule_name}"
violations = rule_entry.get("violations")
error_str = rule_entry.get("error")
if isinstance(violations, list) and violations:
# Fail: build a message from the violations' errors.
msg_parts: list[str] = []
for v in violations:
if not isinstance(v, dict):
continue
for err in v.get("errors", []) or []:
if not isinstance(err, dict):
continue
field = err.get("field", "")
detail = err.get("detail", "")
value = err.get("value", "")
msg_parts.append(
f"{field}: value={value!r} detail={detail}"
)
message = "; ".join(msg_parts) if msg_parts else "policy rule failed"
pcrs.append({
"contractId": contract_id,
"evaluatedAt": _iso8601_now(),
"engine": "kyverno",
"ruleId": rule_id,
"severity": severity,
"result": "fail",
"message": message,
"evidence": {
"resource": resource,
"policy": policy_name,
"rule": rule_name,
"violations": violations,
},
"resourceRef": _resource_ref(resource),
})
elif isinstance(error_str, str) and error_str:
# Policy-evaluation error (e.g. bad JMESPath).
pcrs.append({
"contractId": contract_id,
"evaluatedAt": _iso8601_now(),
"engine": "kyverno",
"ruleId": rule_id,
"severity": severity,
"result": "error",
"message": error_str,
"evidence": {
"resource": resource,
"policy": policy_name,
"rule": rule_name,
},
"resourceRef": _resource_ref(resource),
})
else:
# Pass: no violations, no error.
pcrs.append({
"contractId": contract_id,
"evaluatedAt": _iso8601_now(),
"engine": "kyverno",
"ruleId": rule_id,
"severity": severity,
"result": "pass",
"message": "",
"evidence": {
"resource": resource,
"policy": policy_name,
"rule": rule_name,
},
"resourceRef": _resource_ref(resource),
})
if not pcrs:
pcrs.append(_no_results_pass(contract_id))
return pcrs
def _resource_ref(resource: Any) -> str:
"""Best-effort resource ref from the evaluated payload."""
if isinstance(resource, dict):
for key in ("id", "name", "address"):
v = resource.get(key)
if isinstance(v, str) and v:
return v
return ""
# --- Legacy _to_pcr kept for the existing TestToPcr unit tests ---
# (test_kyverno_json_engine.py::TestToPcr constructs flat `entry`
# dicts with `policy`/`rule`/`result`/`message`/`resource` keys and
# asserts the translated PCR shape. The production _translate path no
# longer calls this helper — it inlines the translation against the
# real kj v0.0.3 nested output — but the unit tests pin the helper's
# contract, so it stays.)
def _to_pcr(entry: dict, contract_id: str, severity: str) -> dict:
"""Translate a flat kyverno-json scan result entry to a PCR dict.
Legacy shape (kept for unit-test backwards compatibility): the
entry is a flat dict with ``policy``/``rule``/``result``/``message``/
``resource`` string keys. The production ``_translate`` path no
longer calls this it inlines translation against the real kj
v0.0.3 nested ``resource``+``results``+``rules`` shape but the
``TestToPcr`` unit tests pin this contract.
"""
policy_name = entry.get("policy", "") or "UNKNOWN"
rule_name = entry.get("rule", "") or ""
rule_id = f"KJ_{policy_name}"
if rule_name:
rule_id = f"{rule_id}/{rule_name}"
result_raw = entry.get("result", "skip")
result = RESULT_MAP.get(str(result_raw).lower(), "error")
message = entry.get("message", "") or ""
resource = entry.get("resource", "")
if not resource and entry.get("name"):
kind = entry.get("kind", "")
ns = entry.get("namespace", "")
resource = f"{kind}/{ns}/{entry.get('name')}" if kind else entry.get("name", "")
return {
"contractId": contract_id,
"evaluatedAt": _iso8601_now(),
"engine": "kyverno",
"ruleId": rule_id,
"severity": severity,
"result": result,
"message": message,
"evidence": {
"resource": resource,
"policy": policy_name,
"rule": rule_name,
"namespace": entry.get("namespace", ""),
"kind": entry.get("kind", ""),
"name": entry.get("name", ""),
},
"resourceRef": resource,
}
if __name__ == "__main__":
if len(sys.argv) < 4:
print(
"usage: kyverno_json_engine.py <payload.json> <policy_dir> <contract-id>",
file=sys.stderr,
)
sys.exit(2)
with open(sys.argv[1], "r", encoding="utf-8") as fh:
pl = json.load(fh)
engine = KyvernoJsonEngine()
out = engine.evaluate(pl, Path(sys.argv[2]), sys.argv[3])
print(json.dumps(out, indent=2))
@@ -1,29 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-contract-id",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "Require contract id"
}
},
"spec": {
"rules": [
{
"name": "require-id",
"assert": {
"all": [
{
"check": {
"id": {
"(regex_match('^[a-z][a-z0-9-]{2,5}$', @))": true
}
}
}
]
}
}
]
}
}
@@ -1,30 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-unknown-fields",
"annotations": {
"nova.cloudinit.dev/severity": "low",
"title.policy.kyverno.io": "Contract has only schema-allowed fields"
}
},
"spec": {
"rules": [
{
"name": "no-unknown-fields",
"assert": {
"all": [
{
"check": {
"(length(keys(@)) == `4`)": true,
"keys(@)": {
"(contains(['id','name','environment','infrastructure'], @))": true
}
}
}
]
}
}
]
}
}
@@ -1,29 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-env-in-enum",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "Contract environment is one of dev/qa/prod/dr"
}
},
"spec": {
"rules": [
{
"name": "env-enum",
"assert": {
"all": [
{
"check": {
"environment": {
"(contains(['dev','qa','prod','dr'], @))": true
}
}
}
]
}
}
]
}
}
@@ -1,29 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-id-pattern",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "Contract id matches operational acronym pattern"
}
},
"spec": {
"rules": [
{
"name": "id-pattern",
"assert": {
"all": [
{
"check": {
"id": {
"(regex_match('^[a-z][a-z0-9-]{2,5}$', @))": true
}
}
}
]
}
}
]
}
}
@@ -1,29 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-infrastructure-min-1",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "Contract declares at least one infrastructure entry"
}
},
"spec": {
"rules": [
{
"name": "infra-min-1",
"assert": {
"all": [
{
"check": {
"infrastructure": {
"(length(keys(@)) > `0`)": true
}
}
}
]
}
}
]
}
}
@@ -1,27 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "block-on-any-critical",
"annotations": {
"nova.cloudinit.dev/severity": "critical",
"title.policy.kyverno.io": "Block on any critical-fail policy result (declarative source of truth)"
}
},
"spec": {
"rules": [
{
"name": "no-critical-fail",
"assert": {
"all": [
{
"check": {
"(severity == 'critical' && result == 'fail')": false
}
}
]
}
}
]
}
}
@@ -1,32 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "tagging-rules-agree",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "Checkov NOVA_TAG_NAMING and kj KJ_REQUIRE_TAGGING_STANDARD agree per resource"
}
},
"spec": {
"rules": [
{
"name": "no-tagging-divergence",
"assert": {
"all": [
{
"check": {
"(ruleId == 'NOVA_TAG_NAMING' && result == 'fail')": false
}
},
{
"check": {
"(ruleId == 'KJ_REQUIRE_TAGGING_STANDARD' && result == 'fail')": false
}
}
]
}
}
]
}
}
@@ -1,27 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "no-placeholder-account",
"annotations": {
"nova.cloudinit.dev/severity": "critical",
"title.policy.kyverno.io": "Env does not use a placeholder AWS account id"
}
},
"spec": {
"rules": [
{
"name": "no-placeholder-account",
"assert": {
"all": [
{
"check": {
"(account_id == '000000000000')": false
}
}
]
}
}
]
}
}
@@ -1,51 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-iam-wildcard",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "No IAM wildcard Actions or Resources"
}
},
"spec": {
"rules": [
{
"name": "no-wildcard-action",
"assert": {
"all": [
{
"check": {
"planned_values": {
"root_module": {
"~.resources": {
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Action, '*'))": false
}
}
}
}
}
]
}
},
{
"name": "no-wildcard-resource",
"assert": {
"all": [
{
"check": {
"planned_values": {
"root_module": {
"~.resources": {
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Resource, '*'))": false
}
}
}
}
}
]
}
}
]
}
}
@@ -1,33 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-plaintext-secrets",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "No plaintext secrets in the terraform plan"
}
},
"spec": {
"rules": [
{
"name": "no-plaintext-db-password",
"assert": {
"all": [
{
"check": {
"planned_values": {
"root_module": {
"~.resources": {
"(type == 'aws_db_instance' && contains(keys(values), 'password') && !contains(['${...}', ''], values.password))": false
}
}
}
}
}
]
}
}
]
}
}
@@ -1,33 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-kms-reference",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "KMS keys referenced by alias, not inline key material"
}
},
"spec": {
"rules": [
{
"name": "kms-by-alias",
"assert": {
"all": [
{
"check": {
"planned_values": {
"root_module": {
"~.resources": {
"(type == 'aws_kms_key' && !contains(keys(values), 'key_id') && !contains(keys(values), 'kms_key_id'))": false
}
}
}
}
}
]
}
}
]
}
}
@@ -1,27 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "cap-013-adapter-dedup",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "No duplicate adapter registrations (CAP-013 declarative mirror)"
}
},
"spec": {
"rules": [
{
"name": "no-duplicate-adapters",
"assert": {
"all": [
{
"check": {
"(max(map(&length(@), values(group_by(adapters, &@)))) == `1`)": true
}
}
]
}
}
]
}
}
@@ -1,29 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "cap-023-metrics-collector",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "Every metric has a grounded/derived/deferred status (CAP-023 declarative mirror)"
}
},
"spec": {
"rules": [
{
"name": "every-metric-has-status",
"assert": {
"all": [
{
"check": {
"~.metrics": {
"(contains(['grounded','derived','deferred'], status))": true
}
}
}
]
}
}
]
}
}
@@ -1,32 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "cap-024-deck-structure",
"annotations": {
"nova.cloudinit.dev/severity": "low",
"title.policy.kyverno.io": "Deck structure matches the documented 4-beat arc (CAP-024 declarative mirror)"
}
},
"spec": {
"rules": [
{
"name": "deck-has-4-beats",
"assert": {
"all": [
{
"check": {
"deck": {
"beats": {
"(length(@) >= `4`)": true,
"(contains(@, 'Problem') && contains(@, 'Solution') && contains(@, 'Proof') && contains(@, 'Roadmap+Ask'))": true
}
}
}
}
]
}
}
]
}
}
@@ -1,27 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "all-matches-committed",
"annotations": {
"nova.cloudinit.dev/severity": "critical",
"title.policy.kyverno.io": "All settlement matches are committed (finalized)"
}
},
"spec": {
"rules": [
{
"name": "all-matches-committed",
"assert": {
"all": [
{
"check": {
"(all_committed)": true
}
}
]
}
}
]
}
}
@@ -1,30 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-public-ingress",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "No resource has public ingress enabled"
}
},
"spec": {
"rules": [
{
"name": "no-public-ingress",
"identifier": "id",
"assert": {
"all": [
{
"check": {
"~.resources": {
"(inputs.public_ingress || `false`)": false
}
}
}
]
}
}
]
}
}
@@ -1,45 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-encryption-by-default",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "S3 buckets and EBS volumes carry encryption config"
}
},
"spec": {
"rules": [
{
"name": "s3-encryption",
"identifier": "id",
"assert": {
"all": [
{
"check": {
"~.resources": {
"(type == 'aws:s3:bucket' && !(contains(keys(inputs), 'bucket_encryption') || contains(keys(inputs), 'kms_key_id')))": false
}
}
}
]
}
},
{
"name": "ebs-encryption",
"identifier": "id",
"assert": {
"all": [
{
"check": {
"~.resources": {
"(type == 'aws:ebs:volume' && !(contains(keys(inputs), 'encrypted') || contains(keys(inputs), 'kms_key_id')))": false
}
}
}
]
}
}
]
}
}
@@ -1,33 +0,0 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-tagging-standard",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "All resources carry required Nova tags"
}
},
"spec": {
"rules": [
{
"name": "require-nova-tags",
"identifier": "id",
"assert": {
"all": [
{
"check": {
"~.resources": {
"(contains(keys(inputs.tags || `{}`), 'nova:owner'))": true,
"(contains(keys(inputs.tags || `{}`), 'nova:contract'))": true,
"(contains(keys(inputs.tags || `{}`), 'nova:environment'))": true,
"(contains(keys(inputs.tags || `{}`), 'nova:cost-center'))": true
}
}
}
]
}
}
]
}
}
+4 -4
View File
@@ -1,7 +1,7 @@
# Kyverno Adapter
The Kyverno adapter translates Kyverno `PolicyReport` results to the
normalized Nova
normalized ACDL
[`PolicyCheckResult`](../../schemas/policy_check_result.schema.json) schema
(engine: `"kyverno"`), mirroring the Checkov/Wiz adapter pattern.
@@ -15,7 +15,7 @@ publishes results to `PolicyReport` resources.
## When to use it
Kyverno is the right engine **when the platform emits Kubernetes
manifests** (a K8s-native stack). The Nova platform today emits Terraform
manifests** (a K8s-native stack). The ACDL platform today emits Terraform
only (D-053), so this adapter is **ready but inactive**: it ships now so
the schema path, severity/result mapping and sample policies are in place
ahead of the GitOps reconciler that will emit K8s manifests (roadmap).
@@ -55,8 +55,8 @@ manifests (documentation-only today — the platform does not run them):
- `disallow-privileged-containers.yml` — fail pods with
`securityContext.privileged: true`.
- `require-resource-labels.yml` — require `nova:owner` and
`nova:environment` labels on all pods (mirrors the Nova tagging standard
- `require-resource-labels.yml` — require `acdl:owner` and
`acdl:environment` labels on all pods (mirrors the ACDL tagging standard
in [`schemas/tagging-standard.json`](../../schemas/tagging-standard.json)).
- `require-image-digests.yml` — require container images to reference a
digest (`image@sha256:...`), not a mutable tag.
+1 -1
View File
@@ -1,4 +1,4 @@
"""Kyverno adapter — translate Kyverno PolicyReport results to Nova PolicyCheckResult records.
"""Kyverno adapter — translate Kyverno PolicyReport results to ACDL PolicyCheckResult records.
Kyverno is a Kubernetes-native policy engine. It evaluates K8s manifests
and produces PolicyReport resources. This adapter translates those results
@@ -3,7 +3,7 @@ kind: ClusterPolicy
metadata:
name: require-resource-labels
annotations:
policies.kyverno.io/title: Require Nova Resource Labels
policies.kyverno.io/title: Require ACDL Resource Labels
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
@@ -11,27 +11,27 @@ spec:
validationFailureAction: audit
background: true
rules:
- name: require-nova-owner-label
- name: require-acdl-owner-label
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Pods must carry the nova:owner label (Nova tagging standard)."
message: "Pods must carry the acdl:owner label (ACDL tagging standard)."
pattern:
metadata:
labels:
nova:owner: "?*"
- name: require-nova-environment-label
acdl:owner: "?*"
- name: require-acdl-environment-label
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Pods must carry the nova:environment label (Nova tagging standard)."
message: "Pods must carry the acdl:environment label (ACDL tagging standard)."
pattern:
metadata:
labels:
nova:environment: "?*"
acdl:environment: "?*"
+12 -54
View File
@@ -1,4 +1,4 @@
"""Nova Terraform adapter — stateless assembler (v1.11 RESTART, P56a).
"""ACDL Terraform adapter — stateless assembler (v1.11 RESTART, P56a).
A STATELESS ASSEMBLER. It owns no module content no resource shape, no
nested HCL blocks, no defaults, no type-specific logic. It reads the
@@ -10,10 +10,9 @@ lives in the per-module terraform/ subdir, NOT in this file.
CLI: adapter.py <instance.json> <out_dir>
"""
import json, os, sys
_R = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, _R) if _R not in sys.path else None
from core import env
import json
import os
import sys
def _load_registry(repo_root):
@@ -30,37 +29,6 @@ def _module_name(resource):
return resource.get("module", "").split("@")[0]
def _load_env_json(env_name, repo_root):
"""Load core/environments/<env_name>.json → dict (P03 W3, REQ-319).
Returns {} if the file is absent (the adapter falls back to the
computed state-bucket name). Sources env.state_backend.bucket +
env.account_id + env.region for the S3 backend block.
"""
env_path = os.path.join(repo_root, "core", "environments", f"{env_name}.json")
if not os.path.isfile(env_path):
return {}
with open(env_path, "r") as fh:
return json.load(fh)
def _resolve_state_bucket(env_json, region):
"""Resolve the S3 state-backend bucket name (P03 W3, REQ-319).
Precedence: (1) env.state_backend.bucket when present + non-empty;
(2) nova-tfstate-{account_id}-{region} from env.account_id + region
(backwards-compat); (3) nova-tfstate-581513795199-{region} when
account_id is absent (the only real account bootstrap bucket).
The env JSON is authoritative; NOVA_AWS_ACCOUNT_ID is no longer
consulted for the bucket name.
"""
bucket = (env_json.get("state_backend") or {}).get("bucket")
if bucket:
return bucket
account_id = env_json.get("account_id") or "581513795199"
return f"nova-tfstate-{account_id}-{region}"
def _ref_expr(value, data_source_names=None, id_remap=None):
"""Translate `ref:<rid>.<output>` → `module.<rid>.<output>` (or
`data.terraform_remote_state.platform.outputs.<output>` for data
@@ -139,23 +107,13 @@ def adapt(stack_instance, out_dir):
resources = stack_instance.get("resources", [])
stack_outputs = stack_instance.get("outputs", {})
stack_name = stack.get("name", "spike")
environment = stack.get("environment", "dev")
# P03 W3 (REQ-319): state backend bucket + account_id + region come
# from the env onboarding JSON (source of truth post-REQ-319). Bucket
# = env.state_backend.bucket when present (fallback to the computed
# nova-tfstate-{account_id}-{region} pattern for backwards compat).
env_json = _load_env_json(environment, repo_root)
region = env_json.get("region") or next(
(r["inputs"]["region"] for r in resources if "region" in r.get("inputs", {})),
"us-east-1",
)
state_bucket = _resolve_state_bucket(env_json, region)
region = next((r["inputs"]["region"] for r in resources if "region" in r.get("inputs", {})), "us-east-1")
providers_tf = f'provider "aws" {{\n region = "{region}"\n}}\n'
# State key is env-scoped (v1.24 REQ-287): the {environment} segment lets
# the env-transition detect-and-destroy step target the PRIOR env's state
# without affecting the new env. No orphan path on environment promotion.
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'
@@ -168,7 +126,7 @@ def adapt(stack_instance, out_dir):
' backend "s3" {\n'
f' bucket = "{state_bucket}"\n'
f' key = "spike/{stack_name}/{environment}/terraform.tfstate"\n'
f' region = "{region}"\n'
' region = "us-east-1"\n'
' }\n'
'}\n'
)
@@ -176,14 +134,14 @@ def adapt(stack_instance, out_dir):
data_source_names = stack_instance.get("data_sources", [])
parts = []
if data_source_names:
remote_state_key = env.get_env("REMOTE_STATE_KEY", "platform/terraform.tfstate")
remote_state_key = os.environ.get("ACDL_REMOTE_STATE_KEY", "platform/terraform.tfstate")
parts.append(
'data "terraform_remote_state" "platform" {\n'
' backend = "s3"\n'
' config = {\n'
f' bucket = "{state_bucket}"\n'
f' key = "{remote_state_key}"\n'
f' region = "{region}"\n'
' region = "us-east-1"\n'
' }\n'
'}\n'
)
+10 -38
View File
@@ -1,4 +1,4 @@
"""Translate Checkov JSON output to Nova PolicyCheckResult records.
"""Translate Checkov JSON output to ACDL PolicyCheckResult records.
Reads Checkov's JSON output (one framework key, e.g. terraform_plan),
emits a list of PolicyCheckResult dicts conforming to
@@ -6,23 +6,16 @@ schemas/policy_check_result.schema.json. Run Checkov with --soft-fail so
Checkov never exits non-zero; the confidence signal decides the gate, not
Checkov's exit code.
The Nova tagging standard (D-054, D-043 closure, D-109 hard mode in P3)
is enforced by a custom Checkov rule at
adapters/terraform/policy/custom_rules/nova_tagging.py, loaded via
--external-checks-dir. The adapter therefore maps NOVA_TAG_NAMING as a
real rule (no synthetic SKIPPED record is emitted). Renamed from
ACDL_TAG_NAMING in P2 (REQ-158); the rule is in hard mode as of P3
(REQ-162: hard-fail on missing nova:* or acdl:*-only tags).
The ACDL tagging standard (D-054, D-043 closure) is enforced by a custom
Checkov rule at adapters/terraform/policy/custom_rules/acdl_tagging.py,
loaded via --external-checks-dir. The adapter therefore maps
ACDL_TAG_NAMING as a real rule (no synthetic SKIPPED record is emitted).
"""
import datetime
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
from core.metrics.event_envelope import emit
RULE_MAP = {
"CKV_AWS_41": ("secrets-in-plaintext", "high"),
@@ -36,12 +29,10 @@ RULE_MAP = {
"CKV_AWS_40": ("iam-wildcard", "medium"),
"CKV_AWS_7": ("kms-key-reference", "medium"),
"CKV_AWS_33": ("kms-key-reference", "medium"),
# D-054 / D-043 closure, D-109 hard mode (P3): NOVA_TAG_NAMING is a real
# custom Checkov rule (adapters/terraform/policy/custom_rules/nova_tagging.py),
# loaded via --external-checks-dir. No synthetic SKIPPED record is emitted.
# Renamed from ACDL_TAG_NAMING in P2 (REQ-158). Hard mode as of P3
# (REQ-162: hard-fail on missing nova:* or acdl:*-only tags).
"NOVA_TAG_NAMING": ("tagging-standard", "medium"),
# D-054 / D-043 closure: ACDL_TAG_NAMING is now a real custom Checkov
# rule (adapters/terraform/policy/custom_rules/acdl_tagging.py), loaded
# via --external-checks-dir. No synthetic SKIPPED record is emitted.
"ACDL_TAG_NAMING": ("tagging-standard", "medium"),
}
_RESULT_MAP = {"PASSED": "pass", "FAILED": "fail", "SKIPPED": "skipped"}
@@ -75,7 +66,7 @@ def _to_pcr(checkov_record, contract_id, result_str):
}
def adapt(checkov_json_path, contract_id, run_id=None, environment="dev"):
def adapt(checkov_json_path, contract_id):
with open(checkov_json_path, "r", encoding="utf-8") as fh:
data = json.load(fh)
out = []
@@ -89,25 +80,6 @@ def adapt(checkov_json_path, contract_id, run_id=None, environment="dev"):
out.append(_to_pcr(rec, contract_id, "FAILED"))
for rec in results.get("skipped_checks", []):
out.append(_to_pcr(rec, contract_id, "SKIPPED"))
# Emit nova.policy.evaluated event (REQ-187).
if run_id:
passed = sum(1 for p in out if p["result"] == "pass")
failed = sum(1 for p in out if p["result"] == "fail")
skipped = sum(1 for p in out if p["result"] == "skipped")
severity_breakdown = {}
for p in out:
sev = p.get("severity", "info")
severity_breakdown[sev] = severity_breakdown.get(sev, 0) + 1
try:
emit("nova.policy.evaluated", run_id, environment, {
"passed": passed, "failed": failed, "skipped": skipped,
"severity_breakdown": severity_breakdown,
"rule_count": len(out),
}, contract_id=contract_id)
except Exception:
pass # metrics emission must never break the policy adapter
return out
@@ -1,24 +1,16 @@
# Nova Custom Checkov Rules
# ACDL Custom Checkov Rules
This directory holds Nova-authored Checkov custom rules, written in the
This directory holds ACDL-authored Checkov custom rules, written in the
[Checkov Python custom-rule framework](https://www.checkov.io/4.Contributing/Custom%20Policies.html).
## Files
- `nova_tagging.py``NOVA_TAG_NAMING` (D-054, D-109 warn mode in P2):
ensures every taggable AWS resource carries the four required Nova tags
(`nova:owner`, `nova:contract`, `nova:environment`, `nova:cost-center`).
This rule replaces the synthetic SKIPPED `NOVA_TAG_NAMING` record that the
Checkov adapter previously emitted (D-043 closure). Renamed from
`acdl_tagging.py` / `ACDL_TAG_NAMING` in P2 (REQ-158). The canonical tag
set is declared in [`schemas/tagging-standard.json`](../../../schemas/tagging-standard.json).
**P2 warn mode (D-109):** existing resources still carry `acdl:*` tag-key
values (left for P3). When a resource has only `acdl:*`-style tags and no
`nova:*` tags, the rule logs a WARNING instead of failing, so the
regression gate stays green during the parallel-tag transition window.
P3 flips to hard-fail once `nova:*` tags are emitted in parallel and the
ABAC policy is swapped.
- `acdl_tagging.py``ACDL_TAG_NAMING` (D-054): ensures every taggable AWS
resource carries the four required ACDL tags
(`acdl:owner`, `acdl:contract`, `acdl:environment`, `acdl:cost-center`).
This rule replaces the synthetic SKIPPED `ACDL_TAG_NAMING` record that the
Checkov adapter previously emitted (D-043 closure). The canonical tag set
is declared in [`schemas/tagging-standard.json`](../../../schemas/tagging-standard.json).
## How Checkov loads them
@@ -31,12 +23,12 @@ checkov -f terraform/spike/main.tf --framework terraform -o json --soft-fail \
```
Checkov imports each `*.py` file in the directory and instantiates the
module-level `check` object (see the `check = NovaTaggingStandard()` line at
the bottom of `nova_tagging.py`).
module-level `check` object (see the `check = AcdlTaggingStandard()` line at
the bottom of `acdl_tagging.py`).
## Severity / result mapping
The Checkov adapter (`adapters/terraform/policy/checkov_adapter.py`)
maps `NOVA_TAG_NAMING` to `(tagging-standard, medium)` in `RULE_MAP`. The
maps `ACDL_TAG_NAMING` to `(tagging-standard, medium)` in `RULE_MAP`. The
custom rule therefore produces real `PASS`/`FAIL` PolicyCheckResult records,
feeding the confidence signal instead of the old SKIPPED placeholder.
@@ -0,0 +1,54 @@
"""ACDL tagging standard custom Checkov rule (D-054).
Checks that all taggable AWS resources have the required ACDL tags:
acdl:owner, acdl:contract, acdl:environment, acdl:cost-center
Fails (severity medium) when any required tag is missing.
Closes the D-043 deferral (the SKIPPED ACDL_TAG_NAMING placeholder
becomes a real check).
"""
from __future__ import annotations
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
REQUIRED_TAGS = ("acdl:owner", "acdl:contract", "acdl:environment", "acdl:cost-center")
# Resources that support tags (exclude resources that have no tags attribute)
NON_TAGGABLE_TYPES = (
"aws_cloudfront_origin_access_control",
"aws_lambda_function_url",
"aws_route_table_association",
"aws_internet_gateway",
)
class AcdlTaggingStandard(BaseResourceCheck):
def __init__(self):
name = "Ensure all taggable AWS resources have required ACDL tags"
check_id = "ACDL_TAG_NAMING"
supported_resources = ["*"] # all resources
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, check_id=check_id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf, entity_type):
# Skip non-taggable resources
if entity_type in NON_TAGGABLE_TYPES:
return CheckResult.PASSED
# Check for a tags block
tags = conf.get("tags")
if not tags:
return CheckResult.FAILED
tag_keys = set()
if isinstance(tags, list) and tags:
tag_block = tags[0]
if isinstance(tag_block, dict):
tag_keys = set(tag_block.keys())
elif isinstance(tags, dict):
tag_keys = set(tags.keys())
missing = [t for t in REQUIRED_TAGS if t not in tag_keys]
if missing:
return CheckResult.FAILED
return CheckResult.PASSED
check = AcdlTaggingStandard()
@@ -1,82 +0,0 @@
"""Nova tagging standard custom Checkov rule (D-054, D-109 hard mode).
Checks that all taggable AWS resources have the required Nova tags:
nova:owner, nova:contract, nova:environment, nova:cost-center
In **hard mode** (P3, REQ-162): the rule hard-fails when a taggable resource
is missing any required `nova:*` tag, OR when a resource carries only the
legacy `acdl:*` tag keys (and no `nova:*` keys). P2 shipped warn mode
(`_WARN_MODE = True`) so the regression gate stayed green during the
parallel-tag transition window; P3 flips to hard-fail (`_WARN_MODE = False`)
once `nova:*` tags are emitted in terraform and the ABAC policy is swapped
to match `nova:*`. P5 keeps hard mode and additionally hard-fails on any
`acdl:*` tag key present at all (no legacy tolerated post-cutoff).
Closes the D-043 deferral (the SKIPPED NOVA_TAG_NAMING placeholder
becomes a real check). Renamed from acdl_tagging.py in P2 (REQ-158);
the Checkov rule ID ACDL_TAG_NAMING NOVA_TAG_NAMING.
"""
from __future__ import annotations
import sys
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
REQUIRED_TAGS = ("nova:owner", "nova:contract", "nova:environment", "nova:cost-center")
# Legacy acdl:* tag keys — the parallel-tag period (P3) emits both nova:*
# and acdl:*; P2 warn mode treats acdl:*-only tags as a warning, not a
# failure. The acdl:* VALUES in tagging-standard.json are left for P3.
LEGACY_TAGS = ("acdl:owner", "acdl:contract", "acdl:environment", "acdl:cost-center")
# Resources that support tags (exclude resources that have no tags attribute)
NON_TAGGABLE_TYPES = (
"aws_cloudfront_origin_access_control",
"aws_lambda_function_url",
"aws_route_table_association",
"aws_internet_gateway",
)
# P5 hard mode (D-109, REQ-164): `_WARN_MODE = False` (set in P3) AND
# any `acdl:*` tag key present at all is a hard FAIL (P5 tightens from
# P3's "acdl:*-only fails" to "any acdl:* key fails"). The legacy tag
# keys are fully removed from terraform (P3); any remaining `acdl:*` key
# is a rebrand regression.
_WARN_MODE = False
class NovaTaggingStandard(BaseResourceCheck):
def __init__(self):
name = "Ensure all taggable AWS resources have required Nova tags"
check_id = "NOVA_TAG_NAMING"
supported_resources = ["*"] # all resources
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, check_id=check_id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf, entity_type):
# Skip non-taggable resources
if entity_type in NON_TAGGABLE_TYPES:
return CheckResult.PASSED
# Check for a tags block
tags = conf.get("tags")
if not tags:
return CheckResult.FAILED
tag_keys = set()
if isinstance(tags, list) and tags:
tag_block = tags[0]
if isinstance(tag_block, dict):
tag_keys = set(tag_block.keys())
elif isinstance(tags, dict):
tag_keys = set(tags.keys())
# P5 (REQ-164): any legacy acdl:* tag key present = hard FAIL.
legacy_present = tag_keys & set(LEGACY_TAGS)
if legacy_present:
return CheckResult.FAILED
missing = [t for t in REQUIRED_TAGS if t not in tag_keys]
if not missing:
return CheckResult.PASSED
return CheckResult.FAILED
check = NovaTaggingStandard()
+5 -34
View File
@@ -1,4 +1,4 @@
"""Wiz adapter — translate Wiz API results to Nova PolicyCheckResult records.
"""Wiz adapter — translate Wiz API results to ACDL PolicyCheckResult records.
Wiz is a SaaS security platform with a GraphQL API. This adapter
translates Wiz issue records to the normalized PolicyCheckResult schema
@@ -186,37 +186,8 @@ def is_configured():
return bool(os.environ.get("WIZ_API_TOKEN") and os.environ.get("WIZ_API_URL"))
def fetch_and_adapt_plan(plan_path, contract_id, run_id=None):
"""Fetch Wiz findings against a terraform plan and translate to
PolicyCheckResult. REQ-250 (v1.21): Wiz scans the terraform plan
output. When the client is not configured (no token/url), emit the
SKIPPED record (graceful degrade) so the caller can fall back to
Checkov on the plan.
"""
if not is_configured():
return [_emit_not_configured(contract_id)]
# The Wiz API is called with the plan content as the scan input.
client = WizClient()
issues = client.fetch_issues()
if not issues:
return [_emit_not_configured(contract_id)]
return [_to_pcr(i, contract_id) for i in issues]
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Wiz adapter (REQ-250: plan-mode supported)")
parser.add_argument("wiz_json", nargs="?", help="wiz_issues.json (legacy positional mode)")
parser.add_argument("contract_id_pos", nargs="?", help="contract-id (legacy positional mode)")
parser.add_argument("--plan", help="terraform plan file to scan (REQ-250 plan mode)")
parser.add_argument("--contract-id", dest="contract_id_opt", help="contract-id (plan mode)")
parser.add_argument("--run-id", help="run-id for the plan scan (plan mode)")
args = parser.parse_args()
if args.plan:
cid = args.contract_id_opt or ""
out = fetch_and_adapt_plan(args.plan, cid, run_id=args.run_id)
print(json.dumps(out, indent=2))
elif args.wiz_json and args.contract_id_pos:
print(json.dumps(adapt(args.wiz_json, args.contract_id_pos), indent=2))
else:
parser.error("either --plan <file> --contract-id <id> OR <wiz_issues.json> <contract-id>")
if len(sys.argv) != 3:
print("usage: wiz_adapter.py <wiz_issues.json> <contract-id>", file=sys.stderr)
sys.exit(2)
print(json.dumps(adapt(sys.argv[1], sys.argv[2]), indent=2))
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — microservice module (dev)
# ACDL sample consumer contract — microservice module (dev)
# Per-environment contract (REQ-105). Promotion = running the dev job;
# no environment field editing. Interpolation resolves against dev.json.
id: msvc
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — microservice module (dr)
# ACDL sample consumer contract — microservice module (dr)
# Per-environment contract (REQ-105). Promotion = running the dr job;
# no environment field editing. Interpolation resolves against dr.json.
id: msvc
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — microservice module (prod)
# ACDL sample consumer contract — microservice module (prod)
# Per-environment contract (REQ-105). Promotion = running the prod job;
# no environment field editing. Interpolation resolves against prod.json.
id: msvc
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — microservice module (qa)
# ACDL sample consumer contract — microservice module (qa)
# Per-environment contract (REQ-105). Promotion = running the qa job;
# no environment field editing. Interpolation resolves against qa.json.
id: msvc
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — microservice module (dev)
# ACDL sample consumer contract — microservice module (dev)
#
# Reference example for an ECS Fargate microservice deployment.
# Interpolation (D-081): bucket_name uses the naming pattern that includes
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — static-assets module (dev)
# ACDL sample consumer contract — static-assets module (dev)
# Per-environment contract (REQ-105). The dev default
# (contracts/static-assets.yml) remains for backwards compat; this file
# is the explicit per-env dev contract. Interpolation resolves against dev.json.
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — static-assets module (dr)
# ACDL sample consumer contract — static-assets module (dr)
# Per-environment contract (REQ-105). Promotion = running the dr job;
# no environment field editing. Interpolation resolves against dr.json.
id: assets
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — static-assets module (prod)
# ACDL sample consumer contract — static-assets module (prod)
# Per-environment contract (REQ-105). Promotion = running the prod job;
# no environment field editing. Interpolation resolves against prod.json.
id: assets
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — static-assets module (qa)
# ACDL sample consumer contract — static-assets module (qa)
# Per-environment contract (REQ-105). Promotion = running the qa job;
# no environment field editing. Interpolation resolves against qa.json.
id: assets
+1 -1
View File
@@ -1,4 +1,4 @@
# Nova sample consumer contract — static-assets module (dev)
# ACDL sample consumer contract — static-assets module (dev)
#
# This is the reference example for a consumer contract. It declares:
# id: short operational acronym (becomes stack.name for state, tags, evidence)

Some files were not shown because too many files have changed in this diff Show More