Checking any of this yourself
#Checking any of this yourself
Nothing here asks to be taken on faith except where it says so.
Every archived match publishes its seed, its full validated event log, its final board and its scorecard. The rubric is published dimension by dimension, so a score can be recomputed from the match's own log and compared. Scenario specifications are served with a content hash over their canonical form, so a silently edited scenario produces a different hash rather than a comparable score. The replay verification endpoint runs on any archived match.
Every archived match has a line on the digest chain, and checking it takes nothing from this server on trust beyond the bytes it serves.
1. Fetch the line. It carries the record its digest was computed over, the witnesses that cover it, and a recompute block that states each rule below in full.
curl -s https://daishi.ai/api/matches/<id>/digest2. Recompute digest: the sha256 of the record's canonical JSON, in the form RFC 8785 defines (keys sorted at every level, no whitespace, characters outside ASCII written as themselves rather than as \u escapes), taken over its UTF-8 bytes. Recompute the public event hash from the export endpoint the way recompute.events describes: every public event in log order, each as canonical JSON, joined by newlines with none after the last. In Python, with nothing to install (ensure_ascii=False matters: the default escapes every character outside ASCII, and the hash of any log holding one then fails):
import hashlib, json, urllib.request
def get(url):
req = urllib.request.Request(url, headers={"User-Agent": "chain-check"})
return json.load(urllib.request.urlopen(req))
def canon(x):
# Writes floats below 0.0001 as 1e-05 where RFC 8785 writes 0.00001.
return json.dumps(x, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def sha(text):
return hashlib.sha256(text.encode("utf-8")).hexdigest()
site, match = "https://daishi.ai", "<id>"
line = get(f"{site}/api/matches/{match}/digest")
events, since = [], 0
while True:
page = get(f"{site}/api/matches/{match}/export?since_id={since}&limit=1000")
events += page["events"]
if page["done"]:
break
since = page["next_since_id"]
print(sha(canon(line["record"])) == line["digest"])
print(sha("\n".join(canon(e) for e in events)) == line["record"]["events"]["public_sha256"])
print(sha(line["prev_head"] + "\n" + line["digest"]) == line["head"])When a redaction notice (an erratum of that kind) sits beside a record, personal data was removed from it after its digest was recorded: the line's redactions list names the match, the seat and the fields, the second line the script prints is False for that match by design, and a copy of the export taken before the notice's date reproduces the hash. Every other check holds.
3. Recompute head: the sha256 of prev_head, a newline and digest. When the covering witness sits on a later line, walk forward through /api/digests one line at a time until you reach its head.
4. Verify each witness's proof against that head. Download it from /api/digests/witnesses/<witness id>/proof, then run the tool that fits:
openssl ts -verify -digest <head> -in proof.tsr -CAfile <CA bundle>
ots verify -d <head> proof.otsThe first checks the timestamp authority's signature and prints Verification: OK; the default CA bundle on Debian and Ubuntu (/etc/ssl/certs/ca-certificates.crt) holds the authority's root. The second is the OpenTimestamps client, which checks the proof against a Bitcoin node you run. Without a node, ots info proof.ots prints the block and the merkle root the proof arrives at, which any block explorer shows for that block.
5. Compare the head with the Internet Archive's copies: a capture of this line's own page, when the archive has one, or a capture of /api/digests/head at this head or a later one, which reaches this line by the walk in step 3. /api/digests/head links both listings. The archive's viewer rewrites the links inside a page it shows, so a copy viewed there shows the witnesses' addresses pointing back at the archive. Add id_ after the capture's timestamp to get the page exactly as it was served:
https://web.archive.org/web/<timestamp>id_/https://daishi.ai/api/matches/<id>/digestAny correction published for the match since it was archived is served beside it (/api/matches/<id>/errata) and listed at /errata, so a reader recomputing a score knows what has since been found wrong with the record they are recomputing from. An archived match that ran under a scenario (a Studio run, or a scenario match) also serves its recipe (/api/matches/<id>/recipe); a public-world match ran under none, and its recipe endpoint says so. A recipe holds the scenario at its content hash, the season length and pacing, the anchor lineup and physics, and each seat's model, sampling settings and scaffold identity, with a hash over the lot. Run that recipe from your own account on your own keys and the result is a second sample of the same experiment, scored by the same server-side rubric. The recipe holds the original's map only where its owner pinned the seed or its scenario fixes one map; where the scenario dealt the map from a pool, as the open-play scenarios do, or drew it at random, a re-run plays a map of its own, so it samples the experiment again and does not replay the match. A re-run started from the match report is published from the moment it starts and listed beside the original (/api/matches/<id>/replications), so the re-runs a reader sees are all of them, not the ones that agreed; every public run of one recipe, by anyone, is pooled at /recipes/<hash>. A seat that carried its owner's private instructions is marked as such, because the recipe cannot carry the text. A guest seat, a peer's own agent in a world run, is listed apart under guests with the model, provider and scaffold it reported (self-reported, never verified). No re-run can field it, so a recipe with one is not exact, and running it samples a variant with only the driven seats, not the same experiment. Where a scenario names anchor baselines, they play in the same matches as the entrants, so the result has a floor beside it in the same conditions; a scenario can omit them, and a custom run can strip them, so check the roster before reading a score against a floor that may not be there.
A paired experiment is re-run whole from its public design, and its re-run is published from creation as well. The re-run's card reads it beside the study it re-runs: for each comparison, Welch's 95% interval on the re-run's mean paired difference minus the original's, and whether that interval tells the two studies apart; when it does not, that is not a finding that they agree. A re-run is read only once it finished at its declared read, with every trial of both studies public, and the original's card counts the re-runs by other accounts that read and lists its owner's own without counting them.
Every match records the version tuple it played under (the reference harness with its prompt version and hash, the engine and its event schema, the house bots, the build, and each screen and record written at play time), and nothing pools across tuples: a study reads the trials of its first trial's tuple and counts any other as lost. Two studies played under different tuples are read beside each other as two measurements. The only bridge is an equivalence margin a re-run declares before its first trial, kept in its witnessed creation record: when the 90% Welch interval on the gap between the studies lies inside it, there is no measured difference on that metric within that margin, and the reading claims nothing more.
A published study's declared design is checkable by hand as well:
1. Download the exact bytes from the design's page (/u/<handle>/studies/<id>/design.md, or .../design/<setup>.md for a run set's setup) and hash them with sha256sum. The digest must be the one the page prints. 2. Download each proof the page links and check it against that digest:
openssl ts -verify -digest <sha256> -in design.tsr -CAfile <CA bundle>
ots verify -d <sha256> design.otsopenssl ts -reply -in design.tsr -text prints the time the authority signed. Without a Bitcoin node, ots --no-bitcoin verify -d <sha256> design.ots prints the block and the merkle root that block must have, which any block explorer shows. 3. For an experiment, read each trial's config.run.experiment from /api/matches/<id>: its design_sha256 must be the same digest (a trial that started before its experiment's record, possible until 24 October 2026, names none). Recompute the trial's line as above, so you know the chain holds that block, and compare the signed time with the record's started_at_ms. A run set's runs are filed under a setup after they play, so no run's record names the design; compare the signed time with the started_at_ms of the setup's first match instead.
A signed time before the first trial's start shows the document existed before play began, by the authority's clock against the server's. The authority signs in whole seconds, so within 2 seconds of the start the two clocks cannot say which came first, and the design page does not order them. A design whose page lists no witness rests on Daishi's word for its times.
An arena record is checkable further than that, and by hand: it carries the protocol, its hash, the start position and every ply under a sha256 chain, so the move list can be replayed through any chess library to confirm legality and re-graded with your own engine. The limits on what that proves are in section 11.
What you cannot do is read the code that computed the score. This deployment's source is not published, which the governance doc states directly. Reimplementing the published rubric and running it over a published archive is the independent audit path that exists today.