@@ -44,35 +44,35 @@ corpus stays a deliberate superset of what the rule proposes rather than equal t
4444result name with a targeted site on the same operation, a fresh generation stops proposing ` furever `
4545` PostPaymentIntents-response ` , and all four gate figures are unchanged by the rule change.
4646
47- ### B53 — A non- UTF-8 ` package.json ` crashes TypeScript adapter selection
48-
49- ` TypeScriptAdapter._declared_dependencies ` ( ` src/sync/index/typescript.py:181 ` ) reads the customer's
50- manifest with ` read_text(encoding="utf-8") ` and catches only ` json.JSONDecodeError ` . A manifest that
51- is not valid UTF-8 raises ` UnicodeDecodeError ` from the read, before ` json.loads ` is reached, and
52- nothing catches it — so ` matches() ` raises out of adapter selection instead of answering "no declared
53- dependency".
54-
55- The method's own docstring states the contract it fails to keep: * "A customer's manifest is untrusted
56- input, so an unparseable one answers 'no declared dependency' rather than raising." * An undecodable
57- file is unparseable in exactly that sense, so this is a gap against stated intent rather than an open
58- question.
59-
60- Reproduced with a control, which is the part that makes it trustworthy: a valid UTF-8 manifest gives
61- ` matches() ` True, ` '{ not json '` gives False by the documented path, and the same JSON as UTF-16
62- raises ` UnicodeDecodeError ` . A first attempt at that repro returned False for every input because
63- ` _package ` was None — a run whose control also fails proves nothing.
64-
65- The identical defect on the Python side was fixed as ` 5cbf7b8 ` . The TypeScript side never was. Every
66- fixture in this repository is ASCII, which is why no existing test catches it.
67-
68- Carries one thing to report rather than build: ` PythonAdapter.matches ` silently declines a
69- repository whose manifest cannot be decoded and carries no reason where ` intake ` reports one. B51
70- ranked that first among its leave-behinds because silent is worse than loud, but where a reason
71- should land is a design decision, not a fix.
72-
73- ** Closes when: ** ` matches() ` returns False on a non-UTF-8 ` package.json ` with a live control proving
74- the path is reached, the new handler has a driver in ` tests/test_decode_handlers.py ` so every decode
75- handler stays entered, and four gates are green.
47+ ### B54 — A UTF-8 byte-order mark makes four manifest readers answer wrongly
48+
49+ ` EF BB BF ` is valid UTF-8, so ` read_text(encoding="utf-8") ` succeeds and hands every parser
50+ downstream a leading ` ` . Measured against ` 83825f6 ` , four sites, ordered by how hard the
51+ failure is to notice:
52+
53+ 1 . ` TypeScriptAdapter._declared_dependencies ` — a BOM'd ` package.json ` makes ` matches() ` return
54+ False silently, with no reason anywhere. ` json.loads ` raises ` Unexpected UTF-8 BOM ` and the
55+ handler turns it into "no declared dependency".
56+ 2 . ` sync.signals.intake ` on ` package.json ` — declined, with an accurate reason naming the BOM. The
57+ answer is wrong but a human can act on it.
58+ 3 . ` sync.signals.intake ` on ` pyproject.toml ` — declined with a * misleading * reason: ` tomllib ` says
59+ ` Invalid statement (at line 1, column 1) ` for a file that is valid TOML.
60+ 4 . ` sync.signals.intake ` on ` requirements.txt ` — ** no error at all and a wrong value ** :
61+ ` name='stripe '` instead of ` 'stripe' ` , with ` unreadable ` empty. The second dependency in
62+ the same file parses correctly, which is why it is easy to miss.
63+
64+ Case 4 is why this is a task rather than a note. Intake decides whether a prospective customer's
65+ repository depends on a vendor we cover, and a BOM'd ` requirements.txt ` makes it answer no for a
66+ repository that does, silently and unrankably. Windows editors write BOMs by default and this
67+ project runs on Windows.
68+
69+ The fix is ` encoding="utf-8-sig" ` , which strips a BOM when present and decodes plain UTF-8
70+ unchanged when not, so it does not move the answer for any manifest that works today.
71+
72+ ** Closes when: ** each fixed case has a test that fails first for the expected reason and a control
73+ proving the plain-UTF-8 answer is unchanged — case 4 asserting on the parsed * value * , since there
74+ is no error to assert on — ` DRIVERS ` keys updated wherever an edit shifted a handler's line, and
75+ four gates green.
7676
7777### B7 — The M0 acceptance run has not executed since the pipeline changed underneath it
7878
@@ -100,8 +100,11 @@ recorded with which change broke it.
100100
101101## In flight
102102
103- - ** B52** — ` task_15860306d7c7 ` , worktree ` sync-solo-a ` .
104- - ** B53** — ` task_45f3e2d8c875 ` , worktree ` sync-solo-b ` .
103+ - ** B52** — ` task_15860306d7c7 ` , worktree ` sync-solo-a ` . Its commit ` 34789db ` leaves the suite
104+ red: ` hold_back ` gained a required ` root: Path ` and five two-argument callers in
105+ ` tests/test_corpus_hold_back.py ` were not updated. Reproduced at clean ` 34789db ` in a throwaway
106+ branch — ` 5 failed, 10 passed ` . Sent back to fix before it lands.
107+ - ** B54** — ` task_1ea8ea5094a0 ` , worktree ` sync-solo-b ` .
105108
106109** Briefs go in a file now, not in the dispatch spec.** Long message bodies are being truncated in
107110delivery — three briefs today, and B52 received a correction paragraph while the four numbered
@@ -625,3 +628,20 @@ is what a reviewer needs and duplicating it here would let the two copies drift.
625628 handler — the arm a line-coverage check would still call covered — and watching the gate name
626629 ` sync/signals/intake.py:275 ` exactly; a bogus driver naming a handler not in ` src/ ` also fired.
627630 Two leave-behinds became B53; the 35 unhandled text decodes still need per-site triage.
631+
632+ - Decline a non-UTF-8 ` package.json ` instead of crashing on it. Landed ` bdabe9c ` , with the driver
633+ it omitted at ` 83825f6 ` . The worker measured two crash shapes rather than one — a UTF-16 manifest
634+ and a cp1252 ` author ` field failing on byte ` 0xe9 ` , the legacy-encoding case CLAUDE.md predicts —
635+ and both decline after. It also found B52's red suite and proved the five failures predate its own
636+ change by stashing its files at clean ` 34789db ` , which is why that commit did not land with it.
637+
638+ ** What it missed is the more useful record.** Its change widened a guard to catch
639+ ` UnicodeDecodeError ` , which adds a row to ` test_decode_handlers.py ` 's AST inventory, and it did not
640+ register a driver — so ` test_every_decode_handler_has_been_entered ` failed naming
641+ ` sync/index/typescript.py:201 ` , one hour after that gate landed. The worker correctly attributed
642+ the five failures it found to another commit and did not notice a sixth was its own. Proving the
643+ five were not its fault is not the same as proving nothing was. The driver was written here and
644+ probed by reverting the fix: the driver's own test then raises ` UnicodeDecodeError ` at
645+ ` typescript.py:200 ` , so it genuinely enters the arm rather than passing beside it.
646+
647+ Left behind and now B54: a BOM'd manifest, which decodes fine and defeats four readers instead.
0 commit comments