Case 69/58: the mirror that reverted its own work

The tool said 69. The disk had 58. Nobody compared the two — for four weeks

Jesús Pérez
Eleven ADRs written and never published. Zero content reaching production. 214 lines of work one command away from vanishing. And not a single error in the logs: the tool reported success and nobody read the disk. The series asks 'with ontoref, would it not have gone like this?' — and this is the case that answers no, because it happened inside ontoref with the rule already accepted.
Case 69/58: the mirror that reverted its own work

🩺 Show the full clinical history → 📋 Session protocol →

Clinical history · Software Pathology

The patient had no symptoms. It published without errors, the tool confirmed success, the logs came back green. It had been publishing into the void for four weeks.

History No. 69/58Diagnosis: ANTI-PAP · THE REPORTER DECIDEDStatus: SOLVED
Show clinical picture
PAP
Project's Architecture Principles — the rules that hold the architecture up: the single source of truth all code must respect.
anti-PAP
Code written against those rules. Here: a mirror that decided by direction instead of by ownership.
the check decides, never the reporter
ADR-066, accepted 4 Jul 2026: wherever a mechanical check exists, the check decides; the word of whoever reports may refuse to be contradicted, but may never impose itself. This case file is what happens on the one surface where that rule was not applied.
witness
An independently verifiable record that something is what it claims to be. A backup with no witness is not a backup: it is a copy you trust.

The protocol to declare, version and verify this → ontoref.dev

The double ledger — what it cost, and what it left

What the crime cost

  • What the tool reported "index.json with 69 posts"
  • What was on the disk 58
  • ADRs written and never published 11 (059→069)
  • Window of invisibility 13 Jun → 11 Jul (the ADRs' own dates)
  • Content that reached production in that time 0
  • Files the site repo had tracked 1 (README.md)
  • git_sha stamped on EVERY deploy pack 5619a40 (always the same)
  • Live lines of work with not one witness 214
  • Commands away from erasing them 1 (<span class='mono'>just templates</span>)
  • Size of the final fix 60 <i>lines</i>

What the case left

  • Decision ADR-070 (accepted)
  • Gates adr-check · templates-check · posts-check
  • Migration 0044 (anchored paths)
  • The slot that was missing site/_htmx/templates/
  • Constraints curated on anchoring 29 (88✓ → 117✓)
  • Ontology the converse in enforcement-vs-emergence

The point of abandonment — what the table doesn't show

This case’s point of abandonment did not arrive with an error, but with a sentence: “I’ve gone crate by crate through a complex codegen system and I’m well past the reasonable point to keep going blind.” That is what a machine says when it runs out of map and keeps walking. And the answer that unblocked it was not technical: it was someone drawing the hierarchy of levels —rustelo → website-htmx-rustelo → outreach/site— that existed in one head and in no file. That is the whole diagnosis, spoken before it was had: whatever is not declared, someone is holding up with their memory. And memory tires.

Differential diagnosis — what was ruled out

The 6 orphans in site/r“Yes, I'm dead content still being served. But I don't stop anything from being published.”symptom, not cause
gen-adr-pages broken“I work perfectly. I generate all 69 ADRs whenever anyone calls me.”innocent — nobody was calling it
content_processor doesn't process case files“I do process them. What happened is that you read me with a head -20 and it cut my output off.”solid alibi (the error was the investigator's)
The index isn't regenerated“I regenerate every time. With 69. What happens afterwards is no business of mine.”innocent — and the key to the case
The graph generator“I export what's in the content tree. If you file a contract in among the data, I blow up. And rightly so.”innocent — it was right

Etiology — the cause — The mirror that decided by direction, not by ownership

# The `content` recipe, exactly as it stood:
content_processor                                     # writes the indexes into site/r
rsync -a "site/public/r/" "site/r/"                   # ← the weapon

# Two trees. Each one the OWNER of different files:
#   site/r         ← content_processor writes here (the server reads it)
#   site/public/r  ← the nu generators write here (and this is what the deploy SHIPS)
#
# The rsync ran one way, and in the wrong way:
#   · it trampled the freshly generated indexes with fossils from the deploy tree
#   · and it NEVER carried the new content to the tree that travels to production
#
# rsync -a without --update does not honour "the destination is newer": it compares size
# and mtime, and if they differ, IT COPIES THE SOURCE. A mirror whose source has fossilised
# is not a no-op: it is a machine for resurrecting old state.
#
# The tool printed "Generated index.json with 69 posts".
# The disk kept 58.
# Nobody compared the two.

just — the content recipe

Treatment — The mirror is typed by ownership, and the check reads the disk

content_processor
# Content indexes → deploy tree. --delete: without it, a renamed slug
# survives as a live URL IN PRODUCTION.
rsync -a --delete \
    --exclude about.json --exclude adr-map.json \
    --exclude taglines.json --exclude content_graph.json \
    "site/r/" "site/public/r/"
# The four nu artifacts → served tree. They are excluded from the outbound leg, or the
# stale copies in site/r would clobber the freshly generated originals.
for f in about.json adr-map.json taglines.json content_graph.json; do
    cp -f "site/public/r/$f" "site/r/$f"
done

# And three gates that ASSERT AGAINST THE DISK, never against the tool's stdout:
#   adr-check        the spine holds no ADR the site does not publish
#   templates-check  the assembled tree reproduces exactly from its declared source
#   posts-check      linkify + graph coverage
#
# All three can be run WHILE the damage is still hypothetical.
# A check you can only run after the destructive step is not a gate:
# it is an autopsy.

just — the content recipe

Prognosis

The series asks one question: with ontoref, would it not have gone like this? Almost always the answer is yes, and that is why there is a series. This case file is the one that answers NO — and that is why it is worth the most. This happened INSIDE ontoref, with the ontology loaded, the gates written and ADR-066 —“the check decides, never the reporter”— accepted a week earlier and proven with an eight-path falsification run. The rule that names this exact pathology already existed, while the pipeline that publishes ontoref to the world went on letting the reporter decide. The protocol did not fail: outreach was the blind spot, the one surface the protocol did not govern. Every mechanism that would have caught each one of these failures already existed and was accepted in this very repo. Not one of them was pointed here. And that is the useful answer, the one a comfortable case never gives: having the mechanism is not enough — you have to point it at the surface that hurts. An enforcement you cannot trust to fail is indistinguishable from having no enforcement, and it is more dangerous, because it gets believed.

The mirror ran by direction, not by ownership of each filedeclared state → ADR-070: every tree declares its OWNER
The tool reported success and nobody checked the diskreview against invariants → ADR-066: the check decides
A generator existed and no recipe ever called it (11 invisible ADRs)declared state → ADR-070: the REPRODUCTION, in the chain
214 lines lived in the one tree the build wipesdeclared state → ADR-070: every tree declares its WITNESS
A contract filed among the data it types (just graph dead)PAP + anti-pattern → contract-in-the-instance-tree
A check meant one thing or another depending on where you launched it fromreview against invariants → migration 0044: anchored paths

Prophylaxis — what enforces the lesson today

  • Every delivery tree declares its owner and witness; a mirror runs by ownership, never by addressadr:070/mirror-typed-by-ownership
  • A generator no recipe calls does not exist: it enters the chainadr:070/generator-in-the-chain
  • The contract lives outside the instance tree it typesadr:070/contract-in-the-instance-tree
  • The served tree is the only one that exists, and the check compares the twoadr:072/the-served-tree-is-the-only-one-that-exists
  • In the chain, with its negative case per axisgate:just expedientes-check

Glosario

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.