Found while implementing #11622 (the MCP stdio pre-bind localization memo). Not fixed there — the code is in @objectstack/core, outside that card's declared file surface, and narrowing it touches a shared read path with its own dogfood pin.
The mechanism
resolveLocalizationContext (packages/core/src/security/resolve-authz-context.ts:798) memoizes an outcome for LOCALIZATION_FAILURE_CACHE_TTL_MS = 30s, keyed on (ql, "tenantId|userId"), whenever the underlying read reports failed (#10221, so a repeatedly-failing sys_setting query does not re-run and re-log every request):
780 const LOCALIZATION_FAILURE_CACHE_TTL_MS = 30_000;
803 if (hit && hit.expiresAt > Date.now()) return hit.value;
failed is set in six places, and only two of them are the backend fault the cache's own docblock describes. The other four are the settings service leg throwing (resolve-authz-context.ts:853, 858, 862, 866) — including the all-or-nothing refusal SettingsService.getMany gives for a localization namespace whose manifest is not (yet) fully registered, which the caller's own comment at line 834 already records as a known degradation.
Why that matters now
It makes the 30s cache reachable inside the settings pre-bind window — and a caller that deliberately re-reads after the bind can then be served the cached pre-bind answer. #11622's repair is exactly such a caller: it discards the memo taken inside the window and re-resolves at kernel:bootstrapped. If the in-window read had set failed, that re-resolution can be answered from the cache within 30s and then memoized as the post-bind value — the correction silently not happening, with nothing in the output saying so.
The same interaction predates #11622 in a worse form (before it, the pre-bind answer was kept unconditionally and forever), so this is a residue of that repair rather than a regression from it.
Scope and honesty about what was measured
Not measured.#11622's pins do not reach it and were not written to: the settings double there never throws and sys_setting answers [], so failed stays false on every leg. This is filed from reading the cache's write condition against its docblock, not from a repro.
Two candidate repairs, both with a cost worth deciding deliberately rather than folding into a bug fix:
- Only cache a genuine backend fault (the direct
ql.find throw), not a settings-service throw. Closest to the cache's stated intent; loses the de-dup for the settings-throw case that also spams. - Give the caller a way to bypass the entry on a read it knows is a deliberate re-read after a state change.
Either one moves a read path pinned by packages/qa/dogfood/test/analytics-timezone.dogfood.test.ts (the #1982/#2018 golden regression, which is why the cache was narrowed to failures in the first place), so it wants its own card.
Generated by Claude Code
Found while implementing #11622 (the MCP stdio pre-bind localization memo). Not fixed there — the code is in
@objectstack/core, outside that card's declared file surface, and narrowing it touches a shared read path with its own dogfood pin.The mechanism
resolveLocalizationContext(packages/core/src/security/resolve-authz-context.ts:798) memoizes an outcome forLOCALIZATION_FAILURE_CACHE_TTL_MS= 30s, keyed on(ql, "tenantId|userId"), whenever the underlying read reportsfailed(#10221, so a repeatedly-failingsys_settingquery does not re-run and re-log every request):failedis set in six places, and only two of them are the backend fault the cache's own docblock describes. The other four are the settings service leg throwing (resolve-authz-context.ts:853,858,862,866) — including the all-or-nothing refusalSettingsService.getManygives for alocalizationnamespace whose manifest is not (yet) fully registered, which the caller's own comment at line 834 already records as a known degradation.Why that matters now
It makes the 30s cache reachable inside the settings pre-bind window — and a caller that deliberately re-reads after the bind can then be served the cached pre-bind answer. #11622's repair is exactly such a caller: it discards the memo taken inside the window and re-resolves at
kernel:bootstrapped. If the in-window read had setfailed, that re-resolution can be answered from the cache within 30s and then memoized as the post-bind value — the correction silently not happening, with nothing in the output saying so.The same interaction predates #11622 in a worse form (before it, the pre-bind answer was kept unconditionally and forever), so this is a residue of that repair rather than a regression from it.
Scope and honesty about what was measured
Not measured.#11622's pins do not reach it and were not written to: the settings double there never throws and
sys_settinganswers[], sofailedstaysfalseon every leg. This is filed from reading the cache's write condition against its docblock, not from a repro.Two candidate repairs, both with a cost worth deciding deliberately rather than folding into a bug fix:
ql.findthrow), not a settings-service throw. Closest to the cache's stated intent; loses the de-dup for the settings-throw case that also spams.Either one moves a read path pinned by
packages/qa/dogfood/test/analytics-timezone.dogfood.test.ts(the #1982/#2018 golden regression, which is why the cache was narrowed to failures in the first place), so it wants its own card.Generated by Claude Code