Filed unassigned from the domain:engine seat while implementing #11222 (PR #11677). Routed here rather than fixed there: #11222's own body flagged this as possibly belonging on getMany's contract rather than at its first call site, and triage designated #11222 a core + rest card. Fixing it in packages/services/service-settings would have been a silent widening into a third package, so it is a card of its own — domain:services.
The behaviour
SettingsService.getMany (packages/services/service-settings/src/settings-service.ts) validates every requested key before loading a single row:
constreg=this.registry.get(namespace);if(!reg)thrownewUnknownNamespaceError(namespace);for(constkeyofkeys){if(!reg.scopes.has(key))thrownewUnknownKeyError(namespace,key);}So one unknown key rejects the whole call — the caller gets nothing, not the subset that was declared. get has no such property: one key failing leaves the others answerable.
This is a legitimate design (a batched read either has a coherent manifest or it does not), and it is pinned — settings-getmany.test.ts asserts await expect(svc.getMany('localization', ['timezone', 'nope'])).rejects.toThrow(/nope/). What is missing is that getMany's doc comment never says it, even though the comment is otherwise detailed: it explains the row-load grouping, the env-override ordering, and claims row-for-row equivalence with per-key get "BY CONSTRUCTION". That equivalence claim is true for every key that resolves and false for the failure mode, and the doc does not draw the line.
Why it matters to a caller
Every batched consumer inherits the all-or-nothing rule, and the first one already had to write the consequence down locally. resolveLocalizationContext degrades correctly — PR #11677 records that a host registering a partiallocalization manifest loses all three keys at once and drops to a shorter cascade (no global scope layer, no OS_LOCALIZATION_* env override) where the per-key path would still have resolved the declared keys. That note is the caller's own degradation and belongs where it is. The rule it is a consequence of belongs on the producer, so consumer number two does not have to rediscover it from a test.
Unreachable against the in-repo localizationSettingsManifest, which declares all three keys — this is reachable only for a host with a partial manifest. Documentation, not a behaviour change.
Suggested shape
A paragraph on getMany's doc comment stating that key validation is up-front and total, that this is the one respect in which it is not equivalent to N get calls, and what a caller should expect on a partial manifest. No code change; the behaviour is correct and already pinned.
Generated by Claude Code
Filed unassigned from the
domain:engineseat while implementing #11222 (PR #11677). Routed here rather than fixed there: #11222's own body flagged this as possibly belonging ongetMany's contract rather than at its first call site, and triage designated #11222 acore+restcard. Fixing it inpackages/services/service-settingswould have been a silent widening into a third package, so it is a card of its own —domain:services.The behaviour
SettingsService.getMany(packages/services/service-settings/src/settings-service.ts) validates every requested key before loading a single row:So one unknown key rejects the whole call — the caller gets nothing, not the subset that was declared.
gethas no such property: one key failing leaves the others answerable.This is a legitimate design (a batched read either has a coherent manifest or it does not), and it is pinned —
settings-getmany.test.tsassertsawait expect(svc.getMany('localization', ['timezone', 'nope'])).rejects.toThrow(/nope/). What is missing is thatgetMany's doc comment never says it, even though the comment is otherwise detailed: it explains the row-load grouping, the env-override ordering, and claims row-for-row equivalence with per-keyget"BY CONSTRUCTION". That equivalence claim is true for every key that resolves and false for the failure mode, and the doc does not draw the line.Why it matters to a caller
Every batched consumer inherits the all-or-nothing rule, and the first one already had to write the consequence down locally.
resolveLocalizationContextdegrades correctly — PR #11677 records that a host registering a partiallocalizationmanifest loses all three keys at once and drops to a shorter cascade (noglobalscope layer, noOS_LOCALIZATION_*env override) where the per-key path would still have resolved the declared keys. That note is the caller's own degradation and belongs where it is. The rule it is a consequence of belongs on the producer, so consumer number two does not have to rediscover it from a test.Unreachable against the in-repo
localizationSettingsManifest, which declares all three keys — this is reachable only for a host with a partial manifest. Documentation, not a behaviour change.Suggested shape
A paragraph on
getMany's doc comment stating that key validation is up-front and total, that this is the one respect in which it is not equivalent to Ngetcalls, and what a caller should expect on a partial manifest. No code change; the behaviour is correct and already pinned.Generated by Claude Code