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

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>
This commit is contained in:
2026-08-31 19:19:09 -05:00
parent efce1d88aa
commit 6304c42dfb
5 changed files with 432 additions and 16 deletions

View File

@@ -7,7 +7,8 @@ docs#182; Phase 4a: website#170 + docs#183; Phase 4b: website#171 + docs#184; Ph
docs#185; Phase 5b: website#173 + docs#186 + runicgateway.com#22; Phase 6: website#174 + docs#187 +
runicgateway.com#23; Phase 7: website#175 + docs#188 + runicgateway.com#24; Phase 8: the Android
half, Android-app#42 + docs#190; Phase 9: website#176 + docs#191); everything from Phase 10 on is
still design. **Phases 10 and 11 were both widened on 2026-08-31, by the org lead, before any code:**
still design. **PHASE 10 IS BUILT (2026-08-31)** — the protocol bump; see its as-built below and
[`../link/v5.md`](../link/v5.md). **Phases 10 and 11 were both widened on 2026-08-31, by the org lead, before any code:**
the protocol bump carries three wire enrichments rather than one, and Phase 11 ships **every ✅ row of
§8.6** rather than a single rule. The scope decisions
below are settled; **eight of the nine questions in §7.1 are answered** - Q1, Q3, Q5 and Q7 on
@@ -3063,6 +3064,67 @@ starts early and lands independently.
---
#### Phase 10 as built (2026-08-31)
**Design of record: [`../link/v5.md`](../link/v5.md).** All three enrichments shipped as scoped,
and the phase found nothing that changed its shape — which is itself worth noting, because the two
*additions* to this phase existed only because reading the emitters had already contradicted §8.6
twice.
**Two design decisions taken during the build, neither of them in the phase text:**
- **Both new field groups are NESTED** (`house.decay.schedule`, `vendor.listing.fees`) rather than
eleven sibling keys. The visibility projection matches literal JSON keys, so a nested group is
ONE admin rule that governs the whole group — and, more importantly, a field added inside it
later inherits the gate instead of defaulting to visible. Eleven flat keys would have been eleven
rules to keep in step. There is a test that adds an imaginary future fee field and asserts it is
still gated.
- **There is no `daysRemaining`.** The obvious name is wrong: under ServUO's old vendor system a pay
period is a UO day (`Clock.MinutesPerUODay`, ≈2 real hours), so the field would have been off by
a factor of twelve on exactly the shards least likely to notice. The wire carries
`periodsRemaining` plus the interval, and resolves the arithmetic into `dismissalAt` — an instant,
which needs no units.
**Three visibility classifications, each chosen rather than inherited** (v5.md §4): the decay
`schedule` is `anonymous` (the countdown is the public IDOC page's content), `fees` is **`admin`** —
the only default in the `market` feature that does not reproduce prior behaviour, because a vendor's
held gold is owner-only in game and publishing it is both a new disclosure and a targeting aid — and
`account.login.result` is admin-only **by omission**, since `KIND_FEATURE` is the map of kinds an
admin may widen and there is no rung below admin an IP-plus-auth-verdict belongs on.
**The REST reads deliberately did not change.** `shard_vendors`' eight new columns are in no read
model's column list: they exist for Phase 11's server-side trigger and reach no client at all.
**One improvement to the pin, which 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**" rather than
"they all say 4", and the one-shot migration test is written once against the current version
instead of being hand-copied per bump.
**Acceptance, all met.** 470 tests in `module-uo/server` (16 new) and 39 in the sidecar; the C#
compiles against real ServUO 57.4 assemblies. On the live rig — real ServUO, seeded world, the
**release** sidecar — a house walked Fairly → Greatly → IDOC produced `estimatedCollapse` on the
IDOC frame **and only there**; every observed vendor's `periodsRemaining` matched
`funds / chargePerPeriod`, including one at `0` whose `dismissalAt` equals its next tick; and a real
socket login produced `accepted:false reason:BadPass` for a wrong password and `accepted:true` for
the right one. **The installer needed no change** — its pairing refusal is version-agnostic and
already tested (`bundle.rs::a_protocol_disagreement_inside_one_bundle_is_refused`).
**Two rig traps, both of which fake a broken emitter by producing silence** (v5.md §6.1): an
in-process login probe can never produce `accepted:true`, because `AccountHandler` calls
`HasAccess(e.State)` before it checks the password and a null `NetState` fails it; and forcing a
decay stage on a house that cannot decay emits **nothing at all**, because an `AutoRefresh` house —
and the owner's newest house is always `AutoRefresh` — resets the forced stage on the next read.
Both are recorded in `servuo-plugins/tools/scaffolding/README.md` beside the new
`BridgeProtocol5Probe.cs`.
**Deferred, deliberately:** `runicgateway.com`'s `platform.json.protocol` → 5 stays Phase 12's, since
`checkFacts.mjs` fetches from `main` and setting it during the `edge` period turns that repo red at
once.
---
### Phase 11 — module-uo's triggers: the full catalogue, and core's `news.post`
**Scope widened 2026-08-31, by the org lead, before any code.** This phase used to say `module-uo`