Uh oh!
There was an error while loading. Please reload this page.
fix(service-settings): user-keyed loadRows includes tenant/global rows on the engine branch (#11228) - #11272
Conversation
…s on the engine branch (#11228) The engine branch built `where.user_id = userId`, which excludes every upper-scope row (user_id NULL) — while the in-memory branch's predicate includes them. Two consumers search the ONE result set a user-keyed load returns, so on every engine-bound deployment: 1. resolveKey's user→tenant→global cascade fell straight through to the manifest default whenever the user had no personal row, silently ignoring persisted tenant/global values; and 2. the Phase-2 upper-scope lock check found no locked tenant/global row, so a lock that should refuse user-scope writes never fired — a policy bypass, not just a stale read. The suite stayed green because the in-memory double answered correctly (the #4434 class — a double looser than the engine — in a WHERE clause): the shared fake matcher did bare field equality, so it would also have silently matched nothing against the new $or. Both test harnesses now implement $or and THROW on any other combinator, and the new suite pins the fallback, the other-user exclusion, an engine/memory differential, and the lock refusal on the engine path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 7 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a94cdf5338664dbcd32617d1ce0e38a3946644fb && git checkout a94cdf5338664dbcd32617d1ce0e38a3946644fb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a2ec377078d41432358aac2a42cd74b87ed473f1 89bd8d6707b1b21331aad6bba1224657911d8cff && git checkout -B drift-repro a2ec377078d41432358aac2a42cd74b87ed473f1 && git merge --no-ff 89bd8d6707b1b21331aad6bba1224657911d8cff
node scripts/docs-audit/affected-docs.mjs --json a2ec377078d41432358aac2a42cd74b87ed473f1 |
Closes#11228.
The defect
loadRows(namespace, userId)on the engine branch builtwhere.user_id = userId, excluding every upper-scope row (they carryuser_id NULL), while the in-memory branch's predicate includes them. Two consumers search the ONE result set a user-keyed load returns, so on every engine-bound deployment — i.e. every real one:The fix
One WHERE clause:
{ namespace, $or: [{ user_id }, { scope: 'tenant' }, { scope: 'global' }] }, mirroring the memory predicate exactly. Both call sites (resolveKey / setMany's lock pre-flight) heal at once;getMany's grouped loads only ever get MORE rows, which its per-scope resolution already filters.Why the suite was green over it
The shared fake engine matcher did bare field equality — the #4434 class (a double looser than the engine), in a WHERE clause — and would ALSO have silently matched nothing against the new
$or. Both harnesses (this suite +settings-getmany.test.ts) now implement$orand THROW on any other combinator, per the WHERE-matcher gate convention. New suite pins: the global fallback, other-user exclusion, an engine/memory differential over three contexts, and the lock refusal on the engine path. 481/481 package tests green.🤖 Generated with Claude Code