# E-032 — Generated mirrors drifted in two days because no check enforced the rule

- Date: 2026-08-04
- Status: organic signal; correction applied in the same round.

## Hierarchy and trajectory

- Parent objective: `O-ADOPT`, make Objective Guard adoptable by newcomers and coding agents through English-first documentation.
- Previous round: `R-EVIDENCE-CONTEXT`, closed and archived from a clean tree.
- Current round: `R-DOCS-EN-FIRST`, promote English documentation to canonical with a Spanish counterpart.

On 2026-08-02 a changeset generated English mirrors of every document under `docs/en/` and added a rule to `AGENTS.md`: *"Any edit to a canonical Spanish doc must regenerate its English mirror in the same changeset. A stale mirror is a bug."*

Two days later, when English was promoted to canonical, the state of those mirrors was measured:

- 8 of the 12 mirrored product and functional documents were stale relative to their Spanish source;
- 26 documents had never been mirrored at all (`product/07-phase-1-calibration-protocol` and the 25 files under `docs/episodes/`);
- the mirrored sources had grown substantially — `02-product-contract` went from 837 to 2345 words, `04-functional-test-matrix` from 860 to 2037;
- the mirrors lived on a separate worktree branch that was never merged into `main`.

## Expected and actual behavior

The rule existed only as prose in `AGENTS.md`. No check enforced it, and the documentation guard's contract only knew the Spanish topology. So `npm run validate:docs` returned PASS in every intermediate round while the documented invariant was already violated.

The guard behaved exactly as configured. It was never asked to verify mirror freshness, so its `pass` said nothing about it.

## Evidence

- `git log`: mirrors generated in `81c5025` (2026-08-02 20:39), sourced from `a514bf2` (20:25);
- `git diff --stat a514bf2..main -- docs`: 35 files changed, 1247 insertions after the mirrors were written;
- word counts of Spanish sources at `a514bf2` versus `main`: 837 → 2345, 638 → 877, 1642 → 2247, 860 → 2037;
- `docs/en/` contained no `episodes/` directory and no mirror of `product/07`;
- every intermediate round closed with `npm run validate:docs` passing.

## Classification and limits

This is a **missed signal caused by an undeclared check**, not a false negative of an existing detector. No detector was bypassed and no result was mislabelled; the invariant simply had no reproducible representation.

It is consistent with E-004 (an absent signal does not prove alignment) and E-016 (a criterion that is not declared is not verified). It is not evidence that the guard is insensitive to what it does check.

## Correction of direction

1. English promoted to canonical at the canonical paths; the Spanish version moved beside it as `<slug>.es.md`.
2. `scripts/validate-docs.mjs` rewritten to enforce pairing mechanically: a missing Spanish counterpart, an orphan Spanish document, a stray file at the `docs/` root, or a link that crosses language now block the build.
3. Structural contracts are now verified in both languages — product headings, capability fields, matrix columns and confidence markers — so a counterpart cannot silently lose structure.
4. The `AGENTS.md` language policy was rewritten to describe the new canonical direction, and the guide layer gained a template plus an index requirement so a new harness guide cannot ship a different shape.

Two secondary findings came out of running the new checks:

- The language-crossing rule produced **4 false positives** on its first run, flagging the deliberate language-switcher link each guide carries to its own counterpart. The rule was narrowed to allow a link to the document's own counterpart and nothing else. A rule written in the same session that motivated it still needed one adversarial run before it was correct.
- The episode index was found to be missing E-004 and E-005 — a pre-existing gap, unrelated to the language work, that no check covered either. The guard now requires every episode file to be listed in the index of its own language, and the missing rows were added.

## Impact on the thesis

It supports a narrow and useful claim: a rule written in an agent instruction file is documentation, not a control. An invariant becomes real only when a reproducible check runs inside the round and can fail it.

It does not show that the guard prevents drift. Here the drift was found by hand while restructuring, not by the guard. The correction moves the invariant from prose into a check so that the next occurrence is observable — that expectation is untested until it actually fires.

## Verifiable follow-up

- Pairing is enforced; **freshness is not**. Two paired files can still diverge in content, which is the exact failure this episode records. Decide between a per-pair source hash marker and a translation-status ledger, and declare the limit explicitly until one exists.
- Add a fixture pair that is deliberately stale and confirm the chosen mechanism fails on it, before claiming the invariant is covered.
- Decide the fate of the superseded `docs/en/` worktree branch so a second, unguarded copy of the documentation cannot be revived by accident.
