feat(events): send the idempotency key, and declare champ.boss.killed (Phase 11a)
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:
@@ -96,8 +96,10 @@ test('the seventeen in-universe families have both channels; the nine plain ones
|
||||
// letter from anybody.
|
||||
assert.equal(r.template_keys.digest, 'notify.digest', `${r.trigger_id} digests generically`)
|
||||
}
|
||||
assert.equal(bespoke, 17)
|
||||
assert.equal(seeds.TEMPLATES.length, 34)
|
||||
// Eighteen since protocol 6: the champion FALLS, in the same crier's voice as
|
||||
// the champion walking, because they are one story told in two mails.
|
||||
assert.equal(bespoke, 18)
|
||||
assert.equal(seeds.TEMPLATES.length, 36)
|
||||
})
|
||||
|
||||
test('a template key is core\'s grammar — dots and hyphens, never an underscore', () => {
|
||||
@@ -222,7 +224,7 @@ test('every declared fragment carries an example that shows its own shape', () =
|
||||
// The `example` is what the template editor previews and test-sends with, so a
|
||||
// trailing fragment whose example omits the leading space teaches an author the
|
||||
// wrong thing about where to put one.
|
||||
const TRAILING = ['slainBy', 'atPlace', 'inSuccessionTo', 'candidateNote']
|
||||
const TRAILING = ['slainBy', 'atPlace', 'inSuccessionTo', 'candidateNote', 'damagerNote']
|
||||
for (const t of TRIGGERS) {
|
||||
for (const v of t.variables.filter((x) => TRAILING.includes(x.name))) {
|
||||
assert.ok(v.example.startsWith(' '), `${t.id}.${v.name} example leads with its space`)
|
||||
@@ -236,7 +238,17 @@ test('one rule group, and appending to it later would reach fresh installs only'
|
||||
// A group is seeded ONCE under its own settings guard, which is 11a's seed-key
|
||||
// finding as a mechanism. This assertion exists so that adding a twenty-sixth
|
||||
// rule has to edit a test whose name says what appending costs.
|
||||
assert.equal(seeds.RULE_GROUPS.length, 1)
|
||||
// TWO groups since protocol 6, and the second one is this test's whole point
|
||||
// made concrete: `uo.champ.boss_killed` could not be appended to `triggers-v1`,
|
||||
// because a deployment that has already stamped that key would never have
|
||||
// received it. A new rule gets a new key.
|
||||
assert.equal(seeds.RULE_GROUPS.length, 2)
|
||||
assert.equal(seeds.RULE_GROUPS[0].key, 'triggers-v1')
|
||||
assert.equal(seeds.RULE_GROUPS[0].rules.length, 26)
|
||||
assert.equal(seeds.RULE_GROUPS[1].key, 'champ-boss-killed-v1')
|
||||
assert.deepEqual(seeds.RULE_GROUPS[1].rules.map((r) => r.trigger_id), ['uo.champ.boss_killed'])
|
||||
// No rule belongs to two groups, and between them they are the whole set.
|
||||
const grouped = seeds.RULE_GROUPS.flatMap((g) => g.rules.map((r) => r.trigger_id))
|
||||
assert.equal(new Set(grouped).size, grouped.length)
|
||||
assert.deepEqual([...grouped].sort(), seeds.RULES.map((r) => r.trigger_id).sort())
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user