docs(link): protocol 6 as built — a guarantee, and the kind that pays for it #219

Merged
whitlocktech merged 1 commits from docs/protocol-v6-idempotency into edge 2026-09-04 23:08:07 +00:00
Member

The docs half of servuo-plugins#21 + link#36 + Module-uo#29. Five files, one of them new.

link/v6.md — the spec of record

Every protocol version so far added data. This one adds a promise, and says so in its first section:

A command carrying an idempotencyKey is executed at most once. A repeat is answered with the original reply.

§2.1 records the four rules that shape the store — reserve on receipt, never release a key that has begun, stamp a replay with the repeat's correlation id, and make the bound loud — each as a decision rather than an implementation detail. §2.1.1 explains the reply-capture filter, which is the non-obvious part: an admin.audit frame emitted alongside a reply is a fact about the world and must not be replayed, while the reply is an answer to a caller and must be.

§3 states plainly what the dumb-forwarder property means here: the sidecar makes no idempotency promise of its own, and the guarantee is the shard's end to end, because that is where the world write happens.

Three plan corrections, all recorded

  • installer is not in this phase. The plan put it there "because of the pairing", but installer/PLAN.md §7.4 made that unnecessary on purpose: no protocol version is hardcoded anywhere in it. bundle.rs reads the number out of the bundle and bundle.yml's Gate 1 reads both halves out of the released artefacts. Version-agnostic — it needed no change for 5 either.
  • Phase 11 splits into 11a and 11b (org lead), landing as one protocol version: 11a bumps to 6 and 11b amends 6 in place on edge, the same rule set for MODULE_API_VERSION on 2026-09-03. v6.md §7 says what 11b will add and, more importantly, warns that an overlay and a sidecar both declaring 6 are only interchangeable within one side of that merge — tolerable exactly because nothing is released from edge, and untenable on main.
  • A boss defeat was more fragile to infer than §G said. "Slightly fragile" undersold it: bossUp also drops when a GM resets a spawn, when a boss despawns, and after a reconnect clears the sweep's diff cache.

The rig trap, written up because the naive check confirms it

v6.md §6.1. A champion killed in the middle of Britain emitted a frame with no region, and three things were true at once: an active ChampionSpawn registers a ChampionSpawnRegion with a null name and the town region as its parent, so the innermost region containing a champion boss is guaranteed to be nameless; Mobile.Region hides that by falling back to the map's unnamed default rather than to null; and region registration is deferred, so a lookup at spawn time answers "Britain" while one at the kill does not.

The probe printed region "Britain" on every run, including the ones whose frame carried nothing. A read at spawn time would have confirmed the bug into the design.

The other four files

  • INTEGRATION.md — §2 gains v6; the write plane gains a "Retrying a command safely" section with the four rules a third-party caller needs (a key belongs to your unit of work, not your attempt; one hour; 425 means come back; a replayed reply is an ordinary 200); §7 gains 425 and a paragraph on why it is deliberately not 409; the event catalog gains champ.boss.killed with the three absences a consumer will otherwise read as errors.
  • PLAN.md — §5.5 records the observation the new kind rests on: a per-hit damage event needs an override, but a per-kill damage table needs nothing, because Mobile.DamageEntries is already there and a CreatureDeath handler reads it before the creature is disposed of. §7 documents the key on the inbound frames.
  • EVENTS.md — §A row 7 closes, two §G capability rows go to built, and the failure table's "an action succeeds but the ack is lost" loses its "and it does not exist today".
  • EVENTS_PLAN.md — Phase 11 split, 11a as built with its seven decisions, and both of the phase's rule-shaped findings: a trigger is not a stream (pre-existing in twenty module-uo rules, flagged not fixed), and a new rule needs a new seed group.

Notes

  • docs has no CI. Every relative link and anchor in the five files was resolved by hand.

  • CRLF preserved throughoutgit diff --numstat reports 110/24/331/11/118 against real content diffs rather than whole-file rewrites, and the tree holds no bare LF and no \r\r\n.

  • AI-assisted: Claude Code (Opus 5).

Plugin: RunicGateway/servuo-plugins#21 · Sidecar: RunicGateway/link#36 · Module: RunicGateway/Module-uo#29

🤖 Generated with Claude Code

