bbfcbcc4d3
---ci--- project: acdl phase: 0 milestone: v1.28 status: grill ---/ci---
604 lines
29 KiB
Markdown
604 lines
29 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 (active)
|
||
|
||
> **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.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 | planned |
|
||
| REQ-324 | P1 | planned |
|
||
| REQ-325 | P1 | planned |
|
||
| REQ-326 | P1 | planned |
|
||
| REQ-327 | P1 | planned |
|
||
| REQ-328 | P1 | planned |
|
||
| REQ-329 | P2 | planned |
|
||
| REQ-330 | P2 | planned |
|
||
| REQ-331 | P2 | planned |
|
||
| REQ-332 | P2 | planned |
|
||
| REQ-333 | P3 | planned |
|
||
| REQ-334 | P3 | planned |
|
||
| REQ-335 | P3 | planned |
|
||
| REQ-336 | P4 | planned |
|
||
| REQ-337 | P4 | planned |
|
||
| REQ-338 | P4 | planned |
|
||
| REQ-339 | P4 | planned |
|
||
| REQ-340 | P4 | planned |
|
||
| REQ-341 | P4 | planned |
|
||
| REQ-342 | P4 | planned |
|
||
| REQ-343 | P4 | planned |
|
||
| REQ-344 | P4 | planned |
|
||
| REQ-345 | P5 | planned |
|
||
| REQ-346 | P5 | planned |
|
||
| REQ-347 | P5 | planned |
|
||
| REQ-348 | P5 | planned |
|
||
| REQ-349 | P5 | planned |
|
||
| REQ-350 | P5 | planned |
|
||
| REQ-351 | P5 | planned |
|
||
| REQ-352 | P6 | planned |
|
||
| REQ-353 | P6 | planned | |