Skip to content

Commit a355d9f

Browse files
committed
Add channel provenance write-gate validator
1 parent b111b14 commit a355d9f

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env python3
2+
"""Validate channel provenance memory write-gate examples."""
3+
4+
from __future__ import annotations
5+
6+
import json
7+
from pathlib import Path
8+
9+
from jsonschema import Draft202012Validator
10+
11+
ROOT = Path(__file__).resolve().parents[1]
12+
SCHEMA = ROOT / "schemas" / "channel-provenance-memory-write-gate.schema.json"
13+
EXAMPLE_DIR = ROOT / "examples" / "channel-provenance"
14+
HIGH_RISK_SINKS = {"confirmed_memory", "graph_edge", "claim_promotion", "policy_binding", "high_risk_action", "publish", "delete", "authorize_agent"}
15+
16+
17+
def load_json(path: Path) -> dict:
18+
with path.open("r", encoding="utf-8") as handle:
19+
return json.load(handle)
20+
21+
22+
def validate_schema(instance: dict, schema: dict, *, source_label: str) -> None:
23+
validator = Draft202012Validator(schema)
24+
errors = sorted(validator.iter_errors(instance), key=lambda error: list(error.path))
25+
if errors:
26+
lines = [f"{source_label} failed schema validation:"]
27+
for error in errors:
28+
location = ".".join(str(part) for part in error.path) or "<root>"
29+
lines.append(f" - {location}: {error.message}")
30+
raise ValueError("\n".join(lines))
31+
32+
33+
def semantic_diagnostics(record: dict) -> list[str]:
34+
diagnostics: list[str] = []
35+
requested_sink = record["promotion"]["requestedSink"]
36+
envelope = record["authorityEnvelope"]
37+
collapse = record.get("collapseDecision")
38+
writeback = record["writeback"]
39+
review = record["review"]
40+
41+
if requested_sink not in envelope["allowedSinks"]:
42+
diagnostics.append(f"requested sink {requested_sink} is not allowed by channel authority envelope")
43+
44+
if requested_sink in envelope["disallowedSinks"]:
45+
diagnostics.append(f"requested sink {requested_sink} is explicitly disallowed by channel authority envelope")
46+
47+
if requested_sink in envelope["requiresRepairFor"]:
48+
repair_refs = [] if collapse is None else collapse.get("repairEventRefs", [])
49+
if not repair_refs:
50+
diagnostics.append(f"requested sink {requested_sink} requires repair event refs")
51+
52+
if requested_sink in HIGH_RISK_SINKS:
53+
if review["status"] != "approved" or not review.get("approvalRef"):
54+
diagnostics.append(f"high-risk sink {requested_sink} requires approved review and approvalRef")
55+
if not writeback.get("enabled"):
56+
diagnostics.append(f"high-risk sink {requested_sink} cannot be promoted with writeback disabled")
57+
58+
if record["redaction"]["rawSensitivePayloadStored"] is not False:
59+
diagnostics.append("raw sensitive payload storage must remain false")
60+
61+
if writeback["performed"] and not writeback.get("writebackRef"):
62+
diagnostics.append("performed writeback requires writebackRef")
63+
64+
if record["memoryClass"] == "confirmed_memory" and record["promotion"]["promotionState"] != "confirmed":
65+
diagnostics.append("confirmed_memory requires confirmed promotionState")
66+
67+
if record["memoryClass"] != "confirmed_memory" and requested_sink == "confirmed_memory":
68+
diagnostics.append("confirmed_memory sink requires confirmed_memory class")
69+
70+
if record["interpretant"]["selectedRef"] not in record["interpretant"]["candidateRefs"]:
71+
diagnostics.append("selected interpretant must be one of candidateRefs")
72+
73+
return diagnostics
74+
75+
76+
def expected_semantic_result(path: Path) -> str:
77+
return "fail" if ".rejected." in path.name or path.name.startswith("bad-") else "pass"
78+
79+
80+
def main() -> int:
81+
schema = load_json(SCHEMA)
82+
Draft202012Validator.check_schema(schema)
83+
examples = sorted(EXAMPLE_DIR.glob("write-gate.*.example.json"))
84+
if not examples:
85+
raise SystemExit("No channel provenance write-gate examples found")
86+
87+
results = []
88+
for path in examples:
89+
record = load_json(path)
90+
validate_schema(record, schema, source_label=str(path))
91+
diagnostics = semantic_diagnostics(record)
92+
actual = "fail" if diagnostics else "pass"
93+
expected = expected_semantic_result(path)
94+
results.append({"example": path.name, "expected": expected, "actual": actual, "diagnostics": diagnostics})
95+
if expected != actual:
96+
raise ValueError(json.dumps(results[-1], indent=2))
97+
98+
print(json.dumps({"ok": True, "checked": results}, indent=2))
99+
return 0
100+
101+
102+
if __name__ == "__main__":
103+
raise SystemExit(main())

0 commit comments

Comments
 (0)