# 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 --payload -o json`, parses the native result list, and translates each entry to a PCR dict (`engine: "kyverno"`, `ruleId` prefixed `KJ_`, 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//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`.