Files
acdl/.ciagent/REQUIREMENTS.md
T
CIAgent 68908d7f6a docs(init): validate specification
---ci---
project: acdl
phase: 0
milestone: v1.31
status: specify
---/ci---
2026-08-20 14:56:47 +00:00

1120 lines
54 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Nova — Requirements
> **Compressed.** The full v1.0v1.25 requirement history (REQ-01..REQ-309)
> is preserved verbatim at `.ciagent/archive/REQUIREMENTS-v1.0-v1.24.md`.
> This file retains only the v1.25 requirement set (the immediate
> predecessor milestone whose policy-engine substrate is load-bearing for
> v1.26) + a pointer to the active v1.26 requirements, which live in the
> consumer subproject at `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md`
> (multi-project mode per `config.json`).
>
> Earlier requirement sets (v1.0v1.24, REQ-01..REQ-290) remain valid for
> the milestones they governed. They are not re-decided by v1.26. Full
> text in the archive snapshot + git history.
## v1.25 — kyverno-json Unified Policy Engine (immediate predecessor, complete)
> **Feature milestone — complete.** `kyverno-json` becomes the primary
> compliance / policy tool, implemented behind a swappable `PolicyEngine`
> adapter so OPA (or any other engine) can replace it one day. Tags run
> on the **v1.24.x** line (milestone v1.25 → tags v1.24.0..v1.24.5). Tag
> `v1.24.5` = the milestone release.
>
> One problem, one architectural correction:
> 1. **Fragmented policy posture.** Nova's compliance rules were split
> across Checkov (imperative YAML + a Python custom rule for tagging),
> Wiz (API findings), the K8s-only Kyverno adapter (inactive for
> Terraform stacks — D-053), and imperative Python in
> `core/env_transition.py` + `core/regression_verify.py`. There was no
> single declarative place where "what Nova considers compliant" lived.
>
> The correction: `kyverno-json` (a Kyverno-ecosystem runtime that applies
> Kyverno policies to **any** JSON/YAML payload) becomes the **unified
> orchestrator** of compliance checks. Checkov and Wiz remain as
> raw-finding adapters feeding *into* kyverno-json meta-policies. The
> engine is behind a `PolicyEngine` protocol so it is replaceable. The
> confidence signal is untouched — it already consumes
> `list[PolicyCheckResult]` engine-agnostically.
### Decisions (locked in CLARIFY, full autonomy — load-bearing for v1.26)
- **D-115 (C-1):** `kyverno-json` is a runtime dependency installed via
`go install github.com/kyverno/kyverno-json/cmd/kj@latest` (pinned in a
`scripts/install-kyverno-json.sh` helper; the CI image installs it).
Not a Python package — kyverno-json is a Go binary. The
`KyvernoJsonEngine.is_configured()` checks `which kj` and skips
gracefully when absent (emits `SKIPPED` PCR, mirroring the Wiz adapter).
- **D-116 (C-2):** kyverno-json PCR records carry `engine: "kyverno"`
(no new enum value). The existing `engine` enum in
`schemas/policy_check_result.schema.json` already includes `"kyverno"`;
adding `"kyverno-json"` would force a schema change + checkov_adapter
test regression for no semantic gain. The `ruleId` prefix `KJ_`
distinguishes kyverno-json rules from the K8s Kyverno adapter's
`KYVERNO_` prefix where they overlap.
- **D-117 (C-3):** Checkov and Wiz adapters keep their current
`adapt() -> list[PolicyCheckResult]` signatures. They emit PCRs as
today. The meta-policies in `adapters/kyverno-json/policies/meta/`
consume the **merged** PCR list (checkov + wiz + kyverno-json) as their
input payload, applying Nova-specific posture rules on top. No adapter
signature changes.
- **D-118 (C-4):** `NOVA_TAG_NAMING` (the Checkov custom rule in
`adapters/terraform/policy/custom_rules/nova_tagging.py`) is **kept**.
A kyverno-json mirror policy `require-tagging-standard.json` is added
in `adapters/kyverno-json/policies/stack-ir/`. The P3 meta-policy
`tagging-rules-agree.json` asserts the two engines agree on every
resource; divergence emits an `error` PCR (defense-in-depth against
rule drift). The Checkov rule stays the source of truth for
Terraform-static scanning; the kyverno-json policy covers Stack IR.
### Category: Policy Engine Core (feat)
- **REQ-291:** `core/policy_engine.py` defines a `PolicyEngine` Python
`Protocol` (PEP 544) with three members: `name -> str`,
`is_configured() -> bool`, and
`evaluate(payload: dict | str, policy_dir: Path, contract_id: str) ->
list[dict]` (where each dict conforms to
`schemas/policy_check_result.schema.json`). A `PolicyEngineRegistry`
singleton selects the active engine from `config.json`'s new
`policy.engine` key (default `"kyverno-json"`); raises
`KeyError` on an unknown engine name. The registry exposes
`get_engine()` and `register(name, factory)`. Pure stdlib, no engine
imports at the protocol layer.
- **REQ-292:** `.ciagent/config.json` gains a new top-level `policy`
object: `{"engine": "kyverno-json", "policy_root":
"adapters/kyverno-json/policies"}`. The registry reads `policy.engine`
to select the active engine and `policy.policy_root` as the default
policy directory. Backward-compatible: if the `policy` key is absent,
the registry returns a `NullEngine` that emits only `SKIPPED` records
(so existing tests that don't set the key still pass).
### Category: kyverno-json Engine Adapter (feat)
- **REQ-293:** `adapters/kyverno-json/kyverno_json_engine.py` implements
`KyvernoJsonEngine` satisfying the `PolicyEngine` protocol.
`is_configured()` returns `True` when `which kj` succeeds. `evaluate()`
writes the payload to a temp JSON file, invokes
`kj scan --policy <policy_dir> --payload <payload.json> -o json`,
parses the native result list, and translates each entry to a PCR dict
(`engine: "kyverno"`, `ruleId` prefixed `KJ_<policy_name>`, severity
mapped, `result` mapped pass/fail/skip → pass/fail/skipped). When
`is_configured()` is false, `evaluate()` returns a single `SKIPPED`
PCR with `ruleId: "KJ_ENGINE_NOT_CONFIGURED"`. Native output parsing
is defensive: any kyverno-json output that doesn't match the expected
shape produces an `error` PCR, never an exception.
- **REQ-294:** `adapters/kyverno-json/__init__.py` exports
`KyvernoJsonEngine`. `adapters/kyverno-json/policies/_smoke.json`
is a single trivial policy (`require-contract-id`) used to validate
the engine round-trip end-to-end in tests. `scripts/install-kyverno-json.sh`
runs `go install github.com/kyverno/kyverno-json/cmd/kj@latest` and
prints `kj version`; documented in `adapters/kyverno-json/README.md`.
The CI image installs Go + kj when `policy.engine == "kyverno-json"`;
the install is cached.
### Category: Contract Policies (feat)
- **REQ-295:** `adapters/kyverno-json/policies/contract/` holds
kyverno-json policies over consumer contract JSON. Four policies
mirroring `schemas/contract.schema.json` constraints:
`require-id-pattern.json`, `require-env-in-enum.json`,
`require-infrastructure-min-1.json`, `forbid-unknown-fields.json`.
Each policy is a single Kyverno `Policy` resource with one
`validate.assert` rule using JMESPath against the payload root.
- **REQ-296:** `core/contract_resolver.py` invokes the
`PolicyEngineRegistry.get_engine().evaluate()` with the contract dict
and `policies/contract/` **before** resolving (early-fail on contract
violations) and emits a `nova.policy.evaluated` metrics event. Failures
feed the confidence signal's `policy` input as `fail` PCRs; the
resolver does not exit — the confidence signal decides the gate
(consistent with the existing `--soft-fail` Checkov pattern).
### Category: Stack-IR Policies (feat)
- **REQ-297:** `adapters/kyverno-json/policies/stack-ir/` holds policies
over the resolved Target Stack IR dict. `require-tagging-standard.json`
(every resource carries `nova:owner` + `nova:environment` tags — ports
`nova_tagging.py` into a declarative Kyverno policy).
`forbid-public-ingress.json` (no resource has `public_ingress: true`).
`require-encryption-by-default.json` (every S3 bucket + EBS volume +
KMS-aliased resource carries encryption config — ports the v1.8
D-encryption-default rule).
- **REQ-298:** `core/contract_resolver.py` invokes the engine with the
resolved Stack IR and `policies/stack-ir/` **after** resolving. The
resulting PCRs are appended to the contract-policy PCRs and fed to the
confidence signal. The resolver's existing
`tests/test_contract_resolver.py` continues to pass (the policy call
is additive — it does not change resolver return values or exceptions).
- **REQ-299:** `tests/test_stack_ir_policies.py` + fixture
`tests/fixtures/stack_ir/` — a passing IR + a failing IR. Tests run
the `KyvernoJsonEngine` against real `kj` when `which kj` succeeds, and
`pytest.skip("kj not installed")` when absent.
### Category: Plan-JSON Policies + Pipeline Wiring (feat)
- **REQ-300:** `adapters/kyverno-json/policies/plan-json/` holds policies
over `terraform show -json` output. `forbid-plaintext-secrets.json`
(ports `CKV_AWS_41/45/46`). `forbid-iam-wildcard.json` (ports
`CKV_AWS_1/40`). `require-kms-reference.json` (ports `CKV_AWS_7/33`).
The Checkov `RULE_MAP` in `checkov_adapter.py` is unchanged — these
are declarative mirrors, not replacements.
- **REQ-301:** `run_platform.sh` Step 5 ("runtime policy scan") gains a
parallel kyverno-json pass: after Checkov/Wiz produce raw PCRs, the
script runs `kj scan` and pipes through
`adapters/kyverno-json/kyverno_json_engine.py` to produce a second PCR
list. Both lists are concatenated and fed to the confidence signal's
`policy` input. When `which kj` is false, the script logs and proceeds
with the Checkov/Wiz list only (no hard failure).
- **REQ-302:** `tests/test_plan_json_policies.py` + fixture
`tests/fixtures/plan_json/` — a passing + failing plan JSON.
`tests/test_run_platform_plan_json_policies.py` asserts `run_platform.sh`
has the kyverno-json Step 5 block and that it concatenates PCR lists.
### Category: Meta-Policies (feat)
- **REQ-303:** `adapters/kyverno-json/policies/meta/` holds policies
whose **payload** is the merged `list[PolicyCheckResult]` itself.
`block-on-any-critical.json` — asserts no PCR in the list has
`severity: "critical"` + `result: "fail"`; if any does, the meta-policy
emits a `fail` PCR with `ruleId: "KJ_META_BLOCK_CRITICAL"` and severity
`critical`. This is the **declarative** source of truth for
"critical = block"; the `confidence_signal.py` `PENALTY["critical"]:
None` hard-override stays as defense-in-depth.
`tagging-rules-agree.json` — for every resource in the Stack IR,
asserts the Checkov `NOVA_TAG_NAMING` result and the kyverno-json
`KJ_REQUIRE_TAGGING_STANDARD` result agree; divergence emits an
`error` PCR. `tests/test_meta_policies.py` covers both.
### Category: Regression-Gate Policies (feat, quality improvement from IDEATE)
- **REQ-304:** `adapters/kyverno-json/policies/regression/` holds
policies over the capability-inventory JSON frontmatter. Three
policies port the imperative checks in `core/regression_verify.py`:
`cap-013-adapter-dedup.json`, `cap-023-metrics-collector.json`,
`cap-024-deck-structure.json`. The existing `core/regression_verify.py`
is **kept** (it drives the CI gate); the policies are the
**declarative mirror** that makes capability regression auditable as a
policy artifact, not imperative Python. Future milestones may switch
the gate to the policy version.
- **REQ-305:** `tests/test_regression_policies.py` + fixture
`tests/fixtures/capability_inventory.json` — a clean inventory (all
caps pass) + a drifted inventory. The regression gate (`pytest` suite)
continues to pass; the new policy tests are additive.
### Category: Documentation (docs)
- **REQ-306:** `adapters/README.md` gains a new row for the
`kyverno-json` adapter + a new section "Policy Engine Protocol"
documenting the `PolicyEngine` Protocol, the registry, and the swap
boundary (how to add an `OpaEngine`). `adapters/kyverno-json/README.md`
documents the engine, the install path, the policy directory layout,
and the four policy categories.
- **REQ-307:** `.ciagent/ARCHITECTURE.md` gains §12.7 "Policy Engine
Registry" with the registry diagram. `schemas/README.md` notes the
`engine: "kyverno"` value is shared by the K8s Kyverno adapter and the
kyverno-json engine (distinguished by `ruleId` prefix).
`modules/STANDARDS.md` gains a "Policy authoring standard" section.
`docs/METRICS.md` notes the policy engine is now swappable (Strategic
Objective #2 — provable trust via a replaceable substrate, not a
vendor lock-in).
### Category: Tests (test)
- **REQ-308:** `tests/test_policy_engine.py` — protocol conformance,
unknown-engine `KeyError`, `NullEngine` fallback when the `policy`
key is absent, `KyvernoJsonEngine.is_configured()` returns false when
`which kj` fails (mocked). `tests/test_kyverno_json_engine.py`
`evaluate()` returns valid PCR dicts validated against
`schemas/policy_check_result.schema.json`; native-output parsing is
defensive (malformed → `error` PCR, not exception);
`is_configured()==false``SKIPPED` PCR with `KJ_ENGINE_NOT_CONFIGURED`.
- **REQ-309:** All new tests use `pytest.skip("kj not installed")` when
`which kj` is absent, so the suite passes in environments without the
binary (CI matrix: with-kj and without-kj). `pyproject.toml` +
`requirements-test.txt` unchanged (kyverno-json is a Go binary, not a
Python dep).
### Out of Scope (v1.25)
- **Removing Checkov or Wiz.** Both stay as raw-finding adapters.
- **`OpaEngine` implementation.** The protocol is the swap boundary;
the OPA implementation is a future milestone.
- **Per-module policies.** `modules/<name>/policies/` is documented as
the future pattern in `modules/STANDARDS.md` but not populated this
milestone.
- **kyverno-json as a long-running service.** v1.25 uses the CLI
(`kj scan`); the `kj serve` web-app mode is future.
- **Replacing the K8s Kyverno adapter.** The K8s adapter
(`adapters/kyverno/`) remains documentation-only (D-053).
### v1.25 Traceability
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-291 | P1 | complete |
| REQ-292 | P1 | complete |
| REQ-293 | P1 | complete |
| REQ-294 | P1 | complete |
| REQ-295 | P2 | complete |
| REQ-296 | P2 | complete |
| REQ-297 | P2 | complete |
| REQ-298 | P2 | complete |
| REQ-299 | P2 | complete |
| REQ-300 | P3 | complete |
| REQ-301 | P3 | complete |
| REQ-302 | P3 | complete |
| REQ-303 | P3 | complete |
| REQ-304 | P4 | complete |
| REQ-305 | P4 | complete |
| REQ-306 | P4 | complete |
| REQ-307 | P4 | complete |
| REQ-308 | P1 | complete |
| REQ-309 | P1 | complete |
## v1.26 — Live Pilot Estate Activation (active)
> **Feature milestone.** The first real consumer estate (a stock
> exchange on a homegrown PoA blockchain, equities only) is activated
> against live AWS account `581513795199`, lifting D-096. Tags run on
> the **v1.25.x** line: `v1.25.0` (P0) → `v1.25.1..v1.25.4` (P1P4) →
> `v1.25.5` (P5 final = milestone release).
>
> **Multi-project mode:** the v1.26 requirements live in
> `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md` (the consumer
> subproject). The platform-side requirement REQ-322 (DynamoDB L1
> primitive) landed in P2 of the platform repo. The 13 requirements
> (REQ-310..322) cover: blockchain core (REQ-310), order engine
> (REQ-311), settlement (REQ-312), consumer contract (REQ-313),
> deploy invocation (REQ-314), settlement-finality policy (REQ-315),
> pilot regression CAP (REQ-316), outcome backfill (REQ-317),
> escalation reason (REQ-318), env-JSON wiring (REQ-319),
> pilot-readiness policy (REQ-320), docs (REQ-321), DynamoDB L1
> primitive (REQ-322).
### v1.26 Traceability (live — see CHECKPOINT.json for authoritative state)
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-310 | P1 | complete (v1.25.1) |
| REQ-311 | P1 | complete (v1.25.1) |
| REQ-312 | P1 | complete (v1.25.1) |
| REQ-322 | P2 | complete (v1.25.2) |
| REQ-313 | P2 | complete (v1.25.2) |
| REQ-314 | P2 | complete (v1.25.2) |
| REQ-315 | P3 | complete (v1.25.3) |
| REQ-316 | P3 + P4 | complete (v1.25.3 — CAP-025; v1.25.4 — live-verify complete) |
| REQ-317 | P3 | complete (v1.25.3) |
| REQ-318 | P3 | complete (v1.25.3) |
| REQ-319 | P3 | complete (v1.25.3) |
| REQ-320 | P3 | complete (v1.25.3) |
| REQ-321 | P4 | complete (v1.25.4) |
Full v1.26 requirement text:
`.ciagent/nova-blockchain-exchange/REQUIREMENTS.md`. Active phase plan:
`.ciagent/PLAN.md`.
## v1.28 — CLI Canonicalization + Identity Layer (complete, tag `v1.27.6`, merged to main 2026-08-19)
> **Feature milestone — complete.** 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.
>
> Tags run on the **v1.27.x** line: `v1.27.0` (P0) →
> `v1.27.1..v1.27.N` → `v1.27.(N+1)` (final = milestone release).
> Milestone branch: `milestone/v1.28-cli-identity`.
>
> **ID re-mapping (no collisions):** the source spec used `REQ-001..031`,
> `CAP-025..030`, `INV-63/64/65/18..21/34`, `D-NEW-26/37..41`, and a `kj`
> engine — none of which exist in this repo (CAP-025..032 and
> INV-1..11 are already allocated to blockchain/pilot work; the policy
> engine is kyverno-json, not `kj`). This file uses the re-mapped IDs:
> `REQ-323..353`, `CAP-033..038`, `INV-12..17`, `D-226..231`. The 1:1
> mapping is recorded in CLARIFY.md. Decisions D-226..D-231 are authored
> in CLARIFY (full autonomy) — they are not pre-existing "locked inputs".
### Decisions (locked in CLARIFY — full autonomy, load-bearing for v1.28)
- **D-226 (Mode resolution priority):** flag → env (`NOVA_CLIENT_MODE`) →
credential type → TTY heuristic. Invalid env values are ignored + warned,
falling through to credential type. No silent fallbacks (NFR-1).
- **D-227 (ABAC engine = kyverno-json):** the token-vend Lambda uses the
existing kyverno-json engine (INV-4 swappable) as the ABAC evaluator,
not a new `kj` engine. Policy at `platform/abac/token-vend.policy`.
- **D-228 (Argon2id in Lambda):** `argon2-cffi` with bundled wheels; if
the C extension fails to load, fall back to the pure-Python
implementation; if both fail, document the Fargate migration path.
- **D-229 (PAT revocation SLO):** strongly-consistent DynamoDB read on
every token-vend request; revocation takes effect within 60s P95 (NFR-4).
- **D-230 (JWKS endpoint):** Lambda function URL behind a custom domain;
rate limiting at the DNS/CDN layer. API Gateway migration deferred to
v1.19+ if throttling requirements grow.
- **D-231 (ABAC policy ownership + versioning):** Platform Security owns
`platform/abac/token-vend.policy`; changes require PR review; the
policy version (git SHA) is recorded in every token-vend audit event.
### P1 — CLI Substrate
#### REQ-323 — CodeArtifact wheel + Lambda layer pipeline
**Journeys:** J3. **Priority:** High.
**AC:** Given a merge to `main` affecting `core/`, when CI runs, then both
the wheel and the Lambda layer are published to CodeArtifact with
identical version strings; if either fails, the merge is rejected.
#### REQ-324 — CLI subcommand per `core/` module
**Journeys:** J3. **Priority:** High.
**AC:** (1) Every module in `core/` has a corresponding `nova/<module>.py`
subcommand. (2) Subcommand files are ≤ 50 lines and contain no business
logic — they delegate to `core/`. (3) CAP-034 verifies delegation by AST
scan.
#### REQ-325 — `nova init` scaffolds project
**Journeys:** J2. **Priority:** High.
**AC:** Given a directory with no `.nova/`, when Dev runs `nova init`,
then `.nova/`, `.nova/contract.yml.attestations/`, and `.gitignore`
(excluding secrets) are created.
#### REQ-326 — `nova cli-action` published
**Journeys:** J3. **Priority:** High.
**AC:** (1) Action is available on both GitHub and Gitea marketplaces.
(2) Integration test verifies byte-identical behavior on both platforms.
(3) Python 3.12 is pinned.
#### REQ-327 — `mode_resolver.py` priority
**Journeys:** J2, J3. **Priority:** High.
**AC:** (1) Explicit `--mode=agent|interactive` flag always wins.
(2) Otherwise `NOVA_CLIENT_MODE` env var. (3) Otherwise credential type
default. (4) Otherwise TTY heuristic. (5) Property tests cover all four
levels. (6) INV-13 (mode determinism) enforced at PR time.
#### REQ-328 — Audit emission with mode + selection_reason
**Journeys:** J3. **Priority:** High.
**AC:** Given any CLI invocation, when the CLI runs, then the emitted
`cli.invocation` audit event contains `mode`, `selection_reason`,
`credential_type`, `command`, and `args`. INV-12 (mode observability)
enforced.
### P2 — Lambda Packaging + Identity Layer
#### REQ-329 — Dual-use Lambda/CLI import
**Journeys:** J2. **Priority:** High.
**AC:** Given `core/lambda/contract_ingestor.py`, when imported from the
Lambda handler, then it executes the Lambda path; when imported from the
CLI, then it executes the local path; and the two paths share ≥ 80% of
their code.
#### REQ-330 — Local env synthesizer
**Journeys:** J2. **Priority:** High.
**AC:** Given a contract and a `--local` flag, when `nova apply --local`
runs, then a local env is synthesized via `core/env.py:get_env()` without
provisioning cloud resources.
#### REQ-331 — Attestations directory scaffolded
**Journeys:** J2. **Priority:** High.
**AC:** Given `nova init` ran, when Dev lists
`.nova/contract.yml.attestations/`, then the directory exists and is empty.
#### REQ-332 — JWS signing key from PAT
**Journeys:** J2. **Priority:** High.
**AC:** Given a PAT, when Dev runs `nova apply --local --sign-local-review`,
then a JWS attestation is produced; the JWS is HMAC-SHA256 with a key
derived from the PAT via `HKDF-SHA256(PAT_bytes, salt='nova-local-attestation',
info='jws-signing-key')` → 32-byte symmetric key (C-5.2 grill fix). The
verification key is derived from the PAT via the same KDF (the PAT is
the shared secret). INV-14..17 (attestation invariants) enforced.
#### REQ-333 — `nova-idp-auth` Lambda
**Journeys:** J1, J2. **Priority:** High.
**AC:** (1) Lambda exposes sign-up, sign-in, and session creation
endpoints. (2) Passwords are hashed with Argon2id. (3) Sessions are
stored in DynamoDB. (4) CAP-036 verifies end-to-end auth flow.
#### REQ-334 — Argon2id password hashing
**Journeys:** J1, J2. **Priority:** High.
**AC:** Given a sign-up request, when the user record is persisted, then
the password is stored as an Argon2id hash; raw passwords never appear in
logs, traces, environment variables, or DynamoDB records.
#### REQ-335 — DynamoDB tables for identity
**Journeys:** J1. **Priority:** High.
**AC:** (1) Tables exist: `nova-users`, `nova-sessions`,
`nova-password-resets`. (2) Tables are provisioned by `nova idp setup`.
(3) Point-in-time recovery is enabled on each.
#### REQ-336 — `nova-idp-token-vend` Lambda
**Journeys:** J1, J2, J4. **Priority:** High.
**AC:** (1) Lambda accepts a PAT (or session token) and returns a
KMS-signed OIDC token. (2) Token claims include `sub`, `aud`, `iss`,
`exp`, and role claims. (3) ABAC policy is evaluated before signing.
#### REQ-337 — KMS-signed OIDC tokens
**Journeys:** J1, J4. **Priority:** High.
**AC:** (1) Signing key is a KMS asymmetric key (RSA or ECDSA).
(2) Token signature is verifiable via the JWKS endpoint. (3) KMS
round-trip test passes. CAP-037 verifies.
#### REQ-338 — JWKS endpoint as Lambda function URL
**Journeys:** J1, J4. **Priority:** High.
**AC:** Given the identity stack is deployed, when a client GETs the JWKS
URL, then the public key(s) for token verification are returned with
`Content-Type: application/json`.
#### REQ-339 — kyverno-json ABAC policy file
**Journeys:** J1, J4. **Priority:** High.
**AC:** (1) Policy at `platform/abac/token-vend.policy`. (2) Policy inputs
include subject, requested claims, target resource, and environment.
(3) kyverno-json `evaluate` returns allow/deny; the decision is emitted to
the audit stream.
#### REQ-340 — `nova idp setup` walks admin
**Journeys:** J1. **Priority:** High.
**AC:** (1) Command supports `--check`, `--apply`, and `--verify` modes.
(2) `--check` reports missing prerequisites and the required IAM policy.
(3) `--apply` generates a CloudFormation template and requires explicit
approval. (4) `--verify` runs the KMS round-trip test.
#### REQ-341 — CloudFormation template for review
**Journeys:** J1. **Priority:** High.
**AC:** Given `nova idp setup --apply`, when the template is generated,
then the template is presented for review; resources are not created until
the operator approves; `--dry-run` shows the resource list without writing.
#### REQ-342 — PAT issuance via portal
**Journeys:** J4. **Priority:** High.
**AC:** (1) PAT is a signed JWT. (2) PAT hash is stored in DynamoDB.
(3) PAT includes a unique `jti` and an expiry claim. (4) Revocation marks
the `jti` as revoked.
#### REQ-343 — PAT hashes in DynamoDB
**Journeys:** J4. **Priority:** High.
**AC:** (1) Only the hash (not the raw PAT) is stored. (2) Table supports
lookup-by-hash and lookup-by-`jti`. (3) Revoked PATs are retained for
audit, not deleted.
#### REQ-344 — `nova auth` commands
**Journeys:** J2, J4. **Priority:** High.
**AC:** (1) `nova auth login` exchanges session → OIDC token, stores
locally. (2) `nova auth revoke --pat <id>` marks a PAT revoked.
(3) `nova auth status` shows current credential, mode, and
selection_reason. (4) All commands emit audit events.
### P3 — Documentation
#### REQ-345 — Operator guide for `nova idp setup`
**Priority:** High.
**AC:** Guide published covering `--check`, `--apply`, `--verify`,
prerequisite IAM policy, and the CloudFormation review flow.
#### REQ-346 — Developer guide for `nova auth login`
**Priority:** High.
**AC:** Guide published covering signup, signin, login, mode resolution,
and credential-type behavior at a TTY vs. piped stdout.
#### REQ-347 — Identity-layer threat model
**Priority:** High.
**AC:** Threat model published covering Argon2id storage, KMS signing,
JWKS exposure, PAT revocation SLO, ABAC token vending, and the no-AWS-
managed-identity constraint (NFR-5).
### P4 — Integration Testing
#### REQ-348 — E2E integration test
**Priority:** High.
**AC:** Given a deployed Nova-idp, when the test runs, then sign-up →
sign-in → token-vend → apply → audit completes successfully; the audit
event chain is verifiable.
#### REQ-349 — Property tests for `mode_resolver`
**Priority:** High.
**AC:** (1) Property tests cover all four priority levels. (2) Edge cases:
TTY but piped stdout, missing credential, conflicting flag/env, invalid
env value. (3) INV-13 enforced via test.
#### REQ-350 — KMS round-trip test
**Priority:** High.
**AC:** Given a token signed by the token-vend Lambda, when the test
fetches the JWKS and verifies the signature, then verification succeeds.
#### REQ-351 — PAT revocation SLO test
**Priority:** High.
**AC:** Issue PAT → use to vend token → revoke → assert denial within 60s
P95. Test passes in CI.
### P5 — Capability Gate
#### REQ-352 — CAP-033..038 gate rules wired into CI
**Priority:** High.
**AC:** (1) CAP-033 (CLI subcommand surface exists): `nova --help` lists a
subcommand for every `core/` module. (2) CAP-034 (subcommand delegates to
`core/`): every `nova/<module>.py` ≤ 50 lines, no business logic, AST
scan. (3) CAP-035 (layer matches wheel): Lambda layer ARN version matches
the `nova-cli` wheel version. (4) CAP-036 (Nova-idp auth flow works): E2E
test (REQ-348) passes. (5) CAP-037 (token-vend signs via KMS): KMS
round-trip (REQ-350) passes. (6) CAP-038 (PAT issuance + revocation):
REQ-351 passes. Failure of any → merge blocked.
#### REQ-353 — Capability gate GREEN for v1.28 release
**Priority:** High.
**AC:** CAP-001..CAP-032 remain Verified; CAP-033..CAP-038 are Verified.
All v1.28 release-gate criteria in PLAN.md §6 met.
### v1.28 Invariants (new — INV-12..INV-17)
- **INV-12 (Mode observability):** Every CLI invocation emits a
`cli.invocation` audit event containing `mode`, `selection_reason`,
`credential_type`, `command`, and `args`.
- **INV-13 (Mode resolution determinism):** Resolution priority is
flag → env (`NOVA_CLIENT_MODE`) → credential type → TTY. No silent
fallbacks. Deviations rejected at PR time.
- **INV-14 (Credential type encodes role):** `developer_pat` /
`nova_oidc_token` + TTY present → `interactive`; TTY absent → `agent`.
- **INV-15 (No AWS-managed identity in path):** Nova-idp MUST NOT depend
on Cognito, IAM Identity Center, or any AWS-managed identity service.
- **INV-16 (Password storage):** Passwords hashed with Argon2id; raw
passwords never in logs/traces/env/DynamoDB.
- **INV-17 (ABAC discipline):** The token-vend Lambda evaluates the
kyverno-json ABAC policy before signing; allow/deny + policy inputs
emitted to the audit stream.
### v1.28 Traceability (live — see CHECKPOINT.json for authoritative state)
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-323 | P1 | complete (v1.27.1) |
| REQ-324 | P1 | complete (v1.27.1) |
| REQ-325 | P1 | complete (v1.27.1) |
| REQ-326 | P1 | complete (v1.27.1) |
| REQ-327 | P1 | complete (v1.27.1) |
| REQ-328 | P1 | complete (v1.27.1) |
| REQ-329 | P2 | complete (v1.27.2) |
| REQ-330 | P2 | complete (v1.27.2) |
| REQ-331 | P2 | complete (v1.27.2) |
| REQ-332 | P2 | complete (v1.27.2) |
| REQ-333 | P3 | complete (v1.27.3) |
| REQ-334 | P3 | complete (v1.27.3) |
| REQ-335 | P3 | complete (v1.27.3) |
| REQ-336 | P4 | complete (v1.27.4) |
| REQ-337 | P4 | complete (v1.27.4) |
| REQ-338 | P4 | complete (v1.27.4) |
| REQ-339 | P4 | complete (v1.27.4) |
| REQ-340 | P4 | complete (v1.27.4) |
| REQ-341 | P4 | complete (v1.27.4) |
| REQ-342 | P4 | complete (v1.27.4) |
| REQ-343 | P4 | complete (v1.27.4) |
| REQ-344 | P4 | complete (v1.27.4) |
| REQ-345 | P5 | complete (v1.27.5) |
| REQ-346 | P5 | complete (v1.27.5) |
| REQ-347 | P5 | complete (v1.27.5) |
| REQ-348 | P5 | complete (v1.27.5) |
| REQ-349 | P5 | complete (v1.27.5) |
| REQ-350 | P5 | complete (v1.27.5) |
| REQ-351 | P5 | complete (v1.27.5) |
| REQ-352 | P6 | complete (v1.27.6) |
| REQ-353 | P6 | complete (v1.27.6) |
---
## v1.29 — Reposplit + Identity Layer Bring-Live (complete, tag `v1.28.6`, merged to main 2026-08-20)
> **Feature milestone — complete.** v1.29 extracts all live platform
> components into a dedicated Gitea-private Terraform repository
> (`nova-platform-ops`), brings Nova-idp live in account `581513795199`
> for the first time, and standardizes `acdl/acdl` on GitHub. `kj` (a
> compiled Go binary, pinned v0.0.3, distinct from the kyverno-json
> engine) has exactly one identity: one ECR image digest shared by the
> production Lambda runtime and its defensive Fargate fallback
> (KJ-LOCKSTEP, REQ-371).
>
> Tags run on the **v1.28.x** line: `v1.28.0` (P0) →
> `v1.28.1..v1.28.5` (execution) → `v1.28.6` (final = milestone release).
> Milestone branch: `milestone/v1.29-reposplit-identity`.
>
> **Scope split (CLARIFY-grounded, full autonomy):** Terraform module
> code is authored out-of-band in `nova-platform-ops`. REQs marked
> `[covered-reference]` have their verification surface in the
> `nova-platform-ops` cutover gates (M1/M1.5/M2), documented in the
> operator guide (`docs/operator-guide-platform-ops.md`). CIAgent in
> `acdl` authors only the acdl-side REQs.
### Decisions (locked in CLARIFY — full autonomy, load-bearing for v1.29)
- **D-232 (Forge parity abandoned):** the byte-identical-forges CI parity
(Gitea + GitHub) is abandoned; `acdl/acdl` standardizes on GitHub. CI
fails with `forge_parity_disabled` (deliberate). Rationale: Vision §4
domain boundaries — operations lives in Gitea-private `nova-platform-
ops`, engineering lives on GitHub.
- **D-233 (JWKS public-read via CloudFront edge):** the JWKS endpoint is
the only public read surface of the live platform (INV-18). All other
platform endpoints gate with `AuthType: AWS_IAM`. CloudFront + OAC
pinning replaces direct Lambda Function URL exposure.
- **D-234 (KMS asymmetric key provisioning):** `alias/nova-oidc-signing`
provisioned with `KeySpec: ECC_NIST_P256`, `KeyUsage: SIGN_VERIFY`,
90-day rotation cadence (matches per-stack CMK rotation per D-069).
- **D-235 (Tag-pin handoff):** engineering hands off to operations via
tags. `acdl/acdl` `publish.yml` attaches artifacts to GitHub Releases
per tag; `nova-platform-ops` declares `local.nova_platform_version` +
`local.kj_source_sha` and resolves substrates through a single
`data.aws_ecr_image.kj_image`.
- **D-236 (Cutover shape + rollback procedure):** M1 day-0 cutover is
conditional on M1.5 verification gate (3 consecutive rebuilds, 12-item
spike per grill CF-1). Rollback = revert `nova_platform_version` pin;
the prior tag's artifacts remain downloadable. M2a (Fargate toggle)
activates only if M1.5 fails 3×.
- **D-237 (Fargate sunset discipline):** the always-warm minimal Fargate
standby (REQ-363b, ~$1520/month) may not be deleted unless REQ-363 has
been green in production for ≥30 consecutive days. Sunset requires an
architecture review.
- **D-238 (KJ-LOCKSTEP release-gate invariant):** the ECR image digest
running on the Fargate standby MUST equal the digest resolved by
`aws_lambda_function.nova_idp_token_vend.image_uri` at every
`terraform plan`. Enforced by `lifecycle.precondition` (mechanism) +
Gitea Actions `if: steps.plan.outcome == 'success'` (mechanism) + PR
comment reporting (observability) + operator review (last, never
first). No second pipeline, no second SHA pin. Vision §6 immutability
+ Vision §5 narrow interfaces.
### P1 — Publish Pipeline
#### REQ-354 — `publish.yml` attaches Lambda zip + layer wheel + Python wheel + ECR container image to GitHub Release for each tag
**Journeys:** J1, J2 (criteria 34). **Priority:** High.
**AC:**
**(1)** Given a tag `v1.29.x` is pushed to `acdl/acdl` main, when
`publish.yml` runs, then the release artifacts `nova-lambda-token-vend-
v1.29.x.zip`, `nova-cli-layer-v1.29.x.zip`, and `nova-1.29.x-py3-none-
any.whl` appear in GitHub Releases with matching SHA-256 in the body.
**(2)** Given two consecutive tags `v1.29.0` and `v1.29.1`, when both
releases are queried, then each tag's artifacts are independent and the
previous tag's artifacts remain downloadable.
**(3)** Given the publish pipeline runs for tag `v1.29.x`, when the
image build step executes, then a single ECR image is pushed at tag
`v1.29.x-kj-<kj-source-sha>` where `<kj-source-sha>` is read from
`platform/abac/kj-version.txt` at build time and embedded in the tag
(D-239: ECR tags reject `+`; corrected from `v1.29.x+kj-<sha>` to
`v1.29.x-kj-<sha>`).
**(4)** Given the image is pushed, when the GitHub Release body lists
artifacts, then the image URI and digest appear alongside the wheel,
layer, and Lambda zip. KJ-STATIC: the `kj` binary is compiled
`CGO_ENABLED=0 GOOS=linux GOARCH=amd64` and `file(1)` reports
`statically linked, no shared library` before embedding.
### P2 — Gitea Scrub + Decisions
#### REQ-367 — Hard scrub of all Gitea references in `acdl/acdl` at v1.29.0
**Journeys:** Cross-cutting. **Priority:** Critical.
**AC:**
**(1)** Given v1.29.0 is cut from main, when `grep -rni gitea .github/
docs/ pyproject.toml README.md .ciagent/` runs, then zero matches
outside this spec's archive section.
**(2)** Given v1.29.0 ships, when `.gitea/` is checked in the working
tree, then `find .gitea` returns nothing.
**(3)** Given v1.29.0 ships, when the bit-identical-forges parity is
asserted in CI, then CI fails with `forge_parity_disabled` (deliberate;
documented in D-232).
#### REQ-368 — Decisions D-232..238 recorded in PROJECT.md + CLARIFY
**Journeys:** Cross-cutting. **Priority:** High.
**AC:**
**(1)** Given the milestone is recorded, when loading `PROJECT.md`, then
decisions D-232 (forge parity abandoned), D-233 (JWKS public-read via
CloudFront edge), D-234 (KMS asymmetric key provisioning), D-235 (tag-
pin handoff), D-236 (cutover shape + rollback procedure), D-237
(Fargate sunset discipline ≥30 days → architecture review), D-238
(KJ-LOCKSTEP release-gate invariant) are present with rationale citing
Vision §4 domain boundaries.
**(2)** Given decisions are present, then each decision references the
source statement from the v1.29 spec.
### P3 — CFN Archive + TF Delegation
#### REQ-369 — CFN → Terraform conversion of `nova idp setup`
**Journeys:** J2. **Priority:** High.
**AC:**
**(1)** Given the CFN template in `acdl/acdl/nova/idp/setup.py`, when
the equivalent Terraform in `nova-platform-ops` runs, then the same
resources (Lambdas, DDB tables, IAM roles, KMS key references) are
created. [covered-reference: nova-platform-ops]
**(2)** Given the conversion, when a new operator runs `nova idp setup
--apply`, then the CLI delegates to `terraform apply`; the CFN code
path is no longer the active path.
**(3)** Given the conversion, the CFN file in `acdl/acdl` is archived
to `docs/archive/nova-idp-cfn-v1.28.md` as read-only reference;
deletion is a follow-up.
### P4 — Operator Guide + Reference Tracking (docs)
#### REQ-OPS-GUIDE — `docs/operator-guide-platform-ops.md`
**Journeys:** J2. **Priority:** High.
**AC:** Given the operator guide is published, when an operator reads
it, then it covers: KMS rotation (90-day cadence, `alias/nova-oidc-
signing`), JWKS reachability via CloudFront edge (OAC pinning, public
read vs. IAM-gated), PITR restore (DynamoDB point-in-time recovery),
PAT revocation (60s SLO), edge configuration (CloudFront + WAF + ACM +
Route53), Fargate standby status checks (`GET /health` every 10s,
`KJ-WARMUP-HEALTH`), cost section (WAF ~$510/month + Fargate
~$1520/month), artifact-mirror fallback (operator-local mirror by
SHA-256 when Gitea `act_runner` cannot reach GitHub Releases), and the
M1/M1.5/M2 cutover gates as release-gate entries for the covered-
reference REQs.
### P5 — Consumer Deploy Bump (cross-project, Edge 8)
#### REQ-CONSUMER-BUMP — `nova-blockchain-exchange` deploy.yml `@v1.25` → `@v1.29`
**Journeys:** J1. **Priority:** High.
**AC:**
**(1)** Given `nova-blockchain-exchange` deploy.yml pins
`acdl/.github/workflows/deploy.yml@v1.25`, when the bump is applied,
then both `.github/workflows/deploy.yml` and
`.gitea/workflows/deploy.yml` reference `@v1.29`.
**(2)** Given the bump, when the smoke test runs (sign-up → sign-in →
token-vend → apply → audit), then the chain completes successfully
against the v1.29 publish artifacts.
### Covered-reference requirements (authored in `nova-platform-ops`, out-of-band)
The following REQs are tracked for milestone completeness but their
code lands in `nova-platform-ops`. Their verification surface is the
M1/M1.5/M2 cutover gates documented in the operator guide.
- **REQ-355** — ops repo pins `local.nova_platform_version` +
`local.kj_source_sha`; CI resolves matching artifacts + image digest.
- **REQ-356** — ops repo CI runs `terraform plan` on every PR; drift
fails with `drift_detected`.
- **REQ-357** — HITL approver distinct from PR author required for
`terraform apply` (INV-3, TFM-HITL).
- **REQ-358** — Operator bumps `nova_platform_version` to roll out
engineering change; `CodeSha256` matches the artifact SHA-256.
- **REQ-359** — ops repo is Gitea-private with no GitHub mirror
(OPER-PRIV).
- **REQ-360** — ops repo IAM scope is bounded; no AdministratorAccess
(IAM-NARROW).
- **REQ-361** — Terraform imports existing live resources idempotently
(IMPORT-IDEMPOTENT).
- **REQ-362** — `alias/nova-oidc-signing` KMS key provisioned
(`ECC_NIST_P256`, `SIGN_VERIFY`, 90-day rotation).
- **REQ-363** — Nova-idp 3 Lambdas deployed on container image with
static `kj` (production substrate, KJ-STATIC).
- **REQ-363b** — Fargate defensive fallback — always-warm minimal
Fargate standby, **same ECR image** (KJ-LOCKSTEP, KJ-WARMUP-HEALTH).
- **REQ-364** — JWKS Function URL reachable only via CloudFront with
OAC pinning (INV-18, JWKS-EDGE-ONLY).
- **REQ-365** — WAF WebACL rate-limit (3000/5min) + AWS Managed Rules.
- **REQ-366** — ACM cert + Route53 alias for the JWKS domain.
- **REQ-371** — KJ-LOCKSTEP applied-at-plan mechanism
(`lifecycle.precondition` on both image-bearing resources; fail-closed
by mechanism, not by discipline).
### v1.29 Invariants + NFR constraints (new)
- **INV-18 (JWKS-EDGE-ONLY):** the JWKS endpoint is the only public read
surface of the live platform. All other platform endpoints MUST gate
with `AuthType: AWS_IAM`.
- **KJ-STATIC (NFR):** `kj` compiled `CGO_ENABLED=0`; `file(1)` reports
`statically linked, no shared library`; SHA-256 matches
`platform/abac/kj-version.txt`; recorded in Terraform state.
- **KJ-LOCKSTEP (NFR):** Fargate standby digest == Lambda `image_uri`
digest at every `terraform plan`. Detected by
`lifecycle.precondition` (mechanism) + CI `if:
steps.plan.outcome == 'success'` (mechanism) + PR comment
(observability) + operator review (last). No second pipeline, no
second SHA pin.
- **KJ-WARMUP-HEALTH (NFR):** Fargate standby `READY` probe (`GET /health
→ 200` every 10s) green before M1 cutover; release-gate entry.
- **OPER-PRIV (NFR):** `nova-platform-ops` `private: true`, not mirrored.
- **IAM-NARROW (NFR):** Gitea OIDC role bounded per REQ-360; no
`Action: "*"` or `Resource: "*"`.
- **DRIFT-DETECT (NFR):** `terraform plan` exit 2 (drift) fails the
apply workflow; manual reconciliation required.
- **IMPORT-IDEMPOTENT (NFR):** re-import exits non-zero with
`resource_already_imported`.
- **TFM-HITL (NFR):** `terraform apply` against `main` requires Gitea
Actions approval from a user distinct from the PR author.
- **JWKS-SLO (NFR):** `GET /.well-known/jwks.json` P95 < 200ms same-
region; `Cache-Control: max-age=3600` honored.
- **JWKS-ROTATION (NFR):** on key rotation, both old + new public keys
published during 24-hour overlap window.
### v1.29 Traceability (live — see CHECKPOINT.json for authoritative state)
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-354 | P1 | complete (v1.28.1) |
| REQ-367 | P2 | complete (v1.28.2) |
| REQ-368 | P2 | complete (v1.28.2) |
| REQ-369 | P3 | complete (v1.28.3) |
| REQ-OPS-GUIDE | P4 | complete (v1.28.4) |
| REQ-CONSUMER-BUMP | P5 | complete (v1.28.5) |
| REQ-355 | covered-reference | planned (M1 gate: nova-platform-ops) |
| REQ-356 | covered-reference | planned (M1 gate: nova-platform-ops) |
| REQ-357 | covered-reference | planned (M1.5 gate: nova-platform-ops) |
| REQ-358 | covered-reference | planned (M2 gate: nova-platform-ops) |
| REQ-359 | covered-reference | planned (M1 gate: nova-platform-ops) |
| REQ-360 | covered-reference | planned (M1.5 gate: nova-platform-ops) |
| REQ-361 | covered-reference | planned (M1 gate: nova-platform-ops) |
| REQ-362 | covered-reference | planned (M1.5 gate: nova-platform-ops) |
| REQ-363 | covered-reference | planned (M1.5 gate: nova-platform-ops) |
| REQ-363b | covered-reference | planned (M1.5 gate: nova-platform-ops) |
| REQ-364 | covered-reference | planned (M1.5 gate: nova-platform-ops) |
| REQ-365 | covered-reference | planned (M1 gate: nova-platform-ops) |
| REQ-366 | covered-reference | planned (M1 gate: nova-platform-ops) |
| REQ-371 | covered-reference | planned (M2 gate: nova-platform-ops) |
> **Covered-reference REQs** are verified via the M1/M1.5/M2 cutover
> gates in `nova-platform-ops` CI (out-of-band). The operator attests
> the results in `docs/operator-guide-platform-ops.md` §18 "Cutover
> Gates" Result column. P6 audit verifies the template + Result column
> exist; the live-green attestation is out-of-band (grill CF-1/CF-2).
## v1.30 — Single-shot Leadership Deck (active milestone)
> **Feature milestone — single-shot PPTX leadership deck.** Ships
> REQ-372.1 through REQ-372.12 in one execution phase. Tags run on the
> **v1.29.x** line (milestone v1.30 → tags v1.29.1..v1.29.3). Tag
> `v1.29.3` = the milestone release. The deck is a discrete artifact,
> hand-authored (NOT a compression of the existing citizen-developer
> pitch per D-241), scoped to a single live presentation to
> Infrastructure & Operations leadership in August 2026, securing
> architecture endorsement and a November 2026 runway.
>
> Source: `docs/presentations/nova-leadership-deck-marp.md` (authored
> against the Slide Content Map in `.ciagent/PROJECT.md` §v1.30 spec).
> Rendered via the existing `scripts/render_pptx.py` (narrowly extended
> per D-242 to accept an explicit source path + custom output filename
> and to add a per-slide footer textbox). Smoke test:
> `scripts/check_leadership_deck.sh` (runnable on demand; NOT a CI gate
> per the single-shot constraint). Vision grounding `[1]` citations
> resolve to `docs/vision.md` (the spec's `acdl-vision.md` reference).
### Decisions (locked in CLARIFY, full autonomy — load-bearing for v1.30)
- **D-241 (Q3 override):** The leadership deck is a **discrete,
hand-authored artifact** — NOT a compression of the existing
23-slide citizen-developer pitch
(`nova-autonomous-cloud-delivery-marp.md`). This overrides the
post-v1.29 STATE.md intake assumption 3 ("is a compression, not a
rewrite"). The existing citizen-developer deck remains untouched.
Rationale: the spec §2.2 + cover note forbid compression/mirroring;
the Slide Content Map is hand-authored content, not derived.
- **D-242 (render pipeline):** The existing `scripts/render_pptx.py`
is narrowly extended to (a) accept an explicit source `.md` path +
custom output `.pptx` filename (the cover note's invocation
`scripts/render_pptx.py docs/presentations/nova-leadership-deck.md`
is honoured via a path-aware argv), and (b) render a right-aligned
footer textbox on every slide with the exact string
`Nova Platform - Infrastructure & Operations` (the python-pptx
renderer does not read the Marp `footer:` directive; REQ-372.5
requires the footer on every rendered slide). This extension is a
non-REQ-372 prerequisite per spec §3.3 Edge 2 ("scope narrowly and
update `render_pptx.py` separately"). The source file is authored as
`nova-leadership-deck-marp.md` to fit the existing `-marp.md`
pipeline convention; the output is `nova-leadership-deck.pptx` per
spec REQ-372.2.
- **D-243 (date anchor):** August 2026 is a month-only presentation
anchor (no specific day); November 2026 is the runway anchor
(~90 days). Slide 7 references "Infrastructure & Operations
leadership" without naming a specific day. Resolves spec §7 Q1.
### Requirements
#### REQ-372.1 — Source markdown exists and is parseable
**Priority:** High · **Journey:** J1
**Given** the deck initiative is scoped, **when**
`docs/presentations/nova-leadership-deck-marp.md` is read, **then** the
file exists, parses as valid Marp markdown, contains exactly 7 slides
delimited by `---`, and the file header carries the related-artifacts
comment (per REQ-372.9).
#### REQ-372.2 — PPTX render via existing pipeline
**Priority:** High · **Journey:** J1
**Given** the source markdown exists (REQ-372.1), **when**
`scripts/render_pptx.py` is invoked against the leadership deck source,
**then** `docs/presentations/nova-leadership-deck.pptx` is written with
7 slides and python-pptx raised no exceptions.
#### REQ-372.3 — Slide count is exactly 7
**Priority:** High · **Journey:** J1
**Given** the source markdown, **when** slide boundaries are counted,
**then** the count equals 7.
#### REQ-372.4 — Speaker notes depth per slide
**Priority:** High · **Journey:** J1
**Given** the source markdown, **when** speaker notes (HTML comments)
are extracted per slide, **then** per-slide word counts fall within:
slides 1/2/4/6 in 150300; slides 3/5 in 250400; slide 7 in 200300.
Smoke test exits non-zero on violation.
#### REQ-372.5 — Footer on every slide
**Priority:** High · **Journey:** J1
**Given** the source markdown's Marp frontmatter `footer:` directive +
the python-pptx renderer extension (D-242), **when** the PPTX is
rendered, **then** every slide carries the right-aligned footer
`Nova Platform - Infrastructure & Operations`.
#### REQ-372.6 — S&P theme tokens are the only colors used
**Priority:** High · **Journey:** J1
**Given** the source markdown, **when** color values are extracted
(Marp directives + inline overrides), **then** the only hex colors
present are `#D6002A`, `#1B1B1B`, `#FFFFFF`, `#F0F0F0`.
#### REQ-372.7 — Slide-by-slide content traceability
**Priority:** High · **Journey:** J1
**Given** the rendered PPTX, **when** any slide N ∈ [1, 7] is opened,
**then** its content matches the **Slide Content Map** in
`.ciagent/PROJECT.md` §v1.30 spec. Any deviation from the map requires
`CLARIFY` before ship. Smoke test does not assert content strings
verbatim (brittle); audit verifies by visual review against the map.
#### REQ-372.8 — Smoke test exits 0 on pass
**Priority:** High · **Journey:** J1
**Given** `scripts/check_leadership_deck.sh` exists, **when** invoked
from the repo root, **then** the script asserts: (a) source file
exists, (b) slide count = 7, (c) per-slide word counts in band, (d)
footer string present in source, (e) only S&P hex colors used, (f)
PPTX file exists. Exits 0 on pass, non-zero on fail. Runnable on
demand; not wired as a CI gate.
#### REQ-372.9 — Related-artifacts comment in source header
**Priority:** Med · **Journey:** J1
**Given** the source markdown, **when** the file header is inspected,
**then** a comment exists that (i) names this deck as the leadership
artifact for Infrastructure & Operations, (ii) names August 2026 as
the presentation date, (iii) names
`nova-autonomous-cloud-delivery-marp.md` as a related-but-distinct
artifact and notes that this deck does not compress or modify it.
#### REQ-372.10 — CAP-042 appended to STATE.md at ship
**Priority:** Med · **Journey:** J1
**Given** the deck has shipped, **when** STATE.md is updated at the
v1.30 milestone ship wave, **then** a CAP-042 row exists capturing
artifact paths (`nova-leadership-deck-marp.md`,
`nova-leadership-deck.pptx`), audience (Infrastructure & Operations
leadership), single-shot intent, presentation month (August 2026).
#### REQ-372.11 — D-241 recorded in PROJECT.md at ship
**Priority:** Med · **Journey:** J1
**Given** the deck has shipped, **when** PROJECT.md is updated at the
v1.30 milestone ship wave, **then** a `D-241` entry exists capturing:
(a) single-shot nature of the deck, (b) audience (Infrastructure &
Operations leadership), (c) August 2026 anchor + November 2026 runway,
(d) explicit decision not to compress the existing citizen-developer
deck.
#### REQ-372.12 — Vision grounding citations in architecture-load slides
**Priority:** Med · **Journey:** J1
**Given** the source markdown, **when** the speaker notes are
inspected, **then** at least one `[1]` citation appears in slides 3,
5, and 7 — the three architecture-load slides — grounding the
principles, anti-goals, and integration-boundary claims to
`docs/vision.md` (the spec's `acdl-vision.md` reference [1]).
### v1.30 Traceability (live — see CHECKPOINT.json for authoritative state)
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-372.1 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.2 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.3 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.4 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.5 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.6 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.7 | P1/P3 | complete (v1.29.5, polished + diagrams) |
| REQ-372.8 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.9 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.10 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.11 | P1/P3 | complete (v1.29.5, polished) |
| REQ-372.12 | P1/P3 | complete (v1.29.5, polished) |
## v1.31 — Leadership Deck Polish II (active milestone)
> **Refinement-only NFR milestone — polish pass on the v1.30 leadership
> deck.** Enriches visible on-slide prose + improves layout, then
> re-renders the PPTX. No new features, no new slides, no theme change,
> no diagram change (D-247). Tags run on the **v1.30.x** line (previous
> minor): `v1.30.0` (P0) → `v1.30.1` (P1 = milestone release). The
> v1.30 requirements (REQ-372.1..12) remain complete and are NOT
> re-opened; v1.31 adds REQ-373.1..4 as a refinement layer over the
> same artifact.
### Decisions (locked in CLARIFY, full autonomy — load-bearing for v1.31)
- **D-247 (refinement-only, theme-preserving):** The polish is
confined to visible prose density + layout. The following are
**invariants** and must not change: the S&P theme tokens
(`#D6002A`, `#1B1B1B`, `#FFFFFF`, `#F0F0F0`); the 7-slide count; the
per-slide speaker-note word-count bands (1/2/4/6: 150300; 3/5:
250400; 7: 200300, per REQ-372.4); the `[1]` citations on slides
3/5/7 (per REQ-372.12); the 7 mermaid diagram PNGs and their `.mmd`
sources; the footer string `Nova Platform - Infrastructure &
Operations`. No new slides, no new diagrams, no renderer changes.
The citizen-developer deck is untouched (D-241 still holds).
### Requirements
#### REQ-373.1 — Visible prose density enriched per slide
**Priority:** High · **Journey:** J1
**Given** the v1.30 deck shipped with sparse visible wording (slides
26 averaged 4367 visible words, leaning on diagrams), **when** the
polished source markdown is inspected, **then** every slide's on-slide
body (excluding speaker-note HTML comments and image references)
carries richer, well-structured wording that lets the slide read as a
standalone artifact — a title, a framing line, a body, and (where
present) a closing italic benefit — without crowding the diagram or
overflowing the 16:9 slide. Verified by visual review against the
Slide Content Map and by the smoke test still passing.
#### REQ-373.2 — Layout improved within the renderer block vocabulary
**Priority:** High · **Journey:** J1
**Given** the existing `scripts/render_pptx.py` block vocabulary
(lead/quote/plain/bullet/ordered/image/table/benefit) is not modified,
**when** the polished PPTX is rendered, **then** each slide balances
its blocks so the reading order is clear (title → frame → body →
diagram → benefit) and the diagram remains the visual anchor. No new
renderer features are added.
#### REQ-373.3 — v1.30 invariants preserved (D-247)
**Priority:** High · **Journey:** J1
**Given** the D-247 invariants, **when** the polished source + PPTX
are validated, **then**: slide count = 7; the only hex colors are the
four S&P tokens; per-slide speaker-note word counts remain in band per
REQ-372.4; `[1]` citations remain in slides 3, 5, 7 speaker notes; the
7 diagram PNGs are reused unchanged; the footer string is unchanged.
The smoke test enforces (a)(f) and must exit 0.
#### REQ-373.4 — PPTX re-rendered; smoke test exits 0
**Priority:** High · **Journey:** J1
**Given** the polished source markdown, **when**
`scripts/render_pptx.py docs/presentations/nova-leadership-deck-marp.md
--output docs/presentations/nova-leadership-deck.pptx` is invoked,
**then** the PPTX is written with 7 slides and python-pptx raises no
exceptions, **and** `bash scripts/check_leadership_deck.sh` exits 0.
### v1.31 Traceability (live — see CHECKPOINT.json for authoritative state)
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-373.1 | P1 | pending |
| REQ-373.2 | P1 | pending |
| REQ-373.3 | P1 | pending |
| REQ-373.4 | P1 | pending |