Skip to content

SettingsService.getMany's all-or-nothing key validation is undocumented on the contract that owns it #11680

Description

@huangyiirene

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

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions