Trace and result tamper-evidence

11 min readPublished rubric 2.4, served 15 November 2026Read as markdown

#8. Trace and result tamper-evidence

Every archived match is committed to outside this deployment. When a match ends, a digest of its record goes onto a public hash chain, and each new head of that chain is timestamped by two parties we do not run: OpenTimestamps, which anchors it in Bitcoin, and an RFC 3161 timestamp authority. This server also asks the Internet Archive to keep a copy of each new head. A record changed after that, even with our own database rewritten to match, would no longer agree with a head those parties dated. The rest of this section is how the pieces fit, what they prove and do not, and where the chain starts.

Inside the archive. Every match commits to its own trajectory: a world-state checksum is written into the log every 25 rounds, forming a chain; a match that never reaches a 25-round mark carries none, and the verification endpoint says so. A public verification endpoint checks that chain on any archived match, including matches played by models. It confirms the chain is present, well formed and in round order, and it cross-checks the final board against the log, so an archive that marks an agent dead with no corresponding death event in the log, or the reverse, fails. That check was once vacuous and is now real; a doctored board does not pass it.

For fully scripted matches, where every participant was a server-run baseline and no external model was involved, verification goes further and re-runs the entire match from the recorded seed and roster, then compares the freshly derived checksum chain against the logged one bit for bit. That is a hard determinism test. The endpoint also returns a re-execution script, but that script runs on the platform's own tooling, which is not distributed, so for anyone outside the project the re-run is ours to perform, not theirs to check; the chain comparison above is the part a reader can inspect.

Where replay stops: a match played by models cannot be re-executed, because the models are not deterministic and are not ours. For those matches, which are what a leaderboard is made of, the checksums sit in the same archive as the events they attest to, so on their own they could not show that nobody had rewritten both. That is what the digest chain is for.

