Repository object · research-note

Verify Protocol V2

Accepted research note in the public catalog.

Source path
research/how-we-know/agent-citation-lineage/verify_protocol_v2.py
Media type
text/x-python
Object ID
em:research-note:sha256:a0040ccfe22e33a9035037a713c0bd25d9df3aab016bf3e69c548e409927237f
Content digest
9e8c5ff832c7c6989e0f40e37f1ce0bfb9e35f1581cfc4300da99c7f972c47ff

Source content

"""Fail-closed verification for the frozen EM-0026 v2 trace protocol."""

from __future__ import annotations

import argparse

import hashlib

import json

from collections import Counter

from pathlib import Path

from typing import Any

REPO_ROOT = Path(__file__).resolve().parents[3]

PACKET_ROOT = Path(__file__).resolve().parent

PROTOCOL_PATH = PACKET_ROOT / "protocol-v2.json"

TARGET_PATH = PACKET_ROOT / "target-decision.json"

PROMPT_PATH = PACKET_ROOT / "frozen-prompt-v2.md"

TEMPLATE_PATH = PACKET_ROOT / "trace-record-template-v2.json"

V1_PROTOCOL_PATH = PACKET_ROOT / "protocol.json"

FAILED_PREFLIGHT_PATH = (

PACKET_ROOT / "failed-preflight" / "20260823T031151Z-v1-transport.json"

)

ANSWER_KEYS = {

"question",

"cutoff",

"answer",

"results",

"sources",

"counterevidence",

"limitations",

"unresolved",

"search_notes",

}

RESULT_KEYS = {

"result_id",

"proposition",

"reported_value",

"scope",

"source_ids",

"exact_span_ids",

"interpretation",

}

SOURCE_KEYS = {

"source_id",

"url",

"title",

"authors_or_org",

"date",

"identifier",

"edition",

"retrieval_status",

"media_type",

"license",

"exact_spans",

}

SPAN_KEYS = {"span_id", "locator", "quote", "supports"}

def fail(message: str) -> None:

raise SystemExit(message)

def require(condition: bool, message: str) -> None:

if not condition:

fail(message)

def load_json(path: Path) -> dict[str, Any]:

try:

value = json.loads(path.read_text())

except (OSError, json.JSONDecodeError) as exc:

fail(f"cannot parse {path.relative_to(REPO_ROOT)}: {exc}")

require(isinstance(value, dict), f"{path.relative_to(REPO_ROOT)} must contain an object")

return value

def identity(path: Path) -> tuple[str, int]:

payload = path.read_bytes()

return hashlib.sha256(payload).hexdigest(), len(payload)

def normalized_text(value: str) -> str:

return " ".join(value.split())

def require_identity(

protocol: dict[str, Any], path: Path, prefix: str, label: str

) -> None:

digest, byte_count = identity(path)

require(protocol.get(f"{prefix}_sha256") == digest, f"{label} digest drift")

require(protocol.get(f"{prefix}_bytes") == byte_count, f"{label} byte-count drift")

def verify_frozen_inputs() -> tuple[dict[str, Any], list[str], Path]:

protocol = load_json(PROTOCOL_PATH)

target = load_json(TARGET_PATH)

template = load_json(TEMPLATE_PATH)

failed_preflight = load_json(FAILED_PREFLIGHT_PATH)

require(protocol.get("task_id") == "EM-0026", "protocol task mismatch")

require(protocol.get("protocol_id") == "em-0026-agent-citation-trace-v2", "protocol ID drift")

require(protocol.get("status") == "frozen-before-traces", "protocol is not frozen")

require(target.get("status") == "frozen-before-traces", "target decision is not frozen")

require(target.get("decision") == "proceed", "target decision does not permit collection")

require(protocol.get("evidence_cutoff") == target.get("evidence_cutoff"), "cutoff mismatch")

require(

protocol.get("supersedes_failed_protocol")

== failed_preflight.get("protocol_id")

== "em-0026-agent-citation-trace-v1",

"failed v1 protocol linkage mismatch",

)

require(

failed_preflight.get("status") == "aborted-before-final-answer-capture",

"v1 failure status drift",

)

