provisioning/reflection/modes/validate-playbooks.ncl

131 lines
6.6 KiB
Text
Raw Permalink Normal View History

2026-05-12 02:40:14 +01:00
# reflection/modes/validate-playbooks.ncl — TASK-C6
#
# Validates all playbooks under catalog/playbooks/ against the PlaybookDef schema
# and checks that required Nushell scripts exist and pass ide-check.
#
# Required params (substituted via {param} in cmd fields):
# {provisioning_root} — absolute path to provisioning/ directory
# {schemas_root} — path to provisioning/schemas/lib/ for playbook.ncl
{
id = "validate-playbooks",
trigger = "Validate all playbooks: schema conformance, script existence, and Nushell ide-check",
strategy = 'Override,
preconditions = [
"{provisioning_root}/catalog/playbooks/ exists",
"nickel is available in PATH",
"nu is available in PATH",
"jq is available in PATH",
"{schemas_root}/playbook.ncl is accessible",
],
steps = [
{
id = "discover_playbooks",
action = "list_playbook_dirs",
actor = 'Agent,
cmd = "find {provisioning_root}/catalog/playbooks -maxdepth 1 -type d ! -name playbooks | sort",
depends_on = [],
on_error = { strategy = 'Stop },
note = "Enumerate all playbook directories. Each must contain playbook.ncl at minimum.",
},
{
id = "check_playbook_ncl_exists",
action = "verify_manifest_present",
actor = 'Agent,
cmd = "find {provisioning_root}/catalog/playbooks -maxdepth 2 -name 'playbook.ncl' | while read f; do echo \"OK: $f\"; done; find {provisioning_root}/catalog/playbooks -maxdepth 1 -mindepth 1 -type d | while read d; do test -f \"$d/playbook.ncl\" || echo \"MISSING playbook.ncl in $d\"; done",
depends_on = [
{ step = "discover_playbooks", kind = 'OnSuccess },
],
on_error = { strategy = 'Continue },
note = "Every playbook directory must contain playbook.ncl. Missing manifests are reported but do not stop subsequent checks on other playbooks.",
},
{
id = "validate_schema_conformance",
action = "nickel_export_each_playbook",
actor = 'Agent,
cmd = "find {provisioning_root}/catalog/playbooks -maxdepth 2 -name 'playbook.ncl' | while read f; do name=$(basename $(dirname $f)); echo \"--- $name\"; echo \"let pb = import \\\"{schemas_root}/playbook.ncl\\\" in (import \\\"$f\\\") | pb.PlaybookDef\" | nickel export /dev/stdin 2>&1 | tail -1 && echo \"$name: schema OK\" || echo \"$name: SCHEMA FAIL\"; done",
depends_on = [
{ step = "check_playbook_ncl_exists", kind = 'Always },
],
on_error = { strategy = 'Continue },
note = "Apply PlaybookDef contract to each playbook.ncl. Schema failure means the playbook runner will refuse to execute this playbook.",
},
{
id = "check_step_scripts_exist",
action = "verify_step_scripts",
actor = 'Agent,
cmd = "find {provisioning_root}/catalog/playbooks -maxdepth 2 -name 'playbook.ncl' | while read f; do dir=$(dirname $f); name=$(basename $dir); nickel export \"$f\" 2>/dev/null | jq -r '.steps[].script' | while read script; do test -f \"$dir/$script\" && echo \"OK: $name/$script\" || echo \"MISSING: $name/$script\"; done; done",
depends_on = [
{ step = "validate_schema_conformance", kind = 'Always },
],
on_error = { strategy = 'Continue },
note = "Every step script declared in playbook.ncl must exist as a file relative to the playbook directory.",
},
{
id = "check_rollback_script",
action = "verify_rollback_nu_when_automatic",
actor = 'Agent,
cmd = "find {provisioning_root}/catalog/playbooks -maxdepth 2 -name 'playbook.ncl' | while read f; do dir=$(dirname $f); name=$(basename $dir); strategy=$(nickel export \"$f\" 2>/dev/null | jq -r '.rollback_strategy // \"none\"'); if [ \"$strategy\" = \"automatic\" ]; then test -f \"$dir/rollback.nu\" && echo \"OK: $name/rollback.nu\" || echo \"MISSING rollback.nu for automatic rollback: $name\"; fi; done",
depends_on = [
{ step = "validate_schema_conformance", kind = 'Always },
],
on_error = { strategy = 'Continue },
note = "When rollback_strategy = 'automatic, rollback.nu MUST exist. validate-playbooks reflection mode enforces this before any execution.",
},
{
id = "nushell_ide_check_scripts",
action = "nu_ide_check_all_playbook_scripts",
actor = 'Agent,
cmd = "find {provisioning_root}/catalog/playbooks -name '*.nu' | while read f; do result=$(nu --ide-check 50 \"$f\" 2>&1); [ -z \"$result\" ] && echo \"OK: $f\" || echo \"FAIL: $f\\n$result\"; done",
depends_on = [
{ step = "check_step_scripts_exist", kind = 'Always },
{ step = "check_rollback_script", kind = 'Always },
],
on_error = { strategy = 'Continue },
note = "All .nu files in playbook directories must pass nu --ide-check 50 with zero diagnostics. This catches Nushell syntax errors and undefined variable references before runtime.",
},
{
id = "run_dry_run_tests",
action = "execute_dry_run_tests",
actor = 'Agent,
cmd = "find {provisioning_root}/catalog/playbooks -path '*/tests/dry_run.nu' | while read f; do name=$(basename $(dirname $(dirname $f))); echo \"--- dry_run: $name\"; nu \"$f\" 2>&1 && echo \"$name dry_run: PASS\" || echo \"$name dry_run: FAIL\"; done",
depends_on = [
{ step = "nushell_ide_check_scripts", kind = 'Always },
],
on_error = { strategy = 'Continue },
note = "When tests/dry_run.nu is present, execute it as a smoke test. Failures are reported but do not fail the overall validation — the dry_run.nu is advisory, not mandatory.",
},
{
id = "summary_report",
action = "emit_playbook_validation_report",
actor = 'Agent,
cmd = "echo '=== playbook validation complete ===' && echo 'All MISSING and FAIL entries above must be resolved before playbooks can be executed'",
depends_on = [
{ step = "run_dry_run_tests", kind = 'Always },
],
on_error = { strategy = 'Stop },
note = "Final summary. MISSING and FAIL lines are blocking. dry_run FAIL lines are advisory.",
},
],
postconditions = [
"All playbook directories contain playbook.ncl",
"All playbook.ncl files conform to PlaybookDef schema",
"All step scripts exist relative to their playbook directory",
"All playbooks with rollback_strategy = automatic have rollback.nu",
"All .nu files pass nu --ide-check 50 with zero diagnostics",
"Dry-run tests (where present) executed and results reported",
],
}