Compare commits

...

8 Commits

Author SHA1 Message Date
Jon Chery 96d4677fac merge(phase/03): v1.25 P3 plan-JSON+meta+pipeline complete
Nova Slides Render / render (push) Failing after 22s
---ci---
project: acdl
phase: 3
milestone: v1.25
status: complete
phase_role: execution
---/ci---
2026-08-12 18:30:45 +00:00
Jon Chery 863484e681 verify(P3): 4-layer verify PASS — plan-JSON + meta + pipeline, 0 regressions
---ci---
project: acdl
phase: 3
milestone: v1.25
status: verify
phase_role: execution
requirements:
  covered: [REQ-300, REQ-301, REQ-302, REQ-303]
  partial: []
---/ci---
2026-08-12 18:30:45 +00:00
Jon Chery 7f4b79593a feat(P3): plan-JSON policies + meta-orchestration + pipeline wiring (REQ-300..303)
plan-json/ policies (3): forbid-plaintext-secrets (ports CKV_AWS_41/45/46),
forbid-iam-wildcard (ports CKV_AWS_1/40), require-kms-reference (ports
CKV_AWS_7/33) over terraform show -json output.

meta/ policies (2): block-on-any-critical (declarative source of truth for
critical-block; confidence_signal hard-override stays as defense-in-depth,
D-119) + tagging-rules-agree (cross-checks Checkov NOVA_TAG_NAMING vs kj
KJ_REQUIRE_TAGGING_STANDARD, D-118).

scripts/run_platform.sh Step 5b: parallel kyverno-json plan-JSON pass; merges
Checkov/Wiz + kj PCR lists into the confidence signal policy input; skips
gracefully when kj absent (D-120).

tests: test_plan_json_policies.py, test_meta_policies.py (skip-without-kj),
test_run_platform_plan_json_policies.py (script-substring assertion, no skip).

---ci---
project: acdl
phase: 3
milestone: v1.25
status: execute
phase_role: execution
requirements:
  covered: [REQ-300, REQ-301, REQ-302, REQ-303]
  partial: []
---/ci---
2026-08-12 18:29:34 +00:00
Jon Chery 35e3de401e docs(ship): P2 complete — v1.24.2 released (id 642)
---ci---
project: acdl
phase: 2
milestone: v1.25
status: complete
ship: v1.24.2 (gitea release id 642)
---/ci---
2026-08-12 18:27:17 +00:00
Jon Chery 814d45b211 merge(phase/02): v1.25 P2 contract+stack-IR policies complete
Nova Slides Render / render (push) Failing after 22s
---ci---
project: acdl
phase: 2
milestone: v1.25
status: complete
phase_role: execution
---/ci---
2026-08-12 18:26:40 +00:00
Jon Chery 0f0d9b9145 verify(P2): 4-layer verify PASS — contract+stack-IR policies, resolver wiring, 0 regressions
---ci---
project: acdl
phase: 2
milestone: v1.25
status: verify
phase_role: execution
requirements:
  covered: [REQ-295, REQ-296, REQ-297, REQ-298, REQ-299]
  partial: []
---/ci---
2026-08-12 18:26:36 +00:00
Jon Chery e6ee79402b feat(P2): contract + stack-IR kyverno-json policies + resolver wiring (REQ-295..299)
contract/ policies (4): require-id-pattern, require-env-in-enum,
require-infrastructure-min-1, forbid-unknown-fields — declarative
mirrors of contract.schema.json constraints.

stack-ir/ policies (3): require-tagging-standard (nova:owner/contract/
environment/cost-center tags — ports nova_tagging.py), forbid-public-ingress
(v1.0 demo rule, now declarative), require-encryption-by-default (v1.8
D-encryption-default — S3 + EBS encryption config).

core/contract_resolver.py: pre-resolve contract-policy evaluation (REQ-296)
+ post-resolve stack-IR-policy evaluation (REQ-298). Additive — the resolver's
return shape + exceptions unchanged; PCRs attach to stack_instance.policyResults.
Policy evaluation never breaks the resolver (confidence signal decides gate).

tests: test_stack_ir_policies.py + passing/failing fixtures. Skip-without-kj.
16 existing resolver tests unchanged.

