diff --git a/Makefile b/Makefile index 8856a654..cead0039 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: validate test validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter +.PHONY: validate test validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter validate-epigov-artifacts -validate: validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter +validate: validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter validate-epigov-artifacts python3 tools/validate_execution_timing.py validate-governance-context: @@ -248,6 +248,9 @@ validate-prophet-mesh-agentplane-adapter: python3 -m json.tool contracts/prophet-mesh/prophet-mesh-agentplane-adapter.v0.1.json >/dev/null python3 tools/validate_prophet_mesh_agentplane_adapter.py +validate-epigov-artifacts: + python3 tools/validate_epigov_artifacts.py + validate-agent-cycle-health: python3 tools/validate_agent_cycle_health.py diff --git a/bundles/epigov-countertest-smoke/bundle.json b/bundles/epigov-countertest-smoke/bundle.json new file mode 100644 index 00000000..c36de58d --- /dev/null +++ b/bundles/epigov-countertest-smoke/bundle.json @@ -0,0 +1,40 @@ +{ + "apiVersion": "agentplane.socioprophet.org/v0.1", + "kind": "Bundle", + "metadata": { + "name": "epigov-countertest-smoke", + "version": "0.1.0", + "createdAt": "2026-06-11T00:00:00Z", + "licensePolicy": { + "allowAGPL": false, + "notes": "Epistemic governance counter-test bundle. Evidence-forward." + }, + "source": { "git": { "dirty": false, "rev": "UNSET" } } + }, + "spec": { + "upstreamAnchors": [ + "SocioProphet/sociosphere#322:epistemic-governance-v0.2.1", + "protocol/epistemic-governance/v1" + ], + "nonClaims": [ + "This bundle does not grant authority to accept or dismiss hygiene findings.", + "This bundle does not implement autonomous adversarial execution.", + "This bundle does not self-modify at runtime." + ], + "artifacts": { "outDir": "./artifacts/epigov-countertest-smoke" }, + "policy": { + "lane": "smoke", + "humanGateRequired": false, + "failOnTimeout": true, + "maxRunSeconds": 60, + "policyPackRef": "policy://agentplane/epigov-smoke-v1", + "policyPackHash": "UNSET" + }, + "determinism": { + "required": true, + "note": "Same input + same ruleset hash must produce same counter-test output" + }, + "smoke": { "script": "bundles/epigov-countertest-smoke/smoke.sh" }, + "secrets": { "required": [], "secretRefRoot": "secrets://tenant" } + } +} diff --git a/bundles/epigov-hygiene-detector-smoke/bundle.json b/bundles/epigov-hygiene-detector-smoke/bundle.json new file mode 100644 index 00000000..03963478 --- /dev/null +++ b/bundles/epigov-hygiene-detector-smoke/bundle.json @@ -0,0 +1,41 @@ +{ + "apiVersion": "agentplane.socioprophet.org/v0.1", + "kind": "Bundle", + "metadata": { + "name": "epigov-hygiene-detector-smoke", + "version": "0.1.0", + "createdAt": "2026-06-11T00:00:00Z", + "licensePolicy": { + "allowAGPL": false, + "notes": "Epistemic governance bundle is evidence-forward. No autonomous adversarial execution." + }, + "source": { "git": { "dirty": false, "rev": "UNSET" } } + }, + "spec": { + "upstreamAnchors": [ + "SocioProphet/sociosphere#322:epistemic-governance-v0.2.1", + "protocol/epistemic-governance/v1" + ], + "nonClaims": [ + "This bundle does not grant authority to automatically remediate detected hygiene issues.", + "This bundle does not implement autonomous adversarial execution.", + "This bundle does not self-modify at runtime.", + "This bundle does not produce external side effects beyond bounded test artifacts." + ], + "artifacts": { "outDir": "./artifacts/epigov-hygiene-detector-smoke" }, + "policy": { + "lane": "smoke", + "humanGateRequired": false, + "failOnTimeout": true, + "maxRunSeconds": 60, + "policyPackRef": "policy://agentplane/epigov-smoke-v1", + "policyPackHash": "UNSET" + }, + "determinism": { + "required": true, + "note": "Same input + same ruleset hash must produce same detector output" + }, + "smoke": { "script": "bundles/epigov-hygiene-detector-smoke/smoke.sh" }, + "secrets": { "required": [], "secretRefRoot": "secrets://tenant" } + } +} diff --git a/bundles/epigov-policy-backtest/bundle.json b/bundles/epigov-policy-backtest/bundle.json new file mode 100644 index 00000000..0555d20f --- /dev/null +++ b/bundles/epigov-policy-backtest/bundle.json @@ -0,0 +1,37 @@ +{ + "apiVersion": "agentplane.socioprophet.org/v0.1", + "kind": "Bundle", + "metadata": { + "name": "epigov-policy-backtest", + "version": "0.1.0", + "createdAt": "2026-06-11T00:00:00Z", + "licensePolicy": { + "allowAGPL": false, + "notes": "Epistemic governance policy backtest bundle. Evidence-forward. No live policy writes." + }, + "source": { "git": { "dirty": false, "rev": "UNSET" } } + }, + "spec": { + "upstreamAnchors": [ + "SocioProphet/sociosphere#322:epistemic-governance-v0.2.1", + "protocol/epistemic-governance/v1" + ], + "nonClaims": [ + "This bundle does not write or modify live policies.", + "This bundle does not implement autonomous adversarial execution.", + "This bundle does not self-modify at runtime.", + "Backtest results are observational evidence only; they do not constitute policy approval." + ], + "artifacts": { "outDir": "./artifacts/epigov-policy-backtest" }, + "policy": { + "lane": "backtest", + "humanGateRequired": true, + "failOnTimeout": true, + "maxRunSeconds": 300, + "policyPackRef": "policy://agentplane/epigov-backtest-v1", + "policyPackHash": "UNSET" + }, + "smoke": { "script": "bundles/epigov-policy-backtest/smoke.sh" }, + "secrets": { "required": [], "secretRefRoot": "secrets://tenant" } + } +} diff --git a/examples/receipts/epigov-strawman-smoke/countertest-run-artifact.example.json b/examples/receipts/epigov-strawman-smoke/countertest-run-artifact.example.json new file mode 100644 index 00000000..7603c1ca --- /dev/null +++ b/examples/receipts/epigov-strawman-smoke/countertest-run-artifact.example.json @@ -0,0 +1,26 @@ +{ + "kind": "CountertestRunArtifact", + "artifact_id": "epigov_countertest_run_strawman_001", + "run_ref": "agentplane://run/epigov-countertest-smoke/run_strawman_001", + "countertest_id": "CTEST.STEELMAN.CONFIRM.V2", + "hygiene_run_ref": "agentplane://run/epigov-hygiene-detector-smoke/run_strawman_001", + "input_hash": "sha256:b4e2f1c3d5a6b7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2", + "ruleset_hash": "sha256:c5f3e1d2b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1", + "findings_addressed": 1, + "countertest_outcomes": [ + { + "outcome_id": "ctest_outcome_001", + "finding_ref": "finding_strawman_001", + "verdict": "confirmed_finding", + "steelman_claim": "The strongest interpretation of the position is that it relies on the weakest formulation of the opposing argument; even under charitable reading this remains a strawman.", + "counter_evidence_refs": [ + "evidence://epigov/primary-source/opposing-position-v2" + ] + } + ], + "validation_artifact_ref": "agentplane://validation/epigov-countertest-smoke/val_strawman_001", + "replay_artifact_ref": "agentplane://replay/epigov-countertest-smoke/replay_strawman_001", + "sociosphere_protocol_ref": "protocol/epistemic-governance/v1", + "replay_verified": true, + "issued_at": "2026-06-11T10:10:00Z" +} diff --git a/examples/receipts/epigov-strawman-smoke/hygiene-run-artifact.example.json b/examples/receipts/epigov-strawman-smoke/hygiene-run-artifact.example.json new file mode 100644 index 00000000..ada9a23b --- /dev/null +++ b/examples/receipts/epigov-strawman-smoke/hygiene-run-artifact.example.json @@ -0,0 +1,30 @@ +{ + "kind": "HygieneRunArtifact", + "artifact_id": "epigov_hygiene_run_strawman_001", + "run_ref": "agentplane://run/epigov-hygiene-detector-smoke/run_strawman_001", + "detector_id": "LOGFALL.STRAWMAN.V2", + "ruleset_hash": "sha256:a3f1e2b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2", + "input_hash": "sha256:b4e2f1c3d5a6b7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2", + "claims_evaluated": 5, + "hygiene_findings": [ + { + "finding_id": "finding_strawman_001", + "claim_ref": "claim://epigov/strawman-test/claim_001", + "finding_type": "logfall", + "severity": "warning", + "description": "Claim presents a weakened version of the opposing position without citation" + } + ], + "repair_suggestions": [ + { + "suggestion_id": "repair_001", + "finding_ref": "finding_strawman_001", + "action": "Add citation to primary source and represent opposing position using its strongest formulation" + } + ], + "validation_artifact_ref": "agentplane://validation/epigov-hygiene-detector-smoke/val_strawman_001", + "replay_artifact_ref": "agentplane://replay/epigov-hygiene-detector-smoke/replay_strawman_001", + "sociosphere_protocol_ref": "protocol/epistemic-governance/v1", + "replay_verified": true, + "issued_at": "2026-06-11T10:00:00Z" +} diff --git a/schemas/countertest-run-artifact.schema.json b/schemas/countertest-run-artifact.schema.json new file mode 100644 index 00000000..d3a7f34f --- /dev/null +++ b/schemas/countertest-run-artifact.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.io/schemas/agentplane/countertest-run-artifact/v0.1", + "title": "CountertestRunArtifact", + "description": "AgentPlane run artifact for epistemic counter-test runs (CTEST.STEELMAN.CONFIRM.V2 and variants). Confirms or refutes hygiene findings by constructing the strongest plausible counter-claim.", + "type": "object", + "required": [ + "kind", + "artifact_id", + "run_ref", + "countertest_id", + "hygiene_run_ref", + "input_hash", + "ruleset_hash", + "findings_addressed", + "countertest_outcomes", + "validation_artifact_ref", + "replay_artifact_ref", + "sociosphere_protocol_ref", + "issued_at" + ], + "additionalProperties": false, + "properties": { + "kind": { "type": "string", "const": "CountertestRunArtifact" }, + "artifact_id": { "type": "string", "minLength": 1 }, + "run_ref": { "type": "string", "minLength": 1 }, + "countertest_id": { + "type": "string", + "description": "Counter-test identifier, e.g. CTEST.STEELMAN.CONFIRM.V2" + }, + "hygiene_run_ref": { + "type": "string", + "description": "Reference to the HygieneRunArtifact this counter-test addresses" + }, + "input_hash": { + "type": "string", + "pattern": "^sha256:[A-Fa-f0-9]{64}$" + }, + "ruleset_hash": { + "type": "string", + "pattern": "^sha256:[A-Fa-f0-9]{64}$" + }, + "findings_addressed": { "type": "integer", "minimum": 0 }, + "countertest_outcomes": { + "type": "array", + "items": { "$ref": "#/$defs/CountertestOutcome" } + }, + "validation_artifact_ref": { "type": "string", "minLength": 1 }, + "replay_artifact_ref": { "type": "string", "minLength": 1 }, + "sociosphere_protocol_ref": { + "type": "string", + "const": "protocol/epistemic-governance/v1" + }, + "replay_verified": { "type": "boolean" }, + "issued_at": { "type": "string", "format": "date-time" } + }, + "$defs": { + "CountertestOutcome": { + "type": "object", + "required": ["outcome_id", "finding_ref", "verdict", "steelman_claim"], + "additionalProperties": false, + "properties": { + "outcome_id": { "type": "string", "minLength": 1 }, + "finding_ref": { "type": "string", "minLength": 1 }, + "verdict": { + "type": "string", + "enum": ["confirmed_finding", "refuted_finding", "inconclusive", "escalate_review"] + }, + "steelman_claim": { + "type": "string", + "description": "Strongest plausible counter-claim against the hygiene finding" + }, + "counter_evidence_refs": { + "type": "array", + "items": { "type": "string" } + } + } + } + } +} diff --git a/schemas/hygiene-run-artifact.schema.json b/schemas/hygiene-run-artifact.schema.json new file mode 100644 index 00000000..0340f16a --- /dev/null +++ b/schemas/hygiene-run-artifact.schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.io/schemas/agentplane/hygiene-run-artifact/v0.1", + "title": "HygieneRunArtifact", + "description": "AgentPlane run artifact for epistemic hygiene detector runs (LOGFALL.STRAWMAN.V2 and variants). Deterministic: same input + same ruleset hash must produce same output.", + "type": "object", + "required": [ + "kind", + "artifact_id", + "run_ref", + "detector_id", + "ruleset_hash", + "input_hash", + "claims_evaluated", + "hygiene_findings", + "validation_artifact_ref", + "replay_artifact_ref", + "sociosphere_protocol_ref", + "issued_at" + ], + "additionalProperties": false, + "properties": { + "kind": { "type": "string", "const": "HygieneRunArtifact" }, + "artifact_id": { "type": "string", "minLength": 1 }, + "run_ref": { "type": "string", "minLength": 1 }, + "detector_id": { + "type": "string", + "description": "Detector identifier, e.g. LOGFALL.STRAWMAN.V2" + }, + "ruleset_hash": { + "type": "string", + "pattern": "^sha256:[A-Fa-f0-9]{64}$" + }, + "input_hash": { + "type": "string", + "pattern": "^sha256:[A-Fa-f0-9]{64}$" + }, + "claims_evaluated": { "type": "integer", "minimum": 0 }, + "hygiene_findings": { + "type": "array", + "items": { "$ref": "#/$defs/HygieneFinding" } + }, + "repair_suggestions": { + "type": "array", + "items": { "$ref": "#/$defs/RepairSuggestion" } + }, + "validation_artifact_ref": { "type": "string", "minLength": 1 }, + "replay_artifact_ref": { "type": "string", "minLength": 1 }, + "sociosphere_protocol_ref": { + "type": "string", + "const": "protocol/epistemic-governance/v1" + }, + "replay_verified": { "type": "boolean" }, + "issued_at": { "type": "string", "format": "date-time" } + }, + "$defs": { + "HygieneFinding": { + "type": "object", + "required": ["finding_id", "claim_ref", "finding_type", "severity"], + "additionalProperties": false, + "properties": { + "finding_id": { "type": "string", "minLength": 1 }, + "claim_ref": { "type": "string", "minLength": 1 }, + "finding_type": { + "type": "string", + "enum": [ + "logfall", + "strawman", + "attribution_without_evidence", + "load_bearing_metaphor", + "missing_non_claim", + "circular_evidence" + ] + }, + "severity": { + "type": "string", + "enum": ["info", "warning", "error", "critical"] + }, + "description": { "type": "string" } + } + }, + "RepairSuggestion": { + "type": "object", + "required": ["suggestion_id", "finding_ref", "action"], + "additionalProperties": false, + "properties": { + "suggestion_id": { "type": "string", "minLength": 1 }, + "finding_ref": { "type": "string", "minLength": 1 }, + "action": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/tools/validate_epigov_artifacts.py b/tools/validate_epigov_artifacts.py new file mode 100644 index 00000000..5be79c7f --- /dev/null +++ b/tools/validate_epigov_artifacts.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Validate Epistemic Governance bundle artifacts. + +Checks HygieneRunArtifact and CountertestRunArtifact schema conformance. +Policy gates: +- sociosphere_protocol_ref must be protocol/epistemic-governance/v1 +- ruleset_hash and input_hash must match sha256:<64-hex> +- Tampered input (replay_verified=false) must fail +- CountertestRunArtifact must reference a hygiene_run_ref +""" +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +try: + import jsonschema +except ImportError: + print("ERROR: jsonschema not installed", file=sys.stderr) + sys.exit(1) + +ROOT = Path(__file__).resolve().parent.parent +HYGIENE_SCHEMA = json.loads( + (ROOT / "schemas" / "hygiene-run-artifact.schema.json").read_text() +) +COUNTERTEST_SCHEMA = json.loads( + (ROOT / "schemas" / "countertest-run-artifact.schema.json").read_text() +) + +HASH_RE = re.compile(r"^sha256:[A-Fa-f0-9]{64}$") +EPIGOV_PROTOCOL = "protocol/epistemic-governance/v1" + +EXAMPLE_PATHS = [ + ROOT / "examples" / "receipts" / "epigov-strawman-smoke" / "hygiene-run-artifact.example.json", + ROOT / "examples" / "receipts" / "epigov-strawman-smoke" / "countertest-run-artifact.example.json", +] + +errors: list[str] = [] +results: list[bool] = [] + + +def ok(label: str) -> None: + print(f"PASS {label}") + results.append(True) + + +def fail(label: str, reason: str) -> None: + errors.append(f"FAIL {label}: {reason}") + results.append(False) + + +def detect_schema(data: dict) -> tuple[str, dict] | None: + kind = data.get("kind") + if kind == "HygieneRunArtifact": + return kind, HYGIENE_SCHEMA + if kind == "CountertestRunArtifact": + return kind, COUNTERTEST_SCHEMA + return None + + +def policy_gate_errors(data: dict, kind: str) -> list[str]: + errs = [] + proto = data.get("sociosphere_protocol_ref", "") + if proto != EPIGOV_PROTOCOL: + errs.append(f"sociosphere_protocol_ref must be {EPIGOV_PROTOCOL!r}, got {proto!r}") + for field in ("ruleset_hash", "input_hash"): + v = data.get(field, "") + if v and not HASH_RE.match(v): + errs.append(f"{field} must match sha256:<64-hex>") + if data.get("replay_verified") is False: + errs.append("replay_verified=false — tampered input detected; run artifact is invalid") + if kind == "CountertestRunArtifact" and not data.get("hygiene_run_ref"): + errs.append("CountertestRunArtifact requires hygiene_run_ref") + return errs + + +for path in EXAMPLE_PATHS: + label = path.name + try: + data = json.loads(path.read_text()) + except (json.JSONDecodeError, FileNotFoundError) as e: + fail(f"load {label}", str(e)) + continue + + ok(f"json-load {label}") + + detected = detect_schema(data) + if detected is None: + fail(f"kind {label}", "unknown artifact kind") + continue + + kind, schema = detected + v = jsonschema.Draft202012Validator(schema) + schema_errs = list(v.iter_errors(data)) + policy_errs = policy_gate_errors(data, kind) + + for e in schema_errs: + fail(f"schema {label}", e.message) + for e in policy_errs: + fail(f"policy-gate {label}", e) + if not schema_errs and not policy_errs: + ok(f"valid {label} ({kind})") + +# Also validate bundle.json files parse as valid JSON +for bundle_dir in ["epigov-hygiene-detector-smoke", "epigov-countertest-smoke", "epigov-policy-backtest"]: + bundle_path = ROOT / "bundles" / bundle_dir / "bundle.json" + try: + data = json.loads(bundle_path.read_text()) + if data.get("kind") != "Bundle": + fail(f"bundle-kind {bundle_dir}", "kind must be Bundle") + else: + ok(f"bundle-json {bundle_dir}") + except (json.JSONDecodeError, FileNotFoundError) as e: + fail(f"bundle-load {bundle_dir}", str(e)) + +passed = sum(results) +if errors: + print(file=sys.stderr) + for e in errors: + print(e, file=sys.stderr) + print(f"\n{passed} passed, {len(errors)} failed", file=sys.stderr) + sys.exit(1) + +print(f"\n{passed} epigov-artifacts checks passed")