feat(engagement): let a module ship its own templates and rules (Phase 11b)
Phase 11a declared 24 triggers and stopped where the plan said it would. Standing
11b up found that the next sentence — "24 rules, all enabled = 0; bespoke template
bodies" — described work with no mechanism to land in: templateSeeds.js and
coreRules.js are core files with core arrays in them, and there was no
registerTemplates or registerRules anywhere in registries.js.
So a module could say what an event's payload was and could never say what the
mail should read like. That is tolerable for one trigger and not for a catalogue,
and it is decisive once the bodies carry domain prose core must not contain (§5.2).
Adds api.registerEngagementSeeds({ templates, ruleGroups }) — MODULE_API 1.9.0.
The module supplies data; core keeps seedOne's customized skip, its seed_version
comparison and the block registry's validation, which is the whole argument for a
registry over the ctx.query a module already holds: a copy of any of those living
outside engagement/ would drift the first time core improved the original, and the
drift would surface as a mail somebody already received.
The two halves behave differently, deliberately:
- Templates re-ensure on every boot, so a bumped seedVersion reaches every
deployment except the ones where an operator edited that row.
- Rule groups are ONE-SHOT, each under its own settings guard — re-ensuring
would resurrect a rule an operator deleted and reset one they enabled. This is
11a's seed-key finding stated as an API rather than as a warning: a rule
appended to an existing group reaches fresh installs only, and one that must
reach stamped deployments takes a new group key.
Three prohibitions, each a shipped mistake that would only surface as mail: a
seeded rule is always enabled = 0 (Q3's invariant, ignored rather than refused so
a typo cannot take a module offline at boot); a module may not mark a template
protected; and a rule may only name its own trigger ids and its own or core's
template keys, with template keys namespaced because the key column is UNIQUE.
Runs from modules/lifecycle.js boot() rather than seedDefaults(), and that is
forced rather than chosen: server.js seeds before it requires app.js, and
requiring app.js is what runs the loader — at the moment core seeds, no module has
registered anything. Placed after the installed_modules reconcile (so a disabled
or failed module is skipped) and before the onBoot dispatch (so a module warming a
cache may assume its rules exist).
16 new tests; 1549 core tests green; check:modules clean.
Refs docs#/ENGAGEMENT.md Phase 11b decision 7.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,39 @@
|
||||
// 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.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
|
||||
// READ like: `templateSeeds.js` and `coreRules.js` are core files with core
|
||||
// arrays in them, so a module's notification was core's generic body or nothing.
|
||||
// Additions only, so minor: every module written against 1.8.0 keeps working and
|
||||
// simply seeds nothing.
|
||||
//
|
||||
// **What a module has to know about it beyond the new name**, because the two
|
||||
// halves behave differently on purpose:
|
||||
//
|
||||
// - **Templates are re-ensured on every boot**, under `seed_key` /
|
||||
// `seed_version` / `customized` - so bumping a body's `seedVersion` reaches
|
||||
// every deployment except the ones where an operator edited that row, and a
|
||||
// template added in a later module version reaches everyone.
|
||||
// - **Rules are one-shot, per named GROUP.** Re-ensuring one would resurrect a
|
||||
// rule an operator deleted and reset one they enabled, so each group carries
|
||||
// its own settings guard. A rule appended to an existing group therefore
|
||||
// reaches FRESH INSTALLS ONLY; one that must reach deployments already
|
||||
// stamped takes a new group key. That is 11a's seed-key finding as an API
|
||||
// rather than as a warning, and the module makes the choice knowingly.
|
||||
//
|
||||
// Two things it deliberately does not permit. A seeded rule is always
|
||||
// `enabled = 0` - it is not a parameter - which is Q3's invariant surviving
|
||||
// contact with the largest seed set in the workstream. And a module may not mark
|
||||
// a template `protected`: that flag means "the system breaks without this body",
|
||||
// which is true of a password reset and of nothing a module ships, and a module
|
||||
// setting it would take an operator's delete button away.
|
||||
//
|
||||
// It runs from `modules/lifecycle.js` `boot()` rather than `seedDefaults()`, and
|
||||
// that is forced rather than chosen: core seeds before `app.js` is required, and
|
||||
// requiring `app.js` is what runs the loader.
|
||||
|
||||
// 1.8.0 - a seventh value in the audience ceiling lattice: `admin`, a child of
|
||||
// `staff` (docs/website/ENGAGEMENT.md Phase 11, decision 1). A module may now
|
||||
// declare `ceiling: 'admin'` on a trigger or an audience, so the set of values
|
||||
@@ -101,6 +134,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.8.0'
|
||||
const MODULE_API_VERSION = '1.9.0'
|
||||
|
||||
module.exports = { MODULE_API_VERSION }
|
||||
|
||||
Reference in New Issue
Block a user