# 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", ], }