test: the kit speaks English, and a test says so rather than anyone's discipline #3

Merged
administrator merged 3 commits from docs/english-is-checked into main 2026-10-27 20:36:58 +00:00
Collaborator

The kit was declared translated twice. Both times the sweep that said so was narrower than the work it was grading, and French survived on main — including in registry/conventions.yaml, the single source of truth.

Three commits. The third exists because the second was wrong, and is the most instructive of the three.

1. The French the first two passes missed

22 files. Three separate causes, only the first a vocabulary gap:

Cause What hid
a character range (U+00C0–U+00FF) the ligature in mono-nœud, U+0153
a word list without this kit's own words nom inattendu, and the label [nom, dette existante] %s : %s
a generator one French string in tools/naming.py became 53 French lines in the generated cases.tftest.hcl

Plus the plan fixtures, realigned by substitution in the previous pass and only partially: 14 occurrences of "Domaine fonctionnel" remained. They now carry the module's current description verbatim. Two of the three are hand-altered copies that cannot be re-captured, so substitution is the only route — but it is exact now.

⚠️ Four message texts change, and policy/fixtures/*.expected.yaml assert them word for word:
[nom, dette existante] → [name, existing debt], [tags, dette existante] → [tags, existing debt]. A consumer grepping those strings in CI has to follow.

2. The guard

make english, in make check. Two independent sweeps, because each sees what the other cannot: every non-ASCII character outside a named allowlist (reported with its codepoint and Unicode name), and French words at word boundaries. The boundary excludes _ and digits, so cert_sans, aws_ses_domain and par1 are code, not prose.

3. The guard could not sweep itself

The second commit was green for the wrong reason. make check ran while tests/english.py was still untracked, so git ls-files never handed it to the sweep. Tracked, it fails on itself — a file that lists French words is a file full of French words.

Skipping the guard's own source would carve out the one file where prose could then hide. So the data left the code:

  • tests/french.txt — one lowercase token per line. A sentence does not fit in it.
  • tests/exceptions.txt — <path>|<word>|<reason>. Only the word field is exempt; the reasons and the comments are swept like any source file.

The exceptions had to move too, and that was only visible once measured. A repository-wide exception declared inside a swept file is kept alive by its own declaration: with the list in Python, removing every real occurrence of a slug left the run green, because the line declaring the exception still contained the word. The stale-exception check was reporting on itself.

And the word list could grow for free. Twelve common French words had zero occurrences here and were simply missing. Adding them plus twenty more found six further French lines in files three passes had already read — a sample call in templates/consumer/naming.tf, notes in the GCP, Proxmox and Tailscale registries, and messages in tests/policy_fixtures.py and tools/naming.py. 130 words → 162.

Mutation-tested, nine ways

M1  an accented word                              -> caught, U+00E8 named
M2  French carrying no accent whatsoever          -> caught
M3  the U+0153 ligature that hid for two passes   -> caught, U+0153 named
M4  French put back in the GENERATOR of 53 lines  -> caught
M5  an exception whose word no longer occurs      -> reported, run fails
M6  the guard run where git lists almost nothing  -> refuses to pass
M7  a sentence appended to the word list          -> refused, not a token
M8  a reason rewritten in French                  -> caught
M9  an exception line missing a field             -> refused

Two of those nine first reported MISSED against a faulty harness — a git add that staged the mutation so the git checkout meant to undo it restored it, and two grep patterns where BRE reads \+ as a quantifier. The guard was right both times.

Verification

make check green on each of the three commits: 66 module tests, 48 policy tests, 11 reference plans, and english: 80 files, 59,279 lines, 162 words, 4 exceptions, 0 findings.

What this does not do

It does not re-capture the plan fixtures. make examples would do that for real and was not run — and the two altered fixtures could not be re-captured in any case.

🤖 Generated with Claude Code

The kit was declared translated twice. Both times the sweep that said so was narrower than the work it was grading, and French survived **on `main`** — including in `registry/conventions.yaml`, the single source of truth. Three commits. The third exists because the second was wrong, and is the most instructive of the three. ## 1. The French the first two passes missed 22 files. Three separate causes, only the first a vocabulary gap: | Cause | What hid | |---|---| | a character *range* (U+00C0–U+00FF) | the ligature in `mono-nœud`, U+0153 | | a word list without this kit's own words | `nom inattendu`, and the label `[nom, dette existante] %s : %s` | | a **generator** | one French string in `tools/naming.py` became 53 French lines in the generated `cases.tftest.hcl` | Plus the plan fixtures, realigned by substitution in the previous pass and only *partially*: 14 occurrences of `"Domaine fonctionnel"` remained. They now carry the module's current description verbatim. Two of the three are hand-altered copies that cannot be re-captured, so substitution is the only route — but it is exact now. ⚠️ **Four message texts change**, and `policy/fixtures/*.expected.yaml` assert them word for word: `[nom, dette existante]` → `[name, existing debt]`, `[tags, dette existante]` → `[tags, existing debt]`. **A consumer grepping those strings in CI has to follow.** ## 2. The guard `make english`, in `make check`. Two independent sweeps, because each sees what the other cannot: every non-ASCII character outside a named allowlist (reported with its codepoint and Unicode name), and French words at word boundaries. The boundary excludes `_` and digits, so `cert_sans`, `aws_ses_domain` and `par1` are code, not prose. ## 3. The guard could not sweep itself **The second commit was green for the wrong reason.** `make check` ran while `tests/english.py` was still untracked, so `git ls-files` never handed it to the sweep. Tracked, it fails on itself — a file that lists French words is a file full of French words. Skipping the guard's own source would carve out the one file where prose could then hide. So the data left the code: - **`tests/french.txt`** — one lowercase token per line. A sentence does not fit in it. - **`tests/exceptions.txt`** — `<path>|<word>|<reason>`. Only the *word* field is exempt; the reasons and the comments are swept like any source file. **The exceptions had to move too, and that was only visible once measured.** A repository-wide exception declared inside a swept file is kept alive *by its own declaration*: with the list in Python, removing every real occurrence of a slug left the run green, because the line declaring the exception still contained the word. The stale-exception check was reporting on itself. **And the word list could grow for free.** Twelve common French words had *zero* occurrences here and were simply missing. Adding them plus twenty more found **six further French lines** in files three passes had already read — a sample call in `templates/consumer/naming.tf`, notes in the GCP, Proxmox and Tailscale registries, and messages in `tests/policy_fixtures.py` and `tools/naming.py`. 130 words → 162. ## Mutation-tested, nine ways ``` M1 an accented word -> caught, U+00E8 named M2 French carrying no accent whatsoever -> caught M3 the U+0153 ligature that hid for two passes -> caught, U+0153 named M4 French put back in the GENERATOR of 53 lines -> caught M5 an exception whose word no longer occurs -> reported, run fails M6 the guard run where git lists almost nothing -> refuses to pass M7 a sentence appended to the word list -> refused, not a token M8 a reason rewritten in French -> caught M9 an exception line missing a field -> refused ``` Two of those nine first reported MISSED against a *faulty harness* — a `git add` that staged the mutation so the `git checkout` meant to undo it restored it, and two grep patterns where BRE reads `\+` as a quantifier. The guard was right both times. ## Verification `make check` green on each of the three commits: 66 module tests, 48 policy tests, 11 reference plans, and `english: 80 files, 59,279 lines, 162 words, 4 exceptions, 0 findings`. ## What this does not do It does not re-capture the plan fixtures. `make examples` would do that for real and was not run — and the two altered fixtures could not be re-captured in any case. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Two passes declared the kit translated. Neither was wrong about what it read; both
were wrong about what they read FOR. What is corrected here sat on main the whole
time, including in registry/conventions.yaml, the single source of truth.

WHY IT SURVIVED - three separate reasons, and only the first is a vocabulary gap:

  - A character range. The sweep looked for U+00C0 to U+00FF, which does not contain
    the ligature in "mono-noeud" (U+0153). One character, invisible to the only
    check that would have seen it.
  - A word list that did not contain the words this kit actually used. "nom
    inattendu" carries no accent and no grammatical French word, and "[nom, dette
    existante] %s : %s" is a LABEL - no vocabulary of articles and prepositions
    reaches a label.
  - A generator. tools/naming.py emitted "nom inattendu" into every assertion of
    modules/naming/tests/cases.tftest.hcl, so one French string in one line of
    Python became 53 French lines in a generated file. Translating the generated
    file would have been undone by the next `make tftest-sync`.

The plan fixtures carried a description the module no longer has. They were
realigned by substitution in the previous pass rather than re-captured, and the
substitution was partial: six occurrences in plan_real_gcp.json, six in
plan_gcp_violations.json, two in plan_aws_unnamed.json still read "Domaine
fonctionnel". They now carry the module's current text, verbatim. Two of those three
are hand-altered copies that cannot be re-captured at all, so substitution is the
only route here - but it is exact now rather than approximate.

FOUR MESSAGE TEXTS CHANGE, and policy/fixtures/*.expected.yaml assert them word for
word: "[nom, dette existante]" becomes "[name, existing debt]", and "[tags, dette
existante]" becomes "[tags, existing debt]". A consumer grepping those strings in CI
has to follow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test: the kit speaks English, and a test says so rather than anyone's discipline
Some checks failed
ci / check (pull_request) Has been cancelled
d36f1e6687
Three translation passes, three times a sweep declared the work finished and was
narrower than the work it graded. The fix is not a fourth, more careful sweep: it is
a sweep that runs in `make check` and that somebody has tried to fool.

TWO INDEPENDENT SWEEPS, because each catches exactly what the other cannot.

  - Every non-ASCII character outside a short allowlist, reported with its codepoint
    and its Unicode name. The allowlist names the typography we write on purpose (em
    dash, ellipsis, arrow, section sign and so on); everything else is a finding.
    This is what an accent RANGE could not do - U+0153 sat outside U+00C0-U+00FF and
    stayed invisible for two passes.
  - A list of French function words at word boundaries, which is what sees prose that
    carries no accent at all: "cette regle doit rester vraie" passes any character
    check ever written.

A line has to get past both to survive.

THE BOUNDARY EXCLUDES "_" AND DIGITS, so `cert_sans`, `aws_ses_domain` and `par1` are
code and not prose. That one decision removed most of what an earlier hand-run sweep
had to triage by eye, and with it the temptation to widen an exception until it covers
a real occurrence.

EXCEPTIONS ARE NAMED, WITH A REASON, AND A STALE ONE IS AN ERROR. "par" is the
Scaleway site code for Paris, "ses" is an AWS service directory, "ces" a GCP one. An
exception that stops matching anything is reported and fails the run: it would
otherwise keep its word unseen for the next, real occurrence - which is precisely the
case it was never written for.

AND IT REFUSES TO PASS ON AN EMPTY READING. A guard anchored on `git ls-files` can
stop seeing files - a different working directory, a rename - and go green having
read nothing. It prints what it read every time (79 files, 59,060 lines, 11
exceptions) and fails below a floor, because a guard that passes in silence cannot be
told from a guard that did not run.

MUTATION-TESTED, six ways, each one a failure this repository actually had:

  M1  an accented word added to CONVENTION.md          -> caught
  M2  French with no accent whatsoever                 -> caught
  M3  the U+0153 ligature that hid for two passes      -> caught
  M4  French put back in tools/naming.py, the GENERATOR
      of 53 assertions in a file nobody edits by hand  -> caught
  M5  an exception whose subject was removed           -> reported, run fails
  M6  the guard run where git lists almost nothing     -> refuses to pass

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
seeds-agent changed title from test: the kit speaks English, and a test says so rather than anyone discipline to test: the kit speaks English, and a test says so rather than anyone's discipline 2026-10-27 20:16:50 +00:00
test: the guard could not sweep itself, and six more French lines proved it
Some checks failed
ci / check (pull_request) Has been cancelled
985ba27293
TWO FAULTS, and the first is mine in a way worth recording.

THE GUARD FAILED ON ITS OWN SOURCE, and the previous commit did not notice because
the only run that mattered happened too early: `make check` was green while
tests/english.py was still UNTRACKED, so `git ls-files` did not hand it to the sweep.
The commit that added the file never verified the state it created. A guard verified
before it was staged is a guard verified against a repository that no longer exists.

It fails for a reason no exception should paper over: a file that lists French words
is a file full of French words. The obvious repair - skip the guard's own source -
carves out the one file where prose could then hide, which is the opposite of what
this is for.

SO THE DATA LEAVES THE CODE. tests/french.txt holds the word list and
tests/exceptions.txt holds the exceptions, both read as data and skipped from the
prose sweep, and neither can hide a sentence:

  - every line of french.txt must be a single lowercase token, so a sentence does
    not fit in it;
  - every line of exceptions.txt is <path>|<word>|<reason>, and only the WORD field
    is exempt - the reasons and the comments are swept like any source file.

AND THE EXCEPTIONS HAD TO MOVE TOO, which was not obvious until it was measured. A
repository-wide exception declared inside a swept file is kept alive BY ITS OWN
DECLARATION: with the list in Python, removing every real occurrence of a slug left
the run green, because the line declaring the exception still contained the word. The
stale-exception check was reporting on itself. It works now - mutation M5.

THE SECOND FAULT. The word list could grow for free. Twelve common French words had
ZERO occurrences in this repository and were simply missing from it, and adding them
with twenty more found SIX further French lines in files three passes had already
read: a sample call in templates/consumer/naming.tf, a note in the GCP registry, a
section heading in the Proxmox one, a Tailscale note, a message in
tests/policy_fixtures.py and one in tools/naming.py. 130 words became 162.

That is the fourth time a sweep here was narrower than the work it graded, and the
first time the sweep itself said so instead of a human noticing afterwards.

NINE MUTATIONS, each a failure this repository actually had:

  M1  an accented word                              -> caught, U+00E8 named
  M2  French carrying no accent whatsoever          -> caught
  M3  the U+0153 ligature that hid for two passes   -> caught, U+0153 named
  M4  French put back in the GENERATOR of 53 lines  -> caught
  M5  an exception whose word no longer occurs      -> reported, run fails
  M6  the guard run where git lists almost nothing  -> refuses to pass
  M7  a sentence appended to the word list          -> refused, not a token
  M8  a reason rewritten in French                  -> caught
  M9  an exception line missing a field             -> refused

Two of those nine first reported MISSED against a faulty harness - a `git add` that
staged the mutation, so the `git checkout` meant to undo it restored it instead, and
two grep patterns where BRE read `\+` as a quantifier. The guard was right both
times. A mutation harness is code, and it gets the same suspicion as the code it
tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
administrator deleted branch docs/english-is-checked 2026-10-27 20:36:58 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
shared/infra-conventions!3
No description provided.