diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json
index 48b01e6..c4b3fa1 100644
--- a/.ciagent/CHECKPOINT.json
+++ b/.ciagent/CHECKPOINT.json
@@ -1,22 +1,17 @@
{
"phase": 0,
- "stage": "ideate",
+ "stage": "plan",
"milestone": "v1.25",
"phase_role": "pre_execution",
"attempts": 0,
- "updated_at": "2026-08-12T16:40:00Z",
+ "updated_at": "2026-08-12T16:45:00Z",
"project": "acdl",
"milestone_complete": false,
"tag_line": "v1.24.x",
"next_tag": "v1.24.0",
- "ideation": {
- "tiers_run": ["mechanical", "backend-enriched"],
- "cross_project": "deferred (single-project, config disabled)",
- "ideas_accepted": 5,
- "ideas_deferred": 3,
- "ideas_rejected": 0,
- "accepted_into_reqs": ["REQ-295", "REQ-297", "REQ-300", "REQ-303", "REQ-304", "REQ-305"],
- "deferred": ["I6-env-transition-as-policy", "I7-drift-as-policy", "I8-cross-project-sharing"]
- },
- "notes": "IDEATE complete — 5 accepted (already in REQ-291..309), 3 deferred with documented blockers. No new reqs added; the pass validated the requirement set."
+ "phases": 6,
+ "execution_phases": 4,
+ "requirements_total": 19,
+ "requirements": ["REQ-291", "REQ-292", "REQ-293", "REQ-294", "REQ-295", "REQ-296", "REQ-297", "REQ-298", "REQ-299", "REQ-300", "REQ-301", "REQ-302", "REQ-303", "REQ-304", "REQ-305", "REQ-306", "REQ-307", "REQ-308", "REQ-309"],
+ "notes": "v1.25 PLAN complete — 4 execution phases (P1 engine-core, P2 contract+stack-IR policies, P3 plan-JSON+meta+pipeline, P4 regression+docs) + P5 final review/ship. Wave ordering with parallelization (3-2-2-3 concurrent personas). Each phase is a vertical slice. Tags v1.24.0..v1.24.5."
}
\ No newline at end of file
diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md
index 04f9752..3d4d4ce 100644
--- a/.ciagent/PLAN.md
+++ b/.ciagent/PLAN.md
@@ -1,180 +1,371 @@
-# PLAN — v1.24 (Consumer Guide Accuracy & Env-Promotion Lifecycle Enforcement)
+# PLAN — v1.25 (kyverno-json Unified Policy Engine)
-> Feature milestone (one `feat` phase: env-transition destroy enforcement;
-> the rest are `fix`/`docs`/`test`). Tags on the **v1.23.x** line:
-> v1.23.0 (P0) → v1.23.1 (P1) → v1.23.2 (P2) → v1.23.3 (P3) → v1.23.4 (P4
-> final = milestone release). 15 requirements (REQ-276..290), 4 phases +
-> P0 pre-execution.
+> Feature milestone. Tags on the **v1.24.x** line: v1.24.0 (P0) →
+> v1.24.1 (P1) → v1.24.2 (P2) → v1.24.3 (P3) → v1.24.4 (P4) → v1.24.5
+> (P5 final = milestone release). 19 requirements (REQ-291..309),
+> 4 execution phases + P0 pre-execution + P5 final review/ship.
+
+## Wave model
+
+Each phase is a **vertical slice** (end-to-end: policy files + Python
+wiring + tests + docs). Phases are ordered by dependency: the engine
+protocol (P1) must exist before policies (P2/P3) can be wired; the
+pipeline wiring (P3) must exist before the meta-policies (P3) can
+consume the merged PCR list; the regression-gate policies (P4) are
+independent of the pipeline and can be authored in parallel with P3's
+tests, but ship after P3 because they reference the engine registry
+finalized in P1. Within each phase, the waves are the persona task
+groups (parallelizable across personas when `parallelization.enabled:
+true`, `max_concurrent_agents: 5`).
## Phase breakdown
-### Phase P1 — consumer-guide-fixes (Wave 1, lead-developer)
+### Phase P1 — engine-core (Wave 1, backend-engineer + policy-engineer + data-engineer)
-**Type:** `docs` + `fix` + `test` (consumer guide accuracy + guide test updates)
+**Type:** `feat` (engine protocol + registry + kyverno-json engine adapter + install + tests)
-**Requirements:** REQ-276, REQ-277, REQ-278, REQ-279, REQ-280, REQ-281, REQ-290
+**Requirements:** REQ-291, REQ-292, REQ-293, REQ-294, REQ-308, REQ-309
**Must-haves:**
-- `docs/consumer-guide.md` Step 3 contract fields table corrected (REQ-276)
-- `docs/consumer-guide.md` Step 4 caller consistent with Step 2 (REQ-277)
-- `docs/consumer-guide.md` Step 5 stage 8 "(dev only)" → "(autonomous in dev; higher environments apply after HITL attestation)" (REQ-278)
-- `docs/consumer-guide.md` Step 8 rewritten with destroy-then-rebuild semantics + cross-ref to Shape B (REQ-279)
-- `docs/consumer-guide.md` Per-env section gains Shape B lead sentence (REQ-280)
-- `docs/consumer-guide.md` Reference table `@v1.19` wording corrected (REQ-281)
-- `tests/test_consumer_guide_per_env_section.py` updated: rename `test_consumer_guide_states_no_field_editing` → `test_consumer_guide_documents_both_promotion_shapes`; add `test_consumer_guide_documents_destroy_on_env_change` (REQ-290)
+- `core/policy_engine.py` — `PolicyEngine` Protocol (PEP 544) +
+ `PolicyEngineRegistry` (selects from `config.json.policy.engine`) +
+ `NullEngine` fallback (emits `SKIPPED` when `policy` key absent)
+ (REQ-291)
+- `.ciagent/config.json` gains `policy` object: `{"engine":
+ "kyverno-json", "policy_root":
+ "adapters/kyverno-json/policies"}` (REQ-292)
+- `adapters/kyverno-json/kyverno_json_engine.py` — `KyvernoJsonEngine`
+ implementing the protocol: `is_configured()` guards on `which kj`;
+ `evaluate()` writes payload to temp JSON, invokes
+ `kj scan --policy
--payload --output json`, translates
+ native output → `list[dict]` PCR records (`engine: "kyverno"`,
+ `ruleId` prefixed `KJ_`, severity from
+ `nova.cloudinit.dev/severity` annotation); defensive parsing
+ (malformed → `error` PCR, never exception); `is_configured()==false`
+ → single `SKIPPED` PCR (`KJ_ENGINE_NOT_CONFIGURED`) (REQ-293)
+- `adapters/kyverno-json/__init__.py` exports `KyvernoJsonEngine`;
+ `adapters/kyverno-json/policies/_smoke.json` trivial
+ `require-contract-id` policy for round-trip validation;
+ `scripts/install-kyverno-json.sh` runs
+ `go install github.com/kyverno/kyverno-json/cmd/kj@latest`;
+ `.github/workflows/ci.yml` + `.gitea/workflows/ci.yml` install Go + kj
+ (cached) (REQ-294)
+- `tests/test_policy_engine.py` — protocol conformance, registry
+ selection, unknown-engine `KeyError`, `NullEngine` fallback,
+ `is_configured()` false when `which kj` absent (mocked) (REQ-308)
+- `tests/test_kyverno_json_engine.py` — `evaluate()` returns PCR dicts
+ validating against `schemas/policy_check_result.schema.json` (via
+ `jsonschema`); defensive parsing (malformed kyverno-json output →
+ `error` PCR); `is_configured()==false` → `SKIPPED` with
+ `KJ_ENGINE_NOT_CONFIGURED`; `pytest.skip("kj not installed")` when
+ `which kj` absent (REQ-309)
-**Vertical slice:** A reader of `docs/consumer-guide.md` can promote via
-either shape (A: edit environment + platform destroys prior; B: per-env
-caller workflow) without contradiction. The guide's field table, caller
-examples, and stage descriptions match the actual schema and platform
-behavior. All consumer-guide tests pass.
+**Vertical slice:** The `PolicyEngineRegistry.get_engine()` returns a
+configured `KyvernoJsonEngine` that can `evaluate()` a trivial payload
+against `_smoke.json` and produce a valid PCR list. The confidence
+signal is unchanged — it already consumes `list[PolicyCheckResult]`.
+The platform runs with or without the `kj` binary (`is_configured()`
+guard). All existing tests pass (NullEngine fallback when `policy` key
+absent in test config — but the v1.25 config.json *sets* the key, so
+existing tests that use the real config get `KyvernoJsonEngine` with
+`is_configured()==false` → `SKIPPED`).
**Files touched:**
-- `docs/consumer-guide.md`
-- `tests/test_consumer_guide_per_env_section.py`
+- `core/policy_engine.py` (NEW)
+- `.ciagent/config.json` (MODIFIED — `policy` object)
+- `adapters/kyverno-json/__init__.py` (NEW)
+- `adapters/kyverno-json/kyverno_json_engine.py` (NEW)
+- `adapters/kyverno-json/policies/_smoke.json` (NEW)
+- `scripts/install-kyverno-json.sh` (NEW)
+- `.github/workflows/ci.yml` (MODIFIED — Go + kj install step)
+- `.gitea/workflows/ci.yml` (MODIFIED — Go + kj install step)
+- `tests/test_policy_engine.py` (NEW)
+- `tests/test_kyverno_json_engine.py` (NEW)
-**Verification:** `pytest tests/test_consumer_guide_per_env_section.py -v`
-(all 7 tests pass). Manual read of `docs/consumer-guide.md` end-to-end
-for internal consistency.
+**Verification:** `pytest tests/test_policy_engine.py
+tests/test_kyverno_json_engine.py tests/test_confidence_signal.py
+tests/test_adapter.py tests/test_checkov_adapter.py
+tests/test_kyverno_adapter.py -v` (new tests pass or skip-without-kj;
+existing adapter/confidence tests unchanged). `python3 -m py_compile
+core/policy_engine.py adapters/kyverno-json/kyverno_json_engine.py`.
---
-### Phase P2 — env-transition-detect-and-destroy (Wave 2, backend-engineer)
+### Phase P2 — contract + stack-IR policies (Wave 2, policy-engineer + backend-engineer)
-**Type:** `feat` (new platform feature: env-transition detect-and-destroy)
+**Type:** `feat` (policies + resolver wiring + tests)
-**Requirements:** REQ-282, REQ-283, REQ-284, REQ-285, REQ-286, REQ-287
+**Requirements:** REQ-295, REQ-296, REQ-297, REQ-298, REQ-299
**Must-haves:**
-- `core/env_transition.py` new module: `detect_prior_env()` + `record_applied_env()` with boto3 DynamoDB queries (REQ-282, REQ-283)
-- `scripts/run_platform.sh` Step 0b: environment-transition check — detect prior env, re-resolve with `environment_override=prior_env` + `deletion_protection=false`, `terraform init -reconfigure` + `terraform destroy -auto-approve` against prior state key, emit `nova.env.destroyed` evidence event, fail closed on destroy failure (REQ-284)
-- `scripts/run_platform.sh` records applied env after successful apply (REQ-285)
-- `.github/workflows/deploy.yml` passes `NOVA_CONSUMER_REPO=${{ github.repository }}` to `run_platform.sh` (REQ-286)
-- `adapters/terraform/adapter.py` state-key block gains doc comment (REQ-287)
+- `adapters/kyverno-json/policies/contract/` — 4 policies over consumer
+ contract JSON: `require-id-pattern.json`,
+ `require-env-in-enum.json`, `require-infrastructure-min-1.json`,
+ `forbid-unknown-fields.json` — each a `ValidatingPolicy` with one
+ `validate.assert` rule using JMESPath against the payload root;
+ severity via `nova.cloudinit.dev/severity` annotation (REQ-295)
+- `core/contract_resolver.py` invokes
+ `PolicyEngineRegistry.get_engine().evaluate(contract_dict,
+ policies/contract/, contract_id)` **before** resolving; failures
+ feed the `policy` input as `fail` PCRs (no resolver exit — confidence
+ signal decides the gate, `--soft-fail` pattern); emits
+ `nova.policy.evaluated` metrics event (REQ-296)
+- `adapters/kyverno-json/policies/stack-ir/` — 3 policies over
+ resolved Stack IR: `require-tagging-standard.json` (ports
+ `nova_tagging.py` — `nova:owner` + `nova:environment` tags on every
+ `resources[]` entry), `forbid-public-ingress.json` (v1.0 demo rule),
+ `require-encryption-by-default.json` (v1.8 D-encryption-default);
+ `~` modifier iterates `resources[]` (REQ-297)
+- `core/contract_resolver.py` invokes the engine with the resolved
+ Stack IR and `policies/stack-ir/` **after** resolving; resulting PCRs
+ appended to the contract-policy PCRs; resolver return values and
+ exceptions unchanged (additive) (REQ-298)
+- `tests/test_stack_ir_policies.py` + `tests/fixtures/stack_ir/` —
+ passing IR (all tags + encryption) + failing IR (missing tags, public
+ ingress, plaintext bucket); each policy in isolation + full dir as
+ bundle; `pytest.skip("kj not installed")` when `which kj` absent
+ (REQ-299)
-**Vertical slice:** When a consumer changes `environment:` on a stable
-`contract.id`, the pipeline detects the prior env from DynamoDB, destroys
-the prior env's Terraform state (with `deletion_protection=false`), emits
-an evidence event, and only then applies the new env. If the destroy
-fails, the pipeline exits non-zero (no orphan path). If no prior env
-exists (first deploy or Shape B), the pipeline proceeds normally.
+**Vertical slice:** A consumer contract passes through the resolver
+and produces two PCR lists (contract policies pre-resolve, stack-IR
+policies post-resolve) that feed the confidence signal. A contract
+with a bad `id` or missing tags produces `fail` PCRs that lower the
+confidence score. The resolver's existing tests pass unchanged (the
+policy call is additive — it does not change resolver return values
+or exceptions).
**Files touched:**
-- `core/env_transition.py` (NEW)
-- `scripts/run_platform.sh`
-- `.github/workflows/deploy.yml`
-- `adapters/terraform/adapter.py` (doc comment only)
+- `adapters/kyverno-json/policies/contract/require-id-pattern.json` (NEW)
+- `adapters/kyverno-json/policies/contract/require-env-in-enum.json` (NEW)
+- `adapters/kyverno-json/policies/contract/require-infrastructure-min-1.json` (NEW)
+- `adapters/kyverno-json/policies/contract/forbid-unknown-fields.json` (NEW)
+- `adapters/kyverno-json/policies/stack-ir/require-tagging-standard.json` (NEW)
+- `adapters/kyverno-json/policies/stack-ir/forbid-public-ingress.json` (NEW)
+- `adapters/kyverno-json/policies/stack-ir/require-encryption-by-default.json` (NEW)
+- `core/contract_resolver.py` (MODIFIED — pre/post resolve engine calls)
+- `tests/test_stack_ir_policies.py` (NEW)
+- `tests/fixtures/stack_ir/passing.json` (NEW)
+- `tests/fixtures/stack_ir/failing.json` (NEW)
-**Verification:** `python3 -m py_compile core/env_transition.py`.
-`pytest tests/test_pipeline.py tests/test_deploy_workflow_env_input.py -v`
-(existing tests still pass). The new tests in P3 validate the behavior.
-
-**Key implementation notes (from RESEARCH §5 pitfalls):**
-1. The destroy step must re-resolve with `environment_override=prior_env`
- so the emitted TF matches the prior env's resources.
-2. `terraform init -reconfigure` is required when switching state backends.
-3. `deletion_protection: false` must be injected (same as decommission
- Step 2 in `scripts/run_decommission.sh:34-37`) or `prevent_destroy`
- blocks the destroy.
-4. DynamoDB unreachable in local/CI → log warning + return `None`
- (conservative, no prior env assumed).
-5. The state key `spike/{id}/{env}/terraform.tfstate` stays as-is — the
- env segment is what lets the destroy target the prior env.
+**Verification:** `pytest tests/test_contract_resolver.py
+tests/test_stack_ir_policies.py tests/test_policy_engine.py -v`
+(existing resolver tests pass; new policy tests pass or skip-without-
+kj). `python3 -m py_compile core/contract_resolver.py`.
---
-### Phase P3 — env-transition-tests (Wave 3, backend-engineer + lead-developer)
+### Phase P3 — plan-JSON policies + meta-orchestration + pipeline wiring (Wave 3, policy-engineer + backend-engineer)
-**Type:** `test` (new test coverage for env-transition + pipeline integration)
+**Type:** `feat` (plan-JSON policies + meta-policies + run_platform.sh wiring + tests)
-**Requirements:** REQ-288, REQ-289
+**Requirements:** REQ-300, REQ-301, REQ-302, REQ-303
**Must-haves:**
-- `tests/test_env_transition.py` (NEW): `detect_prior_env` returns `None` when no record; returns prior env when record differs; returns `None` when record matches; `record_applied_env` writes record. Uses moto for DynamoDB (REQ-288)
-- `tests/test_run_platform_env_transition.py` (NEW): asserts `run_platform.sh` has Step 0b; calls `env_transition.py detect`; calls `terraform destroy` on prior env; fails closed on destroy failure; records applied env after success (REQ-289)
+- `adapters/kyverno-json/policies/plan-json/` — 3 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); JMESPath over
+ `planned_values.root_module.resources[]` (REQ-300)
+- `run_platform.sh` Step 5 gains a parallel kyverno-json pass: after
+ Checkov/Wiz produce raw PCRs, the script runs
+ `kj scan --policy adapters/kyverno-json/policies/plan-json/
+ --payload -o json` and pipes through
+ `adapters/kyverno-json/kyverno_json_engine.py` to produce a second
+ PCR list; both lists concatenated and fed to the confidence signal;
+ `nova.policy.evaluated` event with both engine names; when
+ `which kj` is false, logs and proceeds with Checkov/Wiz list only
+ (no hard failure) (REQ-301)
+- `tests/test_plan_json_policies.py` + `tests/fixtures/plan_json/` —
+ passing plan (no secrets, no wildcard, KMS alias) + failing plan
+ (plaintext password, `Action: "*"`, inline KMS key); policies in
+ isolation + bundle; `tests/test_run_platform_plan_json_policies.py`
+ asserts `run_platform.sh` has the kyverno-json Step 5 block +
+ concatenates PCR lists (script-substring assertion, pattern from
+ `tests/test_pipeline.py:79-95`) (REQ-302)
+- `adapters/kyverno-json/policies/meta/` — `block-on-any-critical.json`
+ (asserts no PCR in merged list has `severity: critical` + `result:
+ fail`; if any does, emits `fail` PCR `KJ_META_BLOCK_CRITICAL`
+ severity `critical` — declarative source of truth; the
+ `confidence_signal.py` hard-override stays as defense-in-depth per
+ D-119) + `tagging-rules-agree.json` (cross-checks Checkov
+ `NOVA_TAG_NAMING` vs kj `KJ_REQUIRE_TAGGING_STANDARD` by
+ `resourceRef`; divergence emits `error` PCR per D-118);
+ `tests/test_meta_policies.py` (REQ-303)
-**Vertical slice:** The env-transition detect-and-destroy behavior is
-fully covered by automated tests. The DynamoDB query logic is unit-tested
-with moto. The pipeline orchestration is tested via shell-text assertions
-(pattern from `tests/test_pipeline.py:79-95`).
+**Vertical slice:** `run_platform.sh` Step 5 produces a merged PCR list
+(Checkov/Wiz + kj plan-JSON policies + kj meta-policies over the
+merged list) that feeds the confidence signal. A plan with a plaintext
+secret produces two `fail` PCRs (one Checkov, one kj) for the same
+resource — visible defense-in-depth. A critical finding anywhere
+produces a `KJ_META_BLOCK_CRITICAL` meta-PCR that the confidence
+signal's hard-override blocks. The pipeline runs with or without `kj`
+(graceful skip).
**Files touched:**
-- `tests/test_env_transition.py` (NEW)
-- `tests/test_run_platform_env_transition.py` (NEW)
+- `adapters/kyverno-json/policies/plan-json/forbid-plaintext-secrets.json` (NEW)
+- `adapters/kyverno-json/policies/plan-json/forbid-iam-wildcard.json` (NEW)
+- `adapters/kyverno-json/policies/plan-json/require-kms-reference.json` (NEW)
+- `adapters/kyverno-json/policies/meta/block-on-any-critical.json` (NEW)
+- `adapters/kyverno-json/policies/meta/tagging-rules-agree.json` (NEW)
+- `scripts/run_platform.sh` (MODIFIED — Step 5 kj parallel pass)
+- `tests/test_plan_json_policies.py` (NEW)
+- `tests/test_meta_policies.py` (NEW)
+- `tests/test_run_platform_plan_json_policies.py` (NEW)
+- `tests/fixtures/plan_json/passing.json` (NEW)
+- `tests/fixtures/plan_json/failing.json` (NEW)
-**Verification:** `pytest tests/test_env_transition.py tests/test_run_platform_env_transition.py -v` (all new tests pass). Full suite: `pytest tests/ -k "env_transition or consumer_guide or pipeline or adapter or deploy_workflow" -v`.
+**Verification:** `pytest tests/test_plan_json_policies.py
+tests/test_meta_policies.py tests/test_run_platform_plan_json_policies.py
+tests/test_pipeline.py -v` (new tests pass or skip-without-kj; existing
+pipeline tests pass). `python3 -m py_compile` on any modified Python.
+Shellcheck on `run_platform.sh` if available.
---
-### Phase P4 — final-review-ship (Wave 4, lead-developer)
+### Phase P4 — regression-gate policies + docs (Wave 4, policy-engineer + data-engineer + lead-developer)
-**Type:** `docs` (review + audit + milestone ship)
+**Type:** `feat` (regression policies) + `docs` (adapter READMEs + ARCHITECTURE + STANDARDS + METRICS)
-**Requirements:** (none new — milestone completion)
+**Requirements:** REQ-304, REQ-305, REQ-306, REQ-307
**Must-haves:**
-- Multi-persona code review across P1-P3 changes (ci-code-reviewer)
-- Project health audit (ci-doc-verifier + ci-audit)
-- Milestone ship: tag v1.23.4 (final phase patch = milestone release), merge to main, Gitea release
-- Update REQUIREMENTS.md traceability (all REQ-276..290 → complete)
-- Update ROADMAP.md (v1.24 → complete)
+- `adapters/kyverno-json/policies/regression/` — 3 policies over
+ capability-inventory JSON frontmatter: `cap-013-adapter-dedup.json`,
+ `cap-023-metrics-collector.json`, `cap-024-deck-structure.json`;
+ emit `pass`/`fail` PCRs per capability; the existing
+ `core/regression_verify.py` is kept (drives the CI gate); the
+ policies are the declarative mirror (REQ-304)
+- `tests/test_regression_policies.py` +
+ `tests/fixtures/capability_inventory/clean.json` +
+ `tests/fixtures/capability_inventory/drifted.json` — clean (all caps
+ pass) + drifted (duplicate adapter, missing metric status, broken
+ deck arc); regression gate still 287/287 baseline (new tests
+ additive, skip-without-kj) (REQ-305)
+- `adapters/README.md` gains new kyverno-json adapter row + "Policy
+ Engine Protocol" section (Protocol, registry, swap boundary,
+ how-to-add-OpaEngine); `adapters/kyverno-json/README.md` documents
+ the engine, install path, policy directory layout, 4 policy
+ categories (REQ-306)
+- `.ciagent/ARCHITECTURE.md` §12.7 (added in RESEARCH) is finalized;
+ `schemas/README.md` notes `engine: "kyverno"` shared by K8s adapter
+ + kj (distinguished by `ruleId` prefix); `modules/STANDARDS.md`
+ gains "Policy authoring standard" section for module owners;
+ `docs/METRICS.md` notes the policy engine is swappable (Strategic
+ Objective #2 — provable trust via a replaceable substrate) (REQ-307)
+
+**Vertical slice:** The regression gate's capability checks are now
+declarative policies auditable as artifacts. A new module owner can
+read `modules/STANDARDS.md` "Policy authoring standard" and write a
+per-module kyverno-json policy. A new engineer can read
+`adapters/README.md` "Policy Engine Protocol" and implement an
+`OpaEngine`. The 287/287 baseline is unchanged.
+
+**Files touched:**
+- `adapters/kyverno-json/policies/regression/cap-013-adapter-dedup.json` (NEW)
+- `adapters/kyverno-json/policies/regression/cap-023-metrics-collector.json` (NEW)
+- `adapters/kyverno-json/policies/regression/cap-024-deck-structure.json` (NEW)
+- `tests/test_regression_policies.py` (NEW)
+- `tests/fixtures/capability_inventory/clean.json` (NEW)
+- `tests/fixtures/capability_inventory/drifted.json` (NEW)
+- `adapters/README.md` (MODIFIED — new row + PolicyEngine Protocol section)
+- `adapters/kyverno-json/README.md` (NEW)
+- `schemas/README.md` (MODIFIED — engine enum note)
+- `modules/STANDARDS.md` (MODIFIED — Policy authoring standard section)
+- `docs/METRICS.md` (MODIFIED — swappable engine narrative)
+
+**Verification:** `pytest tests/test_regression_policies.py
+tests/test_kyverno_json_engine.py -v` (new tests pass or skip-without-
+kj). Full regression gate `pytest tests/` still at 287/287 baseline +
+new tests (skip without kj). Manual read of `adapters/README.md` +
+`adapters/kyverno-json/README.md` + `modules/STANDARDS.md` policy
+section for clarity.
---
-## Wave ordering
+### Phase P5 — final review + audit + milestone ship (Final Phase)
-```
-Wave 1 (P1): consumer-guide-fixes [lead-developer]
- ↓
-Wave 2 (P2): env-transition-detect-and-destroy [backend-engineer]
- ↓
-Wave 3 (P3): env-transition-tests [backend-engineer + lead-developer]
- ↓
-Wave 4 (P4): final-review-ship [lead-developer]
-```
+**Type:** `docs` (review + audit + milestone completion)
-**Dependencies:**
-- P2 depends on P1: the Step 8 rewrite in P1 documents the destroy
- semantics that P2 implements. Doing P1 first ensures the docs and code
- land in the right order (docs describe the intended behavior, then code
- implements it).
-- P3 depends on P2: the tests validate the env-transition module and
- pipeline Step 0b that P2 creates.
-- P4 depends on P1+P2+P3: the final review covers all changes.
+**Requirements:** All REQ-291..309 (mark complete)
-**Parallelization:** P1 and P2 could run in parallel (different
-territories: docs vs code), but the wave ordering is sequential for
-safety — if P1's Step 8 rewrite reveals a design issue, P2's
-implementation should follow the corrected design. With
-`parallelization.enabled=true` and `min_plans_for_parallel=2`, the
-orchestrator *could* run them concurrently; however, the dependency
-(P2 follows P1's design) makes sequential the safer choice. P3 must
-follow P2 (tests validate the code). P4 must follow all.
+**Must-haves:**
+- `ciagent-review` multi-persona code review across P1..P4
+ (lead-developer, backend-engineer, data-engineer, policy-engineer).
+ Auto-fix P0; flag P1+ for post-hoc review. If P1+ issues found, fix
+ them in this final phase (not loop back to EXECUTE).
+- `ciagent-audit` — reconstruction test (git log ↔ `.ciagent/` files),
+ `.ciagent/` file discipline, branch hygiene, commit discipline.
+ Critical issues fixed in this phase.
+- `ciagent-ship` (milestone) — merge `phase/05-final-review-ship` →
+ `milestone/v1.25-kyverno-json` → `main`; tag `v1.24.5` (= the v1.25
+ release per the prev-minor tagging rule); create Gitea release with
+ full milestone summary (all phases, all requirements); delete all
+ milestone branches (local + remote).
+- Update `REQUIREMENTS.md` (mark REQ-291..309 complete),
+ `ROADMAP.md` (mark v1.25 complete), `CHECKPOINT.json`
+ (milestone_complete: true), `NORTH_STAR.md` (note Strategic
+ Objective #2 — provable trust via a replaceable policy-engine
+ substrate).
-## Requirement → Phase mapping
+**Vertical slice:** The v1.25 milestone is complete: kyverno-json is
+the primary policy tool, behind a swappable adapter, with policies
+over all 4 Nova artifacts. Tags v1.24.0..v1.24.5 on the v1.24.x line.
+The milestone branch merges to main.
-| REQ | Phase | Type | Description |
-|-----|-------|------|-------------|
-| REQ-276 | P1 | docs | Contract fields table corrected |
-| REQ-277 | P1 | docs | Step 4 caller consistent with Step 2 |
-| REQ-278 | P1 | docs | Step 5 stage 8 "dev only" corrected |
-| REQ-279 | P1 | docs | Step 8 rewritten with destroy semantics |
-| REQ-280 | P1 | docs | Per-env section Shape B lead sentence |
-| REQ-281 | P1 | docs | Reference table @v1.19 wording corrected |
-| REQ-282 | P2 | feat | env_transition.py detect_prior_env() |
-| REQ-283 | P2 | feat | env_transition.py record_applied_env() |
-| REQ-284 | P2 | feat | run_platform.sh Step 0b detect-and-destroy |
-| REQ-285 | P2 | feat | run_platform.sh records applied env |
-| REQ-286 | P2 | feat | deploy.yml passes NOVA_CONSUMER_REPO |
-| REQ-287 | P2 | docs | adapter.py state-key doc comment |
-| REQ-288 | P3 | test | test_env_transition.py |
-| REQ-289 | P3 | test | test_run_platform_env_transition.py |
-| REQ-290 | P1 | test | consumer guide test updates |
+**Verification:** `pytest tests/ -v` full suite passes (287 baseline +
+new tests). `git log --oneline` shows the v1.25 phase commits.
+`git tag` shows v1.24.0..v1.24.5. `git branch` shows no leftover
+milestone/phase branches (all deleted post-ship).
-## Tag plan
+---
-- P0 (this phase): `v1.23.0` — pre-execution patch
-- P1: `v1.23.1` — consumer guide fixes
-- P2: `v1.23.2` — env-transition detect-and-destroy
-- P3: `v1.23.3` — env-transition tests
-- P4: `v1.23.4` — final review + ship = **milestone release**
\ No newline at end of file
+## Wave ordering (parallelization)
+
+With `parallelization.enabled: true`, `max_concurrent_agents: 5`,
+`min_plans_for_parallel: 2`:
+
+- **P1 Wave 1:** backend-engineer (protocol + registry + install) ‖
+ data-engineer (config.json policy object) ‖ policy-engineer (engine
+ adapter + smoke policy). 3 concurrent personas. Merge in order:
+ data-engineer → backend-engineer → policy-engineer.
+- **P2 Wave 2:** policy-engineer (contract + stack-IR policies) ‖
+ backend-engineer (resolver wiring — depends on P1 registry). 2
+ concurrent. Merge: policy-engineer → backend-engineer (wiring
+ references the policy dirs).
+- **P3 Wave 3:** policy-engineer (plan-JSON + meta policies) ‖
+ backend-engineer (run_platform.sh wiring — depends on P1 engine +
+ P2 resolver pattern). 2 concurrent. Merge: policy-engineer →
+ backend-engineer.
+- **P4 Wave 4:** policy-engineer (regression policies) ‖ data-engineer
+ (capability-inventory fixtures) ‖ lead-developer (docs: READMEs,
+ STANDARDS, METRICS). 3 concurrent. Merge: data-engineer →
+ policy-engineer → lead-developer.
+
+Territory enforcement: `warn` mode (per `config.json
+personas.territory_enforcement: "warn"`). Cross-territory edits
+(e.g., backend-engineer touching a policy file) emit a warning, not a
+block.
+
+## Requirement → phase → persona matrix
+
+| REQ | Phase | Primary persona | Type |
+|-----|-------|-----------------|------|
+| REQ-291 | P1 | backend-engineer | feat |
+| REQ-292 | P1 | data-engineer | feat (config) |
+| REQ-293 | P1 | policy-engineer | feat |
+| REQ-294 | P1 | backend-engineer | feat (install) |
+| REQ-295 | P2 | policy-engineer | feat |
+| REQ-296 | P2 | backend-engineer | feat (wiring) |
+| REQ-297 | P2 | policy-engineer | feat |
+| REQ-298 | P2 | backend-engineer | feat (wiring) |
+| REQ-299 | P2 | policy-engineer | test |
+| REQ-300 | P3 | policy-engineer | feat |
+| REQ-301 | P3 | backend-engineer | feat (pipeline) |
+| REQ-302 | P3 | policy-engineer + backend-engineer | test |
+| REQ-303 | P3 | policy-engineer | feat (meta) |
+| REQ-304 | P4 | policy-engineer | feat |
+| REQ-305 | P4 | policy-engineer + data-engineer | test |
+| REQ-306 | P4 | policy-engineer + lead-developer | docs |
+| REQ-307 | P4 | lead-developer | docs |
+| REQ-308 | P1 | backend-engineer | test |
+| REQ-309 | P1 | policy-engineer | test |
\ No newline at end of file