docs(link): protocol 5 — the decay schedule, vendor fee state and account.login.result #193
@@ -7,7 +7,9 @@ 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. The scope decisions
|
||||
still design. **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
|
||||
2026-08-28, Q6 on 2026-08-29 at the start of Phase 2 (which also settled §7.2's namespace question),
|
||||
**Q2 and Q4 on 2026-08-29 at the start of Phase 4**, and **Q8 on 2026-08-31 at the start of Phase
|
||||
@@ -24,7 +26,10 @@ none.
|
||||
|
||||
1. **The in-app channel is in scope.** It does not exist today and has to be built, not adapted.
|
||||
2. **The Teams notification pipeline is generalized and migrated onto the new system**, not built beside it.
|
||||
3. **The `house.decay` protocol enrichment is in scope**, as a coordinated four-repo `PROTOCOL_VERSION` bump.
|
||||
3. **The `house.decay` protocol enrichment is in scope**, as a coordinated four-repo `PROTOCOL_VERSION`
|
||||
bump. **Widened 2026-08-31:** that single bump now carries **three** enrichments (house decay,
|
||||
player-vendor fee state, and a post-decision login result), because a second bump would cost another
|
||||
release + bundle + operator update. See Phase 10.
|
||||
4. **Gmail OAuth2 is removed, not retained as a transport.** SMTP is the baseline; the OAuth2 consent
|
||||
flow, its two routes, its borrowed Google client and its stored refresh token all go. See §1.2a for
|
||||
what that deletes and §6/Phase 1 for the operator cutover it forces.
|
||||
@@ -1359,8 +1364,8 @@ change is not complete until `docs/` reflects it" — is the floor; this table i
|
||||
| **7** In-app channel (core+web) ✅ | `website/BACKEND_DESIGN.md` routes + tables (the four inbox routes, `user_notifications`) · `website/ENGAGEMENT.md` this phase as built | **`runicgateway.com`**: `notifications-and-email.mdx` gains the in-app channel. Landed with the phase |
|
||||
| **8** In-app (Android) ✅ | `android/PLAN.md` §7 (the Room exception) + §11 (the inbox as built) · `website/ENGAGEMENT.md` this phase as built | `android-app/README.md`. Landed with the phase |
|
||||
| **9** Deliverability | `website/BACKEND_DESIGN.md` §7 · a suppression/bounce operator section (the verification flow is Phase 1b's) | **`runicgateway.com`**: `troubleshooting.mdx` gains bounce/suppression · **`PLAY_DATA_SAFETY.md` + `/privacy`** — see Phase 12 |
|
||||
| **10** Protocol bump | `link/INTEGRATION.md` §Housing (table + example) · `link/PLAN.md` §5/§7 · a `link/v5.md` if the bump earns its own design doc, as v3 and v4 did | `servuo-plugins/overlay.toml` · **`runicgateway.com`**: `platform.json.protocol` → 5, `bundle.*`, `architecture/protocol-versions.mdx` |
|
||||
| **11** module-uo triggers | `modules/uo/API.md` · `modules/uo/README.md` | `module-uo/README.md` |
|
||||
| **10** Protocol bump | `link/INTEGRATION.md` §Housing **and §Market** (tables + examples) + the new `account.login.result` row · `link/PLAN.md` §5/§7 · **a `link/v5.md`** — the bump now carries three enrichments across three subsystems, which is what earned v3 and v4 their own design docs | `servuo-plugins/overlay.toml` · `servuo-plugins/patches/README.md` — `vendor.sale` stays in the tier and the docs must say what that costs · **`runicgateway.com`**: `platform.json.protocol` → 5, `bundle.*`, `architecture/protocol-versions.mdx` |
|
||||
| **11** module-uo triggers | `modules/uo/API.md` — **the full trigger catalogue, its audiences and its ceilings**, not one entry · `modules/uo/README.md` · `website/ENGAGEMENT.md` §8.6 kept true as rows ship | `module-uo/README.md` · **`runicgateway.com`**: `capabilities.mjs` and the notifications page — "one rule" and "the whole catalogue" are different marketing claims |
|
||||
| **12** Public site | — | **`runicgateway.com`**, in full — see the phase |
|
||||
| **13** Cutover | `README.md` index rows · every doc's status line | `.profile/README.md` if this is a headline capability |
|
||||
|
||||
@@ -3000,47 +3005,133 @@ a bounce handler today.
|
||||
|
||||
---
|
||||
|
||||
### Phase 10 — Protocol bump: `house.decay` enrichment *(parallel from day one)*
|
||||
### Phase 10 — Protocol bump: three wire enrichments *(parallel from day one)*
|
||||
|
||||
Four repos plus the overlay declaration, per CLAUDE.md's "The bridge is a contract":
|
||||
**Scope widened 2026-08-31, by the org lead, before any code.** The phase was written as the
|
||||
`house.decay` enrichment alone. It now carries **three** enrichments, for one reason that is a property
|
||||
of this phase and of no other: **a protocol bump costs a sidecar release, a republished bundle and an
|
||||
operator update on every shard.** A field left out here does not cost a follow-up commit — it costs a
|
||||
*second* bump with the same three-part lead time, and an operator population split across two protocol
|
||||
versions in the meantime. So everything the engagement workstream is known to need from the wire is
|
||||
decided **now** and rides the single v4 → v5 bump.
|
||||
|
||||
The two additions come from Phase 11's expanded trigger set (below). Each was verified against the
|
||||
emitters rather than assumed, and each closes a gap that §8.6 did not know it had:
|
||||
|
||||
- **Player-vendor fee state.** `uo.vendor.expiring` is the same shape as the flagship — an owned asset
|
||||
at risk, with a real deadline — and is the second-strongest driver in the catalogue. But
|
||||
`vendor.listing` carries `ownerSerial` and `ownerName` and **no `ownerAcct` and no fee state at
|
||||
all**, so the trigger can today neither be addressed to a person nor know that anything is expiring.
|
||||
- **A login *outcome*.** `EventSink.AccountLogin` is a pre-decision veto hook — the emitter's own
|
||||
comment says so ("Fires before the auth decision, so this is an attempt, not a result"). It therefore
|
||||
fires on every **successful** login too, and `uo.account.login_attempt` as §8.6 describes it
|
||||
("someone tried to log into your game account") cannot be built on it: it would mail a security alert
|
||||
every time the player themselves logged in. This is the one addition that is a new emitter rather
|
||||
than new fields on an existing one.
|
||||
|
||||
**Explicitly NOT in this bump:** moving `vendor.sale` out of the opt-in patch tier. It stays where it
|
||||
is (see §8.6's corrected row), which leaves `uo.vendor.sale` dormant on a shard that declined the
|
||||
patches — a documentation obligation, not a protocol one.
|
||||
|
||||
| Repo | Change |
|
||||
| --- | --- |
|
||||
| `servuo-plugins/` | `BridgeSweeps.WriteDecay` gains `ownerName` (`house.Owner.Name`), `nextStage` (`BaseHouse.NextDecayStage`), `decayPeriod`, and `estimatedCollapse` **only when `to == "IDOC"`** (§0.3) |
|
||||
| `servuo-plugins/overlay.toml` | `protocol = 4` → `5`, **in the same PR as the emitter** |
|
||||
| `link/` | `PROTOCOL_VERSION: u32 = 4` → `5` (`sidecar/src/main.rs:55`); houses board carries the new fields |
|
||||
| `website/` (`module-uo`) | ingest maps the new fields; `uoLinkConfig` protocol version |
|
||||
| `docs/` | `docs/link/INTEGRATION.md` §Housing table + example, `docs/link/PLAN.md` §5/§7 |
|
||||
| `servuo-plugins/` | **(a)** `BridgeSweeps.WriteDecay` gains `ownerName` (`house.Owner.Name`), `nextStage` (`BaseHouse.NextDecayStage`), `decayPeriod`, and `estimatedCollapse` **only when `to == "IDOC"`** (§0.3) · **(b)** `BridgeMarket`'s `vendor.listing` gains `ownerAcct`, `holdGold`, `chargePerDay`, `daysRemaining` · **(c)** a new post-decision `account.login.result` (`acct`, `accepted`, `rejectReason`, `ip`) beside the existing pre-decision attempt |
|
||||
| `servuo-plugins/overlay.toml` | `protocol = 4` → `5`, **in the same PR as the emitters** |
|
||||
| `link/` | `PROTOCOL_VERSION: u32 = 4` → `5` (`sidecar/src/main.rs:55`); the houses board carries the new decay fields; `account.login.result` is a forwarded kind like any other — the sidecar stays a dumb forwarder |
|
||||
| `website/` (`module-uo`) | ingest maps the new fields; `uoLinkConfig` protocol version; **`shardVisibility.js`'s `KIND_FEATURE` gains `account.login.result`** — rule 2 fails an unmapped kind closed to admin-only, which is the right answer for a frame carrying an IP, but it must be *chosen* here rather than inherited by accident |
|
||||
| `docs/` | `docs/link/INTEGRATION.md` §Housing and §Market tables + examples, `docs/link/PLAN.md` §5/§7 |
|
||||
|
||||
**Acceptance:** a live run on the local rig (`C:\Users\colby\Desktop\ServUO` + the Rust sidecar, not the
|
||||
PowerShell stub) shows a real transition carrying the new fields; a v4 overlay paired with a v5 sidecar
|
||||
is **refused by the installer**, not mis-parsed; CI publishes a bundle whose manifest pairs v5 with v5.
|
||||
**Two things the emitter work must not get wrong**, both following from §0.3's finding about how
|
||||
ServUO actually decays:
|
||||
|
||||
- `estimatedCollapse` is exact **only** at IDOC, because dynamic decay draws each stage's duration at
|
||||
random when the stage is entered. Emitting it at an earlier stage would publish a guess as a fact.
|
||||
- `daysRemaining` for a vendor **is** exact (`HoldGold / ChargePerDay`), unlike the house. The two
|
||||
fields must not be documented as though they carried the same confidence — a template that says
|
||||
"your house collapses on the 4th" and one that says "your vendor is dismissed in 2 days" are making
|
||||
very different promises.
|
||||
|
||||
**Acceptance:** a live run on the local rig (`C:\Users\colby\Desktop\ServUO` + the Rust sidecar, not
|
||||
the PowerShell stub) shows **(a)** a real decay transition carrying the new fields, **(b)** a seeded
|
||||
player vendor whose listing carries a fee state that falls as its held gold is drawn down, and **(c)** a
|
||||
*failed* game login producing `account.login.result accepted:false` where a successful one produces
|
||||
`accepted:true`; a v4 overlay paired with a v5 sidecar is **refused by the installer**, not mis-parsed;
|
||||
CI publishes a bundle whose manifest pairs v5 with v5; and the five-rung shard visibility walk still
|
||||
shows no leak, with `account.login.result` reaching **admin only**.
|
||||
**Note:** this phase's lead time is a release plus a bundle plus an operator update, which is why it
|
||||
starts early and lands independently.
|
||||
|
||||
---
|
||||
|
||||
### Phase 11 — module-uo's triggers and the first real rule
|
||||
### Phase 11 — module-uo's triggers: the full catalogue, and core's `news.post`
|
||||
|
||||
`module-uo` registers `uo.house.idoc_warning` (and siblings), emits from `shardIngest`, and ships the
|
||||
"greatly damaged" mapping. **The `Greatly` transition mapping needs no protocol change** and can ship
|
||||
with Phase 6 if Phase 10 is still in flight — the trigger simply omits `nextStage`/`estimatedCollapse`
|
||||
until the v5 overlay is deployed, which the `required: false` declaration already permits.
|
||||
**Scope widened 2026-08-31, by the org lead, before any code.** This phase used to say `module-uo`
|
||||
registers `uo.house.idoc_warning` "(and siblings)", and Part 8's preamble used to say "Phase 11 ships
|
||||
exactly one rule". Both are now wrong on purpose: **Phase 11 ships every ✅ row in §8.6**, with one
|
||||
carve-out named below. §8.6 was written as "a catalogue, not a commitment" — it is now the commitment,
|
||||
and the burden of proof has inverted: a row that does *not* ship needs a reason recorded here.
|
||||
|
||||
**Also core's own `news.post` emitter, which this phase's title understates** (§7.1 Q9). `news.post` is
|
||||
declared with no caller, so a rule naming it can never fire; wiring it is one `ctx.events.emit`-shaped
|
||||
call in `announceIfNewlyPublished`, gated on the same job-id transition signal the push already uses.
|
||||
**The announce legs and the post hooks are untouched** — a module's town-crier leg is a delivery to a
|
||||
channel of the deployment and its news-gump hook is state mirroring, neither of which is a per-person
|
||||
notification. What the emit replaces is the raw `pushDispatch.publish` beside them, and Q9's continuity
|
||||
question has to be answered before it does.
|
||||
**What that is worth, and it is not "more mail".** One trigger proves that a module can register a
|
||||
trigger. Twenty-odd triggers spanning five audience kinds are the first real test of the things Parts
|
||||
3–5 asserted and nothing has yet stressed: that the ceiling lattice holds when most triggers are *not*
|
||||
public, that `check:modules` still finds no UO identifier in core when the module is this large, and
|
||||
that a player's preferences screen stays legible when the catalogue is real rather than a demo. Every
|
||||
rule ships `enabled = 0` (Q3), so this is a catalogue an operator turns on, not a switch that floods
|
||||
anyone.
|
||||
|
||||
**Acceptance:** the five-rung shard visibility walk still shows no leak; a house transitioning to
|
||||
**The carve-out: `uo.market.item_listed` does not ship here**, for the reason §8.6 already gives — it
|
||||
is a *saved search*, not a trigger. Its audience is "users whose stored query matches this listing",
|
||||
and no per-user query store exists anywhere in the tree. It is its own workstream on top of this one.
|
||||
`vendor.listing` remains ingested; nothing is lost by waiting.
|
||||
|
||||
**Two rows ship only because Phase 10 does.** `uo.house.idoc_warning`'s enrichment and
|
||||
`uo.vendor.expiring` both need v5 fields; `uo.account.login_attempt` needs v5's new
|
||||
`account.login.result` emitter and is renamed **`uo.account.login_failed`** to say what it actually
|
||||
is. Everything else is mapping-only and can ship whether or not the bump has landed — the flagship's
|
||||
`Greatly` mapping included, which **needs no protocol change** and can ship with Phase 6 if Phase 10
|
||||
is still in flight, the trigger simply omitting `nextStage`/`estimatedCollapse` until the v5 overlay is
|
||||
deployed, exactly as the `required: false` declaration already permits.
|
||||
|
||||
**The set, grouped by the audience kind each family exercises** — which is the point of grouping them
|
||||
this way, since the audience kind is the thing being tested:
|
||||
|
||||
| Family | Triggers | Audience kind | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| **Owned asset at risk** | `uo.house.idoc_warning`, `uo.house.collapsed`, `uo.vendor.expiring` | `owner` (linked account) | The flagship family. All three resolve through `ownerAcct` → `shard_links` |
|
||||
| **Passive income** | `uo.vendor.sale` | `owner` | **Patch-tier only** — see §8.6's corrected row; dormant on a shard that declined the patches, and the seeded rule's description must say so |
|
||||
| **Personal security** | `uo.account.login_failed`, `uo.account.unlinked`, `uo.link.requested` | `owner`, ceiling `owner` | `uo.link.requested` is also the linking funnel: a player ran `[link` in game, finish it on the site |
|
||||
| **Personal milestone** | `uo.skill.capped`, `uo.quest.complete`, `uo.character.death`, `uo.character.murdered` | `owner`, opt-in | The two death triggers are a killfeed some players want and most do not — both ship `enabled = 0` and default `off` per channel |
|
||||
| **Social / civic** | `uo.guild.joined`, `uo.guild.left`, `uo.guild.disbanded`, `uo.governor.elected`, `uo.election.opened` | `members`, `subscribers` | `uo.election.opened` carries `autoPickAt` — a real deadline, so it is the first trigger whose template has a genuine call to action with an expiry |
|
||||
| **Come online now** | `uo.champ.started`, `uo.champ.boss_up`, `uo.server.up`, `uo.server.down` | `subscribers` | **`uo.server.up`/`down` is the cooldown table's stress test** — a flapping shard emits both repeatedly. Hard per-rule cooldown, not a per-send one |
|
||||
| **Leaderboard** | `uo.points.rank_changed` | `subscribers`, `owner` | Fires both ways (you entered a top N; you were pushed out) |
|
||||
| **Staff-facing** | `uo.page.new`, `uo.cheat.detected`, `uo.audit.staff_action` | ceiling `staff` / `admin` | These are why the ceiling exists. Phase 3 already filters a `staff`-ceiling trigger out of a player's catalogue *and* gates it on write, so this family is the production proof of that work rather than new mechanism |
|
||||
| **Operator-facing** | `uo.economy.milestone`, `uo.world.saved` | ceiling `admin` | Digest-shaped by nature; neither should ever be instant |
|
||||
|
||||
**Also core's own `news.post` emitter, which this phase's title has always understated** (§7.1 Q9).
|
||||
`news.post` is declared with no caller, so a rule naming it can never fire; wiring it is one
|
||||
`ctx.events.emit`-shaped call in `announceIfNewlyPublished`, gated on the same job-id transition signal
|
||||
the push already uses. **The announce legs and the post hooks are untouched** — a module's town-crier
|
||||
leg is a delivery to a channel of the deployment and its news-gump hook is state mirroring, neither of
|
||||
which is a per-person notification. What the emit replaces is the raw `pushDispatch.publish` beside
|
||||
them, and Q9's continuity question has to be answered before it does.
|
||||
|
||||
**On the size of this phase.** It is now the largest in the workstream: ~23 trigger declarations, their
|
||||
audiences and ceilings, their `shardIngest` mappings, and a seeded template each — against Phase 5a's
|
||||
nine seeded bodies in total. It will likely want to land as **11a (declarations, mappings, ceilings —
|
||||
server only) / 11b (the seeded templates and the live walk)**, on the 4a/4b and 5a/5b precedent, and
|
||||
that split should be confirmed with the org lead at the start of the phase rather than assumed here.
|
||||
|
||||
**Acceptance:** the five-rung shard visibility walk still shows no leak, and **no staff- or
|
||||
admin-ceiling trigger appears by name in a player's preferences catalogue**; a house transitioning to
|
||||
`Greatly` on the live rig produces one email to the linked owner and nothing to anyone else; a second
|
||||
transition inside the cooldown produces nothing; a refresh back to `LikeNew` inside the delay window
|
||||
cancels the pending mail; **a news post published on the rig reaches a rule, and the town-crier leg and
|
||||
every registered post hook still fire exactly as they did.**
|
||||
**Guardrails:** `check:modules` proves core gained no UO identifier across every phase to this point.
|
||||
cancels the pending mail; a player vendor drawn down to its last day produces one `uo.vendor.expiring`
|
||||
to its owner; a failed game login produces one `uo.account.login_failed` and a **successful** one
|
||||
produces none; a shard restarted three times in a minute produces **one** downtime mail, not six; and
|
||||
**a news post published on the rig reaches a rule, with the town-crier leg and every registered post
|
||||
hook still firing exactly as they did.**
|
||||
**Guardrails:** `check:modules` proves core gained no UO identifier across every phase to this point —
|
||||
which is a materially stronger claim now that the module registers twenty-odd UO-named triggers.
|
||||
|
||||
---
|
||||
|
||||
@@ -3370,9 +3461,11 @@ Nothing here is "documentation to do at the end" — a phase is not done until i
|
||||
|
||||
## Part 8 — What the system could be used for
|
||||
|
||||
A catalogue, not a commitment. Its purpose is to check that the design in Parts 3–5 is general enough,
|
||||
and to show what an operator gets for the phases they pay for. **Nothing here is scheduled**; Phase 11
|
||||
ships exactly one rule.
|
||||
Written as a catalogue, not a commitment — to check that the design in Parts 3–5 is general enough, and
|
||||
to show what an operator gets for the phases they pay for. **§8.6 is no longer that.** On 2026-08-31 the
|
||||
org lead scheduled **every ✅ row of §8.6 into Phase 11**, so for the `module-uo` section the burden of
|
||||
proof has inverted: a row that does not ship needs a reason recorded in the phase. The rest of Part 8 —
|
||||
§8.1–8.5 and §8.7 — remains unscheduled and stays a catalogue.
|
||||
|
||||
Read the availability column as: **✅** the data exists today and a rule could be written the day the
|
||||
engine lands · **⚠** the data exists but needs a mapper or a resolver · **❌** needs new plumbing
|
||||
@@ -3464,16 +3557,21 @@ which is the one legitimately campaign-shaped use and the one most likely to be
|
||||
|
||||
### 8.6 Module events — `module-uo`, grounded in what the shard actually emits
|
||||
|
||||
Every kind below is real (`docs/link/INTEGRATION.md`). The trigger id is what `module-uo` would register;
|
||||
core stays ignorant of every word in it.
|
||||
Every kind below is real (`docs/link/INTEGRATION.md`). The trigger id is what `module-uo` registers;
|
||||
core stays ignorant of every word in it. **Every ✅ row here is Phase 11 scope** except
|
||||
`uo.market.item_listed`, carved out there with its reason.
|
||||
|
||||
**Three rows were corrected on 2026-08-31**, when the set was scheduled and each claim was checked
|
||||
against the emitters rather than against this table. Two of the corrections are what widened Phase 10
|
||||
from one wire enrichment to three; the third is a caveat that has to reach the operator, not a defect.
|
||||
|
||||
| Trigger | Wire source | Data | Why anyone cares |
|
||||
| --- | --- | --- | --- |
|
||||
| `uo.house.idoc_warning` | `house.decay` → `Greatly` | ⚠ mapping only (the enrichment is Phase 10) | **The flagship.** Your house is decaying; log in or lose it and its contents |
|
||||
| `uo.house.collapsed` | `house.decay` → `Collapsed`, `house.remove` | ✅ | The bad news, so it is not a surprise |
|
||||
| `uo.vendor.sale` | `vendor.sale` | ✅ (`ownerAcct` → linked user) | Your player vendor sold something, for how much |
|
||||
| `uo.vendor.expiring` | `vendor.listing`, vendor fees | ⚠ | Your vendor is about to be dismissed for unpaid fees |
|
||||
| `uo.account.login_attempt` | `account.login.attempt` | ✅ (already a personal stream) | Someone tried to log into your game account, from where |
|
||||
| `uo.vendor.sale` | `vendor.sale` | ✅ (`ownerAcct` → linked user) — **but patch-tier only**, see below | Your player vendor sold something, for how much |
|
||||
| `uo.vendor.expiring` | `vendor.listing` + **v5 fee fields** | ⚠ → ✅ **after Phase 10**; `vendor.listing` carries no `ownerAcct` and no fee state today | Your vendor is about to be dismissed for unpaid fees |
|
||||
| `uo.account.login_failed` | **v5 `account.login.result`** | ⚠ → ✅ **after Phase 10**; the existing `account.login.attempt` is pre-decision, see below | Someone tried to log into your game account, and failed, and from where |
|
||||
| `uo.character.death` / `murdered` | `player.death`, `player.murdered` | ✅ | Opt-in; a killfeed some players want and most do not |
|
||||
| `uo.skill.capped` | `skill.gain` where `base == cap` | ✅ | You hit the cap in a skill — a genuine milestone |
|
||||
| `uo.quest.complete` | `quest.complete` | ✅ | Milestone / achievement mail |
|
||||
@@ -3494,7 +3592,25 @@ core stays ignorant of every word in it.
|
||||
| `uo.economy.milestone` | `economy.supply` | ✅ | Operator-facing; economy health thresholds |
|
||||
| `uo.world.saved` | `world.save.after` | ✅ | Operator-facing only; world-size trend |
|
||||
|
||||
**Three of these are worth calling out as design pressure on Parts 3–5:**
|
||||
**Three rows as originally written were wrong, and finding out cost one sweep of the emitters:**
|
||||
|
||||
- **`uo.vendor.sale` is real and does carry `ownerAcct` — but it lives in `servuo-plugins/patches/`,
|
||||
the opt-in patch tier**, verified only against ServUO 57.4. It is a `PlayerVendorSale` EventSink the
|
||||
patches *add* to core plus a subscriber that reads it, which is why it is absent from `overlay/`. A
|
||||
shard that declined the tier, or runs another ServUO version, emits this kind never — so the rule is
|
||||
silently dormant there rather than broken. Phase 10 deliberately does **not** try to move it into the
|
||||
overlay; the obligation is that the seeded rule and the operator docs say which tier it needs.
|
||||
- **`uo.vendor.expiring` had no data at all, not merely no mapper.** `vendor.listing` carries
|
||||
`ownerSerial` and `ownerName` — no `ownerAcct`, so it cannot even be addressed to a website user —
|
||||
and nothing anywhere on the wire carries a vendor's held gold or daily charge. Phase 10 adds all four.
|
||||
- **`uo.account.login_attempt` could not have been built as described.** `EventSink.AccountLogin` is a
|
||||
pre-decision veto hook; the emitter's own comment says "Fires before the auth decision, so this is an
|
||||
attempt, not a result". A rule on it would have mailed "someone tried to log into your account" every
|
||||
time the player logged in successfully — the exact inversion that makes people distrust security
|
||||
mail. Phase 10 adds a post-decision `account.login.result`, and the trigger is renamed
|
||||
**`uo.account.login_failed`** so its id cannot be misread again.
|
||||
|
||||
**Three others are worth calling out as design pressure on Parts 3–5:**
|
||||
|
||||
- **`uo.market.item_listed` is a *saved search*, not a plain trigger.** The audience is "users whose
|
||||
stored query matches this listing", which `audience: 'computed'` covers but a per-user query store
|
||||
|
||||
Reference in New Issue
Block a user