Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
4.3 KiB
Filbert review, row 148 round 2: changes.
Candidate manifest sha256 2ed5a097ca2c2b68bfcadee9abe6f91f1b5d1da5aa9041b95636c23860ba91da (agents/researcher/work/queue-148/candidate-manifest.sha256), base 63c82f1c4, request 28807.
Rebuild: applied row148.patch at 63c82f1c4 in my own worktree. The manifest matches, git diff --cached --check is clean, and added lines have no dashes, curly quotes or control characters (27 files, +1605 -46). No drift on the manifest paths from 63c82f1c4 to HEAD. The candidate's public diff sha256 is c2befc64026ff88370483d476967384a1d6abe8d72eca8b62168e8673522b191, equal to public-diff.patch.
B1 (blocking): three block regexes are quadratic on one line, and the work budget doesn't see them
markdown-preview.js is new in this row. Three of its block regexes put a greedy run before (.*)$:
- line 21:
FENCE = /^( {0,3})(`{3,}|~{3,})(.*)$/ - line 22:
ATX = /^ {0,3}(#{1,6})(?:[ \t]+(.*))?$/ - line 26:
ITEM = /^( {0,3})([-+*]|\d{1,9}[.)])([ \t]+|$)(.*)$/
Without the s flag, . matches neither U+2028 nor U+2029, and text.split('\n') keeps both inside the line. On a line made of the marker, a long run of blanks (or backticks for FENCE), then U+2028 or U+2029, (.*) stops at the separator and $ fails. The engine then gives back one character of the run and rescans (.*) to the separator, once per character of the run. Dewey found the same three regexes by reading the code and pointed out the U+2029 case (dewey-0566). I ran the probe.
Probe: one render() of one line, made in node from my verified rebuild. The script is agents/filbert/work/queue-148/r2/probe.mjs. One process per cell, timeout 20 s, none timed out. It ran 2026-10-16T20:46:57Z to 20:47:16Z on Sage's slot (sage-0602). The line is the marker plus N KiB of the run, then the tail named in the second column. work is the meter's count after the render.
| shape | tail | 8 KiB | 16 KiB | 32 KiB | 64 KiB | work |
|---|---|---|---|---|---|---|
ITEM - + spaces |
x (control) |
0.9 ms | 1.0 ms | 1.2 ms | 1.5 ms | 0 |
ITEM - + spaces |
U+2028 | 41.9 ms | 169.6 ms | 615.8 ms | 2369.6 ms | 0 |
ITEM - + spaces |
U+2029 | 36.5 ms | 143.5 ms | 568.9 ms | 2221.6 ms | 0 |
ATX # + spaces |
x (control) |
0.6 ms | 0.6 ms | 0.6 ms | 0.6 ms | 0 |
ATX # + spaces |
U+2028 | 35.7 ms | 141.9 ms | 568.4 ms | 2254.7 ms | 0 |
ATX # + spaces |
U+2029 | 37.3 ms | 139.2 ms | 555.3 ms | 2253.9 ms | 0 |
| FENCE backticks | x (control) |
0.4 ms | 0.4 ms | 0.4 ms | 0.4 ms | 0 |
| FENCE backticks | U+2028 | 37.3 ms | 139.2 ms | 560.0 ms | 2241.5 ms | 1 |
| FENCE backticks | U+2029 | 35.6 ms | 140.0 ms | 554.9 ms | 2246.1 ms | 1 |
Each doubling multiplies the time by about 4 with a separator and leaves the control flat, so the cost is quadratic in the run. The meter reads 0 or 1 on every row, so the per-render budget doesn't bound this. I didn't run 1 MiB. At x4 per doubling, the 64 KiB figure extrapolates to roughly 9 to 10 minutes for a single 1 MiB line. That's an extrapolation, not a measurement.
Why the round 2 probe missed it: probe-table.md's "fence line with U+2028 after a long tail" and "list item with U+2028 after a long tail" put text between the marker and the separator, so the greedy run in front of (.*) is short. build.md line 120 lists FENCE, ATX and ITEM as "no: anchored, run once per line", and line 238 says those shapes "were already linear". Being anchored doesn't prevent backtracking inside one line.
A possible fix, the author's choice: the s flag on the three regexes (then (.*)$ matches at the first try), or ([^]*) in place of (.*), or splitting on U+2028/U+2029 as well. A test is needed. The meter can't see regex time, so either a test that renders a long line of each shape with each separator inside a generous bound, or a source pin like v14 and v15, plus the rows above in probe-table.md. SETEXT, HR, QUOTE and the closing-fence check /^(`+|~+)[ \t]*$/ have no greedy run before a ., and I don't count them here.
Not reviewed this round
The blocker sends the row back, so I stopped there. I read markdown-preview.js in full and build.md. I did not finish markdown-preview.test.mjs, the serve.mjs wording or the README NUL change, and I'll review them on the next manifest. I ran no test files. The only run was the probe above.