Repository navigation
abuse-enforcement-service: fail closed when the allowlist store errors - #7
Merged
Merged
Conversation
The allowlist is an exemption check that runs before any rule can fire.
Until now a Manhattan GET failure during that check was swallowed inside
ManhattanAllowlist::get_entity and returned as None, which the fetch
helpers read as is_allowlisted: false. Enforcement then continued into the
rule pipeline for an account or post that may have been exempt, while the
neighbouring Gizmoduck and credibility fetches on the same path abort with
`?` and are retried.
Make the allowlist lookup behave like those fetches:
- ManhattanAllowlist::get / get_entity return anyhow::Result<Option<_>>.
Ok(None) means the store confirmed the key is absent. A GET error or an
undecodable stored entry is returned as Err instead of None.
- fetch_user_allowlist / fetch_entity_allowlist return Result and
propagate the error. Only a confirmed absence maps to "not allowlisted".
- run_enforcement_inner uses `?` on the allowlist lookups, so a store
error aborts the attempt and the score lands in the existing retry
queue (backoff, then dropped without enforcing) rather than proceeding
to rules.
- Admin handlers: GET /allowlist/{id} and GET /allowlist/{type}/{id}
return 500 on a read error instead of 404; bulk upsert reports a failed
pre-read as a per-row error; the DELETE audit snapshot stays best-effort.
Adds unit tests for the lookup-to-facts conversion, including one that
asserts a store error is not turned into is_allowlisted=false.
Co-authored-by: Jon Bailey <Pitchfork-and-Torch@users.noreply.github.com>
cursor Bot
pushed a commit
that referenced
this pull request
Sep 8, 2026
#7) The allowlist is an exemption check that runs before any rule can fire. Until now a Manhattan GET failure during that check was swallowed inside ManhattanAllowlist::get_entity and returned as None, which the fetch helpers read as is_allowlisted: false. Enforcement then continued into the rule pipeline for an account or post that may have been exempt, while the neighbouring Gizmoduck and credibility fetches on the same path abort with `?` and are retried. Make the allowlist lookup behave like those fetches: - ManhattanAllowlist::get / get_entity return anyhow::Result<Option<_>>. Ok(None) means the store confirmed the key is absent. A GET error or an undecodable stored entry is returned as Err instead of None. - fetch_user_allowlist / fetch_entity_allowlist return Result and propagate the error. Only a confirmed absence maps to "not allowlisted". - run_enforcement_inner uses `?` on the allowlist lookups, so a store error aborts the attempt and the score lands in the existing retry queue (backoff, then dropped without enforcing) rather than proceeding to rules. - Admin handlers: GET /allowlist/{id} and GET /allowlist/{type}/{id} return 500 on a read error instead of 404; bulk upsert reports a failed pre-read as a per-row error; the DELETE audit snapshot stays best-effort. Adds unit tests for the lookup-to-facts conversion, including one that asserts a store error is not turned into is_allowlisted=false. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jon Bailey <Pitchfork-and-Torch@users.noreply.github.com>
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.
Problem
In
abuse-enforcement-service, the allowlist is the exemption check that runs before any enforcement rule can fire. A Manhattan GET failure during that check was swallowed insideManhattanAllowlist::get_entityand returned asNone.fetch_user_allowlist/fetch_entity_allowlistreadNoneasis_allowlisted: false, sorun_enforcement_innercontinued into the rule pipeline for an account or post that may have been exempt.The neighbouring Gizmoduck and credibility fetches on the same path behave differently: they abort with
?and the score is retried. Only the exemption lookup failed toward enforcement.Change
Make the allowlist lookup behave like the other critical fetches.
ManhattanAllowlist::get/get_entityreturnanyhow::Result<Option<_>>.Ok(None)means the store confirmed the key is absent. A GET error, or a stored entry that cannot be decoded, is returned asErrinstead ofNone.fetch_user_allowlist/fetch_entity_allowlistreturnResultand propagate the error. Only a confirmed absence maps to "not allowlisted". Shared conversion lives in a smallallowlist_factshelper.run_enforcement_inneruses?on the allowlist lookups. A store error now aborts the attempt and the score lands in the existing retry queue (exponential backoff,MAX_RETRIES, then dropped without enforcing) instead of proceeding to rules.GET /allowlist/{id}andGET /allowlist/{type}/{id}return 500 on a read error instead of 404 (OpenAPI responses updated). Bulk upsert reports a failed pre-read as a per-row error rather than guessingadd. TheDELETEauditbefore_jsonsnapshot stays best-effort and never blocks the delete.No change to behaviour when the store is healthy. No rule, config, or action changes.
Before / after
user_in_allowlist)user_in_allowlist)act_suspend)Err→ retry queue; never enforces on this attemptErr→ retry queueTests
strato.rsfor the lookup-to-facts conversion, including one asserting that a store error is not turned intois_allowlisted=false.xai_*crates that are not in the repo, so it cannot be built here. The changedallowlist.rs/strato.rscode was compiled verbatim in a scratch crate against stubbed store/facts types (sametracing,anyhow,serde_jsonusage) and driven through a scripted store: 9/9 tests pass and the before/after table above is its output. OnlyManhattan GET failedincrementsMANHATTAN_ERRORS_TOTAL; the decode-failure case does not.allowlist.rsandstrato.rsare rustfmt-clean. Remaining rustfmt diffs inlib.rs/service.rsare pre-existing onmainand untouched.Upstream
The touched regions are identical on
xai-org/x-algorithmmainand this commit cherry-picks onto it cleanly. A full sync of this fork'smainwith upstream is not part of this PR: it conflicts in four files owned by the fork's own commits (README.md,home-mixer/candidate_hydrators/vf_candidate_hydrator.rs,home-mixer/params/param.rs,home-mixer/scorers/ranking_scorer.rs).