def validate_trace_surface_payload(surface: Any) -> str | None:
"""Validate the redacted public trace shape; return the first error."""
if not isinstance(surface, dict):
return "trace_surface must be an object"
expected = {
"version",
"verdict",
"status",
"conclusion_claim_id",
"claims",
"citations",
"assertions",
"disclosures",
"abstention",
"telemetry",
}
if set(surface) != expected or surface.get("version") != TRACE_SURFACE_VERSION:
return "trace_surface fields/version do not match the version-1 contract"
if surface.get("verdict") not in _VERDICTS:
return "trace_surface.verdict is not canonical"
status = surface.get("status")
if status is not None and status not in _WIRE_STATUSES:
return "trace_surface.status is not canonical"
if not isinstance(surface.get("claims"), list):
return "trace_surface.claims must be a list"
if not isinstance(surface.get("citations"), list):
return "trace_surface.citations must be a list"
if not isinstance(surface.get("assertions"), list):
return "trace_surface.assertions must be a list"
if not isinstance(surface.get("disclosures"), list):
return "trace_surface.disclosures must be a list"
claim_ids: set[str] = set()
for claim in surface["claims"]:
if not isinstance(claim, dict) or set(claim) != {
"id",
"text",
"inference_form",
"premise_claim_ids",
"status",
"flags",
"citation_addresses",
}:
return "trace_surface claim fields are invalid"
if not isinstance(claim["id"], str) or not claim["id"] or claim["id"] in claim_ids:
return "trace_surface claim ids must be unique non-empty strings"
claim_ids.add(claim["id"])
if not isinstance(claim["text"], str) or not claim["text"]:
return "trace_surface claim text must be non-empty"
if claim["inference_form"] not in _INFERENCE_FORMS:
return "trace_surface claim inference form is not canonical"
if claim["status"] not in _WIRE_STATUSES:
return "trace_surface claim status is not canonical"
flags = claim["flags"]
if not isinstance(flags, dict) or set(flags) != set(_FLAG_FIELDS):
return "trace_surface claim flags are invalid"
if any(not isinstance(flags[field], bool) for field in _FLAG_FIELDS):
return "trace_surface claim flags must be booleans"
for field in ("premise_claim_ids", "citation_addresses"):
if not isinstance(claim[field], list) or any(
not isinstance(item, str) or not item for item in claim[field]
):
return f"trace_surface claim {field} is invalid"
for claim in surface["claims"]:
if any(item not in claim_ids for item in claim["premise_claim_ids"]):
return "trace_surface premise references an unknown claim"
conclusion = surface.get("conclusion_claim_id")
if conclusion is not None and conclusion not in claim_ids:
return "trace_surface conclusion references an unknown claim"
seen_addresses: set[str] = set()
for citation in surface["citations"]:
if not isinstance(citation, dict) or set(citation) != {
"address",
"claim_ids",
"status",
"flags",
}:
return "trace_surface citation fields are invalid"
address = citation["address"]
try:
canonical = Address.parse(address).to_token()
except Exception:
return "trace_surface citation address is invalid"
if canonical != address or address in seen_addresses:
return "trace_surface citation addresses must be canonical and unique"
seen_addresses.add(address)
if citation["status"] not in _WIRE_STATUSES:
return "trace_surface citation status is not canonical"
if (
not isinstance(citation["claim_ids"], list)
or not citation["claim_ids"]
or any(item not in claim_ids for item in citation["claim_ids"])
):
return "trace_surface citation claim ids are invalid"
flags = citation["flags"]
if not isinstance(flags, dict) or set(flags) != set(_FLAG_FIELDS):
return "trace_surface citation flags are invalid"
for assertion in surface["assertions"]:
if (
not isinstance(assertion, dict)
or set(assertion) != {"text", "claim_id"}
or not isinstance(assertion["text"], str)
or not assertion["text"]
or assertion["claim_id"] not in claim_ids
):
return "trace_surface assertion is invalid"
abstention = surface.get("abstention")
if surface["verdict"] == ABSTAIN:
if not isinstance(abstention, dict) or set(abstention) != {
"reasons",
"halted_reason",
"detail",
}:
return "trace_surface abstention fields are invalid"
elif abstention is not None:
return "answer trace must not carry an abstention"
telemetry = surface.get("telemetry")
if not isinstance(telemetry, dict) or set(telemetry) != {
"hops",
"renavigations",
"parse_repairs",
"tokens_spent",
"loop_break",
}:
return "trace_surface telemetry fields are invalid"
if any(
isinstance(telemetry[field], bool)
or not isinstance(telemetry[field], int)
or telemetry[field] < 0
for field in ("hops", "renavigations", "parse_repairs", "tokens_spent")
):
return "trace_surface telemetry counters must be non-negative integers"
if not isinstance(telemetry["loop_break"], str):
return "trace_surface telemetry loop_break must be a string"
return None