fix(faq-bot): bound regex matching to the first 150 characters - #113
Merged
Conversation
The parse-time screen reads the pattern text. That works for the exponential shapes it can recognise syntactically, a quantifier on a group that holds one and an ambiguous repeated alternation, but not for the polynomial ones, where the cost depends on how many ways an input splits between adjacent quantifiers. Those are not reliably visible in the text: three separate attempts to make them so each closed one family of patterns while opening another. Bounding the input needs no model at all. A polynomial blow-up costs about n^3, so the cap sets the ceiling directly, whatever the pattern looks like and including shapes nobody has found yet. Measured on the worst shape that gets past the screen, `((a|b)*(a|b)*)(a|b)*$` against alternating input that fails on the last character: 1000 characters runs past 8 s, 300 takes 5.8 s, 250 takes 2.8 s, 150 takes 362 ms. The host aborts a hook at 5 s and cannot interrupt a running regex, so 150 is chosen to hold on a host several times slower than the machine that measured it. 250 would not: it is 2.8 s here and 5.7 s at half this speed. A fuzz over 106 patterns the screen accepts found one that still exceeds 5 s at 1000 characters and none at 250, so the cap also covers what the screen misses beyond the known cases. Only `regex` rules are affected. `contains` and `exact` cannot backtrack and are not capped, so they still see the whole message, and `contains` remains the right mode for matching a mention anywhere in a long one. The cost is that a regex rule no longer matches text past the first 150 characters, which is a predictable limit rather than a rule that stops answering without saying so.
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.
Closes #112.
The parse-time screen reads the pattern text. That works for the exponential shapes it can recognise syntactically, a quantifier on a group that holds one and an ambiguous repeated alternation, but not for the polynomial ones, where cost depends on how many ways an input splits between adjacent quantifiers. Those are not reliably visible in the text: three separate attempts to make them so each closed one family of patterns while opening another, and one of them shipped a regression that refused ordinary rules such as
.*(\d+).*.Bounding the input needs no model at all. A polynomial blow-up costs about n^3, so the cap sets the ceiling directly, whatever the pattern looks like and including shapes nobody has found yet.
Measured
Worst shape that gets past the screen,
((a|b)*(a|b)*)(a|b)*$, against alternating input that fails on the last character:The host aborts a hook at 5 s and cannot interrupt a running regex, so the ceiling has to hold on a host slower than the one that measured it. 250 does not.
A fuzz over 106 patterns the screen accepts found one still above 5 s at 1000 characters and none at 250, so the cap also covers what the screen misses beyond the eight known cases in #112.
Why not a smarter screen
Five independent designs were implemented and measured against a differential gate, reported in #112. All five closed the known holes and all five opened new ones, in four separate families. The pattern across them is that a text-level model of backtracking cost is at its limit, not that the right rule had not been found.
The two controls now cover what each is actually good at. Exponential shapes stay fatal at any input length, so only the screen can stop them, and they are the ones it recognises reliably. Polynomial shapes are the opposite: hard to see in the text, bounded by the cap.
Cost
A
regexrule no longer matches text past the first 150 characters. That is a predictable limit, documented in the plugin README, rather than a rule that silently stops answering.containsandexactcannot backtrack and are not capped, so they still see the whole message.containsremains the right mode for matching a mention anywhere in a long one.Verification
npm run typecheck,npm test(660 pass),npm run catalog:check,npm run loader:check.Two new tests pin the cap and fail if it is raised, verified at 300 and at 1000. The second builds its input to be adversarial inside the window, since the cap slices from the front and a failing tail appended to a long message is simply cut off, which made an earlier version of that test pass at any cap.
faq-bot 0.2.10.