68908d7f6a
---ci--- project: acdl phase: 0 milestone: v1.31 status: specify ---/ci---
1120 lines
54 KiB
Markdown
1120 lines
54 KiB
Markdown
# Nova — Requirements
|
||
|
||
> **Compressed.** The full v1.0–v1.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.0–v1.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` (P1–P4) →
|
||
> `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, ~$15–20/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 3–4). **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 ~$5–10/month + Fargate
|
||
~$15–20/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 150–300; slides 3/5 in 250–400; slide 7 in 200–300.
|
||
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: 150–300; 3/5:
|
||
250–400; 7: 200–300, 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
|
||
2–6 averaged 43–67 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 | |