feat(events): send the idempotency key, and declare champ.boss.killed (Phase 11a)
All checks were successful
PR Checks / client-build (pull_request) Successful in 20s
PR Checks / server-tests (pull_request) Successful in 26s
PR Checks / frozen-manifest (pull_request) Successful in 39s

The website's half of protocol 6.

Every event-driven write now carries the step's idempotency key, and `uo.broadcast`
stops being un-retryable. Phase 9 shipped it answering `retry: false` to everything
including a 503 from a shard that was merely restarting, with a comment naming the
line that would change when the wire could refuse a repeat. This is that line: it
defers to `sidecarFailure`, the same helper its two siblings already used, so the
hand-rolled variant that forced every outcome terminal is gone rather than re-tuned.

One verb was less idempotent than its own id made it look. Both keyed verbs post
under a run-scoped id and a repeat replaces — but `news.add` with `announce: true`
makes the criers proclaim the title on every post, so a retry replaced the article
silently and proclaimed it again. The key stops the second proclamation.

`champ.boss.killed` is mapped to the `champs` feature (rule 2 would otherwise fail
it closed to admin), with `damagers` a nested `staff` field rule: the kill is public
because a champion falling is what the board is for, the ranked roll of who was
strong enough to fell it is not. `uo.champ.boss_killed` is declared as a trigger —
which is what makes it usable as an event PHASE CONDITION, since a condition is
written over a trigger firing — and it carries `damagerCount`, never a damager name,
because a trigger variable reaches mail an operator may address to every subscriber.

Its seeded rule is its own group, `champ-boss-killed-v1`: `triggers-v1` is stamped
once under a settings guard, so appending a 27th entry would have reached fresh
installs and nothing else. It also ships email+inapp and NOT push, and the comment
says why — no trigger in this module is also a registered stream, so no engagement
rule here can push. That is pre-existing in twenty rules and flagged rather than
fixed; this one declines to be the twenty-first.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-04 14:57:26 -05:00
parent cf60932c85
commit dc13515927
10 changed files with 501 additions and 64 deletions

View File

@@ -600,6 +600,42 @@ const COME_ONLINE = [
description: 'A trailing fragment, LEADING SPACE included, or empty when the frame carries no location.' },
],
},
{
// Protocol 6, and the reason the kind exists at all. Its first consumer is not
// a mail rule but an EVENT PHASE CONDITION: `{ on: 'uo.champ.boss_killed',
// where: [...], count: 1 }` is how an author says "move to the next phase when
// the boss falls", and a condition is expressed over a trigger firing. That is
// also why it is declared here rather than only ingested — a kind nothing
// declares is a kind no event can wait on.
id: 'uo.champ.boss_killed',
label: 'A champion boss was defeated',
description: 'Players brought down a champion spawn boss.',
kind: 'event',
subjectKey: 'spawnSerial',
audience: 'subscribers',
ceiling: 'authenticated',
version: V1,
variables: [
{ name: 'spawnSerial', type: 'string', required: true, example: '0x40012345',
description: 'The spawn controller, or the boss itself where the shard could not name an altar. Also the cooldown subject.' },
{ name: 'bossName', type: 'string', required: true, example: 'Semidar',
description: 'The boss that fell.' },
{ name: 'category', type: 'string', required: false, example: 'champion',
description: 'champion or sea.' },
{ name: 'location', type: 'string', required: false, example: 'Felucca 5187, 570 (Destard)',
description: 'Where, already formatted for reading.' },
{ name: 'killerName', type: 'string', required: false, example: 'Aldric',
description: 'Who struck the last blow, when the shard names one.' },
{ name: 'damagerCount', type: 'int', required: false, example: 14,
description: 'How many players did damage to it. The names themselves are staff-only and are deliberately not offered here.' },
{ name: 'damagerNote', type: 'string', required: false, example: ' 14 players fought it.',
description: 'A trailing sentence, LEADING SPACE included, or empty when nobody is credited.' },
{ name: 'champsUrl', type: 'url', required: false, example: '/uo/champs',
description: 'Site-relative path to the champions page.' },
{ name: 'atPlace', type: 'string', required: false, example: ' at Felucca 1480, 1600 (Destard)',
description: 'A trailing fragment, LEADING SPACE included, or empty when the frame carries no location.' },
],
},
{
id: 'uo.server.up',
label: 'The shard came online',