let s = import "reflection/schema.ncl" in # Mode: provisioning-dag-integrity # Audits the full extension capability DAG for structural integrity: # 1. All taskservs have provides/requires/conflicts_with fields populated # 2. Every Required capability declared in `requires` is provided by at least one other taskserv # 3. ConflictsWith pairs: if both sides exist in the registry, emit an error # 4. No circular capability dependencies (A provides X, B requires X and provides Y, A requires Y) # 5. Summary: capability coverage table + unresolved gaps + conflict pairs # # Run from provisioning root with: # nickel export --format json --import-path reflection/modes/provisioning-dag-integrity.ncl { id = "provisioning-dag-integrity", trigger = "Audit extension capability DAG for completeness, resolution coverage, and conflict safety", strategy = 'Override, preconditions = [ "nickel is in PATH", "jq is in PATH", "catalog/taskservs/ directory exists with at least one metadata.ncl", "NICKEL_IMPORT_PATH includes the provisioning/ root", ], guards = [ { id = "taskservs_dir_exists", cmd = "test -d catalog/taskservs", reason = "catalog/taskservs/ directory not found — run from provisioning root", severity = 'Block, }, ], steps = [ { id = "export_all_metadata", action = "export_taskserv_metadata", actor = 'Agent, cmd = "for ts in catalog/taskservs/*/; do name=$(basename $ts); meta=$ts/metadata.ncl; if [ -f \"$meta\" ]; then echo \"{\\\"name\\\": \\\"$name\\\", \\\"meta\\\": $(nickel export --format json --import-path . $meta 2>/dev/null || echo 'null')}\"; fi; done | jq -s 'map(select(.meta != null))'", depends_on = [], on_error = { strategy = 'Stop }, note = "Export all taskserv metadata.ncl files as JSON. Filters out taskservs with missing or broken metadata.", }, { id = "check_fields_populated", action = "verify_capability_fields_present", actor = 'Agent, cmd = "for ts in catalog/taskservs/*/; do name=$(basename $ts); meta=$ts/metadata.ncl; if [ -f \"$meta\" ]; then result=$(nickel export --format json --import-path . $meta 2>/dev/null); if [ $? -ne 0 ]; then echo \"EXPORT_FAIL: $name\"; continue; fi; has_provides=$(echo $result | jq 'has(\"provides\")'); has_requires=$(echo $result | jq 'has(\"requires\")'); has_conflicts=$(echo $result | jq 'has(\"conflicts_with\")'); if [ \"$has_provides\" != 'true' ] || [ \"$has_requires\" != 'true' ] || [ \"$has_conflicts\" != 'true' ]; then echo \"MISSING_FIELDS: $name (provides=$has_provides requires=$has_requires conflicts_with=$has_conflicts)\"; else echo \"OK: $name\"; fi; fi; done", depends_on = [], on_error = { strategy = 'Continue }, note = "Check that every taskserv metadata.ncl has provides, requires, and conflicts_with fields. MISSING_FIELDS lines indicate incomplete migrations.", }, { id = "build_provides_index", action = "index_all_capabilities", actor = 'Agent, cmd = "for ts in catalog/taskservs/*/; do meta=$ts/metadata.ncl; if [ -f \"$meta\" ]; then nickel export --format json --import-path . $meta 2>/dev/null | jq -r --arg ts \"$(basename $ts)\" '.provides[]? | [$ts, .id] | @tsv'; fi; done | sort", depends_on = [{ step = "check_fields_populated", kind = 'Always }], on_error = { strategy = 'Stop }, note = "Build the capability provides index: one line per (taskserv, capability_id) pair. Used in resolution step.", }, { id = "resolve_required_capabilities", action = "check_required_dep_coverage", actor = 'Agent, cmd = "provides=$(for ts in catalog/taskservs/*/; do meta=$ts/metadata.ncl; if [ -f \"$meta\" ]; then nickel export --format json --import-path . $meta 2>/dev/null | jq -r '.provides[]?.id'; fi; done | sort -u); for ts in catalog/taskservs/*/; do name=$(basename $ts); meta=$ts/metadata.ncl; if [ -f \"$meta\" ]; then nickel export --format json --import-path . $meta 2>/dev/null | jq -r --arg ts \"$name\" '.requires[]? | select(.kind == \"Required\") | .capability' | while read cap; do if echo \"$provides\" | grep -qx \"$cap\"; then echo \"RESOLVED: $ts requires $cap\"; else echo \"UNRESOLVED: $ts requires $cap (Required — no provider found)\"; fi; done; fi; done", depends_on = [{ step = "build_provides_index", kind = 'OnSuccess }], on_error = { strategy = 'Continue }, note = "For every Required capability in requires[], check that at least one other taskserv provides it. UNRESOLVED lines are hard errors — the formula will fail at runtime.", }, { id = "detect_conflict_pairs", action = "check_conflictswith_pairs", actor = 'Agent, cmd = "all_names=$(for ts in catalog/taskservs/*/; do basename $ts; done); for ts in catalog/taskservs/*/; do name=$(basename $ts); meta=$ts/metadata.ncl; if [ -f \"$meta\" ]; then nickel export --format json --import-path . $meta 2>/dev/null | jq -r --arg ts \"$name\" '.conflicts_with[]?' | while read conflicting; do if echo \"$all_names\" | grep -qx \"$conflicting\"; then echo \"CONFLICT_PAIR: $name conflicts_with $conflicting (both present in registry)\"; fi; done; fi; done | sort -u", depends_on = [{ step = "build_provides_index", kind = 'OnSuccess }], on_error = { strategy = 'Continue }, note = "Detect declared ConflictsWith pairs where both extensions exist in the registry. These pairs must not appear together in the same formula. Emit as audit info — enforcement happens in provisioning-validate-formula.", }, { id = "integrity_report", action = "emit_integrity_summary", actor = 'Agent, cmd = "echo '--- Extension Capability DAG Integrity Report ---' && echo '' && echo '== Capability Provides Index ==' && for ts in catalog/taskservs/*/; do meta=$ts/metadata.ncl; if [ -f \"$meta\" ]; then caps=$(nickel export --format json --import-path . $meta 2>/dev/null | jq -r '[.provides[]?.id] | join(\", \")' 2>/dev/null); reqs=$(nickel export --format json --import-path . $meta 2>/dev/null | jq -r '[.requires[]? | \"\\(.capability)[\\(.kind)]\"] | join(\", \")' 2>/dev/null); echo \" $(basename $ts): provides=[$caps] requires=[$reqs]\"; fi; done && echo '' && echo 'Run with --verbose to show RESOLVED/UNRESOLVED resolution details.'", depends_on = [ { step = "resolve_required_capabilities", kind = 'Always }, { step = "detect_conflict_pairs", kind = 'Always }, ], on_error = { strategy = 'Stop }, note = "Print the full capability coverage table showing what each taskserv provides and requires, then a resolution summary.", }, ], postconditions = [ "All taskservs have provides/requires/conflicts_with fields (or MISSING_FIELDS flagged)", "All Required capabilities resolve to at least one provider (or UNRESOLVED flagged)", "ConflictsWith pairs identified (enforcement delegated to provisioning-validate-formula per-formula)", "Capability coverage table emitted", ], } | (s.Mode String)