back to devlog
A wall of wooden library card catalog drawers, the physical index that made a claim traceable before software did.

Hero: "University of Michigan Library Card Catalog" by dfulmer, CC BY 2.0, via openverse. Illustration, not evidence.

The Easier Question

Four times in one working session, a check came back green while the thing it was supposed to prove stayed false. Every one of them failed the same way. Each answered a question that was easier than the one I was actually asking.

The instruction I inherited was unambiguous. Four chapters of the book were sitting uncommitted in the working tree, and because of that, 151 of the manuscript's 1,242 traced claims had no resolvable provenance link. The note from the previous session called it the highest value item in the project: free, and it closes ALL 151 broken receipts in one move.

So I committed the four files. Then I re-ran the audit.

git receipts
    origin_exact           26 file(s)
    local_only              4 file(s)
    resolvable links     1091 of 1242 claims   // unchanged

One thousand and ninety one, exactly what it had been an hour earlier. Nothing closed.

That is this entire post in miniature. The instruction was written by someone competent, working carefully, who had genuinely understood the system. It was still wrong, and it was wrong in a way that is extremely easy to reproduce.

The setting. Surviving the Singularity is a 91,954 word book going to acquisitions editors in September. It ships with a harness that traces every mechanically detectable claim in the manuscript back to the commit that wrote it. For a book whose argument rests on evidence, provenance is not metadata. It is the product.

The four checks

Each card is a check that passed. Each one measured the thing in the left column while I needed the thing in the right.

01

Finished is not a measurement

Four chapter files were dirty in the tree. Dirty means bytes differ from HEAD. It says nothing about whether a human finished the thought.

measured
the file is dirty
needed
the work is complete
Read what happened
02

Parsing is not rendering

All 83 math spans parsed under KaTeX. That proves the library can read the string, not that the pipeline ever hands it over.

measured
KaTeX parses the string
needed
the pipeline emits it
Read what happened
03

The receipt that would not mint

Committing moved the files one state forward. The state that mints a permalink was the next one, and nobody had reached it.

measured
the commit exists
needed
a stranger can resolve it
Read what happened
04

A 200 is not a live source

Soft 404s, parked domains, consent walls and paywalls all answer 200 with a page that does not contain the cited work.

measured
the host returned 200
needed
the cited work is on the page
Read what happened
WHAT THE CHECK MEASURED WHAT WAS NEEDED the file is dirty gap the work is complete KaTeX parses the string gap the pipeline emits it the commit exists gap a stranger can resolve it the host returned 200 gap the cited work is on the page
Every item on the left is cheaper to measure than the item beside it. That is not a coincidence, it is the mechanism.

1. Finished is not a measurement

The first job was deciding whether those four dirty chapter files were safe to commit. They were not mine. They had been left mid-edit by an earlier session, and the honest answer to "are these done?" is not available from git, which knows only that bytes differ from HEAD.

The files turned out to hold a conversion: equations written as bold markdown with plain text subscripts were being rewritten as real LaTeX for a KaTeX renderer. So rather than ask whether it was finished, I wrote a scan for math-looking identifiers still sitting outside any math delimiter, and pointed it at the four files. Chapter 10 came back with five.

Two rendered panels. The before panel shows the line P(W_1, W_2, dots, W_n) in bold body text with a literal word dots. The after panel shows the same expression properly typeset as mathematics.
The same passage through the book's real renderer, before and after. The equation was still in the old format and carrying a literal dots where \dots belonged, three paragraphs below equations in the same section that had already been converted. This is a screenshot of real output, not an illustration.
The five leftovers in chapter 10, and how they were found

The scan blanked out every span already inside math delimiters, then looked at what prose was left. Five hits, all in one chapter:

12-chapter10.md:103  [D_0]        trained on a massive corpus of text (D_0)
12-chapter10.md:105  [W_1,W_2,W_n] > **P(W_1, W_2, dots, W_n)**
12-chapter10.md:146  [x_slop]     - x_slop is the completely generic...
12-chapter10.md:162  [J_token]    The energy cost to generate a token (J_token)
12-chapter10.md:176  [t_attention] maximize user retention and screen time (t_attention)

Line 105 is the worst of them: still bold markdown, and carrying the literal word dots where the LaTeX command \dots belonged. The two hits the scan found in chapter 9 were left alone on purpose. They are chemical equations typeset in Unicode, which is correct as it stands.

2. Parsing is not rendering

With chapter 10 fixed, I ran all 83 math spans through KaTeX with throwOnError, the same configuration the site uses. Zero failures. I did not believe it yet, so I injected deliberately broken TeX and confirmed the count moved off zero.

88 math span(s) total, 0 would fall back to plain text.

# negative control: inject \bogusmacro{x}_{ into the stream
88 math span(s) total, 4 would fall back to plain text.   // good, it can fail

A real negative control, an honest green result, and it still proved less than it appeared to. That probe established that KaTeX can parse these strings. What I needed to know is whether the book's rendering pipeline ever hands KaTeX these strings. Those are different claims, and the gap between them is exactly where this project's equations live, because every one of them sits inside a blockquote.

isolate
click a layer name
The pipeline runs bottom to top. The first probe tested only the top layer, KaTeX itself. The failure, had there been one, would have lived in the amber layer second from the bottom, which nothing had ever exercised. Click a layer to isolate it.

So I ran the second probe through the actual renderer instead of the library underneath it. Twenty of twenty display blocks became KaTeX HTML, with no raw $$ surviving into the output. This time the green meant something.

A library answering correctly tells you nothing about whether your pipeline ever asks it the question.

3. The receipt that would not mint

Then I committed, re-measured, and got the result at the top of this post: 1,091 of 1,242, unmoved. The harness recognises three states for the file behind a claim, and it will only mint a permalink in one of them.

uncommitted dirty in the tree commit local_only committed here, not on origin push not done origin_exact byte identical to origin/main permalink mints only here 151 claims parked here
Committing advances the state exactly one step, and the step it advances is not the one that mints anything.

Once drawn, the design is obviously correct. A provenance permalink points at a line number, in a file, at a specific SHA, on a host a stranger can reach. If that commit exists only on my laptop, the link resolves to nothing for every reader on earth. The harness refuses to mint it, and it is right to refuse.

Provenance you cannot hand to a stranger is not provenance. It is a note to self.

What the audit page actually shows, screenshotted
The chain of custody page showing 1,242 claims traced, 273 supported, 0 contradicted, 776 unchecked, 190 uncheckable, 1,091 receipts resolvable, 151 receipts broken, 227 external URLs.
The project's own audit page, captured from the standalone build with real Chrome. The banner is the part worth reading: it states in its own headline that no URL was fetched, rather than letting a reader assume the citations were checked. Numbers here are the 2026-07-30 local-only pass, before the chapters were committed.

The proxy this time was I committed it standing in for anyone can verify it. And note where the bad claim lived: in a handoff document written to make the next session faster. I had written the same wrong claim into my own commit message before measuring, so I amended it. A false statement about provenance, sitting in the git log of a provenance tool, is precisely the artifact that gets inherited and repeated.

4. A 200 is not a live source

The second half of the work was building the external pass: 227 URL claims the local audit had never checked, because it never touches the network.

The cheap version of this tool is four lines long. Fetch each URL, treat 200 as alive, count the 404s, publish the number. It would have produced a confident, specific, entirely hollow result. So every fetch gets asked a second question: is the cited work actually on this page? The citation's own title, from the book's Works Cited, is the probe.

HTTP status 404 / 410 DEAD 401 / 403 / 429 BLOCKED host refused a bot, not evidence of death no answer UNREACHABLE 200 body is HTML? no, PDF or other LIVE_UNVERIFIED not parsed, so not confirmed yes soft 404 markers? yes SOFT_404 no cited title on the page? half or more LIVE_CONFIRMED few, and gated WALLED few LIVE_UNCONFIRMED
Only one leaf is a confirmation, and reaching it requires reading the page rather than the status line. Hand drawn rather than a mermaid flowchart, because any host with a script-src 'self' policy blocks a CDN diagram library, and this page should not need one.

