Development record
The scanner's count-based cost guards measure scanAllPatterns alone, so the rest of the per-line scan is outside every cost guard. probeWork (internal/adapter/scanner/adjacency_test.go) tallies only the bytes handed to the adjacency probes and the junction probe inside scanAllPatterns; the wall-clock guards it replaced timed scanLine, which is the whole of scanText. Uncovered stages: (1) each pattern's Skip and SkipAt callbacks, run per match against the matched text and the whole line; (2) the identity matchers (matchers.findings), run over the raw line and again over the decoded copy; (3) the percent-decode pre-pass (decodedLineFindings: percentDecodeBounded, the second scanAllPatterns run over the decoded copy, mapDecodedSpan); (4) the top-level cp.Re.FindAllStringIndex pass per pattern, which is RE2-linear but untallied. A regression that makes any of these superlinear passes every guard in the package. Counting them at the scanLine level needs a seam in scanText, which builds its probes internally and constructs the identity matchers from a fixed type with no hook for a counting stand-in, so it is a production change rather than a test-only one; hence captured, not added, in the gallop lane.
The scanner's count-based cost guards measure scanAllPatterns alone, so the rest of the per-line scan is outside every cost guard. probeWork (internal/adapter/scanner/adjacency_test.go) tallies only the bytes handed to the adjacency probes and the junction probe inside scanAllPatterns; the wall-clock guards it replaced timed scanLine, which is the whole of scanText. Uncovered stages: (1) each pattern's Skip and SkipAt callbacks, run per match against the matched text and the whole line; (2) the identity matchers (matchers.findings), run over the raw line and again over the decoded copy; (3) the percent-decode pre-pass (decodedLineFindings: percentDecodeBounded, the second scanAllPatterns run over the decoded copy, mapDecodedSpan); (4) the top-level cp.Re.FindAllStringIndex pass per pattern, which is RE2-linear but untallied. A regression that makes any of these superlinear passes every guard in the package. Counting them at the scanLine level needs a seam in scanText, which builds its probes internally and constructs the identity matchers from a fixed type with no hook for a counting stand-in, so it is a production change rather than a test-only one; hence captured, not added, in the gallop lane.
Grounds
- pursued: a per-line stage whose work outgrows the line fails TestScanLineWorkIsLinear on some shape; a superlinear helper that charges nothing to the meter, or a stage no fixture exercises densely, would show it wrong.
Frontmatter
| id | iss-2609240203462704 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-2609240203462704-the-scanner-s-count-based-cost-guards.md
open on GitHub ↗ · commit history ↗
mentions1
- iss-2609251535090117The generic-account position check in the identity matchers is quadratic in line length. standsAsAccountName (internal/adapter/scanner/identity.go) lower-cases the whole line prefix before every match to test a home-literal suffix and each account-root prefix, and underAbsoluteRoot walks back to the path token's start for every non-boundary home match. A line dense in a generic login (dev, git, test, user, build, app, node: all on genericAccountNames) under a login on that list therefore costs the square of its length: the review measured 80, 160 and 320 KB at 0.92, 3.34 and 13.6 s against 31, 60 and 124 ms for a non-generic control, so one 4 MB transcript line holding a tool result costs about half an hour in the launch gate or the store-before-commit redactor. The identity matchers sat outside every count-based cost guard (iss-2609240203462704), which is why no test saw it.