docs(link): protocol 5 — the decay schedule, vendor fee state and account.login.result #193

Merged
whitlocktech merged 2 commits from docs/protocol-v5 into edge 2026-09-01 00:27:29 +00:00
Member

Design of record for the bump, the reference updates it obliges, and Engagement Phase 10 as built.

Stacked on #192, which records the scope decision this implements. Merge #192 first and this PR's diff reduces to its own commit.

Companion PRs: servuo-plugins#18, link#…, Module-uo#….

link/v5.md — new

Three enrichments in one bump, because a bump costs a sidecar release, a republished bundle and an operator update on every shard. A field left out costs a whole second round of that, not a follow-up commit.

  • house.decay gains ownerName and a nested schedule. estimatedCollapse is present only where it is exact: under dynamic decay each stage's duration is drawn at random on entry, so collapse is knowable only once the house is already at IDOC; under static decay it is exact at every stage. Omitted rather than approximated — an absent field is honest where a wrong date becomes a dated promise in a player's mail.
  • vendor.listing gains ownerAcct — a character name identifies nobody, only the game account joins to shard_account_links — and a fees block resolving PlayerVendor.PayTimer's dismissal rule into a dismissalAt instant. There is deliberately no daysRemaining: a pay period is a UO day on an old-system shard, so the name would be wrong by a factor of twelve.
  • account.login.result is a new kind. account.login.attempt fires from a veto hook that runs before the auth decision and constructs with Accepted = true, so it fires on successful logins too and cannot carry a verdict.

Also records the sidecar's non-change as a property rather than an omission (no store migration, unlike v4 — the dumb-forwarder invariant doing its job), the three visibility classifications, and §6.1's two rig traps.

Corrections to the existing references

Three things the docs asserted that turned out not to be true:

  • PLAN.md §5.1 listed AccountLogin under "Failed-login / IP signals for the website". It cannot give you a failed login — it is a veto hook that fires before the decision, and the args construct with Accepted = true, so a handler reading the verdict there reports every login as accepted.
  • PLAN.md §7's frames are 1.0's design sketch, not the shipped wire, and have not been for four versions — its house.decay shows an owner object and a flat nextStage, neither of which ships. Now marked as such, pointing at INTEGRATION.md for the normative field lists.
  • INTEGRATION.md's account.login.attempt row described an attempt without saying it fires on successes too, which is what made building a security notice on it look reasonable.

Plus the ordinary updates: the version banner to 5, the Housing and Market tables and examples, the new kind, and the README.md index row.

ENGAGEMENT.md — Phase 10 as built

Two decisions taken during the build and not in the phase text:

  • Both new field groups are nested rather than eleven sibling keys. The visibility projection matches literal JSON keys, so a nested group is one admin rule — and a field added inside it later inherits the gate instead of defaulting to visible. There is a test that adds an imaginary future fee field and asserts exactly that.
  • No daysRemaining, for the reason above.

And one improvement worth calling out because it is the protocol-4 bug seen from the other side: module-uo's schema test asserted DEFAULT 4 at each declaration site, so when the emitters moved and one site was left behind, every site still agreed with itself and the test passed. It now reads DEFAULT_PROTOCOL from the model, making the assertion "the declarations agree".

runicgateway.com's platform.json.protocol → 5 stays Phase 12's: checkFacts.mjs fetches from main, so setting it during the edge period turns that repo red at once.


  • AI-assisted — drafted with Claude Code (Claude Opus 5), every claim verified against the trees or the live rig.

🤖 Generated with Claude Code

