Provenance over Titles

The correctness chapter: re-ingest that cleans by provenance, a repair that reloads what it writes, and a reindex that finally rebuilds from the vault.

The features were done. Then cleaning the wiki—re-ingests, re-sorted topics, repaired links—quietly destroyed it. The bugs behind that share one root, and the root is the most important lesson of the project.

Identity Is Provenance

The first re-ingest duplicated everything: new atoms, new pages, no recognition of the old ones. The fix became a principle rather than a patch—identity is provenance, never a title. A re-ingest deletes every claim the source produced and every page that referenced one of them, matched by provenance, then re-derives from scratch. A title match exists nowhere in the pipeline.

If the wiki is a product, it has to hold together. A link pass runs after every compile, reclassify and cleanup, filling each link’s target by exact title, then by slug—so a link to a page nobody wrote becomes a red link, and a red link is a lint error, not a cosmetic warning. Cleanup is the deterministic repair: it drops pages that cite no source, fills missing tags from the pages’ own claims, and rewrites ghost links until none survive.

One Root Cause, Five Symptoms

Then the real one. Save<Entity> treats the entity as authoritative for its child rows—it deletes and re-inserts tags, atoms and links—while the read paths were split: some hydrated a page’s children, some returned only the row. Every “load, change one field, save” therefore destroyed whatever had not been loaded.

  • Cleanup’s tag backfill wiped a page’s atoms and links; the next cleanup found a page citing no source and deleted it.
  • Reclassify changed a topic and erased the link graph of every page it moved. That is why ghost links seemed to return: the links were being deleted, not repaired.
  • Trimming a page after its source changed lost the same data.
  • GetNote never hydrated links, so any single-page read was partial.
  • Atom tags were never hydrated by the list mapper, so tag inheritance only worked in tests.

The fix is a single API: UpdateNote(id, mutate)—load hydrated, apply, save. Every repair and move path uses it, and the contract is written down: a single-entity read is hydrated, partial changes go through update, and an entity is never built from a list read.

Reindex Was Never a Rebuild

The spec says the index is disposable. It was not: reindex reconciled sources/ and nothing else, so a drifted index could not be repaired and the damage above looked permanent. It now walks atoms/ and wiki/, re-reads tags, provenance and links from the files, writes them back and resolves links. The vault is the truth, so the index can genuinely be thrown away—and the destruction was recoverable because the Markdown was never what broke.

The Lesson

A repair that rewrites a record must first reload everything that record owns. Three regression tests guard it now: a mutation preserves children, the tag backfill no longer erases provenance, and a reindex restores provenance, tags and backlinks from files alone.


Every bug in this chapter is a partial view treated as the whole: a title used as an identity, a cached row used as the record, a read used as a snapshot that may be written back. The chapter’s repair is one rule—identity comes from provenance, and a write must reload everything the record owns. Reindex stops being a claim and becomes a guarantee, because something outside the index is now provably authoritative. A cache is only disposable when its source of truth is.

AI Insights: Provenance over Titles
Source Code: MPL-2.0 · Content: CC BY-SA 4.0 · Credits & AI Disclosure