---ci--- project: acdl phase: 0 milestone: v1.29 status: complete ---/ci---
42 KiB
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 perconfig.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-jsonbecomes the primary compliance / policy tool, implemented behind a swappablePolicyEngineadapter 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). Tagv1.24.5= the milestone release.One problem, one architectural correction:
- 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 aPolicyEngineprotocol so it is replaceable. The confidence signal is untouched — it already consumeslist[PolicyCheckResult]engine-agnostically.
Decisions (locked in CLARIFY, full autonomy — load-bearing for v1.26)
- D-115 (C-1):
kyverno-jsonis a runtime dependency installed viago install github.com/kyverno/kyverno-json/cmd/kj@latest(pinned in ascripts/install-kyverno-json.shhelper; the CI image installs it). Not a Python package — kyverno-json is a Go binary. TheKyvernoJsonEngine.is_configured()checkswhich kjand skips gracefully when absent (emitsSKIPPEDPCR, mirroring the Wiz adapter). - D-116 (C-2): kyverno-json PCR records carry
engine: "kyverno"(no new enum value). The existingengineenum inschemas/policy_check_result.schema.jsonalready includes"kyverno"; adding"kyverno-json"would force a schema change + checkov_adapter test regression for no semantic gain. TheruleIdprefixKJ_distinguishes kyverno-json rules from the K8s Kyverno adapter'sKYVERNO_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 inadapters/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 inadapters/terraform/policy/custom_rules/nova_tagging.py) is kept. A kyverno-json mirror policyrequire-tagging-standard.jsonis added inadapters/kyverno-json/policies/stack-ir/. The P3 meta-policytagging-rules-agree.jsonasserts the two engines agree on every resource; divergence emits anerrorPCR (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.pydefines aPolicyEnginePythonProtocol(PEP 544) with three members:name -> str,is_configured() -> bool, andevaluate(payload: dict | str, policy_dir: Path, contract_id: str) -> list[dict](where each dict conforms toschemas/policy_check_result.schema.json). APolicyEngineRegistrysingleton selects the active engine fromconfig.json's newpolicy.enginekey (default"kyverno-json"); raisesKeyErroron an unknown engine name. The registry exposesget_engine()andregister(name, factory). Pure stdlib, no engine imports at the protocol layer. - REQ-292:
.ciagent/config.jsongains a new top-levelpolicyobject:{"engine": "kyverno-json", "policy_root": "adapters/kyverno-json/policies"}. The registry readspolicy.engineto select the active engine andpolicy.policy_rootas the default policy directory. Backward-compatible: if thepolicykey is absent, the registry returns aNullEnginethat emits onlySKIPPEDrecords (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.pyimplementsKyvernoJsonEnginesatisfying thePolicyEngineprotocol.is_configured()returnsTruewhenwhich kjsucceeds.evaluate()writes the payload to a temp JSON file, invokeskj scan --policy <policy_dir> --payload <payload.json> -o json, parses the native result list, and translates each entry to a PCR dict (engine: "kyverno",ruleIdprefixedKJ_<policy_name>, severity mapped,resultmapped pass/fail/skip → pass/fail/skipped). Whenis_configured()is false,evaluate()returns a singleSKIPPEDPCR withruleId: "KJ_ENGINE_NOT_CONFIGURED". Native output parsing is defensive: any kyverno-json output that doesn't match the expected shape produces anerrorPCR, never an exception. - REQ-294:
adapters/kyverno-json/__init__.pyexportsKyvernoJsonEngine.adapters/kyverno-json/policies/_smoke.jsonis a single trivial policy (require-contract-id) used to validate the engine round-trip end-to-end in tests.scripts/install-kyverno-json.shrunsgo install github.com/kyverno/kyverno-json/cmd/kj@latestand printskj version; documented inadapters/kyverno-json/README.md. The CI image installs Go + kj whenpolicy.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 mirroringschemas/contract.schema.jsonconstraints:require-id-pattern.json,require-env-in-enum.json,require-infrastructure-min-1.json,forbid-unknown-fields.json. Each policy is a single KyvernoPolicyresource with onevalidate.assertrule using JMESPath against the payload root. - REQ-296:
core/contract_resolver.pyinvokes thePolicyEngineRegistry.get_engine().evaluate()with the contract dict andpolicies/contract/before resolving (early-fail on contract violations) and emits anova.policy.evaluatedmetrics event. Failures feed the confidence signal'spolicyinput asfailPCRs; the resolver does not exit — the confidence signal decides the gate (consistent with the existing--soft-failCheckov 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 carriesnova:owner+nova:environmenttags — portsnova_tagging.pyinto a declarative Kyverno policy).forbid-public-ingress.json(no resource haspublic_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.pyinvokes the engine with the resolved Stack IR andpolicies/stack-ir/after resolving. The resulting PCRs are appended to the contract-policy PCRs and fed to the confidence signal. The resolver's existingtests/test_contract_resolver.pycontinues to pass (the policy call is additive — it does not change resolver return values or exceptions). - REQ-299:
tests/test_stack_ir_policies.py+ fixturetests/fixtures/stack_ir/— a passing IR + a failing IR. Tests run theKyvernoJsonEngineagainst realkjwhenwhich kjsucceeds, andpytest.skip("kj not installed")when absent.
Category: Plan-JSON Policies + Pipeline Wiring (feat)
- REQ-300:
adapters/kyverno-json/policies/plan-json/holds policies overterraform show -jsonoutput.forbid-plaintext-secrets.json(portsCKV_AWS_41/45/46).forbid-iam-wildcard.json(portsCKV_AWS_1/40).require-kms-reference.json(portsCKV_AWS_7/33). The CheckovRULE_MAPincheckov_adapter.pyis unchanged — these are declarative mirrors, not replacements. - REQ-301:
run_platform.shStep 5 ("runtime policy scan") gains a parallel kyverno-json pass: after Checkov/Wiz produce raw PCRs, the script runskj scanand pipes throughadapters/kyverno-json/kyverno_json_engine.pyto produce a second PCR list. Both lists are concatenated and fed to the confidence signal'spolicyinput. Whenwhich kjis false, the script logs and proceeds with the Checkov/Wiz list only (no hard failure). - REQ-302:
tests/test_plan_json_policies.py+ fixturetests/fixtures/plan_json/— a passing + failing plan JSON.tests/test_run_platform_plan_json_policies.pyassertsrun_platform.shhas 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 mergedlist[PolicyCheckResult]itself.block-on-any-critical.json— asserts no PCR in the list hasseverity: "critical"+result: "fail"; if any does, the meta-policy emits afailPCR withruleId: "KJ_META_BLOCK_CRITICAL"and severitycritical. This is the declarative source of truth for "critical = block"; theconfidence_signal.pyPENALTY["critical"]: Nonehard-override stays as defense-in-depth.tagging-rules-agree.json— for every resource in the Stack IR, asserts the CheckovNOVA_TAG_NAMINGresult and the kyverno-jsonKJ_REQUIRE_TAGGING_STANDARDresult agree; divergence emits anerrorPCR.tests/test_meta_policies.pycovers 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 incore/regression_verify.py:cap-013-adapter-dedup.json,cap-023-metrics-collector.json,cap-024-deck-structure.json. The existingcore/regression_verify.pyis 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+ fixturetests/fixtures/capability_inventory.json— a clean inventory (all caps pass) + a drifted inventory. The regression gate (pytestsuite) continues to pass; the new policy tests are additive.
Category: Documentation (docs)
- REQ-306:
adapters/README.mdgains a new row for thekyverno-jsonadapter + a new section "Policy Engine Protocol" documenting thePolicyEngineProtocol, the registry, and the swap boundary (how to add anOpaEngine).adapters/kyverno-json/README.mddocuments the engine, the install path, the policy directory layout, and the four policy categories. - REQ-307:
.ciagent/ARCHITECTURE.mdgains §12.7 "Policy Engine Registry" with the registry diagram.schemas/README.mdnotes theengine: "kyverno"value is shared by the K8s Kyverno adapter and the kyverno-json engine (distinguished byruleIdprefix).modules/STANDARDS.mdgains a "Policy authoring standard" section.docs/METRICS.mdnotes 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-engineKeyError,NullEnginefallback when thepolicykey is absent,KyvernoJsonEngine.is_configured()returns false whenwhich kjfails (mocked).tests/test_kyverno_json_engine.py—evaluate()returns valid PCR dicts validated againstschemas/policy_check_result.schema.json; native-output parsing is defensive (malformed →errorPCR, not exception);is_configured()==false→SKIPPEDPCR withKJ_ENGINE_NOT_CONFIGURED. - REQ-309: All new tests use
pytest.skip("kj not installed")whenwhich kjis absent, so the suite passes in environments without the binary (CI matrix: with-kj and without-kj).pyproject.toml+requirements-test.txtunchanged (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.
OpaEngineimplementation. 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 inmodules/STANDARDS.mdbut not populated this milestone. - kyverno-json as a long-running service. v1.25 uses the CLI
(
kj scan); thekj serveweb-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 — active. The Nova CLI is installable from internal PyPI (CodeArtifact); every
core/module is reachable as anova <subcommand>; the CLI and Lambda functions share a singlecore/source tree; and Nova owns its identity layer end-to-end (Nova-idp:nova-idp-auth+nova-idp-token-vendLambdas, 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 akjengine — 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, notkj). 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
kjengine. Policy atplatform/abac/token-vend.policy. - D-228 (Argon2id in Lambda):
argon2-cffiwith 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.invocationaudit event containingmode,selection_reason,credential_type,command, andargs. - 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 account581513795199for the first time, and standardizesacdl/acdlon 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 thenova-platform-opscutover gates (M1/M1.5/M2), documented in the operator guide (docs/operator-guide-platform-ops.md). CIAgent inacdlauthors 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/acdlstandardizes on GitHub. CI fails withforge_parity_disabled(deliberate). Rationale: Vision §4 domain boundaries — operations lives in Gitea-privatenova-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-signingprovisioned withKeySpec: 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/acdlpublish.ymlattaches artifacts to GitHub Releases per tag;nova-platform-opsdeclareslocal.nova_platform_version+local.kj_source_shaand resolves substrates through a singledata.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_versionpin; 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_uriat everyterraform plan. Enforced bylifecycle.precondition(mechanism) + Gitea Actionsif: 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 planon every PR; drift fails withdrift_detected. - REQ-357 — HITL approver distinct from PR author required for
terraform apply(INV-3, TFM-HITL). - REQ-358 — Operator bumps
nova_platform_versionto roll out engineering change;CodeSha256matches 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-signingKMS 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.preconditionon 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):
kjcompiledCGO_ENABLED=0;file(1)reportsstatically linked, no shared library; SHA-256 matchesplatform/abac/kj-version.txt; recorded in Terraform state. - KJ-LOCKSTEP (NFR): Fargate standby digest == Lambda
image_uridigest at everyterraform plan. Detected bylifecycle.precondition(mechanism) + CIif: steps.plan.outcome == 'success'(mechanism) + PR comment (observability) + operator review (last). No second pipeline, no second SHA pin. - KJ-WARMUP-HEALTH (NFR): Fargate standby
READYprobe (GET /health → 200every 10s) green before M1 cutover; release-gate entry. - OPER-PRIV (NFR):
nova-platform-opsprivate: true, not mirrored. - IAM-NARROW (NFR): Gitea OIDC role bounded per REQ-360; no
Action: "*"orResource: "*". - DRIFT-DETECT (NFR):
terraform planexit 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 applyagainstmainrequires Gitea Actions approval from a user distinct from the PR author. - JWKS-SLO (NFR):
GET /.well-known/jwks.jsonP95 < 200ms same- region;Cache-Control: max-age=3600honored. - 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) |