ADR-0038 — Declaring operations at all makes it an exclusive whitelist
Status: accepted (issue #257)
Context
Before this change, EntityConfig.operations merged per key: naming one standard operation (operations: { updateOne: { cache: { ttl: 60 } } } }) configured only that operation, and every other standard id resolved from its own built-in/global/soft-delete default, untouched. That reads naturally for the case the shape was built for — override one operation's settings, leave the rest alone — but it does not match how a caller who names a subset of operations actually reads their own config: writing down createOne, updateOne, and deleteOne and nothing else looks like "this is the CRUD surface I want," not "this is the CRUD surface I want, plus five operations I didn't mention." The gap between what the config says and what a reader expects it to mean was the original report behind this issue.
The two readings can't coexist under one resolution rule: a config that names one operation to tweak a setting and a config that names one operation to declare the whole surface are written identically. Something has to decide which one operations means, and it can't be per-entry, because the ambiguity is about the set of operations, not about any one entry's own shape.
Decision
operations is resolved in two distinct modes, selected by whether the key is present at all:
- Absent (
config.operations === undefined): every standard id resolves from the existing chain — built-in default, then the globalKavoSettings.operationsboolean map (ADR-0015), then the soft-delete-declared auto-enable forrestoreOne(ADR-0013). Nothing about this mode changed. - Present, even as
{}: every standard id not named in it is disabled, regardless of what the global default, the soft-delete auto-enable, or the built-in table would otherwise say. A named id resolves its ownenabled: thetrue/falseshorthand says so explicitly, in either direction; an object enables by being named — there is noenabledfield to check instead, since the object's own presence already answers the question. A key present with anundefinedvalue counts as absent, not as naming the id (the same way an optional property beingundefinedis conventionally "not set" rather than "set to nothing").
Custom operation ids are unaffected: they are registered whenever named, independent of the standard-id whitelist rule above, exactly as before (ADR-0006).
Consequences
- This is a breaking change to a public config surface. A config that named a subset of standard operations to configure them, relying on the rest staying at their default, now silences the rest. Every caller with such a config has to enumerate the full operation set it wants once it names any of them.
restoreOneandpurgeOneno longer get their special-cased defaults (soft-delete auto-enable, permanently-off-until-named) onceoperationsis declared at all — naming them is what enables them, the same as any other id. ADR-0013's rule still governs the absent case.- The global
KavoSettings.operationsdefault map (ADR-0015) is bypassed entirely for any id an entity's declaredoperationsdoesn't name — not merely overridden for the ids it does name, which is what ADR-0015 originally described. See that ADR's issue #257 update. - There is no
enabledfield onOperationConfigfor a standard id, and no long form of the boolean shorthand —{ enabled: true }is a type error. The shorthand and object-presence are the only two ways to name an id, deliberately kept to two rather than three equivalent spellings.