test: the kit speaks English, and a test says so rather than anyone's discipline #3
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "docs/english-is-checked"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 inregistry/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:
mono-nœud, U+0153nom inattendu, and the label[nom, dette existante] %s : %stools/naming.pybecame 53 French lines in the generatedcases.tftest.hclPlus 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.yamlassert 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, inmake 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, socert_sans,aws_ses_domainandpar1are code, not prose.3. The guard could not sweep itself
The second commit was green for the wrong reason.
make checkran whiletests/english.pywas still untracked, sogit ls-filesnever 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 intests/policy_fixtures.pyandtools/naming.py. 130 words → 162.Mutation-tested, nine ways
Two of those nine first reported MISSED against a faulty harness — a
git addthat staged the mutation so thegit checkoutmeant to undo it restored it, and two grep patterns where BRE reads\+as a quantifier. The guard was right both times.Verification
make checkgreen on each of the three commits: 66 module tests, 48 policy tests, 11 reference plans, andenglish: 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 exampleswould 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
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>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>test: the kit speaks English, and a test says so rather than anyone disciplineto test: the kit speaks English, and a test says so rather than anyone's disciplineTWO 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>