Skip to content

Commit 9b0c778

Browse files
stroland02claude
andcommitted
docs: record B54 landed at seven sites, queue B56
The worker widened its own scope from four sites to seven and justified it: intake reads the same three manifests the index does, and its instance was the worst one in the family -- it reported a dependency named with the byte-order mark attached rather than reporting an absence. The claim worth checking was not that BOMs now parse but that utf-8-sig narrows rather than decodes leniently, since a lenient decode would have converted every unreadable manifest into confident mojibake. UTF-16 still refuses on both manifests, so it narrows. B56 is the first of this family that fails in the dangerous direction: it sits on the verification gate and reports that a patch broke a file the patch never wrote. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 43f2cfc commit 9b0c778

1 file changed

Lines changed: 50 additions & 30 deletions

File tree

docs/superpowers/BACKLOG.md

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,35 +39,37 @@ general limitation, so overloading it with a per-repository fact would give one
3939
with a control proving a legitimate silent decline stays silent, `LanguageAdapter` and the
4040
conformance 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
102104
delivery — 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

Comments
 (0)