Files
acdl/.ciagent/REQUIREMENTS.md
T
Jon Chery bbfcbcc4d3 docs(P00): grill — v1.28 adversarial review (PROCEED 0.76, 3 critical + 16 tracked conditions applied)
---ci---
project: acdl
phase: 0
milestone: v1.28
status: grill
---/ci---
2026-08-19 22:10:15 +00:00

604 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Nova — Requirements
> **Compressed.** The full v1.0v1.25 requirement history (REQ-01..REQ-309)
> is preserved verbatim at `.ciagent/archive/REQUIREMENTS-v1.0-v1.24.md`.
> This file retains only the v1.25 requirement set (the immediate
> predecessor milestone whose policy-engine substrate is load-bearing for
> v1.26) + a pointer to the active v1.26 requirements, which live in the
> consumer subproject at `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md`
> (multi-project mode per `config.json`).
>
> Earlier requirement sets (v1.0v1.24, REQ-01..REQ-290) remain valid for
> the milestones they governed. They are not re-decided by v1.26. Full
> text in the archive snapshot + git history.
## v1.25 — kyverno-json Unified Policy Engine (immediate predecessor, complete)
> **Feature milestone — complete.** `kyverno-json` becomes the primary
> compliance / policy tool, implemented behind a swappable `PolicyEngine`
> adapter so OPA (or any other engine) can replace it one day. Tags run
> on the **v1.24.x** line (milestone v1.25 → tags v1.24.0..v1.24.5). Tag
> `v1.24.5` = the milestone release.
>
> One problem, one architectural correction:
> 1. **Fragmented policy posture.** Nova's compliance rules were split
> across Checkov (imperative YAML + a Python custom rule for tagging),
> Wiz (API findings), the K8s-only Kyverno adapter (inactive for
> Terraform stacks — D-053), and imperative Python in
> `core/env_transition.py` + `core/regression_verify.py`. There was no
> single declarative place where "what Nova considers compliant" lived.
>
> The correction: `kyverno-json` (a Kyverno-ecosystem runtime that applies
> Kyverno policies to **any** JSON/YAML payload) becomes the **unified
> orchestrator** of compliance checks. Checkov and Wiz remain as
> raw-finding adapters feeding *into* kyverno-json meta-policies. The
> engine is behind a `PolicyEngine` protocol so it is replaceable. The
> confidence signal is untouched — it already consumes
> `list[PolicyCheckResult]` engine-agnostically.
### Decisions (locked in CLARIFY, full autonomy — load-bearing for v1.26)
- **D-115 (C-1):** `kyverno-json` is a runtime dependency installed via
`go install github.com/kyverno/kyverno-json/cmd/kj@latest` (pinned in a
`scripts/install-kyverno-json.sh` helper; the CI image installs it).
Not a Python package — kyverno-json is a Go binary. The
`KyvernoJsonEngine.is_configured()` checks `which kj` and skips
gracefully when absent (emits `SKIPPED` PCR, mirroring the Wiz adapter).
- **D-116 (C-2):** kyverno-json PCR records carry `engine: "kyverno"`
(no new enum value). The existing `engine` enum in
`schemas/policy_check_result.schema.json` already includes `"kyverno"`;
adding `"kyverno-json"` would force a schema change + checkov_adapter
test regression for no semantic gain. The `ruleId` prefix `KJ_`
distinguishes kyverno-json rules from the K8s Kyverno adapter's
`KYVERNO_` prefix where they overlap.
- **D-117 (C-3):** Checkov and Wiz adapters keep their current
`adapt() -> list[PolicyCheckResult]` signatures. They emit PCRs as
today. The meta-policies in `adapters/kyverno-json/policies/meta/`
consume the **merged** PCR list (checkov + wiz + kyverno-json) as their
input payload, applying Nova-specific posture rules on top. No adapter
signature changes.
- **D-118 (C-4):** `NOVA_TAG_NAMING` (the Checkov custom rule in
`adapters/terraform/policy/custom_rules/nova_tagging.py`) is **kept**.
A kyverno-json mirror policy `require-tagging-standard.json` is added
in `adapters/kyverno-json/policies/stack-ir/`. The P3 meta-policy
`tagging-rules-agree.json` asserts the two engines agree on every
resource; divergence emits an `error` PCR (defense-in-depth against
rule drift). The Checkov rule stays the source of truth for
Terraform-static scanning; the kyverno-json policy covers Stack IR.
### Category: Policy Engine Core (feat)
- **REQ-291:** `core/policy_engine.py` defines a `PolicyEngine` Python
`Protocol` (PEP 544) with three members: `name -> str`,
`is_configured() -> bool`, and
`evaluate(payload: dict | str, policy_dir: Path, contract_id: str) ->
list[dict]` (where each dict conforms to
`schemas/policy_check_result.schema.json`). A `PolicyEngineRegistry`
singleton selects the active engine from `config.json`'s new
`policy.engine` key (default `"kyverno-json"`); raises
`KeyError` on an unknown engine name. The registry exposes
`get_engine()` and `register(name, factory)`. Pure stdlib, no engine
imports at the protocol layer.
- **REQ-292:** `.ciagent/config.json` gains a new top-level `policy`
object: `{"engine": "kyverno-json", "policy_root":
"adapters/kyverno-json/policies"}`. The registry reads `policy.engine`
to select the active engine and `policy.policy_root` as the default
policy directory. Backward-compatible: if the `policy` key is absent,
the registry returns a `NullEngine` that emits only `SKIPPED` records
(so existing tests that don't set the key still pass).
### Category: kyverno-json Engine Adapter (feat)
- **REQ-293:** `adapters/kyverno-json/kyverno_json_engine.py` implements
`KyvernoJsonEngine` satisfying the `PolicyEngine` protocol.
`is_configured()` returns `True` when `which kj` succeeds. `evaluate()`
writes the payload to a temp JSON file, invokes
`kj scan --policy <policy_dir> --payload <payload.json> -o json`,
parses the native result list, and translates each entry to a PCR dict
(`engine: "kyverno"`, `ruleId` prefixed `KJ_<policy_name>`, severity
mapped, `result` mapped pass/fail/skip → pass/fail/skipped). When
`is_configured()` is false, `evaluate()` returns a single `SKIPPED`
PCR with `ruleId: "KJ_ENGINE_NOT_CONFIGURED"`. Native output parsing
is defensive: any kyverno-json output that doesn't match the expected
shape produces an `error` PCR, never an exception.
- **REQ-294:** `adapters/kyverno-json/__init__.py` exports
`KyvernoJsonEngine`. `adapters/kyverno-json/policies/_smoke.json`
is a single trivial policy (`require-contract-id`) used to validate
the engine round-trip end-to-end in tests. `scripts/install-kyverno-json.sh`
runs `go install github.com/kyverno/kyverno-json/cmd/kj@latest` and
prints `kj version`; documented in `adapters/kyverno-json/README.md`.
The CI image installs Go + kj when `policy.engine == "kyverno-json"`;
the install is cached.
### Category: Contract Policies (feat)
- **REQ-295:** `adapters/kyverno-json/policies/contract/` holds
kyverno-json policies over consumer contract JSON. Four policies
mirroring `schemas/contract.schema.json` constraints:
`require-id-pattern.json`, `require-env-in-enum.json`,
`require-infrastructure-min-1.json`, `forbid-unknown-fields.json`.
Each policy is a single Kyverno `Policy` resource with one
`validate.assert` rule using JMESPath against the payload root.
- **REQ-296:** `core/contract_resolver.py` invokes the
`PolicyEngineRegistry.get_engine().evaluate()` with the contract dict
and `policies/contract/` **before** resolving (early-fail on contract
violations) and emits a `nova.policy.evaluated` metrics event. Failures
feed the confidence signal's `policy` input as `fail` PCRs; the
resolver does not exit — the confidence signal decides the gate
(consistent with the existing `--soft-fail` Checkov pattern).
### Category: Stack-IR Policies (feat)
- **REQ-297:** `adapters/kyverno-json/policies/stack-ir/` holds policies
over the resolved Target Stack IR dict. `require-tagging-standard.json`
(every resource carries `nova:owner` + `nova:environment` tags — ports
`nova_tagging.py` into a declarative Kyverno policy).
`forbid-public-ingress.json` (no resource has `public_ingress: true`).
`require-encryption-by-default.json` (every S3 bucket + EBS volume +
KMS-aliased resource carries encryption config — ports the v1.8
D-encryption-default rule).
- **REQ-298:** `core/contract_resolver.py` invokes the engine with the
resolved Stack IR and `policies/stack-ir/` **after** resolving. The
resulting PCRs are appended to the contract-policy PCRs and fed to the
confidence signal. The resolver's existing
`tests/test_contract_resolver.py` continues to pass (the policy call
is additive — it does not change resolver return values or exceptions).
- **REQ-299:** `tests/test_stack_ir_policies.py` + fixture
`tests/fixtures/stack_ir/` — a passing IR + a failing IR. Tests run
the `KyvernoJsonEngine` against real `kj` when `which kj` succeeds, and
`pytest.skip("kj not installed")` when absent.
### Category: Plan-JSON Policies + Pipeline Wiring (feat)
- **REQ-300:** `adapters/kyverno-json/policies/plan-json/` holds policies
over `terraform show -json` output. `forbid-plaintext-secrets.json`
(ports `CKV_AWS_41/45/46`). `forbid-iam-wildcard.json` (ports
`CKV_AWS_1/40`). `require-kms-reference.json` (ports `CKV_AWS_7/33`).
The Checkov `RULE_MAP` in `checkov_adapter.py` is unchanged — these
are declarative mirrors, not replacements.
- **REQ-301:** `run_platform.sh` Step 5 ("runtime policy scan") gains a
parallel kyverno-json pass: after Checkov/Wiz produce raw PCRs, the
script runs `kj scan` and pipes through
`adapters/kyverno-json/kyverno_json_engine.py` to produce a second PCR
list. Both lists are concatenated and fed to the confidence signal's
`policy` input. When `which kj` is false, the script logs and proceeds
with the Checkov/Wiz list only (no hard failure).
- **REQ-302:** `tests/test_plan_json_policies.py` + fixture
`tests/fixtures/plan_json/` — a passing + failing plan JSON.
`tests/test_run_platform_plan_json_policies.py` asserts `run_platform.sh`
has the kyverno-json Step 5 block and that it concatenates PCR lists.
### Category: Meta-Policies (feat)
- **REQ-303:** `adapters/kyverno-json/policies/meta/` holds policies
whose **payload** is the merged `list[PolicyCheckResult]` itself.
`block-on-any-critical.json` — asserts no PCR in the list has
`severity: "critical"` + `result: "fail"`; if any does, the meta-policy
emits a `fail` PCR with `ruleId: "KJ_META_BLOCK_CRITICAL"` and severity
`critical`. This is the **declarative** source of truth for
"critical = block"; the `confidence_signal.py` `PENALTY["critical"]:
None` hard-override stays as defense-in-depth.
`tagging-rules-agree.json` — for every resource in the Stack IR,
asserts the Checkov `NOVA_TAG_NAMING` result and the kyverno-json
`KJ_REQUIRE_TAGGING_STANDARD` result agree; divergence emits an
`error` PCR. `tests/test_meta_policies.py` covers both.
### Category: Regression-Gate Policies (feat, quality improvement from IDEATE)
- **REQ-304:** `adapters/kyverno-json/policies/regression/` holds
policies over the capability-inventory JSON frontmatter. Three
policies port the imperative checks in `core/regression_verify.py`:
`cap-013-adapter-dedup.json`, `cap-023-metrics-collector.json`,
`cap-024-deck-structure.json`. The existing `core/regression_verify.py`
is **kept** (it drives the CI gate); the policies are the
**declarative mirror** that makes capability regression auditable as a
policy artifact, not imperative Python. Future milestones may switch
the gate to the policy version.
- **REQ-305:** `tests/test_regression_policies.py` + fixture
`tests/fixtures/capability_inventory.json` — a clean inventory (all
caps pass) + a drifted inventory. The regression gate (`pytest` suite)
continues to pass; the new policy tests are additive.
### Category: Documentation (docs)
- **REQ-306:** `adapters/README.md` gains a new row for the
`kyverno-json` adapter + a new section "Policy Engine Protocol"
documenting the `PolicyEngine` Protocol, the registry, and the swap
boundary (how to add an `OpaEngine`). `adapters/kyverno-json/README.md`
documents the engine, the install path, the policy directory layout,
and the four policy categories.
- **REQ-307:** `.ciagent/ARCHITECTURE.md` gains §12.7 "Policy Engine
Registry" with the registry diagram. `schemas/README.md` notes the
`engine: "kyverno"` value is shared by the K8s Kyverno adapter and the
kyverno-json engine (distinguished by `ruleId` prefix).
`modules/STANDARDS.md` gains a "Policy authoring standard" section.
`docs/METRICS.md` notes the policy engine is now swappable (Strategic
Objective #2 — provable trust via a replaceable substrate, not a
vendor lock-in).
### Category: Tests (test)
- **REQ-308:** `tests/test_policy_engine.py` — protocol conformance,
unknown-engine `KeyError`, `NullEngine` fallback when the `policy`
key is absent, `KyvernoJsonEngine.is_configured()` returns false when
`which kj` fails (mocked). `tests/test_kyverno_json_engine.py`
`evaluate()` returns valid PCR dicts validated against
`schemas/policy_check_result.schema.json`; native-output parsing is
defensive (malformed → `error` PCR, not exception);
`is_configured()==false``SKIPPED` PCR with `KJ_ENGINE_NOT_CONFIGURED`.
- **REQ-309:** All new tests use `pytest.skip("kj not installed")` when
`which kj` is absent, so the suite passes in environments without the
binary (CI matrix: with-kj and without-kj). `pyproject.toml` +
`requirements-test.txt` unchanged (kyverno-json is a Go binary, not a
Python dep).
### Out of Scope (v1.25)
- **Removing Checkov or Wiz.** Both stay as raw-finding adapters.
- **`OpaEngine` implementation.** The protocol is the swap boundary;
the OPA implementation is a future milestone.
- **Per-module policies.** `modules/<name>/policies/` is documented as
the future pattern in `modules/STANDARDS.md` but not populated this
milestone.
- **kyverno-json as a long-running service.** v1.25 uses the CLI
(`kj scan`); the `kj serve` web-app mode is future.
- **Replacing the K8s Kyverno adapter.** The K8s adapter
(`adapters/kyverno/`) remains documentation-only (D-053).
### v1.25 Traceability
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-291 | P1 | complete |
| REQ-292 | P1 | complete |
| REQ-293 | P1 | complete |
| REQ-294 | P1 | complete |
| REQ-295 | P2 | complete |
| REQ-296 | P2 | complete |
| REQ-297 | P2 | complete |
| REQ-298 | P2 | complete |
| REQ-299 | P2 | complete |
| REQ-300 | P3 | complete |
| REQ-301 | P3 | complete |
| REQ-302 | P3 | complete |
| REQ-303 | P3 | complete |
| REQ-304 | P4 | complete |
| REQ-305 | P4 | complete |
| REQ-306 | P4 | complete |
| REQ-307 | P4 | complete |
| REQ-308 | P1 | complete |
| REQ-309 | P1 | complete |
## v1.26 — Live Pilot Estate Activation (active)
> **Feature milestone.** The first real consumer estate (a stock
> exchange on a homegrown PoA blockchain, equities only) is activated
> against live AWS account `581513795199`, lifting D-096. Tags run on
> the **v1.25.x** line: `v1.25.0` (P0) → `v1.25.1..v1.25.4` (P1P4) →
> `v1.25.5` (P5 final = milestone release).
>
> **Multi-project mode:** the v1.26 requirements live in
> `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md` (the consumer
> subproject). The platform-side requirement REQ-322 (DynamoDB L1
> primitive) landed in P2 of the platform repo. The 13 requirements
> (REQ-310..322) cover: blockchain core (REQ-310), order engine
> (REQ-311), settlement (REQ-312), consumer contract (REQ-313),
> deploy invocation (REQ-314), settlement-finality policy (REQ-315),
> pilot regression CAP (REQ-316), outcome backfill (REQ-317),
> escalation reason (REQ-318), env-JSON wiring (REQ-319),
> pilot-readiness policy (REQ-320), docs (REQ-321), DynamoDB L1
> primitive (REQ-322).
### v1.26 Traceability (live — see CHECKPOINT.json for authoritative state)
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-310 | P1 | complete (v1.25.1) |
| REQ-311 | P1 | complete (v1.25.1) |
| REQ-312 | P1 | complete (v1.25.1) |
| REQ-322 | P2 | complete (v1.25.2) |
| REQ-313 | P2 | complete (v1.25.2) |
| REQ-314 | P2 | complete (v1.25.2) |
| REQ-315 | P3 | complete (v1.25.3) |
| REQ-316 | P3 + P4 | complete (v1.25.3 — CAP-025; v1.25.4 — live-verify complete) |
| REQ-317 | P3 | complete (v1.25.3) |
| REQ-318 | P3 | complete (v1.25.3) |
| REQ-319 | P3 | complete (v1.25.3) |
| REQ-320 | P3 | complete (v1.25.3) |
| REQ-321 | P4 | complete (v1.25.4) |
Full v1.26 requirement text:
`.ciagent/nova-blockchain-exchange/REQUIREMENTS.md`. Active phase plan:
`.ciagent/PLAN.md`.
## v1.28 — CLI Canonicalization + Identity Layer (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 |