{ "guards": [ { "cmd": "test -d catalog/taskservs", "id": "taskservs_dir_exists", "reason": "catalog/taskservs/ directory not found — run from provisioning root", "severity": "Block" } ], "id": "provisioning-dag-integrity", "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" ], "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" ], "steps": [ { "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": [], "id": "export_all_metadata", "note": "Export all taskserv metadata.ncl files as JSON. Filters out taskservs with missing or broken metadata.", "on_error": { "backoff_s": 5, "max": 3, "strategy": "Stop" } }, { "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": [], "id": "check_fields_populated", "note": "Check that every taskserv metadata.ncl has provides, requires, and conflicts_with fields. MISSING_FIELDS lines indicate incomplete migrations.", "on_error": { "backoff_s": 5, "max": 3, "strategy": "Continue" } }, { "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": [ { "kind": "Always", "step": "check_fields_populated" } ], "id": "build_provides_index", "note": "Build the capability provides index: one line per (taskserv, capability_id) pair. Used in resolution step.", "on_error": { "backoff_s": 5, "max": 3, "strategy": "Stop" } }, { "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": [ { "kind": "OnSuccess", "step": "build_provides_index" } ], "id": "resolve_required_capabilities", "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.", "on_error": { "backoff_s": 5, "max": 3, "strategy": "Continue" } }, { "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": [ { "kind": "OnSuccess", "step": "build_provides_index" } ], "id": "detect_conflict_pairs", "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.", "on_error": { "backoff_s": 5, "max": 3, "strategy": "Continue" } }, { "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": [ { "kind": "Always", "step": "resolve_required_capabilities" }, { "kind": "Always", "step": "detect_conflict_pairs" } ], "id": "integrity_report", "note": "Print the full capability coverage table showing what each taskserv provides and requires, then a resolution summary.", "on_error": { "backoff_s": 5, "max": 3, "strategy": "Stop" } } ], "strategy": "Override", "trigger": "Audit extension capability DAG for completeness, resolution coverage, and conflict safety" }