The docs half of `servuo-plugins#21` + `link#36` + `Module-uo#29`. Five files, one of them new. ## `link/v6.md` — the spec of record Every protocol version so far added **data**. This one adds a **promise**, and says so in its first section: > A command carrying an `idempotencyKey` is executed at most once. A repeat is answered with the original reply. §2.1 records the four rules that shape the store — reserve on receipt, never release a key that has begun, stamp a replay with the *repeat's* correlation id, and make the bound loud — each as a decision rather than an implementation detail. §2.1.1 explains the reply-capture filter, which is the non-obvious part: an `admin.audit` frame emitted alongside a reply is a fact about the world and must **not** be replayed, while the reply is an answer to a caller and must be. §3 states plainly what the dumb-forwarder property means here: **the sidecar makes no idempotency promise of its own**, and the guarantee is the shard's end to end, because that is where the world write happens. ## Three plan corrections, all recorded - **`installer` is not in this phase.** The plan put it there *"because of the pairing"*, but `installer/PLAN.md` §7.4 made that unnecessary on purpose: no protocol version is hardcoded anywhere in it. `bundle.rs` reads the number out of the bundle and `bundle.yml`'s Gate 1 reads both halves out of the released artefacts. Version-agnostic — it needed no change for 5 either. - **Phase 11 splits into 11a and 11b** (org lead), landing as **one** protocol version: 11a bumps to 6 and 11b amends 6 in place on `edge`, the same rule set for `MODULE_API_VERSION` on 2026-09-03. v6.md §7 says what 11b will add and, more importantly, warns that an overlay and a sidecar both declaring `6` are only interchangeable within one side of that merge — tolerable exactly because nothing is released from `edge`, and untenable on `main`. - **A boss defeat was more fragile to infer than §G said.** "Slightly fragile" undersold it: `bossUp` also drops when a GM resets a spawn, when a boss despawns, and after a reconnect clears the sweep's diff cache. ## The rig trap, written up because the naive check confirms it `v6.md` §6.1. A champion killed in the middle of Britain emitted a frame with **no region**, and three things were true at once: an active `ChampionSpawn` registers a `ChampionSpawnRegion` with a **null name** and the town region as its parent, so the innermost region containing a champion boss is guaranteed to be nameless; `Mobile.Region` hides that by falling back to the map's unnamed default rather than to null; and region registration is **deferred**, so a lookup at spawn time answers `"Britain"` while one at the kill does not. The probe printed `region "Britain"` on every run, including the ones whose frame carried nothing. **A read at spawn time would have confirmed the bug into the design.** ## The other four files - **`INTEGRATION.md`** — §2 gains v6; the write plane gains a *"Retrying a command safely"* section with the four rules a third-party caller needs (a key belongs to your unit of work, not your attempt; one hour; 425 means come back; a replayed reply is an ordinary 200); §7 gains 425 and a paragraph on why it is deliberately not 409; the event catalog gains `champ.boss.killed` with the three absences a consumer will otherwise read as errors. - **`PLAN.md`** — §5.5 records the observation the new kind rests on: a per-**hit** damage event needs an override, but a per-**kill** damage *table* needs nothing, because `Mobile.DamageEntries` is already there and a `CreatureDeath` handler reads it before the creature is disposed of. §7 documents the key on the inbound frames. - **`EVENTS.md`** — §A row 7 closes, two §G capability rows go to ✅ built, and the failure table's *"an action succeeds but the ack is lost"* loses its *"and it does not exist today"*. - **`EVENTS_PLAN.md`** — Phase 11 split, 11a as built with its seven decisions, and both of the phase's rule-shaped findings: a trigger is not a stream (pre-existing in twenty `module-uo` rules, flagged not fixed), and a new rule needs a new seed group. ## Notes - `docs` has no CI. Every relative link and anchor in the five files was resolved by hand. - **CRLF preserved throughout** — `git diff --numstat` reports 110/24/331/11/118 against real content diffs rather than whole-file rewrites, and the tree holds no bare LF and no `\r\r\n`. - [x] AI-assisted: Claude Code (Opus 5). Plugin: RunicGateway/servuo-plugins#21 · Sidecar: RunicGateway/link#36 · Module: RunicGateway/Module-uo#29 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 1 commit 2026-09-04 23:02:10 +00:00
`link/v6.md` is the spec of record. It is the first bump that adds a promise
rather than data: a command carrying an `idempotencyKey` is executed at most once,
and a repeat is answered with the original reply.

Also in this PR:

- `INTEGRATION.md` — §2 gains v6, the write plane gains a "retrying a command
  safely" section with the four rules a caller needs, §7 gains 425 and the
  paragraph on why it is not 409, and the event catalog gains `champ.boss.killed`.
- `PLAN.md` — §5.5 records that a per-KILL damage table needs no override even
  though a per-HIT event does, which is the observation the new kind rests on;
  §7 documents the key on the inbound frames.
- `EVENTS.md` — §A row 7 and two §G capability rows close, and the failure table's
  "an action succeeds but the ack is lost" loses its "does not exist today".
- `EVENTS_PLAN.md` — Phase 11 splits into 11a and 11b, and `installer` leaves the
  phase: PLAN.md §7.4 made sure no protocol version is hardcoded there, so the
  bundle gate is version-agnostic and needed no change for 5 either.

The live walk's defect is written up in v6.md §6.1 because the naive check
confirms it: an active ChampionSpawn registers an unnamed region over its own
spawn area, so the innermost region containing a champion boss is guaranteed to
have no name — and region registration is deferred, so a lookup at spawn time
answers "Britain" while one at the kill does not.

CRLF preserved throughout; links and anchors checked by hand, this repo has no CI.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 1521c5af7e into edge 2026-09-04 23:08:07 +00:00
whitlocktech deleted branch docs/protocol-v6-idempotency 2026-09-04 23:08:08 +00:00
Sign in to join this conversation.
No description provided.