@@ -39,35 +39,37 @@ general limitation, so overloading it with a per-repository fact would give one
3939with a control proving a legitimate silent decline stays silent, ` LanguageAdapter ` and the
4040conformance kit unchanged, and four gates green.
4141
42- ### B54 — A UTF-8 byte-order mark makes four manifest readers answer wrongly
43-
44- ` EF BB BF ` is valid UTF-8, so ` read_text(encoding="utf-8") ` succeeds and hands every parser
45- downstream a leading ` ` . Measured against ` 83825f6 ` , four sites, ordered by how hard the
46- failure is to notice:
47-
48- 1 . ` TypeScriptAdapter._declared_dependencies ` — a BOM'd ` package.json ` makes ` matches() ` return
49- False silently, with no reason anywhere. ` json.loads ` raises ` Unexpected UTF-8 BOM ` and the
50- handler turns it into "no declared dependency".
51- 2 . ` sync.signals.intake ` on ` package.json ` — declined, with an accurate reason naming the BOM. The
52- answer is wrong but a human can act on it.
53- 3 . ` sync.signals.intake ` on ` pyproject.toml ` — declined with a * misleading* reason: ` tomllib ` says
54- ` Invalid statement (at line 1, column 1) ` for a file that is valid TOML.
55- 4 . ` sync.signals.intake ` on ` requirements.txt ` — ** no error at all and a wrong value** :
56- ` name='stripe' ` instead of ` 'stripe' ` , with ` unreadable ` empty. The second dependency in
57- the same file parses correctly, which is why it is easy to miss.
58-
59- Case 4 is why this is a task rather than a note. Intake decides whether a prospective customer's
60- repository depends on a vendor we cover, and a BOM'd ` requirements.txt ` makes it answer no for a
61- repository that does, silently and unrankably. Windows editors write BOMs by default and this
62- project runs on Windows.
63-
64- The fix is ` encoding="utf-8-sig" ` , which strips a BOM when present and decodes plain UTF-8
65- unchanged when not, so it does not move the answer for any manifest that works today.
66-
67- ** Closes when:** each fixed case has a test that fails first for the expected reason and a control
68- proving the plain-UTF-8 answer is unchanged — case 4 asserting on the parsed * value* , since there
69- is no error to assert on — ` DRIVERS ` keys updated wherever an edit shifted a handler's line, and
70- four gates green.
42+ ### B56 — Static verification calls a file broken that Python compiles fine
43+
44+ ` PythonAdapter._syntax_errors ` (` src/sync/index/python_lang.py:709 ` ) reads customer source with
45+ ` read_text(encoding="utf-8") ` and hands the string to ` ast.parse ` . A ` .py ` file beginning with a
46+ UTF-8 byte-order mark decodes cleanly, so the mark arrives as the first character and ` ast.parse `
47+ rejects it. Measured with a control:
48+
49+ plain UTF-8, valid Python _syntax_errors -> none
50+ same file with a BOM _syntax_errors -> ['billing.py: invalid non-printable character U+FEFF']
51+ the same BOM'd file py_compile -> compiles fine
52+
53+ The docstring is what makes it a defect rather than a preference: it chooses ` ast.parse ` over the
54+ tree-sitter grammar because "the interpreter's own parser is the authority on that", and CPython
55+ strips a BOM when reading a * file* — so decoding it ourselves defeats the argument the method rests
56+ on.
57+
58+ ** This is the first of the family that fails in the dangerous direction.** It is consumed at
59+ ` python_lang.py:770 ` as part of static verification, the gate the project's central invariant rests
60+ on. The earlier BOM defects made Sync miss a dependency — a missed finding. This one reports that a
61+ patch broke a file the patch never wrote, so a correct fix fails verification and the finding is
62+ abandoned, and the run names our own output as the thing at fault.
63+
64+ Carries a second, smaller deliverable: ` tests/test_decode_handlers.py ` keys ` DRIVERS ` by
65+ ` path:line ` and its docstring never says so, though it explains at length why * entry* is attributed
66+ by exception type rather than by line. Two people hit that in one hour — seven handlers moved out
67+ from under their drivers by one worker's comment blocks, and a key re-anchored 201 to 206 by the
68+ coordinator. Document the trade; do not redesign the keying.
69+
70+ ** Closes when:** a BOM'd but valid ` .py ` file is no longer reported as a syntax error, a plain UTF-8
71+ file with a real syntax error still is, a genuinely undecodable file still reports through the
72+ ` UnicodeDecodeError ` arm, ` DRIVERS ` is re-anchored from what the gate reports, and four gates green.
7173
7274### B7 — The M0 acceptance run has not executed since the pipeline changed underneath it
7375
@@ -95,8 +97,8 @@ recorded with which change broke it.
9597
9698## In flight
9799
98- - ** B54** — ` task_1ea8ea5094a0 ` , worktree ` sync-solo-b ` .
99100- ** B55** — ` task_e03a2a5bb93f ` , worktree ` sync-solo-a ` .
101+ - ** B56** — ` task_e07b696d699d ` , worktree ` sync-solo-b ` .
100102
101103** Briefs go in a file now, not in the dispatch spec.** Long message bodies are being truncated in
102104delivery — three briefs today, and B52 received a correction paragraph while the four numbered
@@ -656,3 +658,21 @@ is what a reviewer needs and duplicating it here would let the two copies drift.
656658 no branch. Caught within a minute and preserved as ` unreviewed/b52-hold-back-scope ` , then
657659 finished here. Two habits earned from that: stage by explicit path, and run
658660 ` git log --oneline main..HEAD ` before any ` git reset --hard ` .
661+
662+ - Read a customer's manifest as ` utf-8-sig ` , so a byte-order mark stops changing the answer. Landed
663+ ` 9352dbe ` . ** Seven sites, not the four the brief named** — the brief counted ` sync/index/ ` and the
664+ worker found ` sync/signals/intake.py ` reads the same three files for the intake report, correctly
665+ widened the scope, and said why. The worst instance is the one it added: intake * reported* a
666+ dependency called ` stripe ` with an empty ` unreadable ` beside it, answering a wrong fact
667+ rather than an absence.
668+
669+ The claim worth checking was that ` utf-8-sig ` narrows rather than decodes leniently, since a
670+ lenient decode would have made every unreadable manifest "readable" and turned the whole family of
671+ defects into silent mojibake. Verified here: BOM'd manifests now resolve to ` stripe ` , and UTF-16
672+ still refuses with ` unreadable ` set, on both ` package.json ` and ` requirements.txt ` .
673+
674+ Landing it conflicted in three files, all of them "keep both halves" rather than a real
675+ disagreement: the worker's base predated B53's widened ` except ` , so ` typescript.py ` wanted its
676+ ` utf-8-sig ` read * and* main's ` UnicodeDecodeError ` clause. Its report said B53's fix was missing
677+ from ` origin/main ` ; that was its stale base showing, not origin — ` bdabe9c ` is in origin's
678+ ancestry and the clause is there at line 201.
0 commit comments