building vodou.

Every copy of the rule agreed. That was the bug.

Four surfaces minted the same token four different ways, and all four agreed. How we found the drift, why the tests couldn't see it, and the guard that now can.

Chad Priest / / 9 min read

Any AI system past a certain size has the same rule written down in more than one place. A slug format. A status word. A receipt string. A retry policy. You wrote it twice because the two surfaces are in different languages, or different processes, or one of them ships to a browser and the other doesn’t. You left a comment saying MUST match.

Both copies agree today. That is not a contract. That is a coincidence you have not audited yet.

I spent three days in August auditing every one of those coincidences in Vodou. Thirty-two commits, eighty-seven files. Almost none of it was new features. The interesting part is what the audit found, and how much of it no test could have caught.

A finding is a number in FINDINGS.md, and walks are immutable

The unit of work is a finding. Someone walks a flow end to end as a user would, writes down everything that contradicts something else, and the contradictions land in a FINDINGS.md with a number. Walks are immutable once written. Fixes cite the number in the commit subject: fix(coherence): F13 — the @doc: token is minted once, by the code that resolves it.

There is no coherence module. No vodou-core coherence command. No scheduled job. It is a documents-and-commits discipline, and the only executable parts are at the two ends: a lab that breaks the product on purpose, and a commit guard that refuses the specific defects the audit proved were real.

enforceablemeasurableWalk a flowas a user, not as an authorFINDINGS.mdnumbered, immutableFix cites F-numberCommit guardif the rule is mechanicalInstrumentif it needs live evidenceA finding that ends at the fix comes back. A finding that ends at a guard does not.
What happens to a finding

The slug rule was written four times, the fifth copy was the test

The slug rule was written four times, and the fifth copy was the test.

Vodou resolves @doc:contract-2026 tokens to attached documents. The slug rule, how a filename becomes a token, lived in the resolver, in the library page, and twice inline in the browser panel, once for the documents lane and once for the page-memory lane. I had expected two copies. There were four.

The fifth was in the end-to-end test, in a comment that read verbatim from sidepanel.js. That test could only ever prove that two copies of one expression agree with each other. It had passed every day of its life and was structurally incapable of failing for the reason it existed.

All four product copies agreed. That is the whole problem. The day one drifts, the failure is silent and total: a token minted one way and resolved another names a document that does not exist, so the system tells you it attached your contract and then answers about something else.

Synchronising the copies would have been the wrong fix. I deleted the deciders instead. /api/library, /api/library/match and /api/page-match now mint the slug with the resolver’s own function and ship it on the row. The surfaces paste what they were handed. Where a client needs a fallback, it falls back to the id, which the resolver already accepts.

A test that goes red because the code improved is measuring the wrong thing.

The same week I found three byte-identical copies of the phrase 4 memories · 2 tools · 1 skill: one in the content script injected into ChatGPT, one in the panel, one in Console Two. Each built the counting and the pluralisation itself.

There was already a parity test for this. It grepped the panel’s render function for three ternaries. When I moved those rules into a shared receipt.js, the test went red, not because the receipt was wrong, but because the ternaries had moved somewhere better. I had written a test that asserted the location of a bug’s workaround. It had to be replaced, not repaired.

@doc: slug format4 copiesreceipt phrase3 copies
Independent implementations of one rule

The lab I built to test broken states was not isolated.

Phase 0 was scripts/broken-lab.sh: induce daemon-down, empty-account, unreadable-db, no-memory in a throwaway instance, then print what every surface says in each, verbatim and side by side. It found three real defects in the first hour. The best one: a status command was reporting the health of a different installation entirely, so it answered “healthy” for a stack that was face down.

The first version of the lab also wrote to the live database, health-checked a stranger, and leaked eight daemons. A harness that claims isolation has to prove it on every run, not in its README. It now owns its project root, its DBs and its socket, calls no start or stop script, and kills no pid it did not spawn.

One word meant five things, and two of my renames were wrong.

The vocabulary pass was the one I thought would be mechanical. It was not. Two of the renames changed a word that was correct in its context, and I had to back them out. A separate finding, F43, turned out to be false: the contract I claimed was missing already existed, and what had actually failed was the guard’s ability to see it. Another was already fixed months earlier, and the only real defect left was a report implying otherwise.

Roughly one finding in six did not survive contact with the code. If your audit has a 100% confirmation rate, you are writing down conclusions, not observations.

Two questions rode one exit code.

runtime-status printed Overall: down and exited 0. mem search exited 1 for the same condition. So vodou-core runtime-status && start_work walked straight into a dead stack.

They disagreed because the exit code was carrying two different questions, did the command work and is the subject healthy, which are not the same answer for a reporting command. They now get separate codes, the way systemctl has always done it.

# 0 = healthy   2 = reported, and unhealthy   1 = the command itself failed
vodou-core runtime-status; case $? in
  0) start_work ;;
  2) echo "stack is down; the report is trustworthy" ;;
  1) echo "could not determine anything" ;;
esac

An unknown verdict is never healthy. Finding the two existing callers and teaching them this was half the work: both read any non-zero as “the probe is broken”, so one printed a fallback one-liner underneath a complete report, and the other discarded the payload and said “unavailable” for a stack that had just described itself in detail.

The version string was never going to answer “which build is this”.

We once lost 38 hours to a daemon quietly running the previous build. Both sides reported the same version. Three of our extension folders still do. What actually discriminates a build is the file it was loaded from, so that is what every long-lived process now reports: path, size, mtime, and whether debug assertions are compiled in. One command cross-checks them.

The ordering is load-bearing and easy to get backwards. The binary swap does rm then cp, so an identity read lazily at status time would describe the replacement and report a stale process as current. It has to be stamped at process start or it is worse than nothing.

08-20walk finds a down-messagepointing at a menu barthat does not exist08-21build identity, receiptsharing, slug decidersdeleted08-22lab leaked 8 daemons;isolation asserted perrun08-22rules become a commitguard, 11 hermetic cases

More than one implementation is an outage with a delay fuse

A rule with more than one implementation is an outage with a delay fuse. The copies agree right up until someone improves one of them, and improvement is exactly what your review process rewards. So the fix is never “sync the copies”. It is: delete every decider but one, and have the others paste what they were handed.

Where you genuinely cannot share code, different language, different runtime, ships to a different place, you do not get a contract, so buy the next best thing: a test that reads both implementations from their real locations and asserts they produce the same output for the same input. Not one that greps one of them.

And check what your parity tests actually compare. If a test’s fixture was copy-pasted from the implementation, it is a mirror, not a check.

Four slug copies lived under a comment saying MUST match

The instinct is to solve this with review prose: a CONVENTIONS.md, a comment reading MUST match, a checklist item. We had all three. The four slug copies were written under a comment saying they must match.

The working idiom in this repo is commit guards, and the audit’s mechanical rules became one: scripts/coherence-guard.py, with scripts/test-coherence-guard.sh covering eleven hermetic cases. It reads staged added lines on shipping surfaces only, and blocks three things the audit proved were real: an internal enum reaching textContent untranslated; a *_count that is displayed but that nothing ever writes; an iframe framing one of our own surfaces with no declared min-width. When a flag is deliberate you say so in the diff, in a comment the guard reads.

The second half is measurement, because some rules are not statically checkable. Grading has to compare two records of one event: capture-to-searchable latency per lane, a receipt’s claimed count against the ids it actually recorded, last_run_at against the newest run row. A grader with no evidence answers unknown, never ok. That distinction is the whole point. “Nobody looked” and “fine” are different states, and only one of them is safe to ship on.

Console Two cannot import the shared receipt module

Console Two is a separate codebase and cannot import the shared receipt module, so that phrase still has two homes and a parity test standing between them.

The guard only sees staged added lines on four directory trees. Move a violation into an unlisted path and it passes.

Committed source and the committed binary in this repo drift apart. A fix can be at HEAD and still not be what the machine runs. Build identity makes that visible now; it does not make it stop.

And one measurement I would rather not have: an extraction rule nobody had ever checked is holding at 30%. It is written down, numbered, and open.