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---
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.py—PolicyEngineProtocol (PEP 544) +PolicyEngineRegistry(selects fromconfig.json.policy.engine) +NullEnginefallback (emitsSKIPPEDwhenpolicykey absent) (REQ-291).ciagent/config.jsongainspolicyobject:{"engine": "kyverno-json", "policy_root": "adapters/kyverno-json/policies"}(REQ-292)adapters/kyverno-json/kyverno_json_engine.py—KyvernoJsonEngineimplementing the protocol:is_configured()guards onwhich kj;evaluate()writes payload to temp JSON, invokeskj scan --policy <dir> --payload <json> --output json, translates native output →list[dict]PCR records (engine: "kyverno",ruleIdprefixedKJ_<policy_name>, severity fromnova.cloudinit.dev/severityannotation); defensive parsing (malformed →errorPCR, never exception);is_configured()==false→ singleSKIPPEDPCR (KJ_ENGINE_NOT_CONFIGURED) (REQ-293)adapters/kyverno-json/__init__.pyexportsKyvernoJsonEngine;adapters/kyverno-json/policies/_smoke.jsontrivialrequire-contract-idpolicy for round-trip validation;scripts/install-kyverno-json.shrunsgo install github.com/kyverno/kyverno-json/cmd/kj@latest;.github/workflows/ci.yml+.gitea/workflows/ci.ymlinstall Go + kj (cached) (REQ-294)tests/test_policy_engine.py— protocol conformance, registry selection, unknown-engineKeyError,NullEnginefallback,is_configured()false whenwhich kjabsent (mocked) (REQ-308)tests/test_kyverno_json_engine.py—evaluate()returns PCR dicts validating againstschemas/policy_check_result.schema.json(viajsonschema); defensive parsing (malformed kyverno-json output →errorPCR);is_configured()==false→SKIPPEDwithKJ_ENGINE_NOT_CONFIGURED;pytest.skip("kj not installed")whenwhich kjabsent (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()==false → SKIPPED).
Files touched:
core/policy_engine.py(NEW).ciagent/config.json(MODIFIED —policyobject)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 aValidatingPolicywith onevalidate.assertrule using JMESPath against the payload root; severity vianova.cloudinit.dev/severityannotation (REQ-295)core/contract_resolver.pyinvokesPolicyEngineRegistry.get_engine().evaluate(contract_dict, policies/contract/, contract_id)before resolving; failures feed thepolicyinput asfailPCRs (no resolver exit — confidence signal decides the gate,--soft-failpattern); emitsnova.policy.evaluatedmetrics event (REQ-296)adapters/kyverno-json/policies/stack-ir/— 3 policies over resolved Stack IR:require-tagging-standard.json(portsnova_tagging.py—nova:owner+nova:environmenttags on everyresources[]entry),forbid-public-ingress.json(v1.0 demo rule),require-encryption-by-default.json(v1.8 D-encryption-default);~modifier iteratesresources[](REQ-297)core/contract_resolver.pyinvokes the engine with the resolved Stack IR andpolicies/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")whenwhich kjabsent (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 overterraform show -jsonoutput: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 overplanned_values.root_module.resources[](REQ-300)run_platform.shStep 5 gains a parallel kyverno-json pass: after Checkov/Wiz produce raw PCRs, the script runskj scan --policy adapters/kyverno-json/policies/plan-json/ --payload <tfshow.json> -o jsonand pipes throughadapters/kyverno-json/kyverno_json_engine.pyto produce a second PCR list; both lists concatenated and fed to the confidence signal;nova.policy.evaluatedevent with both engine names; whenwhich kjis 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.pyassertsrun_platform.shhas the kyverno-json Step 5 block + concatenates PCR lists (script-substring assertion, pattern fromtests/test_pipeline.py:79-95) (REQ-302)adapters/kyverno-json/policies/meta/—block-on-any-critical.json(asserts no PCR in merged list hasseverity: critical+result: fail; if any does, emitsfailPCRKJ_META_BLOCK_CRITICALseveritycritical— declarative source of truth; theconfidence_signal.pyhard-override stays as defense-in-depth per D-119) +tagging-rules-agree.json(cross-checks CheckovNOVA_TAG_NAMINGvs kjKJ_REQUIRE_TAGGING_STANDARDbyresourceRef; divergence emitserrorPCR 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; emitpass/failPCRs per capability; the existingcore/regression_verify.pyis 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.mdgains new kyverno-json adapter row + "Policy Engine Protocol" section (Protocol, registry, swap boundary, how-to-add-OpaEngine);adapters/kyverno-json/README.mddocuments the engine, install path, policy directory layout, 4 policy categories (REQ-306).ciagent/ARCHITECTURE.md§12.7 (added in RESEARCH) is finalized;schemas/README.mdnotesengine: "kyverno"shared by K8s adapter- kj (distinguished by
ruleIdprefix);modules/STANDARDS.mdgains "Policy authoring standard" section for module owners;docs/METRICS.mdnotes the policy engine is swappable (Strategic Objective #2 — provable trust via a replaceable substrate) (REQ-307)
- kj (distinguished by
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-reviewmulti-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) — mergephase/05-final-review-ship→milestone/v1.25-kyverno-json→main; tagv1.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 |