Design of record for the bump, the reference updates it obliges, and **Engagement Phase 10 as built**. > **Stacked on #192**, which records the scope decision this implements. Merge #192 first and this PR's diff reduces to its own commit. Companion PRs: **servuo-plugins#18**, **link#…**, **Module-uo#…**. ## `link/v5.md` — new Three enrichments in one bump, because a bump costs a sidecar release, a republished bundle and an operator update on every shard. A field left out costs a whole second round of that, not a follow-up commit. - **`house.decay`** gains `ownerName` and a nested `schedule`. `estimatedCollapse` is present **only where it is exact**: under dynamic decay each stage's duration is drawn at random on entry, so collapse is knowable only once the house is already at IDOC; under static decay it is exact at every stage. Omitted rather than approximated — an absent field is honest where a wrong date becomes a dated promise in a player's mail. - **`vendor.listing`** gains `ownerAcct` — a character name identifies nobody, only the game account joins to `shard_account_links` — and a `fees` block resolving `PlayerVendor.PayTimer`'s dismissal rule into a `dismissalAt` instant. **There is deliberately no `daysRemaining`**: a pay period is a UO day on an old-system shard, so the name would be wrong by a factor of twelve. - **`account.login.result`** is a new kind. `account.login.attempt` fires from a veto hook that runs *before* the auth decision and constructs with `Accepted = true`, so it fires on successful logins too and cannot carry a verdict. Also records the sidecar's non-change as a *property* rather than an omission (no store migration, unlike v4 — the dumb-forwarder invariant doing its job), the three visibility classifications, and §6.1's two rig traps. ## Corrections to the existing references Three things the docs asserted that turned out not to be true: - **`PLAN.md` §5.1** listed `AccountLogin` under *"Failed-login / IP signals for the website"*. It cannot give you a failed login — it is a veto hook that fires before the decision, and the args construct with `Accepted = true`, so a handler reading the verdict there reports **every** login as accepted. - **`PLAN.md` §7's frames are 1.0's design sketch, not the shipped wire**, and have not been for four versions — its `house.decay` shows an `owner` object and a flat `nextStage`, neither of which ships. Now marked as such, pointing at `INTEGRATION.md` for the normative field lists. - **`INTEGRATION.md`'s `account.login.attempt` row** described an attempt without saying it fires on successes too, which is what made building a security notice on it look reasonable. Plus the ordinary updates: the version banner to 5, the Housing and Market tables and examples, the new kind, and the `README.md` index row. ## `ENGAGEMENT.md` — Phase 10 as built Two decisions taken during the build and not in the phase text: - **Both new field groups are nested** rather than eleven sibling keys. The visibility projection matches literal JSON keys, so a nested group is one admin rule — and a field added inside it later **inherits the gate** instead of defaulting to visible. There is a test that adds an imaginary future fee field and asserts exactly that. - **No `daysRemaining`**, for the reason above. And one improvement worth calling out because it is the protocol-4 bug seen from the other side: `module-uo`'s schema test asserted `DEFAULT 4` at each declaration site, so when the emitters moved and one site was left behind, **every site still agreed with itself and the test passed**. It now reads `DEFAULT_PROTOCOL` from the model, making the assertion *"the declarations agree"*. `runicgateway.com`'s `platform.json.protocol` → 5 stays **Phase 12's**: `checkFacts.mjs` fetches from `main`, so setting it during the `edge` period turns that repo red at once. --- - [x] AI-assisted — drafted with Claude Code (Claude Opus 5), every claim verified against the trees or the live rig. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 2 commits 2026-09-01 00:21:33 +00:00
Two scope decisions taken by the org lead on 2026-08-31, before any Phase 10 code,
plus the three factual corrections that finding them out produced.

Phase 10 — the protocol bump now carries three enrichments, not one. The argument is
specific to this phase: a bump costs a sidecar release, a republished bundle and an
operator update on every shard, so a field left out does not cost a follow-up commit,
it costs a second bump with the same lead time and a split operator population. The
two additions:

  * player-vendor fee state on vendor.listing (ownerAcct, holdGold, chargePerDay,
    daysRemaining), because uo.vendor.expiring is the same "owned asset at risk with
    a deadline" shape as the flagship and today has neither an address nor a deadline;
  * a post-decision account.login.result, because EventSink.AccountLogin is a veto
    hook that fires BEFORE the auth decision.