require(failed_preflight.get("answers_captured") == 0, "v1 answer count drift")

require(failed_preflight.get("citations_admitted") == 0, "v1 citation count drift")

require(failed_preflight.get("replacement_runs_started") is False, "v1 replacement drift")

require_identity(protocol, PROMPT_PATH, "prompt", "frozen v2 prompt")

require_identity(protocol, TARGET_PATH, "target_decision", "target decision")

require_identity(

protocol,

V1_PROTOCOL_PATH,

"supersedes_failed_protocol",

"superseded v1 protocol",

)

require_identity(

protocol,

FAILED_PREFLIGHT_PATH,

"failed_preflight",

"v1 failed-preflight record",

)

prompt_text = "\n".join(

line.removeprefix("> ") for line in PROMPT_PATH.read_text().splitlines()

)

require(

normalized_text(target["question"]) in normalized_text(prompt_text),

"target question missing from prompt",

)

matrix = protocol.get("capture_matrix")

require(isinstance(matrix, list) and len(matrix) == 8, "capture matrix must have eight slots")

require(all(isinstance(item, dict) for item in matrix), "capture slots must be objects")

run_ids = [item.get("run_id") for item in matrix]

require(len(set(run_ids)) == 8, "capture matrix run IDs must be unique")

require(

Counter(item.get("requested_model_profile") for item in matrix)

== Counter({"gpt-5.6-sol": 4, "gpt-5.6-terra": 4}),

"capture matrix must contain four runs per frozen profile",

)

require(

all(item.get("requested_reasoning_effort") == "high" for item in matrix),

"all capture slots must request high reasoning effort",

)

controls = protocol.get("run_controls", {})

require(controls.get("inherited_conversation_turns") == 0, "context isolation drift")

require(controls.get("same_prompt_for_every_run") is True, "prompt equality is not required")

require(controls.get("replacement_runs") == "forbidden", "replacement runs are not forbidden")

prompt_sha256, prompt_bytes = identity(PROMPT_PATH)

require(

template.get("protocol_id") == protocol.get("protocol_id"),

"template protocol mismatch",

)

require(template.get("prompt", {}).get("sha256") == prompt_sha256, "template prompt drift")

require(template.get("prompt", {}).get("bytes") == prompt_bytes, "template prompt bytes drift")

require(template.get("inherited_conversation_turns") == 0, "template context drift")

trace_relative = protocol.get("trace_directory")

require(isinstance(trace_relative, str), "trace directory is not declared")

trace_path = REPO_ROOT / trace_relative

require(

trace_path.resolve().is_relative_to(PACKET_ROOT.resolve()),

"trace directory escapes the research packet",

)

return protocol, run_ids, trace_path

def verify_answer_shape(answer_path: Path, run_id: str) -> None:

answer = load_json(answer_path)

require(answer.keys() >= ANSWER_KEYS, f"missing required answer keys for {run_id}")

for key in ("results", "sources", "counterevidence", "limitations", "unresolved"):

require(isinstance(answer[key], list), f"answer {key} must be an array for {run_id}")

for result in answer["results"]:

require(isinstance(result, dict), f"result must be an object for {run_id}")

require(result.keys() >= RESULT_KEYS, f"result fields missing for {run_id}")

for source in answer["sources"]:

require(isinstance(source, dict), f"source must be an object for {run_id}")

require(source.keys() >= SOURCE_KEYS, f"source fields missing for {run_id}")

require(

isinstance(source["exact_spans"], list),

f"source spans must be an array for {run_id}",

)

for span in source["exact_spans"]:

require(isinstance(span, dict), f"span must be an object for {run_id}")

require(span.keys() >= SPAN_KEYS, f"span fields missing for {run_id}")

def verify_traces(

protocol: dict[str, Any], run_ids: list[str], trace_path: Path, require_complete: bool

) -> int:

trace_files = sorted(trace_path.glob("*.json")) if trace_path.exists() else []

if require_complete:

require(len(trace_files) == 8, "complete packet requires exactly eight trace records")

seen: set[str] = set()

matrix = {item["run_id"]: item for item in protocol["capture_matrix"]}

