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:
@@ -9,6 +9,27 @@
|
||||
// Deliberately separate from PROTOCOL_VERSION (which versions the shard wire and
|
||||
// has nothing to say about a website module) and from any module's own version.
|
||||
|
||||
// 1.10.0 — the event contract opens to modules: `api.registerEventActions`,
|
||||
// `api.registerEventBudgets`, `api.registerEventLeases` and
|
||||
// `api.registerEventOptionSources` (docs/website/EVENTS.md §F, EVENTS_PLAN.md
|
||||
// Phase 7). Four names, and only one of them is new machinery: the ACTION
|
||||
// registry has staged core's `core.announce`, `core.wait` and `core.cue` on every
|
||||
// boot since Events Phase 1, and `loader.js` simply had no method that delegated
|
||||
// to it. What Phase 7 adds is the facade, the three declarations beside it, and
|
||||
// the fail-closed rule they exist for — a `cost()` naming a dimension no module
|
||||
// registered is REFUSED at save, at the dry run and at dispatch, so a module
|
||||
// cannot spend a budget it did not declare.
|
||||
//
|
||||
// Additions only, so minor: a module written against 1.9.0 registers no actions
|
||||
// and the deployment simply has fewer verbs an event can use. That is §F's own
|
||||
// posture stated as a version rule — core with no module installed is still an
|
||||
// event engine that can announce, wait, cue a human and publish results.
|
||||
//
|
||||
// **A lease is DECLARED here and acquired by nothing.** Core owns a lease's
|
||||
// duration and its conflict check, and both live in the resource ledger, which is
|
||||
// Phase 8's. It is in 1.10.0 rather than in 1.11.0 so that the module contract is
|
||||
// one version a module author reads once, not two.
|
||||
//
|
||||
// 1.9.0 - a sixth registration call: `api.registerEngagementSeeds({ templates,
|
||||
// ruleGroups })` (docs/website/ENGAGEMENT.md Phase 11b, decision 7). A module
|
||||
// could declare a trigger from 1.7.0 and could never say what the mail should
|
||||
@@ -134,6 +155,6 @@
|
||||
// an admin action a module performs belongs in core's one audit log, the
|
||||
// extension slot needs the user its prefix names, and §2.7 forbids a module
|
||||
// reading core's `APP_BASE_URL` for itself. Additions only, so minor.
|
||||
const MODULE_API_VERSION = '1.9.0'
|
||||
const MODULE_API_VERSION = '1.10.0'
|
||||
|
||||
module.exports = { MODULE_API_VERSION }
|
||||
|
||||
Reference in New Issue
Block a user