fix(categorization): escape the NUL delimiter instead of embedding the byte - #119
Merged
Conversation
…e byte
engine.ts carried a literal 0x00 byte in its source, inside the template
literal that builds the assignment-grouping key:
const key = `${assignment.categoryId}<0x00 byte>${assignment.source}`;
NUL is a sound delimiter here -- it cannot occur in a category id or a
source, so the composite key cannot collide. Writing it as a raw byte
rather than the \0 escape is the problem: grep classifies the whole file
as binary and silently drops matching lines from its output. No error,
no warning, just absent results.
That is not hypothetical. Searching for `categoryRules` across src/
returned only the schema definition, which reads as "nothing consumes
this table" -- while engine.ts in fact queries it on lines 106-113 and
runs it on every sync. The file was invisible to the search that would
have shown otherwise.
`\0` is byte-identical at runtime (verified: same string, same code
point at the delimiter, same length) and the file is now ASCII text, so
grep, git diff and editors all read it normally.
Adds a regression test for the grouping itself, which nothing covered:
two transactions reaching two different categories through two different
tiers in one call must each land on the right category and source.
Verified it has teeth -- collapsing the key to a constant fails this
test and only this test, out of the six in that file.
Owner
Author
Mutation gate: baseline comparison
Identical score. The killed/timeout split differs (23/0 vs 20/3), but Stryker scores a timeout as killed, so both are 23 — the difference is runner load, not code. Score-neutral is the expected result: the change is The file is structurally capped regardless — 25 no-coverage + 53 errors out of 102 mutants, because Note the earlier Re-ran, and it passed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The byte
engine.tscarried a literal 0x00 byte in its source, inside the template literal that builds the assignment-grouping key:NUL is a sound delimiter here — it cannot occur in a category id or a source string, so the composite key cannot collide. The problem is writing it as a raw byte rather than the
\0escape: grep classifies the whole file as binary and silently drops matching lines. No error, no warning, just absent results.One hit — reads as "nothing consumes this table."
engine.tsqueries the table on lines 106–113 and runs the rules on every sync. The file was invisible to the search that would have shown that.This is a live trap for anyone investigating this codebase — it produced a confidently wrong conclusion about the categorization pipeline while triaging #89.
The fix
\0instead of the raw byte. Verified byte-identical at runtime:filenow reportsASCII textinstead ofdata, so grep,git diff, and editors all read it normally.Test
Adds the regression test that nothing covered: the grouping itself. Two transactions reaching two different categories through two different tiers (
ruleandmerchant_default) in a single call must each land on the right category and the right source.Verified the test has teeth rather than assuming it: collapsing the key to a constant (
const key = "COLLIDE") fails this test — and only this test, out of the six in that file. The existing five pass either way, which is why this gap survived.Full suite green: 753 passed / 113 files. Typecheck clean,
npx eslint src testsclean.🤖 Generated with Claude Code