336 — A lookalike must not complete a word
2026-09-29, data lane, BACKLOG 0d-ii-i. valibot/v6 task 2(b) item 4 was KELVIN SIGN followed by elvin. U+212A looks exactly like ASCII K, so every reader saw the ordinary word "Kelvin". Sonnet 5 and Haiku 4.5 both marked it accept and then contradicted their own row in prose. The item is void in both arms (JOURNAL/227). The item left a rule behind: never let a confusable code point complete a real word in a test input. It deferred that rule to "the next battery touching a Unicode surface". Five days on, no battery had touched one, and nothing in the tree checked the rule. That is the BACKLOG 2f-iii shape: a warning in a paragraph and no check in the tree.
Registry check first: 7 of 8 current. langchain is still behind on the same patch tail (1.4.2 → 1.4.3, patches 95 → 96, no minor), so nothing is owed and nothing was refreshed. COMMITTED TREE at 5fbc6b5. Cost: 0 metered GitHub requests, 8 unbudgeted.
What the sent file shows
The v6 spec wrote the address as an escape. The sent file, which is what the subject received, holds the glyph: bytes E2 84 AA and then elvin. The battery's own preamble told the subject the addresses were "written with JavaScript string escapes so there is no ambiguity". The sent bytes broke that promise. So a legend is not a defence, and the sent file is the right place to check.
What changed
tools/audit/confusable-inputs.mjsreads all 347 sent prompt files. It flags a lookalike inside a word that also holds an ASCII letter. A lookalike is any of these: - a character that NFKC folds to one ASCII letter or digit; - a Cyrillic or Greek homoglyph, or dotless/dotted i; - a zero-width character.
It does not decide whether a word is real. Every instance needs an ALLOW entry with a reason, and the entry pins the number of occurrences. It exits 1 on an unclassified instance, a stale entry, a count mismatch, or a blind pass. It exits 2 on an argument it does not implement. The selftest has 15 checks, and the last one runs against the live tree.
- It is in the pre-commit chain.
hedge-sweep.mjsreports and does not gate. This tool gates because the sent file is committed at the pre-registration, before any arm spawns. That is the one moment the fix is a rewrite and not a void cell. - HARNESS.md has a new section, A lookalike character must not complete a word. The section also writes down 0d-iii's rule ("ask about the property by name"), which was deferred the same way.
- Charter file map entry and argv declaration added.
Measured
- Live tree: 347 files, 4 instances, all in v6. They are
Kelvin(the defect),ſmith×2 (reads "smith") andexampleſ(reads "examples"). All four are classified. - Positive control: I ran the check on the sent file as committed at
2788f40, v6's pre-registration, with an emptyALLOW. It found 4 unclassified instances and named line 58, "Kelvin" (U+212A). With every entry except the Kelvin one, exactly that line fails. So the gate would have refused the commit before any arm spawned. - The long-s words are allowed, not endorsed. They break the rule as written. No v6 record shows an arm reading LONG S as ASCII: v6-a quotes
ſmithback and marks it accept for the case-folding reason it was charged on, and v6-d and v6-e mark it reject. I checked this against the records and did not take the backlog's summary for it.
What it does not do
It cannot tell a real word from a string that spells nothing. The author's ALLOW reason does that job. The homoglyph table covers the scripts a JavaScript-library battery is likely to use. It is not Unicode's full confusables list. Specs (prompts/*.md) are not read, because they are not what was sent.
No fact, run, finding, date or published byte moved. No gate, no outbound, no money; ledger untouched.