Case study
The public audit trail
Every operation in this category publishes a record and none of them are checkable. This one was built so that a reader who assumes I am lying can prove it, without an account and without my cooperation.
Everyone in this category publishes a record
Every operation that sells sports predictions publishes a record, and there is no reason to believe any of them.
There are two ways to lie about one and neither looks like lying. The first is to edit the history: a schema change, a corrected row, a cleanup of something that was obviously wrong. From the outside, a maintenance window and a rewrite are the same event. The second is to select what gets published — post the plays that won, and simply never mention the ones that did not. Every number in that record is true. The set is the lie.
The second is the common one, and it is the more effective, because nothing was falsified and no individual claim is checkable as false. A record that shows only what its author chose to show is not evidence. It is marketing with arithmetic in it.
Third-party verification services narrow the first problem and leave the second untouched: an operator stays free to submit some plays and not others, and a verified record of a chosen subset is a verified subset.
I could not find a way to prove I was not doing either of those things. Promising is not proving, and anyone can promise. So the requirement became making it impossible rather than saying it was not happening.
The test was a desk, not a bettor
The bar I set was not what a customer would accept. It was what a trading desk would accept from a vendor it had every reason to distrust.
A desk does not ask whether your numbers look good. It asks when the number existed, whether the timestamp is yours to move, what happens to your record on a day you would rather not discuss, and who has to cooperate for the check to work. If the answer to that last one is you, the check is not a check.
That rules out almost every design. A database I control proves nothing. A signature I issue proves nothing about time. A git commit proves nothing either, because history can be rewritten and force-pushed, and the platform hosting it is choosing timestamps on my behalf.
What survives is narrow: a commitment published into a system I cannot reach, before the outcome is known.
Two roots, neither of them mine
Every prediction is hashed into an append-only ledger the moment it is made. One row per prediction, enforced by a uniqueness constraint; UPDATE and DELETE rejected by a database trigger rather than by policy.
Each day, the day’s rows are sealed into a single manifest and attested into two independent roots before the games settle: a Bitcoin timestamp through OpenTimestamps, and a signed entry in the Sigstore Rekor transparency log.
Two rather than one, because they fail in unrelated ways and prove different things. Bitcoin gives a block height that a prediction demonstrably preceded, and no outage takes that back — a confirmed proof lives in the chain, not in a calendar server. Rekor gives attribution: the entry carries a signature over the anchor’s bytes by a key whose public half is published in the repository and kept there forever, including superseded ones, so historical entries stay checkable. A timestamp alone tells you something existed. It does not tell you whose it was.
Neither root can be checked away by the failure of the other, and neither is a fallback for the other. Both are produced for every anchor, and if either becomes unreachable, publication continues and the affected anchors carry one root until the other returns — which is itself a disclosable event with a stated deadline.
Each anchor also commits to the previous anchor’s exact file bytes. The sequence of days is therefore tamper-evident as a sequence, not just day by day. A missing day is visible. It can be explained, but it cannot be erased.
Publishing the commitment without publishing the picks
A prediction has commercial value right up until the game starts, which is exactly the window in which it has to be published to prove it existed. Publishing the picks to prove they existed gives away the thing being sold.
So the day’s manifest is sealed under a fresh 32-byte salt with HMAC-SHA256. The published manifest commits irrevocably to the exact set of rows, and the salt is what prevents anyone reconstructing the day’s picks from it by working through the possibilities. Customers receive the salt under contract and can recompute the entire day themselves.
There is a second mode that needs no salt at all: given the full rows, it recomputes each row hash directly. Anyone holding the data can check the data, and the operator is not in the loop for either path.
What it proves, and the list of what it does not
The methodology document states five properties: existence in time under two independent roots, attribution to a published key, integrity of every row and every day, continuity across the chain, and the binding of each published performance report into the following day’s anchor so a metric inherits the timestamp of the predictions it describes.
Immediately after that list is a second one, headed what is not proven. It says the ledger does not prove the predictions are good — it is provenance, not endorsement. It says the ledger discloses nothing about how predictions are made, and that training lineage never appears in a row.
The second list is the one that does the work. A document that only enumerates its strengths has told you who wrote it.
The verifier is the product
The check is one file: 560 lines of Python. Five of its six modes are pure standard library and need no network, no account, no key and no API. Clone the repository and run it.
Golden vectors ship alongside it, so the first thing you can do is verify the verifier — check that it produces known answers on known inputs before trusting what it says about anything else. The chain mode walks the previous-anchor links. The Bitcoin mode is the only one with a dependency, a pinned OpenTimestamps client, and it is there because that check is the binding one.
This is the part the whole design exists to reach. Nothing in the trust chain routes through me, including the tool you use to check it.
Disclosure is the other half
A tamper-evident chain says nothing about the days that never appear in it. Integrity and completeness are different properties, and only one of them can be enforced with a hash.
So the protocol names, in advance and in writing, what has to be disclosed and by when. Seven days from detection, append-only, never rewritten — an error in a disclosure is corrected by appending a follow-up, not by force-pushing a better version of the past.
The thresholds are specific rather than aspirational: an anchor landing more than 24 hours after the activity it covers; an anchor carrying only one of its two attestations for more than 72 hours; a signing key rotated or suspected to have left custody; any committed artifact changing after commit; a salt leaving custody outside a contract; a verifier release that alters the meaning of any past verification.
Writing the thresholds down before anything goes wrong is the entire trick. Afterwards, every threshold is negotiable, and the person doing the negotiating is the one who needs it moved.
What the alpha cost me to admit
The public alpha ran 29 anchored days between 19 May and 25 June 2026, with 25 model registrations and 41 daily reports. It is sealed byte-identical under the product’s previous name, with a manifest of every file in it timestamped into Bitcoin at sealing — an independent bound on when the corpus existed, over and above each anchor’s own proof.
Sealing it meant writing down the complete list of what had gone wrong, including two things that were never disclosed while it was running.
The first is the one that counts. On 11 June a schema migration rewrote an anchor that had already been published — schema 3 to 4, in place, on a committed file, with a new manifest hash and a new publication time. The Bitcoin proof for that date had been stamped against the original bytes, so it can no longer be bound to the file sitting there now. The day still verifies against the ledger rows. Its timestamp claim does not, and the record states it in those words: not established.
That cannot be repaired. Repairing it would mean deleting or restamping a proof, which is the exact operation the system exists to make impossible. So it stands permanently, in a file anyone can read, and the official protocol makes that entire failure class mechanically impossible by requiring schema changes to go forward only, enforced in CI.
The first: two predictions got duplicate intermediate rows because a slate listed the same game twice inside one batch. Both were classified as skips, neither was a bet, no customer saw either, and no published figure moved. The superseded rows are still in the ledger and always will be, because removing them is precisely the operation the ledger exists to make impossible.
The second: an ingestion fault stopped one sport’s inputs refreshing for four days, and on the last of those the morning run did not execute at all, so that day has no anchor. The gap is stated rather than smoothed. The predictions a healthy pipeline would have produced were not generated afterwards, because backfilling them would have violated the live-timing guarantee the whole system exists to protect. They are recorded as never having existed, which is what they are.
The third is still open. That sport has been offline since 25 June 2026 for a hardware migration, with no estimated restart date, and the disclosure says exactly that — including that a follow-up will be appended when it resumes.
Sealing added the rest of the accounting. Nine dates in the alpha window carry no anchor. Twelve report dates have no anchor beside them, and reports were never inside the manifest hash at all — a gap the official ledger closes by binding each report’s bytes into the following day’s anchor.
None of it had to be published. Every one of these was found by my own review and nobody else was looking. Publishing them is the only thing that makes the remaining days worth anything, because a record with no bad days in it is the exact shape of a record that has been curated.
The verifier was the thing that broke
The second undisclosed item is the one that changed the design, and it is the reason there is a version two at all.
The alpha verifier’s offline Bitcoin mode read the block height out of each proof and never confirmed that the proof committed to the anchor file’s digest — while laying its output out as though that were exactly what it had done. Somebody running the offline check saw a pass. The check they believed they were running was not being run.
That is the failure this entire site is about, and it had gotten into the one piece of software whose only job was to catch it. A tool that answers confidently and wrongly is worse than one that refuses, because a refusal costs ten minutes and a confident wrong answer costs everything downstream. Mine answered confidently, about the thing I was asking readers to trust least.
The other half of the same problem was versioning. Each anchor pinned the verifier it was published with, so the sealed alpha spans two generations: sixteen anchors check against one file, thirteen against another, and the repository has to hand you a table explaining which is which. That works, and it hands the reader a job I created for them by shipping a fix.
So the official ledger inverts the relationship. Verifier identity lives in an append-only release registry rather than inside the anchors, each row carrying that release’s own SHA-256, and the governing rule is that the current release must verify every anchor, every schema and every golden vector ever published on the chain. CI fails the build when it cannot. One file, the newest one, works across the whole history including the parts published before it existed.
That constraint is deliberately expensive, because the alternative is worse. It means a change to the verifier can never quietly redefine what a past verification meant — which is itself one of the disclosure thresholds, written down before there was anything to disclose.
One operator, stated as a limitation
The operations document opens by saying the ledger is run by a single operator, that this is a real limitation, and that the mitigations are disclosed rather than hidden.
It then names the problem precisely. The operator holds the database service role, the publishing credential and the salt store. Somebody with those keys could disable the triggers, rewrite rows and re-enable them.
And then it names what the operator cannot rewrite: Bitcoin attestations already published, the anchor chain as it exists in customers’ clones, and the disclosure record itself. Anchoring is automated rather than ceremonial, so publication does not wait on a person remembering.
That is what a threat model looks like when it is honest. Assume the operator is the adversary — because to a desk evaluating a vendor, the operator always is — then state which properties survive that assumption and which ones simply do not.
Where it actually stands
The specification, the verifier and its release registry, the key history, the payload schemas and the disclosure policy are published now. The alpha record is sealed at 29 anchors with its incidents and its post-mortem attached.
The official chain has not opened. Its first anchor is still ahead of it, one sport first and the others joining as their seasons begin, and nothing has been anchored between the alpha’s last day and that first anchor. The gap is written into the README as a fact rather than smoothed into a continuous-sounding history.
I am stating that plainly for the same reason everything else here is built the way it is. Whether a chain is live is a fact about the world at a moment in time, and it is false right up until it is true. The way to make that claim is to publish the thing and let somebody check, which is what the next paragraph is for.
Check it yourself
Clone the repository, run the self-test against the golden vectors, then walk the chain. Neither needs a network connection or anything from me. Read the methodology, then read the list of what it does not prove. Read the incidents, including the one that is still open.
If any of it fails, that is a finding you can publish, and I would rather you found it than that nobody looked.