terminal = {"completed", "failed", "refused", "empty", "malformed", "incomplete"}

for path in trace_files:

trace = load_json(path)

run_id = trace.get("run_id")

require(run_id in run_ids, f"unexpected trace run ID in {path.name}")

require(run_id not in seen, f"duplicate trace record for {run_id}")

seen.add(run_id)

require(trace.get("task_id") == "EM-0026", f"task mismatch for {run_id}")

require(

trace.get("protocol_id") == protocol["protocol_id"],

f"protocol mismatch for {run_id}",

)

require(

isinstance(trace.get("protocol_commit"), str)

and len(trace["protocol_commit"]) == 40

and all(character in "0123456789abcdef" for character in trace["protocol_commit"]),

f"protocol commit is not an exact SHA for {run_id}",

)

require(trace.get("status") in terminal, f"nonterminal status for {run_id}")

require(trace.get("replacement_for") is None, f"replacement trace forbidden for {run_id}")

require(trace.get("inherited_conversation_turns") == 0, f"context drift for {run_id}")

require(

trace.get("requested_model_profile") == matrix[run_id]["requested_model_profile"],

f"model-profile mismatch for {run_id}",

)

require(

trace.get("requested_reasoning_effort")

== matrix[run_id]["requested_reasoning_effort"],

f"reasoning-effort mismatch for {run_id}",

)

require(

trace.get("prompt", {}).get("sha256") == protocol["prompt_sha256"],

f"prompt drift for {run_id}",

)

require(

trace.get("prompt", {}).get("bytes") == protocol["prompt_bytes"],

f"prompt bytes drift for {run_id}",

)

disclosure = trace.get("disclosure_review", {})

for key in (

"hidden_reasoning_included",

"private_context_included",

"credentials_or_account_state_included",

"personal_data_included",

):

require(disclosure.get(key) is False, f"disclosure boundary failed for {run_id}: {key}")

answer = trace.get("answer", {})

answer_path = answer.get("path")

if isinstance(answer_path, str) and answer_path != "unknown":

candidate = REPO_ROOT / answer_path

require(candidate.is_file(), f"answer artifact missing for {run_id}")

answer_sha256, answer_bytes = identity(candidate)

require(answer.get("sha256") == answer_sha256, f"answer digest drift for {run_id}")

require(answer.get("bytes") == answer_bytes, f"answer byte-count drift for {run_id}")

if answer.get("json_valid") is True:

verify_answer_shape(candidate, run_id)

if trace.get("status") == "completed":

require(

isinstance(answer_path, str) and answer_path != "unknown",

f"completed trace lacks answer artifact for {run_id}",

)

require(

answer.get("json_valid") is True,

f"completed trace is not valid JSON for {run_id}",

)

if require_complete:

require(seen == set(run_ids), "trace records do not close the frozen matrix")

return len(trace_files)

def main() -> None:

parser = argparse.ArgumentParser()

parser.add_argument("--require-traces", action="store_true")

args = parser.parse_args()

protocol, run_ids, trace_path = verify_frozen_inputs()

trace_count = verify_traces(protocol, run_ids, trace_path, args.require_traces)

print(

json.dumps(

{

"protocol_valid": True,

"protocol_id": protocol["protocol_id"],

"superseded_protocol": protocol["supersedes_failed_protocol"],

"frozen_run_slots": len(run_ids),

"captured_trace_records": trace_count,

"trace_matrix_complete": trace_count == len(run_ids),

},

sort_keys=True,

)

)

if __name__ == "__main__":

main()

Build receipt

Reproduce this projection

Reproducible projection
Catalog
em:catalog:sha256:9bfc972213cba2cde167386103dc2c011ee74639fb7f0794c54120fbbdef1a5d
Frontier
em:frontier:sha256:f33be3eae4c75232d56750ef9a1aa79d96274ece3417d65a75c1391bf61a81bf
Accepted commit
f92846570180dfa4511263f8ba98ecd18f7772c9
Epistemic policy
commons-balanced-v0.1
Disclosure policy
public-noninterference-v0.1
Compiler
epistemedia/0.2.0