# reflection/modes/validate-keeper-policy.ncl — TASK-C3 # # Validates keeper auto-sign policy files in all workspace policy repos. # Enforces ADR-038 constraint: policy-files-are-declarative-only. # # Required params (substituted via {param} in cmd fields): # {policy_repos_root} — path to root where policy- repos are cloned # (e.g., ~/.local/share/provisioning/radicle/clones) # {schemas_root} — path to provisioning/schemas/lib/ for keeper_policy.ncl { id = "validate-keeper-policy", trigger = "Validate all workspace keeper policy files: schema conformance + declarative-only enforcement", strategy = 'Override, preconditions = [ "{policy_repos_root} exists and contains at least one policy-/ directory", "nickel is available in PATH", "schemas/lib/keeper_policy.ncl is accessible at {schemas_root}/keeper_policy.ncl", "Each policy-/ directory contains policy.ncl", ], steps = [ { id = "discover_policy_repos", action = "list_policy_repos", actor = 'Agent, cmd = "find {policy_repos_root} -maxdepth 1 -type d -name 'policy-*' | sort", depends_on = [], on_error = { strategy = 'Stop }, note = "Enumerate all policy- repos present in the configured clones root. Hard stop — if no repos are found the subsequent steps have nothing to validate.", }, { id = "check_declarative_only", action = "grep_forbidden_patterns", actor = 'Agent, cmd = "find {policy_repos_root} -name 'policy.ncl' | xargs grep -lE 'fun |let [a-z_]+ = fun ' 2>/dev/null && echo 'VIOLATION: function definitions found in policy files' || echo 'OK: no function definitions'", depends_on = [ { step = "discover_policy_repos", kind = 'OnSuccess }, ], on_error = { strategy = 'Stop }, note = "ADR-038 constraint policy-files-are-declarative-only: policy.ncl files must contain only data conforming to PolicyDef schema. Any Nickel function definition (fun or let x = fun) is a hard violation.", }, { id = "validate_schema_conformance", action = "nickel_export_each_policy", actor = 'Agent, cmd = "find {policy_repos_root} -name 'policy.ncl' | while read f; do ws=$(basename $(dirname $f)); echo \"--- $ws\"; echo \"let kp = import \\\"{schemas_root}/keeper_policy.ncl\\\" in (import \\\"$f\\\") | kp.PolicyDef\" | nickel export /dev/stdin 2>&1 && echo \"$ws: OK\" || echo \"$ws: SCHEMA FAIL\"; done", depends_on = [ { step = "check_declarative_only", kind = 'OnSuccess }, ], on_error = { strategy = 'Continue }, note = "Apply keeper_policy.ncl PolicyDef contract to each policy.ncl. A policy that fails to export against the schema is invalid and will be rejected by keeper-daemon at startup.", }, { id = "check_version_field", action = "verify_schema_version", actor = 'Agent, cmd = "find {policy_repos_root} -name 'policy.ncl' | while read f; do ws=$(basename $(dirname $f)); v=$(nickel export \"$f\" 2>/dev/null | jq -r '.version // \"MISSING\"'); echo \"$ws version: $v\"; [ \"$v\" = \"1\" ] || echo \"$ws: WARNING — expected version=1, got $v\"; done", depends_on = [ { step = "validate_schema_conformance", kind = 'Always }, ], on_error = { strategy = 'Continue }, note = "Verify each policy file declares schema_version = 1. Version mismatch means keeper-daemon will reject the policy file at startup (fail-fast per ADR-038).", }, { id = "summary_report", action = "emit_validation_report", actor = 'Agent, cmd = "echo '=== keeper policy validation complete ===' && echo 'All violations above must be resolved before keeper-daemon will start'", depends_on = [ { step = "check_version_field", kind = 'Always }, ], on_error = { strategy = 'Stop }, note = "Final report: any VIOLATION or SCHEMA FAIL lines above indicate files that keeper-daemon will refuse to load.", }, ], postconditions = [ "No policy.ncl files contain Nickel function definitions", "All policy.ncl files export valid JSON conforming to PolicyDef schema", "All policy.ncl files declare version = 1", "Validation report produced listing pass/fail per workspace", ], }