Files
acdl/.ciagent/REQUIREMENTS.md
T
CIAgent Orchestrator 0789c27ca2
Nova Slides Render / render (push) Failing after 14m27s
docs(P00): complete v1.29 pre-execution — SPECIFY+CLARIFY+RESEARCH+PLAN+GRILL+MVP/UX
---ci---
project: acdl
phase: 0
milestone: v1.29
status: complete
---/ci---
2026-08-20 05:00:35 +00:00

42 KiB
Raw Blame History

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.pyevaluate() returns valid PCR dicts validated against schemas/policy_check_result.schema.json; native-output parsing is defensive (malformed → error PCR, not exception); is_configured()==falseSKIPPED 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 — active. 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.Nv1.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 (active, milestone branch milestone/v1.29-reposplit-identity)

Feature milestone — active. 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-362alias/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 planned
REQ-367 P2 planned
REQ-368 P2 planned
REQ-369 P3 planned
REQ-OPS-GUIDE P4 planned
REQ-CONSUMER-BUMP P5 planned
REQ-355 covered-reference planned (nova-platform-ops)
REQ-356 covered-reference planned (nova-platform-ops)
REQ-357 covered-reference planned (nova-platform-ops)
REQ-358 covered-reference planned (nova-platform-ops)
REQ-359 covered-reference planned (nova-platform-ops)
REQ-360 covered-reference planned (nova-platform-ops)
REQ-361 covered-reference planned (nova-platform-ops)
REQ-362 covered-reference planned (nova-platform-ops)
REQ-363 covered-reference planned (nova-platform-ops)
REQ-363b covered-reference planned (nova-platform-ops)
REQ-364 covered-reference planned (nova-platform-ops)
REQ-365 covered-reference planned (nova-platform-ops)
REQ-366 covered-reference planned (nova-platform-ops)
REQ-371 covered-reference planned (nova-platform-ops)