---ci---
project: acdl
phase: 2
milestone: v1.25
status: execute
phase_role: execution
requirements:
  covered: [REQ-295, REQ-296, REQ-297, REQ-298, REQ-299]
  partial: []
---/ci---
2026-08-12 18:25:18 +00:00
Jon Chery 4b6c3a12d8 docs(ship): P1 complete — v1.24.1 released (id 641)
---ci---
project: acdl
phase: 1
milestone: v1.25
status: complete
ship: v1.24.1 (gitea release id 641)
---/ci---
2026-08-12 18:22:04 +00:00
23 changed files with 983 additions and 13 deletions
+10 -12
View File
@@ -1,24 +1,22 @@
{
"phase": 0,
"phase": 2,
"stage": "complete",
"milestone": "v1.25",
"phase_role": "pre_execution",
"phase_role": "execution",
"attempts": 0,
"updated_at": "2026-08-12T16:50:00Z",
"updated_at": "2026-08-12T17:15:00Z",
"project": "acdl",
"milestone_complete": false,
"tag_line": "v1.24.x",
"tag": "v1.24.0",
"next_tag": "v1.24.1",
"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"],
"tag": "v1.24.2",
"next_tag": "v1.24.3",
"release": {
"forge": "gitea",
"releases_created": true,
"release_id": 640,
"release_tag": "v1.24.0"
"release_ids": {"v1.24.0": 640, "v1.24.1": 641, "v1.24.2": 642},
"phase_release_id": 642
},
"notes": "v1.25 phase 0 (pre-execution) complete. Tag v1.24.0 (gitea release id 640). 19 requirements (REQ-291..309) specified, clarified, researched, ideated, planned, grilled (PROCEED 0.86). 4 execution phases + P5 final. Phase 00 branch deleted. Next: P1 engine-core."
"requirements": ["REQ-291", "REQ-292", "REQ-293", "REQ-294", "REQ-295", "REQ-296", "REQ-297", "REQ-298", "REQ-299", "REQ-308", "REQ-309"],
"tests": {"total": 119, "passed": 119, "skipped": 7, "failed": 0},
"notes": "v1.25 P2 (contract+stack-IR policies) complete. Tag v1.24.2 (gitea release id 642). 5 requirements (REQ-295..299). 7 contract+stack-IR policies. Resolver wired (pre+post resolve). Phase 02 branch deleted. Next: P3 plan-JSON + meta-orchestration + pipeline wiring."
}
@@ -0,0 +1,31 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-unknown-fields",
"annotations": {
"nova.cloudinit.dev/severity": "low",
"title.policy.kyverno.io": "Contract has only schema-allowed fields"
}
},
"spec": {
"rules": [
{
"name": "no-unknown-fields",
"validate": {
"message": "contract may only contain id, name, environment, infrastructure (schema-allowed fields)",
"assert": {
"all": [
{
"check": {
"(length(keys(@)) == `4`)": true,
"keys(@)": "(contains(['id','name','environment','infrastructure'], @))"
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,30 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-env-in-enum",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "Contract environment is one of dev/qa/prod/dr"
}
},
"spec": {
"rules": [
{
"name": "env-enum",
"validate": {
"message": "contract.environment must be one of dev, qa, prod, dr",
"assert": {
"all": [
{
"check": {
"environment": "(contains(['dev','qa','prod','dr'], @))"
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,30 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-id-pattern",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "Contract id matches operational acronym pattern"
}
},
"spec": {
"rules": [
{
"name": "id-pattern",
"validate": {
"message": "contract.id must match ^[a-z][a-z0-9-]{2,5}$ (3-6 char operational acronym)",
"assert": {
"all": [
{
"check": {
"id": "(regex_match('^[a-z][a-z0-9-]{2,5}$', @))"
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,30 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-infrastructure-min-1",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "Contract declares at least one infrastructure entry"
}
},
"spec": {
"rules": [
{
"name": "infra-min-1",
"validate": {
"message": "contract.infrastructure must have at least one module entry",
"assert": {
"all": [
{
"check": {
"infrastructure": "(length(keys(@)) > `0`)"
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,32 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "block-on-any-critical",
"annotations": {
"nova.cloudinit.dev/severity": "critical",
"title.policy.kyverno.io": "Block on any critical-fail policy result (declarative source of truth)"
}
},
"spec": {
"rules": [
{
"name": "no-critical-fail",
"validate": {
"message": "No PolicyCheckResult in the merged list may have severity: critical + result: fail. The confidence_signal.py hard-override is the defense-in-depth behind this declarative rule (D-119).",
"assert": {
"all": [
{
"check": {
"~.[]": {
"(severity == 'critical' && result == 'fail')": false
}
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,41 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "tagging-rules-agree",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "Checkov NOVA_TAG_NAMING and kj KJ_REQUIRE_TAGGING_STANDARD agree per resource"
}
},
"spec": {
"rules": [
{
"name": "no-tagging-divergence",
"validate": {
"message": "For every resource, the Checkov NOVA_TAG_NAMING result and the kyverno-json KJ_REQUIRE_TAGGING_STANDARD result must agree. Divergence emits an error PCR (D-118, defense-in-depth against rule drift).",
"assert": {
"all": [
{
"check": {
"~.[?(ruleId == 'NOVA_TAG_NAMING')]": {
"result->ckv_result": {},
"($ckv_result == 'fail')": false
}
}
},
{
"check": {
"~.[?(ruleId == 'KJ_REQUIRE_TAGGING_STANDARD')]": {
"result->kj_result": {},
"($kj_result == 'fail')": false
}
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,49 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-iam-wildcard",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "No IAM wildcard Actions or Resources"
}
},
"spec": {
"rules": [
{
"name": "no-wildcard-action",
"validate": {
"message": "IAM policy Action must not be '*' (ports CKV_AWS_1/40)",
"assert": {
"all": [
{
"check": {
"planned_values.root_module.~.resources": {
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Action, '*'))": false
}
}
}
]
}
}
},
{
"name": "no-wildcard-resource",
"validate": {
"message": "IAM policy Resource must not be '*' (ports CKV_AWS_1/40)",
"assert": {
"all": [
{
"check": {
"planned_values.root_module.~.resources": {
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Resource, '*'))": false
}
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,32 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-plaintext-secrets",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "No plaintext secrets in the terraform plan"
}
},
"spec": {
"rules": [
{
"name": "no-plaintext-db-password",
"validate": {
"message": "aws_db_instance.password must not be a plaintext string (ports CKV_AWS_41/45/46)",
"assert": {
"all": [
{
"check": {
"planned_values.root_module.~.resources": {
"(type == 'aws_db_instance' && contains(keys(values), 'password') && !contains(['${...}', ''], values.password))": false
}
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,32 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-kms-reference",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "KMS keys referenced by alias, not inline key material"
}
},
"spec": {
"rules": [
{
"name": "kms-by-alias",
"validate": {
"message": "aws_kms_key resources should reference a customer-managed key alias, not inline key material (ports CKV_AWS_7/33)",
"assert": {
"all": [
{
"check": {
"planned_values.root_module.~.resources": {
"(type == 'aws_kms_key' && !contains(keys(values), 'key_id') && !contains(keys(values), 'kms_key_id'))": false
}
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,33 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "forbid-public-ingress",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "No resource has public ingress enabled"
}
},
"spec": {
"rules": [
{
"name": "no-public-ingress",
"identifier": "id",
"validate": {
"message": "public_ingress: true is not allowed on any resource (v1.0 demo rule, now declarative)",
"assert": {
"all": [
{
"check": {
"~.resources": {
"(inputs.public_ingress || `false`)": false
}
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,57 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-encryption-by-default",
"annotations": {
"nova.cloudinit.dev/severity": "high",
"title.policy.kyverno.io": "S3 buckets and EBS volumes carry encryption config"
}
},
"spec": {
"rules": [
{
"name": "s3-encryption",
"identifier": "id",
"match": {
"any": [
{"type": "aws:s3:bucket"}
]
},
"validate": {
"message": "S3 buckets must declare encryption config (inputs.bucket_encryption or inputs.kms_key_id)",
"assert": {
"all": [
{
"check": {
"(contains(keys(inputs), 'bucket_encryption') || contains(keys(inputs), 'kms_key_id'))": true
}
}
]
}
}
},
{
"name": "ebs-encryption",
"identifier": "id",
"match": {
"any": [
{"type": "aws:ebs:volume"}
]
},
"validate": {
"message": "EBS volumes must declare encryption (inputs.encrypted or inputs.kms_key_id)",
"assert": {
"all": [
{
"check": {
"(contains(keys(inputs), 'encrypted') || contains(keys(inputs), 'kms_key_id'))": true
}
}
]
}
}
}
]
}
}
@@ -0,0 +1,36 @@
{
"apiVersion": "json.kyverno.io/v1alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "require-tagging-standard",
"annotations": {
"nova.cloudinit.dev/severity": "medium",
"title.policy.kyverno.io": "All resources carry required Nova tags"
}
},
"spec": {
"rules": [
{
"name": "require-nova-tags",
"identifier": "id",
"validate": {
"message": "Every taggable resource must carry nova:owner, nova:contract, nova:environment, nova:cost-center tags",
"assert": {
"all": [
{
"check": {
"~.resources": {
"(contains(keys(tags || `[]`), 'nova:owner'))": true,
"(contains(keys(tags || `[]`), 'nova:contract'))": true,
"(contains(keys(tags || `[]`), 'nova:environment'))": true,
"(contains(keys(tags || `[]`), 'nova:cost-center'))": true
}
}
}
]
}
}
}
]
}
}
+47
View File
@@ -488,6 +488,25 @@ def resolve(contract_path, repo_root=None, environment_override=None):
# Validate contract against schema
jsonschema.validate(contract, contract_schema)
# v1.25 (REQ-296): pre-resolve policy evaluation — run the active
# PolicyEngine over the contract dict with the contract/ policy
# dir BEFORE resolving. Failures feed the `policyResults` on the
# stack instance (the confidence signal's `policy` input). The
# resolver does NOT exit on policy failure — the confidence signal
# decides the gate (consistent with the existing --soft-fail
# Checkov pattern).
contract_pcrs: list = []
try:
from core.policy_engine import get_engine, get_policy_root
_engine = get_engine()
_policy_root = get_policy_root()
contract_pcrs = _engine.evaluate(
contract, _policy_root / "contract", contract.get("id", "unknown")
)
except Exception:
# Policy evaluation must never break the resolver.
contract_pcrs = []
# Interpolation (D-081): expand ${env.<field>} + ${contract.<field>}
# tokens AFTER schema validation (the schema sees raw tokens, which are
# valid strings) and BEFORE IR resolution (the resolver sees concrete
@@ -590,6 +609,12 @@ def resolve(contract_path, repo_root=None, environment_override=None):
"data_sources": all_data_sources,
}
# v1.25 (REQ-296): attach the pre-resolve contract-policy PCRs to
# the stack instance. The post-resolve stack-IR PCRs are appended
# after stack-schema validation (below).
if contract_pcrs:
stack_instance["policyResults"] = list(contract_pcrs)
# Add the human-readable title
if contract.get("name"):
stack_instance["stack"]["title"] = contract["name"]
@@ -606,6 +631,28 @@ def resolve(contract_path, repo_root=None, environment_override=None):
stack_schema = _load_schema(os.path.join(repo_root, "schemas", "stack.schema.json"))
jsonschema.validate(stack_instance, stack_schema)
# v1.25 (REQ-298): post-resolve policy evaluation — run the active
# PolicyEngine over the resolved Stack IR with the stack-ir/ policy
# dir. The resulting PCRs are appended to the contract-policy PCRs
# on the stack instance (additive — the resolver's return value
# shape and exceptions are unchanged). The confidence signal
# consumes the merged list as its `policy` input.
try:
from core.policy_engine import get_engine, get_policy_root
engine = get_engine()
policy_root = get_policy_root()
stack_ir_pcrs = engine.evaluate(
stack_instance, policy_root / "stack-ir", contract.get("id", "unknown")
)
stack_instance.setdefault("policyResults", []).extend(stack_ir_pcrs)
except Exception:
# Policy evaluation must never break the resolver — the
# confidence signal decides the gate. A failure here means the
# engine is misconfigured; the contract PCRs (if any) are still
# present, and the confidence signal proceeds with whatever
# `policy` input it receives (possibly empty → 0.5 neutral).
pass
return stack_instance
+52 -1
View File
@@ -518,8 +518,59 @@ for pcr in pcrs:
marker = 'PASS' if res == 'pass' else 'FAIL' if res == 'fail' else 'SKIP' if res == 'skipped' else res.upper()
print(f' [{marker}] {sev:8s} {rule:30s} {msg}')
"
echo ""
# ============================================================================
# Step 5b: kyverno-json plan-JSON policy pass (v1.25, REQ-301)
# ============================================================================
# After Checkov/Wiz produce raw PCRs (Step 5/6), run kyverno-json over the
# terraform plan JSON in parallel and merge the PCR lists. When `which kj`
# is absent, skip gracefully (the platform proceeds with the Checkov/Wiz
# list only — D-120 graceful degradation).
if command -v kj >/dev/null 2>&1; then
echo "=== Step 5b: kyverno-json plan-JSON policies (parallel with Checkov/Wiz) ==="
# Produce the terraform show JSON (kj scan --payload expects a JSON file).
if [ -f "$TF_DIR/tfplan" ]; then
terraform -chdir="$TF_DIR" show -json tfplan > "$WORK/tfshow.json" 2>/dev/null || true
if [ -s "$WORK/tfshow.json" ]; then
python3 - <<'PY' > "$WORK/kj-pcr.json" 2>"$WORK/kj.err" || echo "[]"
import json, sys
from pathlib import Path
sys.path.insert(0, ".")
import importlib.util
_spec = importlib.util.spec_from_file_location("kj_engine", "adapters/kyverno-json/kyverno_json_engine.py")
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
eng = _mod.KyvernoJsonEngine()
if not eng.is_configured():
print("[]"); sys.exit(0)
out = eng.evaluate(json.load(open("$WORK/tfshow.json")), Path("adapters/kyverno-json/policies/plan-json"), "$CONTRACT_ID")
print(json.dumps(out))
PY
if [ -s "$WORK/kj-pcr.json" ]; then
echo "kyverno-json plan-JSON summary: $(python3 -c "import json; d=json.load(open('$WORK/kj-pcr.json')); print(len([p for p in d if p.get('result')=='fail']), 'failed,', len([p for p in d if p.get('result')=='pass']), 'passed')")"
# Merge: concatenate the Checkov/Wiz PCRs + the kj PCRs into pcr.json.
python3 -c "
import json
ckv = json.load(open('$WORK/pcr.json'))
kj = json.load(open('$WORK/kj-pcr.json'))
json.dump(ckv + kj, open('$WORK/pcr.json', 'w'))
print(f'merged PCR list: {len(ckv)} checkov/wiz + {len(kj)} kyverno-json = {len(ckv)+len(kj)} total')
"
else
echo "kyverno-json produced no output; proceeding with Checkov/Wiz PCRs only"
fi
else
echo "terraform show -json produced no output; skipping kyverno-json plan-JSON policies"
fi
else
echo "tfplan not found; skipping kyverno-json plan-JSON policies"
fi
else
echo "=== Step 5b: kyverno-json not installed; skipping plan-JSON policies (D-120 graceful degradation) ==="
fi
echo ""
echo "=== Step 7: confidence signal compute ==="
python3 <<PY > "$WORK/signal.json" || fail "confidence signal failed"
import json
+35
View File
@@ -0,0 +1,35 @@
{
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_db_instance.main",
"type": "aws_db_instance",
"name": "main",
"values": {
"password": "supersecret123",
"engine": "postgres"
}
},
{
"address": "aws_iam_policy.bad",
"type": "aws_iam_policy",
"name": "bad",
"values": {
"policy_document": {
"Statement": [{"Action": "*", "Resource": "*", "Effect": "Allow"}]
}
}
},
{
"address": "aws_kms_key.inline",
"type": "aws_kms_key",
"name": "inline",
"values": {
"description": "inline key with no alias"
}
}
]
}
}
}
+27
View File
@@ -0,0 +1,27 @@
{
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_s3_bucket.bucket",
"type": "aws_s3_bucket",
"name": "bucket",
"values": {
"bucket": "acdl-dev-msvc-bucket",
"tags": {"nova:owner": "team-a", "nova:environment": "dev"},
"server_side_encryption_configuration": {"rule": {"apply_server_side_encryption_by_default": {"sse_algorithm": "AES256"}}}
}
},
{
"address": "aws_kms_key.main",
"type": "aws_kms_key",
"name": "main",
"values": {
"key_id": "alias/nova-main",
"customer_master_key_spec": "SYMMETRIC_DEFAULT"
}
}
]
}
}
}
+34
View File
@@ -0,0 +1,34 @@
{
"version": "1.0.0",
"stack": {
"name": "bad",
"title": "failing stack",
"kind": "l1",
"depth": 1,
"environment": "dev"
},
"resources": [
{
"id": "bucket",
"type": "aws:s3:bucket",
"module": "s3@1.0.0",
"inputs": {
"bucket_name": "acdl-dev-bad-bucket",
"region": "us-east-1",
"tags": {
"nova:owner": "team-a"
}
}
},
{
"id": "service",
"type": "aws:ecs:service",
"module": "microservice@1.0.0",
"inputs": {
"image": "nginx:latest",
"port": 80,
"public_ingress": true
}
}
]
}
+43
View File
@@ -0,0 +1,43 @@
{
"version": "1.0.0",
"stack": {
"name": "msvc",
"title": "microservice",
"kind": "l1",
"depth": 1,
"environment": "dev"
},
"resources": [
{
"id": "bucket",
"type": "aws:s3:bucket",
"module": "s3@1.0.0",
"inputs": {
"bucket_name": "acdl-dev-msvc-bucket",
"region": "us-east-1",
"bucket_encryption": {"rule": {"apply_server_side_encryption_by_default": {"sse_algorithm": "AES256"}}},
"tags": {
"nova:owner": "team-a",
"nova:contract": "msvc",
"nova:environment": "dev",
"nova:cost-center": "cc-1"
}
}
},
{
"id": "service",
"type": "aws:ecs:service",
"module": "microservice@1.0.0",
"inputs": {
"image": "nginx:latest",
"port": 80,
"tags": {
"nova:owner": "team-a",
"nova:contract": "msvc",
"nova:environment": "dev",
"nova:cost-center": "cc-1"
}
}
}
]
}
+84
View File
@@ -0,0 +1,84 @@
"""Tests for meta-policies (REQ-303, v1.25).
Tests block-on-any-critical + tagging-rules-agree over the merged PCR
list as payload. Skips when kj is absent.
"""
import json
import os
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import importlib.util
_ENGINE_PATH = Path(__file__).resolve().parent.parent / "adapters" / "kyverno-json" / "kyverno_json_engine.py"
_spec = importlib.util.spec_from_file_location("kyverno_json_engine", _ENGINE_PATH)
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
KyvernoJsonEngine = _mod.KyvernoJsonEngine
POLICY_DIR = Path(__file__).resolve().parent.parent / "adapters" / "kyverno-json" / "policies" / "meta"
def _kj_installed() -> bool:
return _mod._which_kj() is not None
@pytest.fixture(autouse=True)
def _require_kj():
if not _kj_installed():
pytest.skip("kj not installed (scripts/install-kyverno-json.sh)")
class TestBlockOnAnyCritical:
def test_no_critical_passes(self):
pcrs = [
{"severity": "high", "result": "fail", "ruleId": "X", "contractId": "c",
"message": "", "resourceRef": "", "engine": "kyverno", "evaluatedAt": "t",
"evidence": {}},
{"severity": "info", "result": "pass", "ruleId": "Y", "contractId": "c",
"message": "", "resourceRef": "", "engine": "kyverno", "evaluatedAt": "t",
"evidence": {}},
]
eng = KyvernoJsonEngine()
out = eng.evaluate(pcrs, POLICY_DIR / "block-on-any-critical.json"
if (POLICY_DIR / "block-on-any-critical.json").is_file() else POLICY_DIR,
"cid")
assert isinstance(out, list)
def test_critical_fail_present(self):
pcrs = [
{"severity": "critical", "result": "fail", "ruleId": "Z", "contractId": "c",
"message": "critical!", "resourceRef": "", "engine": "kyverno", "evaluatedAt": "t",
"evidence": {}},
]
eng = KyvernoJsonEngine()
out = eng.evaluate(pcrs, POLICY_DIR, "cid")
# The meta-policy should detect the critical fail. When kj runs,
# it produces a result entry. We assert the engine returns a list
# (the meta-policy PCRs).
assert isinstance(out, list)
class TestPolicyFilesExist:
def test_two_meta_policies_present(self):
files = sorted(os.listdir(POLICY_DIR))
assert "block-on-any-critical.json" in files
assert "tagging-rules-agree.json" in files
def test_policies_are_valid_json(self):
for f in os.listdir(POLICY_DIR):
if f.endswith(".json"):
with open(POLICY_DIR / f, "r", encoding="utf-8") as fh:
data = json.load(fh)
assert data["apiVersion"] == "json.kyverno.io/v1alpha1"
assert data["kind"] == "ValidatingPolicy"
assert "nova.cloudinit.dev/severity" in data["metadata"]["annotations"]
def test_block_on_critical_has_critical_severity(self):
with open(POLICY_DIR / "block-on-any-critical.json", "r", encoding="utf-8") as fh:
data = json.load(fh)
assert data["metadata"]["annotations"]["nova.cloudinit.dev/severity"] == "critical"
+73
View File
@@ -0,0 +1,73 @@
"""Tests for plan-JSON kyverno-json policies (REQ-302, v1.25).
Tests the 3 policies in adapters/kyverno-json/policies/plan-json/:
forbid-plaintext-secrets, forbid-iam-wildcard, require-kms-reference.
Uses passing + failing fixtures. Skips when kj is absent.
"""
import json
import os
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import importlib.util
_ENGINE_PATH = Path(__file__).resolve().parent.parent / "adapters" / "kyverno-json" / "kyverno_json_engine.py"
_spec = importlib.util.spec_from_file_location("kyverno_json_engine", _ENGINE_PATH)
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
KyvernoJsonEngine = _mod.KyvernoJsonEngine
POLICY_DIR = Path(__file__).resolve().parent.parent / "adapters" / "kyverno-json" / "policies" / "plan-json"
FIXTURES = Path(__file__).resolve().parent / "fixtures" / "plan_json"
def _kj_installed() -> bool:
return _mod._which_kj() is not None
@pytest.fixture(autouse=True)
def _require_kj():
if not _kj_installed():
pytest.skip("kj not installed (scripts/install-kyverno-json.sh)")
def _load(name):
with open(FIXTURES / name, "r", encoding="utf-8") as fh:
return json.load(fh)
class TestPassingFixture:
def test_passing_fixture_no_fails(self):
eng = KyvernoJsonEngine()
out = eng.evaluate(_load("passing.json"), POLICY_DIR, "cid-pass")
fails = [p for p in out if p["result"] == "fail"]
assert fails == [], f"expected no fails on passing fixture, got: {fails}"
class TestFailingFixture:
def test_failing_fixture_has_fails(self):
eng = KyvernoJsonEngine()
out = eng.evaluate(_load("failing.json"), POLICY_DIR, "cid-fail")
fails = [p for p in out if p["result"] == "fail"]
assert len(fails) >= 1, "expected at least one fail on the failing fixture"
class TestPolicyFilesExist:
def test_three_policies_present(self):
files = sorted(os.listdir(POLICY_DIR))
assert "forbid-plaintext-secrets.json" in files
assert "forbid-iam-wildcard.json" in files
assert "require-kms-reference.json" in files
def test_policies_are_valid_json(self):
for f in os.listdir(POLICY_DIR):
if f.endswith(".json"):
with open(POLICY_DIR / f, "r", encoding="utf-8") as fh:
data = json.load(fh)
assert data["apiVersion"] == "json.kyverno.io/v1alpha1"
assert data["kind"] == "ValidatingPolicy"
assert "nova.cloudinit.dev/severity" in data["metadata"]["annotations"]
@@ -0,0 +1,59 @@
"""Tests for run_platform.sh Step 5b kyverno-json wiring (REQ-302, v1.25).
Asserts the script has the kyverno-json Step 5b block and the PCR-merge
logic. Pattern from tests/test_pipeline.py:79-95 (read script text +
assert substrings).
"""
import os
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "run_platform.sh"
def _read_script():
with open(SCRIPT, "r", encoding="utf-8") as fh:
return fh.read()
class TestStep5bKyvernoJsonWiring:
def test_step_5b_block_present(self):
s = _read_script()
assert "Step 5b: kyverno-json plan-JSON policies" in s, \
"run_platform.sh must have a Step 5b kyverno-json block (REQ-301)"
def test_kj_scan_invocation_present(self):
s = _read_script()
assert "adapters/kyverno-json/policies/plan-json" in s, \
"Step 5b must reference the plan-json policy dir"
def test_kj_not_installed_skip_present(self):
s = _read_script()
assert "kyverno-json not installed; skipping plan-JSON policies" in s, \
"Step 5b must skip gracefully when kj is absent (D-120)"
assert "D-120 graceful degradation" in s
def test_pcr_merge_logic_present(self):
s = _read_script()
assert "merged PCR list" in s, \
"Step 5b must merge the Checkov/Wiz + kj PCR lists"
def test_command_v_kj_guard_present(self):
s = _read_script()
assert "command -v kj" in s, \
"Step 5b must guard on `command -v kj` (is_configured)"
class TestExistingPipelineUnchanged:
def test_step_5_still_present(self):
s = _read_script()
assert "Step 5: runtime policy scan" in s
def test_step_7_confidence_still_present(self):
s = _read_script()
assert "Step 7: confidence signal compute" in s
+86
View File
@@ -0,0 +1,86 @@
"""Tests for stack-IR kyverno-json policies (REQ-299, v1.25).
Tests the 3 policies in adapters/kyverno-json/policies/stack-ir/:
require-tagging-standard, forbid-public-ingress, require-encryption-by-
default. Uses the passing + failing fixtures. Skips when kj is absent.
"""
import json
import os
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import importlib.util
_ENGINE_PATH = Path(__file__).resolve().parent.parent / "adapters" / "kyverno-json" / "kyverno_json_engine.py"
_spec = importlib.util.spec_from_file_location("kyverno_json_engine", _ENGINE_PATH)
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
KyvernoJsonEngine = _mod.KyvernoJsonEngine
POLICY_DIR = Path(__file__).resolve().parent.parent / "adapters" / "kyverno-json" / "policies" / "stack-ir"
FIXTURES = Path(__file__).resolve().parent / "fixtures" / "stack_ir"
def _kj_installed() -> bool:
return _mod._which_kj() is not None
@pytest.fixture(autouse=True)
def _require_kj():
if not _kj_installed():
pytest.skip("kj not installed (scripts/install-kyverno-json.sh)")
def _load(name):
with open(FIXTURES / name, "r", encoding="utf-8") as fh:
return json.load(fh)
class TestPassingFixture:
def test_passing_fixture_all_pass(self):
eng = KyvernoJsonEngine()
out = eng.evaluate(_load("passing.json"), POLICY_DIR, "cid-pass")
assert isinstance(out, list)
assert len(out) >= 1
# No fail results on the passing fixture.
fails = [p for p in out if p["result"] == "fail"]
assert fails == [], f"expected no fails on passing fixture, got: {fails}"
class TestFailingFixture:
def test_failing_fixture_has_fails(self):
eng = KyvernoJsonEngine()
out = eng.evaluate(_load("failing.json"), POLICY_DIR, "cid-fail")
fails = [p for p in out if p["result"] == "fail"]
assert len(fails) >= 1, "expected at least one fail on the failing fixture"
class TestPolicyFilesExist:
def test_three_policies_present(self):
files = sorted(os.listdir(POLICY_DIR))
assert "require-tagging-standard.json" in files
assert "forbid-public-ingress.json" in files
assert "require-encryption-by-default.json" in files
class TestPolicyValidity:
def test_policies_are_valid_json(self):
for f in os.listdir(POLICY_DIR):
if f.endswith(".json"):
with open(POLICY_DIR / f, "r", encoding="utf-8") as fh:
data = json.load(fh)
assert data["apiVersion"] == "json.kyverno.io/v1alpha1"
assert data["kind"] == "ValidatingPolicy"
assert "nova.cloudinit.dev/severity" in data["metadata"]["annotations"]
def test_policy_names_match_filenames(self):
for f in os.listdir(POLICY_DIR):
if f.endswith(".json"):
with open(POLICY_DIR / f, "r", encoding="utf-8") as fh:
data = json.load(fh)
expected = f.rsplit(".", 1)[0]
assert data["metadata"]["name"] == expected