Four things the review found, all of them the code telling a player
something that isn't true.
A run that walks into a dead end filed its ending as "cleared" whatever
the caller said, so both the liveblog and the summary reported a clear
for a party that merely ran out of map — and the end beat is
first-writer-wins, so nothing later could take it back. It says
"cleared" only for a boss now.
The re-offer branches of babysit and resume returned a zero cost, so a
player who was in fact charged read "0 coins" in the verdict. Both
re-quote the price they actually took.
And the realm-firsts reseed retired its one-shot job even when the read
under it had failed, which on a transient fault at Init would have left
the ledger mis-dated permanently. The read now says whether it worked,
and the job stays open when it didn't.
Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
The one-shot that seeded news_realm_firsts filtered on `abandoned = 0`, and
`abandoned` does not mean anybody gave up: abandonZoneRunByID exists to retire a
run whose boss is already dead when the expedition travels onward, which is how
30 of prod's 32 boss kills are stored. So the ledger holds 6 zones where the run
history knows 9, and it dates every row to the minute the job ran rather than to
the clear.
The ledger is what claimRealmFirst tiers live dispatches against, so each missed
zone is a spurious PRIORITY "realm first" waiting to fire the next time somebody
clears it, months late.
Fix the shared query, and add a re-seed (not a re-run) under its own job name: it
writes the ledger, backdated to the real earliest clear, and emits nothing at
all. It runs regardless of the news switches, because a ledger that is right only
while emission is on mis-tiers the first dispatch after somebody flips it. Kept
in place afterwards as an ordinary bootstrap for fresh deploys.
Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
The live emitters stopped filing priority facts in 1cbd68a, but the launch
backfill kept doing it for historical zone-firsts and deaths. It is inert today
— Pete's handler short-circuits on no_push before the Matrix post, and headlines
key off event_type rather than tier — so nothing on the site or in the room
moves. It was a landmine: the echo rule was being enforced by a guard that
happens to sit in front of it, not by the tier itself, and anyone touching that
guard resurrects the echo at back-catalogue scale.
Bulletin is the rule. Nothing in the back catalogue is news anyway.
Closes the two deferred code-review follow-ups on the adventure-news
seam, plus folds in two pre-committed WIP fixes.
A. Privacy — the public GUID no longer leaks a stable per-player id.
Replaced userHash(userID)=sha256(userID)[:6] with
eventToken(userID, discriminator)=HMAC-SHA256(salt, userID‖disc).
The salt is 32 random bytes, auto-generated once and persisted in the
durable news_config table (cached via sync.Once). Because each event
uses a distinct HMAC message, tokens are a PRF output and are BOTH
uncomputable from a Matrix handle (no enumeration of a player's
events, incl. ones anonymized after !news optout) AND mutually
unlinkable (a named event can't be walked back to a player's other,
anonymized events). Updated all emit sites: pete.go zone, dnd_combat
death, adventure_duel rival, dnd_setup arrival, achievements
milestone, bootstrap x3.
B. Taxonomy — repeat zone clears were mislabeled zone_first. Now emit
zone_clear (bulletin) vs zone_first (realm-first, priority). Adopted
the invariant GUID-prefix == event_type, which also fixes latent
permalink mislabels (achv->milestone, rival->rival_result rendered a
neutral "Dispatch" on their permalink pages).
Folded-in WIP fixes: create the news_config table b42beec's
newsEmissionOn reads but never created; reap sent pete_emit_queue rows
in RunMaintenance; don't burn a retry attempt when shutdown cancels an
in-flight send.
Tests: TestEventToken (salted/stable/per-event/persisted),
TestEmitZoneClearTaxonomy (first->zone_first, repeat->zone_clear),
updated pete_test.go prefixes. Full internal suite + vet green.
Unshipped. Deploy Pete first (it must know zone_clear), then gogobee.