# 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`.