Files
acdl/.ciagent/PLAN.md
T
Jon Chery ec53302014 docs(P00): create phase plans — v1.25 (4 phases, 4 waves)
PLAN.md: 4 execution phases (P1 engine-core, P2 contract+stack-IR policies,
P3 plan-JSON+meta+pipeline wiring, P4 regression-gate+docs) + P5 final
review/ship. Wave ordering with parallelization (3-2-2-3 concurrent personas).
Each phase is a vertical slice (end-to-end: policies + Python wiring + tests +
docs). Tags v1.24.0..v1.24.5. 19 requirements (REQ-291..309) mapped to phases
and personas.

---ci---
project: acdl
phase: 0
milestone: v1.25
status: plan
---/ci---
2026-08-12 18:11:00 +00:00

18 KiB

PLAN — v1.25 (kyverno-json Unified Policy Engine)

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 — engine-core (Wave 1, backend-engineer + policy-engineer + data-engineer)

Type: feat (engine protocol + registry + kyverno-json engine adapter + install + tests)

Requirements: REQ-291, REQ-292, REQ-293, REQ-294, REQ-308, REQ-309

Must-haves:

  • core/policy_engine.pyPolicyEngine 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.pyKyvernoJsonEngine implementing the protocol: is_configured() guards on which kj; evaluate() writes payload to temp JSON, invokes kj scan --policy <dir> --payload <json> --output json, translates native output → list[dict] PCR records (engine: "kyverno", ruleId prefixed KJ_<policy_name>, 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.pyevaluate() returns PCR dicts validating against schemas/policy_check_result.schema.json (via jsonschema); defensive parsing (malformed kyverno-json output → error PCR); is_configured()==falseSKIPPED with KJ_ENGINE_NOT_CONFIGURED; pytest.skip("kj not installed") when which kj absent (REQ-309)

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()==falseSKIPPED).

Files touched:

  • 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_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 — contract + stack-IR policies (Wave 2, policy-engineer + backend-engineer)

Type: feat (policies + resolver wiring + tests)

Requirements: REQ-295, REQ-296, REQ-297, REQ-298, REQ-299

Must-haves:

  • 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.pynova: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: 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:

  • 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: 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 — plan-JSON policies + meta-orchestration + pipeline wiring (Wave 3, policy-engineer + backend-engineer)

Type: feat (plan-JSON policies + meta-policies + run_platform.sh wiring + tests)

Requirements: REQ-300, REQ-301, REQ-302, REQ-303

Must-haves:

  • 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 <tfshow.json> -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: 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:

  • 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_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 — regression-gate policies + docs (Wave 4, policy-engineer + data-engineer + lead-developer)

Type: feat (regression policies) + docs (adapter READMEs + ARCHITECTURE + STANDARDS + METRICS)

Requirements: REQ-304, REQ-305, REQ-306, REQ-307

Must-haves:

  • 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.


Phase P5 — final review + audit + milestone ship (Final Phase)

Type: docs (review + audit + milestone completion)

Requirements: All REQ-291..309 (mark complete)

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-shipmilestone/v1.25-kyverno-jsonmain; 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).

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.

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).


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