feat(web): the borrowed planes and the one-shots on the wire (Phase 12b) #39

Merged
whitlocktech merged 1 commits from feature/events-p12b-borrowed-and-oneshots into edge 2026-09-07 16:23:21 +00:00
Member

Events Phase 12b, sidecar half. One of five: website (website#194), servuo-plugins (servuo-plugins#24), link, Module-uo (#—), docs (docs#222). Spec of record: docs/link/v7.md §11–§13.

PROTOCOL_VERSION stays 7. 12b amends 7 in place rather than bumping again, tolerable for the single reason 6 and 7 already are and no other: nothing is released from edge.

The lease family gains a target, not a family of its own

A property lease, a seasonal toggle and a config key are one protocol with three catalogs — so there is one deadline, one compare-and-set, one grace window and one set of counters instead of three of each.

Route Change
GET /lease?key=&target= narrows to one row and fills current; always carries holds
POST /lease target added
POST /lease/release target added; may answer targetGone
GET /items the shard's grant allowlist and its bounds
POST /items/grant names a run, never a recipient list
POST /world/save 429 when it comes too soon

A targeted key needs the narrowing. Spawner.MaxCount is one capability over thousands of spawners, so it has no single current and the catalog walk cannot fill one in — while the website's read() needs exactly one value for exactly one target before it applies anything.

The frame also always carries holds: every lease the shard is actually holding, whatever key or target. A catalog walk can enumerate the keys but never the holds on a targeted one — there is no list of spawners to walk — so without it a reconcile after an outage would have no way to ask "what are you still holding?". inForce() reads that.

POST /items/grant names a run and never a recipient list. The shard has held the run's participation ledger since protocol 6 part b, keyed by the same character serials the website's member_key holds, so sending a list would put it on the wire twice with a window in which the two disagree.

Two status mappings are the point of the diff

A run with no ledger open is a 404 and a run whose ledger is open and empty is a 200 with granted: 0. "You never told me to count" and "nobody came" are different facts, and only the first is a mistake — an event nobody attended still happened, and answering it as a failure would have the module retry against a ledger that will be just as empty next time.

A save refused for coming too soon is a 429, not the 400 every other refusal on this plane is. It is the one refusal here that the same request gets past by waiting, so 429 says exactly that — and keeps it out of the module's permanent-status set, which is what makes a phase boundary retried rather than abandoned.

Tests

cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test all clean: 51 passed (was 49). The two new tests pin those two mappings, including the pair that look alike and are not (404 against granted: 0), and that the gate being off is still a 403 on this plane.

Also exercised end to end against the real local ServUO 57.4 world driving this binary's REST — including that /world/save is not eaten by /world/:run_id next door, which is the one thing about this route table worth checking rather than reasoning about. See servuo-plugins#24 for the walk.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

Events Phase 12b, sidecar half. One of **five**: `website` (website#194), `servuo-plugins` (servuo-plugins#24), `link`, `Module-uo` (#—), `docs` (docs#222). Spec of record: [`docs/link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/222) §11–§13. **`PROTOCOL_VERSION` stays 7.** 12b amends 7 in place rather than bumping again, tolerable for the single reason 6 and 7 already are and no other: nothing is released from `edge`. ## The lease family gains a `target`, not a family of its own A property lease, a seasonal toggle and a config key are **one protocol with three catalogs** — so there is one deadline, one compare-and-set, one grace window and one set of counters instead of three of each. | Route | Change | |---|---| | `GET /lease?key=&target=` | narrows to one row and fills `current`; always carries `holds` | | `POST /lease` | `target` added | | `POST /lease/release` | `target` added; may answer `targetGone` | | `GET /items` | the shard's grant allowlist and its bounds | | `POST /items/grant` | names a run, never a recipient list | | `POST /world/save` | 429 when it comes too soon | **A targeted key needs the narrowing.** `Spawner.MaxCount` is one capability over thousands of spawners, so it has no single `current` and the catalog walk cannot fill one in — while the website's `read()` needs exactly one value for exactly one target *before* it applies anything. **The frame also always carries `holds`**: every lease the shard is actually holding, whatever key or target. A catalog walk can enumerate the *keys* but never the *holds* on a targeted one — there is no list of spawners to walk — so without it a reconcile after an outage would have no way to ask "what are you still holding?". `inForce()` reads that. **`POST /items/grant` names a run and never a recipient list.** The shard has held the run's participation ledger since protocol 6 part b, keyed by the same character serials the website's `member_key` holds, so sending a list would put it on the wire twice with a window in which the two disagree. ## Two status mappings are the point of the diff **A run with no ledger open is a 404 and a run whose ledger is open and empty is a 200 with `granted: 0`.** *"You never told me to count"* and *"nobody came"* are different facts, and only the first is a mistake — an event nobody attended still happened, and answering it as a failure would have the module retry against a ledger that will be just as empty next time. **A save refused for coming too soon is a 429**, not the 400 every other refusal on this plane is. It is the one refusal here that the same request gets past by waiting, so 429 says exactly that — and keeps it out of the module's permanent-status set, which is what makes a phase boundary retried rather than abandoned. ## Tests `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings` and `cargo test` all clean: **51 passed** (was 49). The two new tests pin those two mappings, including the pair that look alike and are not (`404` against `granted: 0`), and that the gate being off is still a 403 on this plane. Also exercised end to end against the real local ServUO 57.4 world driving this binary's REST — **including that `/world/save` is not eaten by `/world/:run_id` next door**, which is the one thing about this route table worth checking rather than reasoning about. See servuo-plugins#24 for the walk. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 1 commit 2026-09-07 13:11:16 +00:00
feat(web): the borrowed planes and the one-shots on the wire (Phase 12b)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 3m8s
d83bb1748c
The sidecar half of protocol 7 part b. `PROTOCOL_VERSION` stays 7: 12b amends 7
in place rather than bumping again, which is tolerable for the single reason 6
and 7 already are and no other -- nothing is released from `edge`.

The lease family gains a `target` rather than a family of its own. A property
lease, a seasonal toggle and a config key are one protocol with three catalogs,
so there is one deadline, one compare-and-set, one grace window and one set of
counters instead of three of each.

`GET /lease?key=&target=` narrows to one row, and a targeted key needs it.
`Spawner.MaxCount` is one capability over thousands of spawners, so it has no
single `current` and the catalog walk cannot fill one in -- while the website's
`read()` needs exactly one value for exactly one target BEFORE it applies
anything. Naming both answers that.

The frame also always carries `holds`: every lease the shard is actually holding,
whatever key or target it is on. A catalog walk can enumerate the KEYS but never
the holds on a targeted one -- there is no list of spawners to walk -- so without
it a reconcile after an outage would have no way to ask "what are you still
holding?". `inForce()` reads that.

Three new routes. `GET /items` is the shard's own grant allowlist, so the
website's dropdown offers what this shard will actually build. `POST
/items/grant` names a RUN and never a recipient list: the shard has held the
run's participation ledger since protocol 6 part b, keyed by the same character
serials the website's `member_key` holds, so sending a list would put it on the
wire twice with a window in which the two disagree. `POST /world/save` starts a
save; what actually happened rides `world.save.before`/`after`, which have been
on the stream since protocol 2.

Two status mappings are the point of the diff rather than plumbing:

A run with no ledger open is a 404 and a run whose ledger is open and empty is a
200 with `granted: 0`. "You never told me to count" and "nobody came" are
different facts, and only the first is a mistake -- an event nobody attended
still happened, and answering it as a failure would have the module retry against
a ledger that will be just as empty next time.

A save refused for coming too soon is a 429, not the 400 every other refusal on
this plane is. It is the one refusal here that the same request gets past by
waiting, so 429 says exactly that and keeps it out of the module's
permanent-status set -- which is what makes a phase boundary retried rather than
abandoned.

`cargo fmt --check`, `cargo clippy --all-targets -- -D warnings` and `cargo test`
all clean: 51 passed (was 49). The two new tests pin those two mappings.

Also exercised end to end against the real local ServUO 57.4 world driving this
binary's REST -- including that `/world/save` is not eaten by `/world/:run_id`
next door. See servuo-plugins for the walk.

Refs: docs/link/v7.md §11-§13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
whitlocktech merged commit f39dfa4f84 into edge 2026-09-07 16:23:21 +00:00
whitlocktech deleted branch feature/events-p12b-borrowed-and-oneshots 2026-09-07 16:23:22 +00:00
Sign in to join this conversation.
No description provided.