Amends 0098 (decision 2:
@AsListener is removed),
0144 (decision 5: @Setting stays a field
decorator), 0126 (decision 3: new kind folders),
0130 (decision 1: new kind folders),
0131 (decision 1: the key is one segment; decision
3: name is gone),
0133 (decision 3:
model keys are snake_case),
0145 (decision 2: the
define family), 0231 (module keys and model keys in the
naming table) and 0234 (decision 2: the namespace is the
module key) and 0247
(decision 2: the family).Context
Keys appear in permissions, events, action keys, settings and i18n namespaces. Two key forms were in use: a modulekey and a separate
name, and dotted module keys that make <module>.<resource>.<action> and <module>.<aggregate>.<verb>.v<n> ambiguous. The define
family was described in three places and had no closed list. Kind folders were listed without hooks, widgets, pages, links, drivers and
ai. The owner settled these on 2026-10-21.
Decision
- One key grammar. A module
keyis one segment, snake_case for a multi-word key (orders,giftcards,loyalty_kit), and globally unique. The key is also the namespace for permissions, events, action keys, settings, i18n, assets and templates. The@Module({ name })field is removed, sonameandkeyare never both used. - Model keys follow it. A model key is snake_case under its module,
inventory.stock_level, notinventory.stockLevel. - Dotted module keys are refused. The grammars of 0132 and 0141 parse only when the module segment has no dot.
- The define family is closed. The family has eight entries:
defineToken,defineConfig,definePermissions,defineEvents,defineHooks,defineComponents,defineMeterswithdefineEntitlements, anddefineSkillwithdefinePrompts. The last two pairs count as one entry each.defineHooksanddefineComponentsare new. A new member of the family takes an ADR. - Settings stay a decorated class. A
@Settingsclass holds@Settingfields (0144). Code reads them withSettingsReader.get(OrderSettings), which returns the typed object for the current scope. - The authoring rule. Decorators annotate what the framework calls.
define*creates values that other code references (0247). A string literal where a reference exists is refused, in a permission, an event,refreshOn,invalidateOn, an@OnEventargument or a hook point. The exceptions are a remote component key and the wire names in generated files. - Kind folders.
hooks/,widgets/,pages/,links/,drivers/andai/join the kind folders of 0130. Each appears with its first file. - One listener form.
@AsListeneris removed. A reaction is@OnEvent(Events.x, options)on a method of a class inlisteners/. A listener that must run a whole action calls it through the action bus. - Jobs are actions. “Job” is not a class kind. It is the
@AsJobadapter on an@Action, and@Crononly dispatches it. - Two senses of “service” stay. A deployable is
services/<name>/. A module service is an@Injectableinservices/inside a module. Agents tell them apart by context.
Consequences
- A permission, an event, an action key and an i18n key share one prefix, so a reviewer reads the module from any of them.
- A typo in a key is a type error at the use site, because the key is a value from a
define*call. - Renaming a module key is a breaking change under 0131. A key is chosen once.
- The family is closed, so a new kind of value needs an ADR. That is intended.
Enforcement
planned — gen fails on a module key collision, on a key with uppercase letters or dots, on adefine* outside the family, on a name
field in a @Module, and on a string where a reference is required (the lint of 0247).
Nothing checks these yet.