Core's half of ENGAGEMENT.md Phase 11a: the two decisions the org lead settled before any code that land in core rather than in module-uo. Pairs with Module-uo#22 and docs#194. ## Decision 1 -- a seventh ceiling, `admin`, as a child of `staff` Phase 11's operator-facing triggers (uo.audit.staff_action, uo.economy.milestone, uo.world.saved) are described as admin-audience everywhere, and the narrowest value the lattice had was `staff` -- which ceilings.js defines as admin, editor AND moderator. Ceilinging them there would have let an operator save a rule that mails the staff audit digest to every moderator in it. `admin` is the ONLY genuine refinement in the tree -- every admin is staff, which is exactly the containment every other pair of branches lacks -- so it is a child rather than a seventh leaf, and permits/meet/meetAll needed no change beyond the new PARENT entry. **The one non-obvious consequence, and the reason for ROLE_CEILINGS.** notificationChannelPrefs' `visibleTo` asked `item.ceiling !== 'staff'`. That was correct while `staff` was the only role-gated value, and the day `admin` arrived it would have silently published every admin-ceilinged id -- the staff audit digest, the economy thresholds -- to every player's preferences screen by name. It now reads a TABLE (`ceilings.reachableBy`), so a ceiling added without an entry fails closed instead. An EDITOR is the viewer that tells the two rules apart, and the new tests use one. MODULE_API_VERSION -> 1.8.0 on both halves. Additive: every declaration valid under 1.7.0 is valid now and no stored value changes. ## Decision 5 -- 7.1 Q9: news.post gets an emitter, and it REPLACES the tickle `news.post` has been a declared payload contract with no caller since Phase 2, so a rule naming it could never fire. utils/newsNotify.js is the caller; announceIfNewlyPublished now calls it instead of pushDispatch.publish, gated on the same enqueueIfNeeded job id -- the single "newly published news" transition signal, not re-derived. **News push therefore stops on upgrade** until an operator enables the seeded rule. That is the org lead's decision, taken over keeping the raw call beside the emit "for one release": an exception with a deadline nobody owns, which Phase 6 already refused for Teams. The Rules screen gains a second migration notice naming news, and Phase 13's release note carries it as an upgrade step. **The seed needed its own one-shot key, and this is the trap worth recording.** `engagement_team_rules_seeded` is already stamped on every deployment that has booted since Phase 6, and the guard reads its presence -- so appending news to RULES would have seeded it on fresh installs only, and on exactly the upgrades that lose their raw push, never. One key per seed GROUP is now the rule; seedGroup() is the shared implementation and seedCoreRules() is what boot calls. Also fixes news.post's `postUrl` example, which named `/news/<slug>` -- a path App.jsx does not mount. An example is what the template editor previews and test-sends with, so a wrong one is a preview that looks right and a mail that is not. It is `/site/news`, the list, which is what the Discord and town-crier announcements have always linked. 1550 tests pass (16 new), 327 client tests pass, client builds, check:modules clean -- core still names no module identifier with module-uo now registering 24 UO-named triggers. 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.8.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."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|