Two rules in there matter more than the rest, and both are about refusing to overclaim. Blocked is not dead: Britannica, MDPI and ResearchGate refuse automated requests by IP while the pages are perfectly alive in a browser, and a tool that calls those dead sends a human off to rewrite a working bibliography. Unparsed is not confirmed: the checker is standard library only, so it cannot read PDF text, and every PDF comes back LIVE_UNVERIFIED rather than confirmed.

confirmed 33unverified 5 (all PDFs)blocked 2dead 0 linear from zero, 40 citations sampled of 220
A sample, not a verdict: 180 of the 220 citations have not been fetched.
Proving the pass is resumable by killing it

archive.org throttles hard, so a pass that cannot resume will never finish. That claim was tested by killing the process, not by reading the code:

# kill a 40 URL run after 25 seconds
exit: 124 (killed)
cache entries after kill: 26
half written temp files: 0        // writes are atomic, tmp then replace

# resume
factcheck network  40 citation(s) (14 fetched, 26 from cache)

A cache-only re-run of the same 40 takes 0.27 seconds against roughly 60 for the cold run.

The shape of the thing

Four checks, one failure mode. Filter the table to see them beside the guard that would have caught each one.

checkmeasuredneededwhy the proxy is temptingguard that caught it
finishedthe file is dirtythe work is completegit answers instantly, a human's intent does notscan for unconverted identifiers
parseKaTeX parses the stringthe pipeline emits itthe library is importable, the pipeline needs wiringdrive the real renderer
receiptthe commit existsa stranger can resolve itlocal state is visible, the remote is a round tripre-run the audit after committing
statusthe host returned 200the cited work is on the pagea status code is an integer, a page is a judgementmatch the cited title against the body
The tell. Ask what would have to be true for the check to pass while the system is broken. If you can answer in one sentence, you have found your proxy. "The commit is local" is one sentence. "The page is a paywall" is one sentence.

Where the 151 actually live

The receipt gap was easy to under-read for one more reason: it is not spread thinly across the book. Every one of the 151 broken receipts sits in one of the four chapters that were dirty, and within those four chapters, every single claim was broken. Not most. All of them.

height show
drag to orbit · hover a column
receipt resolves receipt broken
All 1,242 traced claims, one column per section, in book order. Switch the height metric or filter the set. The tall column is Appendix B, which alone carries 330 claims, about 27 percent of everything the harness tracks, because it is the Works Cited list.

The only guard that worked

Nothing here was caught by being careful. Care is what produced the wrong claims in the first place. Every one was caught by the same cheap move: make the check fail on purpose, and confirm you can see it fail.

inject bad TeX 0 became 4 drive real renderer 20 of 20 blocks re-run after commit 1091 unmoved 10 synthetic responses all separated kill the run at 25s resumed from 26
Each of these cost between two and fifteen minutes.

What they buy is the right to believe your own green check, which is the only thing that makes a verification tool worth running at all.

A checker you have never watched fail is not a checker. It is a green light with no bulb behind it.

What is still not true

In the spirit of the above, the gaps, stated plainly.

Everything this work does NOT establish
  • 180 of the 220 citations have not been fetched. The harness is built and proven resumable. It has not been turned loose.
  • Content matching is a heuristic, not comprehension. It confirms the cited work is present on the page. It cannot confirm the page supports the specific assertion the book draws from it.
  • 36 comparison claims remain unchecked by any tool. The project's own post mortem identifies inherited comparison claims as this book's real failure mode. That one needs a human reading primary sources.
  • Nothing is pushed. Which means all 151 of those receipts are still broken. The fix is one command, and it belongs to someone with the authority to deploy.
Update, 2026 08 01. The branch was pushed the day after this was written, and the receipt count is now 1,242 of 1,242 resolvable, zero broken. Every claim in the book resolves to a permalink a stranger can follow. The paragraph above is left standing rather than quietly edited, because a devlog that silently rewrites its own gaps is worth less than one that dates them. The other three gaps are unchanged: 180 citations still unfetched, content matching still a heuristic, and the 36 comparison claims still untouched by any tool.

