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>
212 lines
6.5 KiB
JSON
212 lines
6.5 KiB
JSON
{
|
|
"_comment": "Generated event-trigger inventory - the authoritative freeze of CORE's engagement contract (docs/website/ENGAGEMENT.md 4.3). Regenerate with `npm run engagement:manifest` in website/server. A renamed variable, a changed type or a widened ceiling breaks stored templates and rules, so the diff here is the review signal. A module ships its own copy in its bundle; this file never contains one.",
|
|
"moduleApiVersion": "1.9.0",
|
|
"triggers": [
|
|
{
|
|
"id": "news.post",
|
|
"owner": "core",
|
|
"label": "News post published",
|
|
"description": "A news / Five-on-Friday / newsletter post was published.",
|
|
"kind": "event",
|
|
"subjectKey": null,
|
|
"audience": "subscribers",
|
|
"ceiling": "authenticated",
|
|
"version": 1,
|
|
"variables": [
|
|
{
|
|
"name": "title",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "Five on Friday — the Yew invasion",
|
|
"description": "The post title."
|
|
},
|
|
{
|
|
"name": "excerpt",
|
|
"type": "string",
|
|
"required": false,
|
|
"example": "Four new champion spawns, and the fate of the Yew moongate…",
|
|
"description": "A plain-text summary, already stripped of markup."
|
|
},
|
|
{
|
|
"name": "category",
|
|
"type": "string",
|
|
"required": false,
|
|
"example": "Five on Friday",
|
|
"description": "The post category, when it has one."
|
|
},
|
|
{
|
|
"name": "postUrl",
|
|
"type": "url",
|
|
"required": true,
|
|
"example": "/site/news",
|
|
"description": "Site-relative path to the post. The news list today — the site has no per-post route."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "team.announcement",
|
|
"owner": "core",
|
|
"label": "Team — announcement",
|
|
"description": "A leader posted an announcement in a Team.",
|
|
"kind": "event",
|
|
"subjectKey": "teamName",
|
|
"audience": "members",
|
|
"ceiling": "members",
|
|
"version": 1,
|
|
"variables": [
|
|
{
|
|
"name": "teamName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "The Silver Anvil",
|
|
"description": "The Team the event is about. Also the cooldown subject."
|
|
},
|
|
{
|
|
"name": "authorName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "Marisol",
|
|
"description": "Display name of the leader who posted."
|
|
},
|
|
{
|
|
"name": "title",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "Siege practice moved to Sunday",
|
|
"description": "The announcement title."
|
|
},
|
|
{
|
|
"name": "excerpt",
|
|
"type": "string",
|
|
"required": false,
|
|
"example": "We are moving practice to Sunday 8pm…",
|
|
"description": "Plain-text excerpt of the announcement body."
|
|
},
|
|
{
|
|
"name": "postUrl",
|
|
"type": "url",
|
|
"required": false,
|
|
"example": "/guilds/the-silver-anvil/forum/419",
|
|
"description": "Site-relative path to the announcement."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "team.forum.post",
|
|
"owner": "core",
|
|
"label": "Team — new forum post",
|
|
"description": "A new thread or reply in a Team forum.",
|
|
"kind": "event",
|
|
"subjectKey": "teamName",
|
|
"audience": "members",
|
|
"ceiling": "members",
|
|
"version": 1,
|
|
"variables": [
|
|
{
|
|
"name": "teamName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "The Silver Anvil",
|
|
"description": "The Team the event is about. Also the cooldown subject."
|
|
},
|
|
{
|
|
"name": "authorName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "Darrow",
|
|
"description": "Display name of the poster."
|
|
},
|
|
{
|
|
"name": "threadTitle",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "Tuesday champ rotation",
|
|
"description": "Title of the thread the post belongs to."
|
|
},
|
|
{
|
|
"name": "excerpt",
|
|
"type": "string",
|
|
"required": false,
|
|
"example": "Moving the Tuesday run an hour later…",
|
|
"description": "Plain-text excerpt of the post body, already stripped of markup."
|
|
},
|
|
{
|
|
"name": "postUrl",
|
|
"type": "url",
|
|
"required": false,
|
|
"example": "/guilds/the-silver-anvil/forum/412",
|
|
"description": "Site-relative path to the post."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "team.leadership.changed",
|
|
"owner": "core",
|
|
"label": "Team — leadership change",
|
|
"description": "Leadership changed in a Team.",
|
|
"kind": "event",
|
|
"subjectKey": "teamName",
|
|
"audience": "members",
|
|
"ceiling": "members",
|
|
"version": 1,
|
|
"variables": [
|
|
{
|
|
"name": "teamName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "The Silver Anvil",
|
|
"description": "The Team the event is about. Also the cooldown subject."
|
|
},
|
|
{
|
|
"name": "leaderName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "Marisol",
|
|
"description": "Display name of the new leader."
|
|
},
|
|
{
|
|
"name": "teamUrl",
|
|
"type": "url",
|
|
"required": false,
|
|
"example": "/guilds/the-silver-anvil",
|
|
"description": "Site-relative path to the Team page."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "team.member.joined",
|
|
"owner": "core",
|
|
"label": "Team — new member",
|
|
"description": "Someone joined a Team.",
|
|
"kind": "event",
|
|
"subjectKey": "teamName",
|
|
"audience": "members",
|
|
"ceiling": "members",
|
|
"version": 1,
|
|
"variables": [
|
|
{
|
|
"name": "teamName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "The Silver Anvil",
|
|
"description": "The Team the event is about. Also the cooldown subject."
|
|
},
|
|
{
|
|
"name": "memberName",
|
|
"type": "string",
|
|
"required": true,
|
|
"example": "Darrow",
|
|
"description": "Display name of the member who joined."
|
|
},
|
|
{
|
|
"name": "teamUrl",
|
|
"type": "url",
|
|
"required": false,
|
|
"example": "/guilds/the-silver-anvil",
|
|
"description": "Site-relative path to the Team page. Absent when no module supplies a pageUrlTemplate."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|