You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
settings: loadRows engine branch drops tenant/global rows for user-scope reads — cascade dies at the user level on every engine-bound deployment #11228
Found while implementing #10826's getMany (the extraction preserved the behavior faithfully; this predates it — flagging rather than silently widening that PR's scope).
The divergence
SettingsService.loadRows(namespace, userId) answers differently by backend when userId != null:
engine branch: if (userId !== null) where.user_id = userId → the find returns ONLY rows with user_id = <userId>. Global-scope and tenant-scope rows carry user_id = NULL, so they are excluded.
memory branch: userId === null || r.user_id === userId || r.scope === 'tenant' || r.scope === 'global' → the user's rows PLUS tenant and global rows.
Consequence
resolveKey cascades user → tenant → global → defaultwithin the one rows array it was handed. For a user-scope key on an engine-bound deployment — which is every real deployment — a user with no personal row never sees a persisted tenant or global value: the cascade falls straight through to the manifest default. The same read against the in-memory store resolves correctly, which is exactly why the suite is green over a live defect (the fake is looser than the engine — the #4434 class, this time in a WHERE clause rather than a verb).
getMany (#11200) groups user-scope keys under the userId load and tenant/global keys under the null load, so tenant/global keys resolve fine either way; the broken path is specifically the user-scope key's fallback.
Fix shape
Make the engine WHERE mirror the memory predicate — e.g. { namespace, $or: [{ user_id: userId }, { scope: 'tenant' }, { scope: 'global' }] } — and pin it with a test that runs the same fixture through BOTH branches and asserts identical resolution (the differential harness in settings-getmany.test.ts is most of the scaffolding).
Evidence: packages/services/service-settings/src/settings-service.ts, loadRows, verified on origin/main today.
Triage: pm:queue, domain:services, Bug — engine WHERE excludes tenant/global rows for user-scope reads, so the declared user → tenant → global → default cascade dies at the user level on every engine-bound deployment; memory branch resolves correctly (fake-looser-than-engine, #4434 class). Fix shape is in the card (mirror the memory predicate; differential both-branches pin reusing the settings-getmany.test.ts harness). Serial constraint: settings-service.ts is hot with the #10826/#11200 getMany work — the services seat answers fold-or-serial against any in-flight settings PR at claim time.
Activity
os-zhuang commentedon Aug 23, 2026
Triage:
pm:queue,domain:services, Bug — engine WHERE excludes tenant/global rows for user-scope reads, so the declareduser → tenant → global → defaultcascade dies at the user level on every engine-bound deployment; memory branch resolves correctly (fake-looser-than-engine, #4434 class). Fix shape is in the card (mirror the memory predicate; differential both-branches pin reusing thesettings-getmany.test.tsharness). Serial constraint:settings-service.tsis hot with the #10826/#11200 getMany work — the services seat answers fold-or-serial against any in-flight settings PR at claim time.Generated by Claude Code
fix(service-settings): user-keyed loadRows includes tenant/global row…