open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcr1/conformance/verify_event_log.py
Transaction Science 49175df95e jouleclaw: wave-54 — event-sourced agent substrate (ActiveGraph ingest)
Ingested from ActiveGraph (Nakajima, "The Log is the Agent",
arxiv 2605.21997, May 2026 — v1.0 May 2026, MIT/Apache). The
focused web search across 5 themes (event-sourced agent runtimes,
deterministic replay + content-addressed caching, multimodal data
primitives, context compression, durable workflows) confirmed
ActiveGraph is the uncontested 2026 SOTA at the event-sourced-
substrate layer. The other four themes have alternatives but
none with the same level of articulation; this wave ingests the
typed primitive JouleClaw's receipts + cascade had been pointing
at but didn't name.

1. jouleclaw-event-log (new crate) — typed substrate.
   - EventKind { Goal | Object | Relation { from, to, edge_kind } |
     LlmReq { request_hash, prompt_summary } | LlmResp {
     request_hash, response_summary, tokens } | ToolReq { tool_id,
     args_hash } | ToolResp { tool_id, args_hash, response_summary }
     | Patch | Receipt }. The Relation variant uses `edge_kind`
     (not `kind`) to avoid the silent serde-tag collision bug.
   - Event { prev_hash, ts_ns, kind, event_hash } sealed at
     append. EventLog::append computes prev_hash from the
     previous event's hash (genesis = BLAKE3 of zero bytes for
     the first event).
   - EventLog::verify_chain catches tampering at the first
     break with the exact index.
   - replay() — deterministic fold producing a ProjectedGraph
     (goal + typed objects + typed relations + receipts), each
     element annotated with the event_hash that produced it.
   - replay_strict() — compares a fresh fold to an expected
     projection byte-for-byte under canonical JSON; returns
     ReplayError::Divergence with the diverging content hashes.
     A green strict replay is a proof the run is reproducible
     (the published ActiveGraph claim).
   - ContentAddressedCache — populates from the log's LlmResp +
     ToolResp events, indexed by the documented 2026 SOTA
     convention: LLM key = BLAKE3 over (system, user_messages,
     model_id, tool_defs, output_schema); tool key = (tool_id,
     args_hash). On replay, a behavior firing with a recorded
     hash hits cache; no new model call.
   - lineage_of(log, object_id) — given an artifact, return every
     event in its causal chain (goal that motivated it, LlmReq/
     LlmResp pair that produced it, ToolReq/ToolResp if any,
     Object event itself, Relations referencing it after creation).
   - 13 tests.

2. jouleclaw-jcr1::event_log — JCR-1 event-log/1 schema.
   - EventLogReceipt { jc_receipt, id, closed_at, steward, log_id,
     head_event_hash, chain_depth, counts: EventLogCounts,
     total_tokens }.
   - 2 conformance vectors with embedded receipt_hash.
   - 3 jcr1 event_log tests.

3. Cross-language conformance — Rust + Python + Go verifiers all
   shipped, byte-equal across the three implementations. The
   three-impl pattern (matching JCR-1 v1) is preserved.

4. Demo binary — examples/wave54_event_log_replay.rs in
   jouleclaw-stack. Builds a 9-event agent run (goal → LLM
   exchange → tool exchange → object → relation → inline receipt),
   demonstrates: (1) replay determinism; (2) strict-mode rejection
   of a tampered projection with divergence pinned to a content
   hash; (3) cache-served replay of the recorded LLM request (no
   new model call); (4) lineage trace recovering 7 events in the
   causal chain back to the goal; (5) event-log/1 JCR-1 receipt
   sealed over the log snapshot.

The triangle (conservation + cone + oracle + elicitor) stays the
type system; wave-54 adds the event-sourced substrate that any
JouleClaw agent runtime can be built ON. The log IS the agent.

Workspace check clean.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-05 13:01:12 -04:00

76 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""
verify_event_log.py — independent Python verifier for the
JCR-1 `schema_id` schema.
Reads `event-log-vectors.json`, recanonicalises each receipt under
sorted-key JSON (UTF-8, no whitespace), BLAKE3-hashes the bytes,
and asserts the hex matches the vector's embedded `receipt_hash`.
Deps: blake3 (`pip install blake3`).
Usage:
python3 verify_event_log.py [event-log-vectors.json]
"""
import sys, json
from pathlib import Path
try:
import blake3
except ImportError:
sys.stderr.write(
"this verifier requires the blake3 module — `pip install blake3`\n"
)
sys.exit(2)
def canonicalise(value):
if isinstance(value, dict):
return {k: canonicalise(value[k]) for k in sorted(value)}
if isinstance(value, list):
return [canonicalise(v) for v in value]
return value
def canonical_bytes(value) -> bytes:
return json.dumps(
canonicalise(value),
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
def main():
path = (
Path(sys.argv[1])
if len(sys.argv) > 1
else Path(__file__).parent / "event-log-vectors.json"
)
suite = json.loads(path.read_text())
expected_spec = "JCR-1 event-log v1"
if suite.get("spec") != expected_spec:
sys.stderr.write(f"spec mismatch: got {suite.get('spec')!r}, want {expected_spec!r}\n")
sys.exit(1)
failed = 0
for vec in suite["vectors"]:
n = vec["n"]
desc = vec["description"]
expected_hash = vec["receipt_hash"]
payload = vec["receipt"]
canon = canonical_bytes(payload)
computed = blake3.blake3(canon).hexdigest()
if computed == expected_hash:
print(f" ✓ vector {n}: {desc}")
else:
print(f" ✗ vector {n}: {desc}")
print(f" expected: {expected_hash}")
print(f" computed: {computed}")
failed += 1
if failed:
sys.stderr.write(f"\n{failed} vector(s) failed verification\n")
sys.exit(1)
print(f"\nverify_event_log.py — all {len(suite['vectors'])} vectors PASS")
if __name__ == "__main__":
main()