feat(events): open the event contract to modules (Phase 7)
MODULE_API 1.10.0. Four names forwarded on the module-facing `api` -- registerEventActions, registerEventBudgets, registerEventLeases and registerEventOptionSources -- one new route, and one rule made real: a `cost()` naming a dimension no module declared is refused. Only one of the four is new machinery. The action registry has staged core's three actions on every boot since Phase 1; what it never had was a way in, because loader.js builds its own `api` facade and had no method that delegated to it. So the registry a module now reaches is one that has been exercised on every boot for six phases. Four decisions, settled 2026-09-03, all as recommended: - Option sources are their own registration, modelled on registerAudiences, because a catalog has more than one consumer. - An undeclared dimension is refused -- at save, at the dry run and at dispatch -- with its own code, because the fix is a module's declaration and not a deployment's cap. - A lease is declared here and acquired by nothing; the ledger is Phase 8. - Core registers core.options.legs, so an announce leg is a dropdown rather than the free-text box whose typo Phase 6's walk caught mid-run. Proved with a throwaway module through the real loader, not with module-uo: eventModuleContract.test.js writes a module to a real directory and lets the loader scan it, covering all five envelope failure shapes, verify: true, the four id spaces and dormancy on uninstall. The live walk found the one defect nothing else could: the option-source loader wrote its "already asked?" guard inside a setState updater and read it on the next line, so the request was never made and the field sat on "Reading the list..." for ever. It is a useRef now. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6t8mrAWhZU5vnyYgZTMtL
This commit is contained in:
@@ -51,10 +51,17 @@ const ACTIONS = [
|
||||
// A leg id, checked against the announce-leg registry at dispatch rather
|
||||
// than here: legs are registered by modules, and this file is evaluated
|
||||
// before any module has registered anything.
|
||||
//
|
||||
// **`source` is what moves that check earlier** (Phase 7). The dispatch
|
||||
// check stays — a module can boot between authoring and the run — but
|
||||
// until now a typo here was caught mid-run and nowhere else, which is the
|
||||
// defect Phase 6's walk hit: an announce leg "site" no module registers,
|
||||
// found by a dry run rather than by the form that accepted it.
|
||||
name: 'leg',
|
||||
type: 'string',
|
||||
required: true,
|
||||
example: 'discord',
|
||||
source: 'core.options.legs',
|
||||
description: 'The announce leg to publish on. Registered legs only.',
|
||||
},
|
||||
{
|
||||
@@ -209,4 +216,27 @@ const ACTIONS = [
|
||||
},
|
||||
]
|
||||
|
||||
module.exports = { ACTIONS }
|
||||
// ── Core's own param option sources (§F, Phase 7) ──────────────────
|
||||
//
|
||||
// One, and it is core's half of the seam it hands a module on the same boot: a
|
||||
// param's `source` names a registered option source, core asks it for values, and
|
||||
// the authoring form renders a dropdown instead of a text box.
|
||||
//
|
||||
// **The legs are already a registry with labels in it**, so this costs nothing
|
||||
// new — which is what makes it the right first exercise. `resolve()` is called
|
||||
// per request rather than read once, for the same reason `core.announce` looks a
|
||||
// leg up inside `perform()`: a leg registered by a module that booted after this
|
||||
// file was evaluated must still appear, and a module uninstalled since must stop
|
||||
// appearing.
|
||||
const OPTION_SOURCES = [
|
||||
{
|
||||
id: 'core.options.legs',
|
||||
label: 'Announce legs',
|
||||
description: 'Every delivery leg registered on this deployment right now.',
|
||||
async resolve() {
|
||||
return registries.announceLegs().map((l) => ({ value: l.leg, label: l.label || l.leg }))
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
module.exports = { ACTIONS, OPTION_SOURCES }
|
||||
|
||||
Reference in New Issue
Block a user