docs/contracts/validate_crc_v1.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""CRC-v1 conformance validator — the portable conformance oracle.
Validates response fixtures (or real captured responses) against
`crc-v1.schema.json` PLUS the contract invariants that JSON Schema can't fully
express. Repos that emit CRC-v1 (CoreCrux, VaultCrux, the daemon) vendor this
file + the schema and run it in CI against representative responses.
Usage:
python3 validate_crc_v1.py crc-v1.schema.json fixtures/crc-v1-*.json
cat response.json | python3 validate_crc_v1.py crc-v1.schema.json -
Exit 0 = all valid; non-zero = at least one failure (prints each).
Uses `jsonschema` if installed; otherwise falls back to a structural subset
check so CI works without the dependency.
"""
import json
import sys
def _schema_validate(schema, doc):
"""Prefer jsonschema; fall back to a minimal structural check."""
try:
import jsonschema # type: ignore
jsonschema.validate(doc, schema)
return []
except ImportError:
return _fallback_validate(doc)
except Exception as e: # jsonschema.ValidationError
return [f"schema: {getattr(e, 'message', str(e))}"]
def _fallback_validate(d):
errs = []
if not isinstance(d, dict):
return ["root is not an object"]
if d.get("contract") != "crc-v1":
errs.append("contract must be 'crc-v1'")
if d.get("kind") not in ("search", "addressed", "fact", "session"):
errs.append(f"kind invalid: {d.get('kind')!r}")
if d.get("hydrate_tier") not in ("pointer", "summary", "full"):
errs.append(f"hydrate_tier invalid: {d.get('hydrate_tier')!r}")
return errs
def _invariants(d):
"""Contract invariants beyond raw schema (the load-bearing rules)."""
errs = []
kind = d.get("kind")
tier = d.get("hydrate_tier")
# INV-1: pointer tier returns no hydrated bodies.
if tier == "pointer" and d.get("content"):
errs.append("INV-1: hydrate_tier=pointer must NOT include non-empty 'content'")
# INV-2: search must carry pointers + cost_estimate.
if kind == "search":
if not d.get("pointers"):
errs.append("INV-2: kind=search requires non-empty 'pointers'")
if not isinstance(d.get("cost_estimate"), dict):
errs.append("INV-2: kind=search requires 'cost_estimate'")
# INV-3: agent_decision is non-null ONLY for search.
ad = d.get("agent_decision")
if kind == "search" and ad is None:
errs.append("INV-3: kind=search requires non-null 'agent_decision'")
if kind != "search" and ad not in (None,):
errs.append(f"INV-3: kind={kind} must have null 'agent_decision'")
# INV-4: memory resolves (fact/session) carry freshness + a canonical slug so
# the next turn re-addresses by key. `fact` strictly requires freshness (decay/
# supersession is the whole point); `session` echoes the slug. `addressed`
# (content-pointer hydration, e.g. fetch-content/query_expand) has both
# optional — chunk bodies don't decay like facts.
if kind == "fact":
env = d.get("envelope") or {}
if "freshness" not in env or env.get("freshness") is None:
errs.append("INV-4: kind=fact requires envelope.freshness")
if kind in ("fact", "session"):
if not (d.get("next") or {}).get("canonical_slug"):
errs.append(f"INV-4: kind={kind} should echo next.canonical_slug")
# INV-5: cost_estimate, when present, has all three tiers as ints.
ce = d.get("cost_estimate")
if isinstance(ce, dict):
for t in ("pointer", "summary", "full"):
if not isinstance(ce.get(t), int):
errs.append(f"INV-5: cost_estimate.{t} must be an integer")
return errs
def main(argv):
if len(argv) < 3:
print("usage: validate_crc_v1.py <schema.json> <fixture.json|-> ...", file=sys.stderr)
return 2
schema = json.load(open(argv[1]))
targets = argv[2:]
failures = 0
for t in targets:
doc = json.load(sys.stdin) if t == "-" else json.load(open(t))
errs = _schema_validate(schema, doc) + _invariants(doc)
label = "<stdin>" if t == "-" else t
if errs:
failures += 1
print(f"FAIL {label}")
for e in errs:
print(f" - {e}")
else:
print(f"ok {label} (kind={doc.get('kind')} tier={doc.get('hydrate_tier')})")
print(f"\n{len(targets) - failures}/{len(targets)} valid")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main(sys.argv))