Skip to content

fix(user-access-policy): route non-block strict-match lookups through split-query path#2822

Merged
forgetso merged 1 commit into
mainfrom
fix/split-restrict-rule-lookup
Jul 10, 2026
Merged

fix(user-access-policy): route non-block strict-match lookups through split-query path#2822
forgetso merged 1 commit into
mainfrom
fix/split-restrict-rule-lookup

Conversation

@forgetso

@forgetso forgetso commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

Root cause and fix for the 2026-07-10 regression where a Restrict/image rule was silently dropped from the frictionless access-policy lookup even though the rule was correctly stored in Redis and indexed on all 10 pronodes. The DM fell through to default_pow and served POW instead of the configured image challenge.

Test plan

  • test:unit — 75 passed
  • test:integration — 45 passed (including new Twickets regression test)
  • redisRulesReaderRank.benchmark.integration — 4 passed
  • redisRulesReaderLoad.benchmark.integration — 6 passed
  • @prosopo/provider test:unit — 944 passed
  • Hotfix image built locally and deployed to pronodes for live confirmation

🤖 Generated with Claude Code

… split-query path

Live 2026-07-10 Twickets regression: a Restrict/image rule for the IP
`82.17.209.70` was correctly stored in Redis and indexed on all 10
pronodes, yet the frictionless DM fell through to `default_pow` and
served POW instead of image.

Root cause: `findRulesRanked` (the non-`blockOnly` matchingFieldsOnly
path) capped candidates at `SERVER_SIDE_RANK_TOP_N=20` after a server
`SORTBY @_rank`. Under greedy `matchingFieldsOnly=true` the query is a
wide OR over `ismissing(@headHash)` / `ismissing(@coords)` /
`ismissing(@headersHash)`, so for a Twickets-shaped tenant (~860
candidates on the same clientId) the top 20 slots were filled by
higher-specificity SIMD_REPLAY / SUDDEN_VOLUME_INCREASE Block rules
that didn't apply to the request. The specific-IP rule (spec=2, sev=0,
rank=4) was pushed out. Node saw only irrelevant candidates,
`rankCandidateRules` filtered them all via `ruleApplies`, and the
lookup returned `[]`. `handleAccessPolicy` returned `handled: false`
and the DM ran with default POW.

Fix: extend the existing split-query path (previously `blockOnly=true`
hot path only) to cover every matchingFieldsOnly caller. Each probe
hits a discriminating posting list; the ip:exact probe returns exactly
the rules that literally match this IP. `blockOnly` is now a flag on
the sub-query builder that gates the `@type:{block}` filter. Reader
sorts by (specificity desc, block-first) so direct consumers see the
same ordering the old FT.AGGREGATE ranker imposed.

Regression coverage:
- unit: `buildScopedRulesSubQueries` emits/omits `@type:{block}` per
  `blockOnly` flag; probe shape unchanged.
- integration: specific-IP Restrict rule survives when 40
  higher-specificity irrelevant Block rules co-exist on the same
  tenant (mirrors the live Twickets shape).

Benchmarks unchanged: split hot path p50=1.4ms / p99=2.2ms against
19,300 seeded rules; 100×10 concurrent storm holds ~990 req/s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@forgetso forgetso enabled auto-merge (squash) July 10, 2026 13:02
@forgetso forgetso merged commit b07b448 into main Jul 10, 2026
11 checks passed
@forgetso forgetso deleted the fix/split-restrict-rule-lookup branch July 10, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant