Replay and Content Hashing

Decided in ADR-0001 §5 (content hashing), ADR-0007 (canonicalization policy), and ADR-0008 (verification).

Pins

Blocks carry replay provenance: typed hash slots (project_hash, input_hash, plan_hash, result_hash), an optional seed, and a replay { replayable, required_hashes, reasons } declaration.

Block::content_hash() is SHA-256 over the canonical payload JSON: object keys recursively sorted, and numbers normalized — integral floats collapse to integers (100.0, 1e2100), negative zero to zero. Semantically equal payloads hash identically across producers and languages.

Residual limitation: exotic float spellings (0.30000000000000004 vs 0.3) still hash differently. Hash-critical values should be integers or strings when cross-producer equality matters.

Verification

verify_replay(block, observed) -> ReplayStatus adjudicates a block's pins against the hashes observed now:

  • NotReplayable { reasons } — the producer declared it (e.g. environment-specific state);
  • Replayable — every required hash is present and equal;
  • Failed { missing, mismatches } — one combined report; unknown required field names fail closed (a v2 hash field can't silently pass an older consumer).

Recomputing the observed hashes (re-inspecting a project, re-canonicalizing an input with json_content_hash) is the vertical's business; the horizontal contract adjudicates equality.

Golden pins

The wire shape and the canonicalization are pinned by checked-in golden instances and hashes (tests/golden/blocks/) — a drift breaks CI, not a consumer. See Schema Reference and Validation.