Needs website#<core> (the cooldown key and the seed-rule ceiling).
1. Every owner-audienced trigger reached NOBODY. `resolveTarget` read
`link.user_id`; the model's `toSafe` returns `userId`. So the whole flagship
family -- houses, vendors, logins, unlinks, deaths, the governor's letter --
resolved to null and looked exactly like the ordinary unlinked-account case,
which the code treats as normal and deliberately does not log.
The test fake returned `user_id` and therefore agreed with the bug, while
`shardStreams.test.js`'s fake next door -- same model, the path this file says
it copies -- returned `userId`. The fake is now built by running the real
`toSafe` over a stubbed db row, so the shape is not a hand-written opinion.
2. `uo.house.refreshed`, the 26th trigger (the org lead's decision 11). The
warning's rule carries `delay_seconds: 900` so a player who repairs the house
inside the quarter-hour is never told it is in peril -- and nothing could
cancel it: `cancel_on` named only the collapse. The wire had carried the
transition all along; the mapper returned early on it.
It fires on `Ageless` as well as `LikeNew`, and `Ageless` is the common case:
a condemned house cannot be refreshed at all (`RefreshDecay()` refuses
`DecayType.Condemned`), so the rescue is the owner logging in, and their
newest house then reads `Ageless`. Ships a body and a seeded (disabled) rule
of its own; the cancellation is read off the WARNING's rule and works whether
or not the new one is enabled.
3. Every call-to-action in every in-universe body was a dead link, from two
independent mistakes. The client router prefixes a module's routes with its
ID (`/uo/houses`), not with module.json's `mounts` (`/shard/...`), so every
declared `example` was a 404 -- and an example is what the template editor
previews and test-sends with. And no `url` variable was ever populated by the
mapper, so the buttons rendered with an empty href and dropped out of the text
part entirely. Both now read `config/clientPaths.js`. Two tests close it.
4. A raw wire timestamp was signing off the Merchants' Guild's letter
(`2026-09-02T04:06:43.8397548Z`, mid-sentence). Core has no interpolation
filters by design, so the readable form is assembled in the mapper and arrives
as its own variable; the machine value stays, because an operator writes
`is at most` conditions against it.
Also fixes a latent flake: `hoursRemaining` floors a live clock, so a fixture at
a whole number asserted 19 or 20 depending on sub-millisecond timing.
527 module tests green (3 new). Proved end to end against real ServUO + the
release sidecar + a live SMTP catcher; see docs#<docs>.
Co-Authored-By: Claude <noreply@anthropic.com>