diff --git a/contracts/examples/ho-det-001-runtime-contract.sample.json b/contracts/examples/ho-det-001-runtime-contract.sample.json index 37ffd98..f5ca105 100644 --- a/contracts/examples/ho-det-001-runtime-contract.sample.json +++ b/contracts/examples/ho-det-001-runtime-contract.sample.json @@ -17,6 +17,63 @@ "ai_may_promote": false, "ai_may_close": false, "human_review_required": true, + "runtime_truth_spine": { + "source_truth": { + "state": "SOURCE_EXISTS", + "owner": "hawkinsoperations-detections", + "refs": [ + "hawkinsoperations-detections/detections/successor/ho-det-001/rule.yml", + "hawkinsoperations-detections/detections/successor/ho-det-001/splunk.spl" + ] + }, + "validation_truth": { + "state": "CONTROLLED_TEST_VALIDATED", + "owner": "hawkinsoperations-validation", + "refs": [ + "hawkinsoperations-validation/reports/ho-det-001/validation-result.json", + "hawkinsoperations-validation/reports/ho-det-001/pipeline-proof.json" + ] + }, + "runtime_truth": { + "state": "RUNTIME_EVIDENCE_VERIFIED_PRIVATE", + "public_runtime_claim_status": "PUBLIC_RUNTIME_BLOCKED", + "verified_runtime_evidence_refs": [ + "HawkinsOperations/hawkinsoperations-validation#22", + "hawkinsoperations-validation/scripts/verify-ho-det-001-runtime-packet.py" + ] + }, + "signal_truth": { + "state": "SIGNAL_OBSERVED_PRIVATE", + "public_signal_claim_status": "PUBLIC_RUNTIME_BLOCKED", + "verified_signal_record_refs": [ + "HawkinsOperations/hawkinsoperations-validation#22", + "proof/records/HO-DET-001.md#controlled-runtime-signal-packet-001" + ] + }, + "evidence_truth": { + "state": "RUNTIME_EVIDENCE_VERIFIED_PRIVATE", + "raw_private_evidence_public_safe": false, + "repo_contains_raw_private_evidence": false, + "hash_only_private_refs": true + }, + "ai_triage_truth": { + "support_state": "AI_SUPPORT_ONLY", + "triage_output_state": "AI_TRIAGE_OUTPUT_PRIVATE", + "authority_state": "AI_NOT_AUTHORITY", + "ai_decided_disposition": false, + "human_review_required": true + }, + "public_proof_truth": { + "state": "PUBLIC_RUNTIME_BLOCKED", + "proof_ceiling": "CONTROLLED_TEST_VALIDATED", + "public_safe_status": "NOT_PUBLIC_SAFE" + }, + "human_review_truth": { + "state": "HUMAN_REVIEW_REQUIRED", + "public_runtime_summary_state": "PUBLIC_RUNTIME_BLOCKED", + "approval_required_for_public_summary": true + } + }, "allowed_claims": [ "validation PR #18 clone-runnable proof pack merged", "platform runtime contract enforcement exists", diff --git a/contracts/schemas/ho-det-001-runtime-contract.schema.json b/contracts/schemas/ho-det-001-runtime-contract.schema.json index 3748c7c..3763e9e 100644 --- a/contracts/schemas/ho-det-001-runtime-contract.schema.json +++ b/contracts/schemas/ho-det-001-runtime-contract.schema.json @@ -23,6 +23,7 @@ "ai_may_promote", "ai_may_close", "human_review_required", + "runtime_truth_spine", "allowed_claims", "blocked_claims", "privacy_boundary", @@ -91,6 +92,181 @@ "type": "boolean", "const": true }, + "runtime_truth_spine": { + "type": "object", + "additionalProperties": false, + "required": [ + "source_truth", + "validation_truth", + "runtime_truth", + "signal_truth", + "evidence_truth", + "ai_triage_truth", + "public_proof_truth", + "human_review_truth" + ], + "properties": { + "source_truth": { + "type": "object", + "additionalProperties": false, + "required": ["state", "owner", "refs"], + "properties": { + "state": { "const": "SOURCE_EXISTS" }, + "owner": { "const": "hawkinsoperations-detections" }, + "refs": { + "type": "array", + "minItems": 2, + "items": { "type": "string" } + } + } + }, + "validation_truth": { + "type": "object", + "additionalProperties": false, + "required": ["state", "owner", "refs"], + "properties": { + "state": { "const": "CONTROLLED_TEST_VALIDATED" }, + "owner": { "const": "hawkinsoperations-validation" }, + "refs": { + "type": "array", + "minItems": 2, + "items": { "type": "string" } + } + } + }, + "runtime_truth": { + "type": "object", + "additionalProperties": false, + "required": ["state", "public_runtime_claim_status", "verified_runtime_evidence_refs"], + "properties": { + "state": { + "enum": [ + "RUNTIME_NOT_CAPTURED", + "RUNTIME_CAPTURE_STAGED_PRIVATE", + "RUNTIME_EVIDENCE_VERIFIED_PRIVATE" + ] + }, + "public_runtime_claim_status": { + "enum": [ + "PUBLIC_RUNTIME_BLOCKED", + "PUBLIC_RUNTIME_SUMMARY_ELIGIBLE", + "PUBLIC_RUNTIME_SUMMARY_APPROVED" + ] + }, + "verified_runtime_evidence_refs": { + "type": "array", + "minItems": 2, + "items": { "type": "string" } + } + } + }, + "signal_truth": { + "type": "object", + "additionalProperties": false, + "required": ["state", "public_signal_claim_status", "verified_signal_record_refs"], + "properties": { + "state": { + "enum": [ + "SIGNAL_NOT_OBSERVED", + "SIGNAL_OBSERVED_PRIVATE" + ] + }, + "public_signal_claim_status": { + "enum": [ + "PUBLIC_RUNTIME_BLOCKED", + "PUBLIC_RUNTIME_SUMMARY_ELIGIBLE", + "PUBLIC_RUNTIME_SUMMARY_APPROVED" + ] + }, + "verified_signal_record_refs": { + "type": "array", + "minItems": 2, + "items": { "type": "string" } + } + } + }, + "evidence_truth": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "raw_private_evidence_public_safe", + "repo_contains_raw_private_evidence", + "hash_only_private_refs" + ], + "properties": { + "state": { + "enum": [ + "RUNTIME_NOT_CAPTURED", + "RUNTIME_CAPTURE_STAGED_PRIVATE", + "RUNTIME_EVIDENCE_VERIFIED_PRIVATE" + ] + }, + "raw_private_evidence_public_safe": { "type": "boolean", "const": false }, + "repo_contains_raw_private_evidence": { "type": "boolean", "const": false }, + "hash_only_private_refs": { "type": "boolean", "const": true } + } + }, + "ai_triage_truth": { + "type": "object", + "additionalProperties": false, + "required": [ + "support_state", + "triage_output_state", + "authority_state", + "ai_decided_disposition", + "human_review_required" + ], + "properties": { + "support_state": { + "enum": ["AI_NOT_USED", "AI_SUPPORT_ONLY"] + }, + "triage_output_state": { + "enum": ["AI_NOT_USED", "AI_TRIAGE_OUTPUT_PRIVATE", "AI_TRIAGE_REVIEWED"] + }, + "authority_state": { "const": "AI_NOT_AUTHORITY" }, + "ai_decided_disposition": { "type": "boolean", "const": false }, + "human_review_required": { "type": "boolean", "const": true } + } + }, + "public_proof_truth": { + "type": "object", + "additionalProperties": false, + "required": ["state", "proof_ceiling", "public_safe_status"], + "properties": { + "state": { + "enum": [ + "PUBLIC_RUNTIME_BLOCKED", + "PUBLIC_RUNTIME_SUMMARY_ELIGIBLE", + "PUBLIC_RUNTIME_SUMMARY_APPROVED" + ] + }, + "proof_ceiling": { "const": "CONTROLLED_TEST_VALIDATED" }, + "public_safe_status": { "const": "NOT_PUBLIC_SAFE" } + } + }, + "human_review_truth": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "public_runtime_summary_state", + "approval_required_for_public_summary" + ], + "properties": { + "state": { "const": "HUMAN_REVIEW_REQUIRED" }, + "public_runtime_summary_state": { + "enum": [ + "PUBLIC_RUNTIME_BLOCKED", + "PUBLIC_RUNTIME_SUMMARY_ELIGIBLE", + "PUBLIC_RUNTIME_SUMMARY_APPROVED" + ] + }, + "approval_required_for_public_summary": { "type": "boolean", "const": true } + } + } + } + }, "allowed_claims": { "type": "array", "minItems": 4, diff --git a/scripts/verify-ho-det-001-runtime-contract.py b/scripts/verify-ho-det-001-runtime-contract.py index c2fa750..a11681a 100644 --- a/scripts/verify-ho-det-001-runtime-contract.py +++ b/scripts/verify-ho-det-001-runtime-contract.py @@ -30,6 +30,59 @@ "human_review_required": True, } +REQUIRED_TRUTH_PLANES = { + "source_truth", + "validation_truth", + "runtime_truth", + "signal_truth", + "evidence_truth", + "ai_triage_truth", + "public_proof_truth", + "human_review_truth", +} + +TRUTH_PLANE_INVARIANTS = { + "source_truth": { + "state": "SOURCE_EXISTS", + "owner": "hawkinsoperations-detections", + }, + "validation_truth": { + "state": "CONTROLLED_TEST_VALIDATED", + "owner": "hawkinsoperations-validation", + }, + "runtime_truth": { + "state": "RUNTIME_EVIDENCE_VERIFIED_PRIVATE", + "public_runtime_claim_status": "PUBLIC_RUNTIME_BLOCKED", + }, + "signal_truth": { + "state": "SIGNAL_OBSERVED_PRIVATE", + "public_signal_claim_status": "PUBLIC_RUNTIME_BLOCKED", + }, + "evidence_truth": { + "state": "RUNTIME_EVIDENCE_VERIFIED_PRIVATE", + "raw_private_evidence_public_safe": False, + "repo_contains_raw_private_evidence": False, + "hash_only_private_refs": True, + }, + "ai_triage_truth": { + "support_state": "AI_SUPPORT_ONLY", + "triage_output_state": "AI_TRIAGE_OUTPUT_PRIVATE", + "authority_state": "AI_NOT_AUTHORITY", + "ai_decided_disposition": False, + "human_review_required": True, + }, + "public_proof_truth": { + "state": "PUBLIC_RUNTIME_BLOCKED", + "proof_ceiling": "CONTROLLED_TEST_VALIDATED", + "public_safe_status": "NOT_PUBLIC_SAFE", + }, + "human_review_truth": { + "state": "HUMAN_REVIEW_REQUIRED", + "public_runtime_summary_state": "PUBLIC_RUNTIME_BLOCKED", + "approval_required_for_public_summary": True, + }, +} + BLOCKED_ALLOWED_CLAIM_PATTERNS = [ r"runtime-active", r"signal-observed", @@ -159,12 +212,60 @@ def require_blocked_claim_inventory(sample: dict) -> None: fail(f"missing blocked_claims entries: {', '.join(missing)}") +def require_object(value: object, name: str) -> dict: + if not isinstance(value, dict): + fail(f"{name} must be an object") + return value + + +def require_expected_field(container: dict, plane_name: str, field_name: str, expected: object) -> None: + actual = container.get(field_name) + if actual != expected: + fail(f"{plane_name}.{field_name} must remain {expected!r}, got {actual!r}") + + +def require_minimum_refs(container: dict, plane_name: str, field_name: str) -> None: + refs = container.get(field_name) + if not isinstance(refs, list) or len(refs) < 2: + fail(f"{plane_name}.{field_name} requires at least two refs") + + +def require_truth_spine(sample: dict) -> None: + spine = require_object(sample.get("runtime_truth_spine"), "runtime_truth_spine") + missing = sorted(REQUIRED_TRUTH_PLANES - set(spine)) + if missing: + fail(f"runtime_truth_spine missing truth planes: {', '.join(missing)}") + + planes = { + plane_name: require_object(spine.get(plane_name), f"runtime_truth_spine.{plane_name}") + for plane_name in sorted(REQUIRED_TRUTH_PLANES) + } + + for plane_name, invariants in TRUTH_PLANE_INVARIANTS.items(): + for field_name, expected in invariants.items(): + require_expected_field(planes[plane_name], plane_name, field_name, expected) + + require_minimum_refs(planes["source_truth"], "source_truth", "refs") + require_minimum_refs(planes["validation_truth"], "validation_truth", "refs") + require_minimum_refs( + planes["runtime_truth"], + "runtime_truth", + "verified_runtime_evidence_refs", + ) + require_minimum_refs( + planes["signal_truth"], + "signal_truth", + "verified_signal_record_refs", + ) + + def main() -> int: sample = load_json(SAMPLE_PATH) schema = load_json(SCHEMA_PATH) validate_schema_if_possible(sample, schema) require_expected_values(sample) + require_truth_spine(sample) require_privacy_boundary(sample) require_blocked_claim_inventory(sample) reject_promoted_allowed_claims(sample) @@ -177,6 +278,8 @@ def main() -> int: print("PROMOTION_STATUS=BLOCKED") print("RUNTIME_ACTIVE=false") print("SIGNAL_OBSERVED=false") + print("PUBLIC_RUNTIME_CLAIM_STATUS=PUBLIC_RUNTIME_BLOCKED") + print("AI_TRIAGE_TRUTH=AI_SUPPORT_ONLY/AI_TRIAGE_OUTPUT_PRIVATE/AI_NOT_AUTHORITY") print("AI_DECIDED_DISPOSITION=false") return 0