Moving vendor.sale out of the opt-in patch tier was explicitly declined.

Phase 11 — ships every checked row of 8.6 rather than a single rule, carving out
uo.market.item_listed (a saved search; no per-user query store exists). ~23 triggers
grouped by the audience kind each family exercises, since exercising the ceiling
lattice at scale is the point rather than volume of mail. Every rule still ships
enabled = 0 per Q3. Notes that the phase will likely want an 11a/11b split on the
4a/4b precedent, to confirm at its start.

Three corrections to 8.6, each verified against the emitters rather than the table:

  * uo.vendor.sale is real and does carry ownerAcct, but lives in servuo-plugins/
    patches/ (opt-in, verified only against ServUO 57.4) — dormant, not broken, on a
    shard that declined the tier;
  * uo.vendor.expiring had no data at all, not merely no mapper — vendor.listing
    carries ownerSerial/ownerName and nothing carries held gold or daily charge;
  * uo.account.login_attempt could not have been built as described — it would have
    mailed "someone tried to log into your account" on every successful login.
    Renamed uo.account.login_failed so the id cannot be misread again.

Also amended: the status header, scope decision 3, and 6.0b's documentation
assignment for rows 10 and 11 (v5.md now earned; the patch-tier caveat is an
operator-facing doc obligation; runicgateway.com's capability claim changes when
"one rule" becomes "the catalogue").

Co-Authored-By: Claude <noreply@anthropic.com>
Design of record for the bump, plus the reference updates it obliges.
Engagement Phase 10, as built.

link/v5.md is new. Three enrichments in one bump, because a bump costs a sidecar
release, a republished bundle and an operator update on every shard — a field left
out costs a whole second round of that, not a follow-up commit.

  a. house.decay gains ownerName and a nested `schedule`. estimatedCollapse is
     present ONLY where it is exact: under dynamic decay each stage's duration is
     drawn at random on entry, so collapse is knowable only once the house is
     already at IDOC; under static decay it is exact at every stage. Omitted
     rather than approximated — an absent field is honest where a wrong date
     becomes a dated promise in a player's mail.
  b. vendor.listing gains ownerAcct — a character name identifies nobody, only the
     game account joins to shard_account_links — and a `fees` block resolving
     PlayerVendor.PayTimer's dismissal rule into a dismissalAt instant. There is
     deliberately no daysRemaining: a pay period is a UO day on an old-system
     shard, so the name would be wrong by a factor of twelve.
  c. account.login.result is a NEW kind. account.login.attempt fires from a veto
     hook that runs before the auth decision and constructs with Accepted = true,
     so it fires on successful logins too and cannot carry a verdict.

INTEGRATION.md: the version banner to 5, the Housing and Market field tables and
examples, and the new kind beside the attempt — with the attempt's own row
corrected to say why it is not a failure signal.

PLAN.md: 5.1's "failed-login signals" row was wrong about what AccountLogin can
tell you; 5.3 and 5.4 gain the two new signals; 5.4's dynamic-decay model gets the
consequence for publishing a collapse time, and the CanDecay rule gets its testing
trap. 7's frames are marked as 1.0's design sketch rather than the shipped wire,
which they have not been for four versions.

ENGAGEMENT.md: Phase 10 as built. Two decisions taken during the build (both field
groups nested, so one admin rule governs each and a future field inherits the gate;
no daysRemaining), the three visibility classifications, and the pin improvement —
the schema test now asserts the declarations AGREE rather than that they all say a
literal, which is the protocol-4 bug seen from the other side.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 7866bd7ab8 into edge 2026-09-01 00:27:29 +00:00
whitlocktech deleted branch docs/protocol-v5 2026-09-01 00:27:30 +00:00
Sign in to join this conversation.
No description provided.