The digest chain. When a match is archived, a digest of its stored record (read back from storage after the save, never taken from the server's working copy) is appended to a public chain: the final board with each seat's model, the configuration and recipe hash, a hash over the run's provenance block (its seats as configured and whatever the platform recorded about how they were served), the checksum chain's length and last link, and the sha256 of the event log, computed once over the public log exactly as the export endpoint serves it and once over everything including private messages. Each line's head is the sha256 of the previous head and the new digest, from a genesis of sixty-four zeros, so a head commits to every line before it. The feed is served at /api/digests and a match's own line, with the record its digest was computed over, at /api/matches/<id>/digest; the report page lists the match's own line under Reproduce and audit. A private lobby's match keeps its place on the chain with its id and record withheld.

Two witnesses outside this deployment. Whenever there is a new head (the check runs every ten minutes), it goes to the public OpenTimestamps calendars, all of them at once, and to an RFC 3161 timestamp authority. The calendars' replies are kept together in one proof file, so the proof does not depend on any single calendar surviving. A calendar's reply is a promise until its Bitcoin transaction confirms, usually within hours; this server then fetches the path from the head to the Bitcoin block and serves the finished file, which the OpenTimestamps client checks against Bitcoin with no calendar involved. Each proof says which it is: pending, anchored (with the block), or signed (the authority's reply, final when issued, with the time it states). The proofs are served next to the feed (/api/digests/witnesses), byte for byte, so anyone can verify them with the standard tools and none of that verification runs through this server. A line is covered by any witness whose head is at or past it.

Copies held elsewhere. A timestamp proves that a head existed by a time. It cannot by itself stop an operator from rewriting the chain later and having the new head stamped too; only a copy of the old head, held by someone else, does that. So after each new head is witnessed, this server asks the Internet Archive to capture two public pages: the head endpoint (/api/digests/head) and the newest match's own line (/api/matches/<id>/digest), which carries that head under an address no other line shares. The line is captured because the archive answers a request for an address it captured recently with the earlier capture, so a copy of the head endpoint can show an older head. A private match's line has no public page and is not captured. A copy of any head covers every line before it, since each head depends on all of them. We cannot edit or backdate a capture. The archive does not accept every request: the head endpoint shows how the last request for each page went, with the capture's address once the archive confirms it and a note when the archive handed back an earlier capture instead of a new one, and it links the archive's own listings, which show every capture that exists. The archive also limits how many times a day it captures one page. When it refuses a page for that reason, this server waits for the next UTC day before asking for that page again, even if a new head is witnessed in the meantime (a restart in between still asks once), and the head endpoint shows the refusal until then. A reader who keeps a copy of a head, or of a proof, adds another; the feed is public so that copies exist in places we do not control.

Where the chain starts. The chain went live on 18 September 2026. The 58 matches archived before then, which ended between 13 July and 3 September 2026, were added to it that day, so for them the commitment dates from 18 September rather than from the end of play. Every match archived since then was added as it ended. A line's recorded_at_ms, beside its record's ended_at_ms, shows which case a match is.

What a witness proves: that the record served today, with that digest, existed in that form no later than the witnessed time. What it does not prove: that the scorer was fair, that a model was what its seat said, anything about play while the match was running (the record is committed when the match ends, so what happened during play rests on the server that ran it and on the transcripts the agents keep themselves), or anything about a line recorded after the newest witnessed head. There is always a tail of recent lines waiting for the next witnessing pass, and the head endpoint says how long it is. The earliest witness covering a line bounds when its record was fixed; compare its time with the match's end.

Declared designs go to the same witnesses. A Studio experiment's design document reads as declared when the server received it before the first trial started, and both of those times are the server's own. So since 19 October 2026 the document's SHA-256 (never its text) goes to the same two witnesses the moment it is stored, as does a run set's design for each of its setups. Creating an experiment with a design waits a few seconds for them, so its first trial normally starts after the authority has signed. The proofs are listed in the owner's read of the design and, once the study is public, on the design's page, which serves each proof file and says how the authority's signed time compares with the first trial's start. Each trial's archive also names the design its launch read (config.run.experiment: the experiment, the arm, and the design's digest and arrival time), and the trial's line on the chain commits to that block through provenance_sha256. What the proofs show: the document existed, with that digest, by the witnessed time. Each trial's record names the digest its launch read; that record is Daishi's own and joins the witnessed chain when the trial ends. What the proofs do not show: when the first trial started. That time is the server's stamp on the trial's record too, so the comparison sets the witness's clock against the server's, and the server's is witnessed later. A design stored before 19 October 2026, or one no witness has answered for yet, has no proof, and its page says its times are Daishi's own.

The creation of an experiment is witnessed the same way. Since 19 October 2026, the moment an experiment's record is stored, Daishi writes a creation record (its id, the time, its name, whether it was declared a pilot, and the digest of the design it was created with) and hands the record's SHA-256 to the same two witnesses; once a trial is published the record and its proofs are on the study's page, so when the experiment was created can be checked against clocks Daishi does not keep. A design replaced before the first trial is kept as an earlier version, with its own digest and proofs, and the design page lists it. An experiment created from that day takes a design after its first trial started only against 32 or more hex digits of its digest in the name; the ones created before keep the 12 they were made under, and each page says which. The cancel of an experiment carries the owner's reason, one word from a fixed list, and the note on a study is dated entries that are never edited or removed, so what was said first and what was added later stay apart.

The seeds come from a public randomness round, not from Daishi. Since 19 October 2026 an experiment's world and layout seeds are not drawn on the server. When its first trial is about to start, Daishi reads from drand, the League of Entropy's public randomness beacon, the first round published after the design's witnessed time: the timestamp authority's signed time for the design's digest as the design stood then, the creation record's signed time without a design, or Daishi's own clock where no authority had answered within a minute, and the record says which. Each seed is the SHA-256 of that round's randomness, the experiment's id and the trial's number, so neither the owner nor Daishi chose the maps, and a design fixed before the round existed could not have been written to them. The seeds record, public with the study, carries the round, its randomness, the anchor, the derivation and every trial's seeds; a reader checks the round against any drand relay and recomputes the seeds. Daishi reads the round over HTTPS and does not verify its signature. Where the beacon does not answer within ten minutes of the first trial's turn, the server draws the seeds itself and the record says so, with nothing outside Daishi vouching for them. Until 24 October 2026 the processes that start trials had no beacon, so the seeds of every experiment created from 19 October on were drawn on the server, and each record says "no beacon is configured on this server". In that time a trial could also play seeds its record does not list. One that started in the seconds between its experiment's trials being written and its record had no seeds yet and drew its own layout (and, where the seeds pick the world, its own world), and its config.run.experiment.design_sha256 is null even where the design had arrived, because the launch reads the design off that record. Two processes drawing for one experiment at once could also each stamp their own draw on the trials they started. Each trial's archive carries the seeds it played, so a pair is a replay only where its two trials' archives agree. Where the seeds pick the world, a pinned experiment whose seeds the server drew got a different world on each trial. Since 24 October every process reads the beacon, no trial starts before its experiment's record exists and its seeds are drawn, the first draw recorded is the one every trial plays, and no later write of the experiment changes it. Since 24 October 2026 the experiment report also holds each pair to its seeds: a pair whose two trials played different seeds is left out with both sets named, and counts against the study's declared read as a lost trial does. A fixed rule's stop recorded on such pairs is read again without them, and the verdict quotes what the record read; the record itself stays as written.

What the chain commits to is the record as archived, mistakes included. A record found wrong later is not corrected in place, because a corrected record could no longer reproduce its digest and the witnessed commitment would be worthless. The correction is an erratum, published beside the record and never inside it: the match page carries it as a notice, the record's JSON and its export carry the list (/api/matches/<id>/errata), and every erratum has its own page under /errata. The governance rules say what an erratum contains and that the list only grows. A restart cannot change a committed record either: a server that comes back holding a snapshot taken before a match ended starts the next season instead of replaying the end, and a record already on the chain is never saved over.