docs(link): the cutover published, and the bundle that did not recompose (Phase 9b)
Adds §17.16 — what the five-repo cutover published, and the one step that needed a hand. sidecar v2.3.0, overlay v1.3.0, module-uo v1.3.0, installer v0.2.0, bundle 2026.09.15, all at protocol 8. `edge` stays standing everywhere. ## The bundle did not recompose on its own Two compose runs failed for two unrelated reasons and only their logs separate them. Run 111 was **Gate 1 working**, refusing `sidecar v2.2.0 speaks 7, overlay v1.3.0 declares 8` in the nine-minute window between the two releases -- the concrete case for landing the pair together. Run 114 was the **runner**: `Set up job` 14m33s, every later step failing at 0s, no log blob stored. A failed compose is silent from outside. The bundle keeps its previous value, so operators keep installing the previous protocol while `main` carries the new one, and nothing says so. Dispatching bundle.yml recovered it within a minute. The lesson recorded for the next cutover: publishing the releases is not the last step, verifying the bundle moved is -- and read `current.json` through `/contents/`, never `/raw/`, which is CDN-cached for six hours. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
44
link/v8.md
44
link/v8.md
@@ -1702,7 +1702,7 @@ disagree, so a split bump means the next bundle silently fails to compose.
|
||||
| 7 | **DONE 2026-09-14.** The atlas over the sidecar (§10); shared-filesystem requirement retired. The measurement came first again and changed the shape: `tree/<label>` → bytes **cannot work** — `Spawns/trammel.xml` is 4.03 MB against a 1 MiB line cap — so a file crosses as **512 KiB chunks, each gzipped**, which is §5's depth scheme paying for itself a second time (§10.1). It is a `tree` **family** on `assets.fetch` rather than §14's separate commands, with `assets.manifest` generalised to match and its **own consent, `Bridge.TreeEnabled`** (§10.2) — so `link` needed nothing for the third phase running. **141 files / 11.9 MB / 158 chunks / 3 pages / 1.33 MB on the wire / 512 ms**, and the atlas built over the bridge is identical to the one built off the disk. Two defects, each found by a different harness: an empty `catalog` refusing every fetch, and `GZipStream` emitting **nothing** for the two empty files stock ServUO ships (§10.3). `PARSER_VERSION` bumped for one canonical read order — written as 4 → 5, renumbered to **6** at the cutover (§10.4, §17.15); protocol stays 8; `EXTRACTOR_VERSION` stays 3 | servuo-plugins, module-uo |
|
||||
| 8 | **DONE 2026-09-14.** The admin surface (§12.2): `Admin → Client Files` — one page over all three planes, because they come off one client and change on one event. The cliloc pair had had no UI since phase 2, which on a bridge install meant `curl` was the only way to import 67,496 names. §14's "activity view" is the **last import's own summary** rather than a filtered feed, which kept the phase to one repo (org lead, 2026-09-14). The walk imported **1,095 portraits in 3.5 s, warmed 313 item pictures in 0.6 s and reloaded 67,496 cliloc rows in 1.7 s** against a real shard — and found **two deletions nobody could see before a screen put the numbers together**: the body import diffing its manifest against *every* family's rows, which staged all 313 item pictures for deletion, and an approved vanish that unlinked the sprite and kept the row, so the key came back for review forever. Both fixed here; `EXTRACTOR_VERSION` and the protocol are untouched | module-uo |
|
||||
| 9a | **DONE 2026-09-14.** The docs pass and the acceptance walk (§17.13). `INTEGRATION.md` carried to protocol 8 — it had stopped at **6**, contradicted itself, and documented none of this plane; the operator-facing upgrade note; the index rows; the installer's `libgdiplus` `doctor` check and the kit's chapter note, both promised by §15 and built by no phase. The walk wiped every asset row and every imported sprite and went through it as a new operator: **1,095 portraits in 3.18 s, 67,496 names in 1.42 s, 313 item pictures in 1.38 s, the atlas over the bridge in ~2.0 s, and an Update with no drift answered in 0.99 s**. Two defects (§17.14), the larger of them **not ours and released** — Events phase 12b's `unique_id` has been failing every spawn-atlas import on every upgraded install since v1.2.0, which only a walk on a rig whose tables predate it could see | docs, module-uo, installer, integration-kit, servuo-plugins |
|
||||
| 9b | **DONE 2026-09-14.** The `edge → main` cutover, and it is **five repos, not four** — `installer`'s `doctor` check was on `edge` too, and `SHARD_PREREQS.md` on `main` had been describing it as if it shipped (§17.15). Order: the `module-uo` sync first, then `link` + `servuo-plugins` **together** because `bundle.yml` Gate 1 refuses a protocol-mismatched pair, then `website`, `module-uo`, `installer`. The sync found the one real conflict in the whole cutover: both branches had bumped `PARSER_VERSION` **4 → 5** for different reasons and main's 5 was **released**, so keeping it would have made phase 7's re-read unreachable on every existing install — the merged value is **6** (§10.4). Core's entire share of eight phases is a **deletion**: `MODULE_API` never moved, because `ctx.uploads` was already exposed | link, servuo-plugins, module-uo, website, installer |
|
||||
| 9b | **DONE 2026-09-14.** The `edge → main` cutover, and it is **five repos, not four** — `installer`'s `doctor` check was on `edge` too, and `SHARD_PREREQS.md` on `main` had been describing it as if it shipped (§17.15). Order: the `module-uo` sync first, then `link` + `servuo-plugins` **together** because `bundle.yml` Gate 1 refuses a protocol-mismatched pair, then `website`, `module-uo`, `installer`. The sync found the one real conflict in the whole cutover: both branches had bumped `PARSER_VERSION` **4 → 5** for different reasons and main's 5 was **released**, so keeping it would have made phase 7's re-read unreachable on every existing install — the merged value is **6** (§10.4). Core's entire share of eight phases is a **deletion**: `MODULE_API` never moved, because `ctx.uploads` was already exposed. Published: sidecar **v2.3.0**, overlay **v1.3.0**, module **v1.3.0**, installer **v0.2.0**, bundle **2026.09.15 (protocol 8)**. `edge` stays standing in every repo | link, servuo-plugins, module-uo, website, installer |
|
||||
| 9c | runicgateway.com: `platform.json`'s protocol 7 → 8 and the bundle triple, and any prose the bridge changed. Must follow 9b — `checkFacts.mjs` reads the protocol from `link`'s `main` | runicgateway.com |
|
||||
|
||||
Phase 0 exists because §4 chose to call code that can take the shard down if it is wrong, and the
|
||||
@@ -1949,4 +1949,44 @@ in the document.
|
||||
The ordering itself was not a question: `link` and `servuo-plugins` must land **together**,
|
||||
because `bundle.yml` Gate 1 refuses a sidecar/overlay pair that disagrees on protocol and
|
||||
whichever lands alone leaves a compose that cannot run. That is not a prediction — it is what
|
||||
happened at the Event System's cutover, compose run 102.
|
||||
happened at the Event System's cutover, compose run 102, and it happened again here: the overlay
|
||||
released nine minutes ahead of the sidecar and **compose run 111 refused the pair in exactly
|
||||
those terms** — *"sidecar v2.2.0 speaks 7, overlay v1.3.0 declares 8. Refusing to publish a
|
||||
bundle that would install a shard whose events the sidecar rejects with 409."* The gate is not
|
||||
a formality; it is the only thing standing between a half-landed cutover and an operator
|
||||
installing a shard that cannot talk to its own sidecar.
|
||||
|
||||
16. **What the cutover published, and the one thing that needed a hand.**
|
||||
|
||||
| Component | Release | Protocol |
|
||||
|---|---|---|
|
||||
| `link` sidecar | **v2.3.0** | 8 |
|
||||
| `servuo-plugins` overlay | **v1.3.0** | 8 |
|
||||
| `module-uo` | **v1.3.0** | — |
|
||||
| `installer` | **v0.2.0** | — |
|
||||
| bundle | **2026.09.15** | 8 |
|
||||
|
||||
`edge` stays standing in every repo.
|
||||
|
||||
The re-verify §17.15 chose — check the pairing rather than rebuild the whole rig — is what
|
||||
found the one thing that did not happen by itself. **The bundle did not recompose on its own.**
|
||||
Two compose runs failed for two unrelated reasons, and only reading both logs separates them:
|
||||
|
||||
- **Run 111 was Gate 1**, correctly refusing the mismatched pair in the window between the two
|
||||
releases. Expected, and the reason the pair lands together.
|
||||
- **Run 114 was the runner**, after both releases were out: `Set up job` took **14m33s** and
|
||||
then every subsequent step failed at **0s** with **no log blob stored** (its log route 500s).
|
||||
That is the same signature as the 2026-09-09 outage and has nothing to do with the bundle's
|
||||
content.
|
||||
|
||||
A failed compose is silent from the outside: the bundle simply stays at its previous value, so
|
||||
operators keep installing the **previous protocol** while `main` carries the new one, and
|
||||
nothing says so. `bundle.yml`'s nightly cron exists to self-heal exactly this, but that is up to
|
||||
a day of operators fetching a stale pair. Recovery is
|
||||
`POST /api/v1/repos/RunicGateway/installer/actions/workflows/bundle.yml/dispatches {"ref":"main"}`
|
||||
→ 204, and the recomposed bundle published within the minute.
|
||||
|
||||
**The lesson for any future cutover: publishing the releases is not the last step — verifying
|
||||
the bundle actually moved is.** A green release run does not imply a recomposed bundle, and the
|
||||
gap between them is invisible to everything except a direct read of `current.json` (through
|
||||
`/contents/`, never `/raw/`, which is CDN-cached for six hours).
|
||||
|
||||
Reference in New Issue
Block a user