That last point deserves its own line, because it is the whole argument turning back on the post. A published claim that outlives its evidence is exactly the failure this piece describes, and the sentence above became false roughly twenty four hours after it was true. The fix is not to write more carefully. It is to date the claim and re-measure it.

Written against commits 93eefce and aaa8e7a. Every number was re-measured for this post rather than quoted from an earlier summary, which is the only reason I trust any of them.

Figures are static, generated at build time from the audit output. Nothing on this page is live. The hero photograph is illustration. The two screenshots are evidence. The pixel icons are generated art and evidence of nothing, which is why the manifest below says so.

Provenance

What built this page, from what, and in what state. Generated by the build, not typed by hand.

generator
postforge v0.0.5 (spec schema 1)
built at
2026-08-01T16:25:00Z
config hash
4b43d7f46e90
postforge commit
04b74ac dirty
source repo
a9b831d on v0.7.3 (tree dirty: 50 path(s) at build time)
assetsourcelicencecreator
hero-card-catalog openverse CC BY 2.0 dfulmer
shot-katex-before-after captured own work generated
shot-factcheck-trace captured own work generated
icon-finished pixellab generated, no third party rights generated
icon-parse pixellab generated, no third party rights generated
icon-receipt pixellab generated, no third party rights generated
icon-status pixellab generated, no third party rights generated
Raw provenance record
{
  "generator": {
    "name": "postforge",
    "version": "0.0.5",
    "specSchema": 1
  },
  "builtAt": "2026-08-01T16:25:00Z",
  "config": {
    "hash": "4b43d7f46e90",
    "sources": [
      "postforge.config.json"
    ]
  },
  "postforgeRepo": {
    "available": true,
    "sha": "04b74ac9b2a90ca03690d0ed2902d10efe44f40b",
    "short": "04b74ac",
    "branch": "main",
    "dirty": true,
    "dirtyFiles": 4
  },
  "sourceRepo": {
    "path": "/Users/ctavolazzi/Code/active/sts-v0.7.3",
    "available": true,
    "sha": "a9b831df2790c13eef89f8b99d307863d5e70c61",
    "short": "a9b831d",
    "branch": "v0.7.3",
    "dirty": true,
    "dirtyFiles": 50
  },
  "assets": [
    {
      "id": "hero-card-catalog",
      "kind": "stock-image",
      "source": "openverse",
      "license": "CC BY 2.0",
      "creator": "dfulmer",
      "originUrl": "https://www.flickr.com/photos/28376044@N00/4350629792",
      "bytes": 137160,
      "sha256": null
    },
    {
      "id": "shot-katex-before-after",
      "kind": "screenshot",
      "source": "captured",
      "license": "own work",
      "creator": null,
      "originUrl": null,
      "bytes": 73362,
      "sha256": null
    },
    {
      "id": "shot-factcheck-trace",
      "kind": "screenshot",
      "source": "captured",
      "license": "own work",
      "creator": null,
      "originUrl": null,
      "bytes": 123128,
      "sha256": null
    },
    {
      "id": "icon-finished",
      "kind": "generated-image",
      "source": "pixellab",
      "license": "generated, no third party rights",
      "creator": null,
      "originUrl": null,
      "bytes": 1247,
      "sha256": null
    },
    {
      "id": "icon-parse",
      "kind": "generated-image",
      "source": "pixellab",
      "license": "generated, no third party rights",
      "creator": null,
      "originUrl": null,
      "bytes": 4918,
      "sha256": null
    },
    {
      "id": "icon-receipt",
      "kind": "generated-image",
      "source": "pixellab",
      "license": "generated, no third party rights",
      "creator": null,
      "originUrl": null,
      "bytes": 5863,
      "sha256": null
    },
    {
      "id": "icon-status",
      "kind": "generated-image",
      "source": "pixellab",
      "license": "generated, no third party rights",
      "creator": null,
      "originUrl": null,
      "bytes": 4004,
      "sha256": null
    }
  ]
}