docs(events): the Event System record — the cutover step 16b missed (edgemain) #232

Merged
whitlocktech merged 46 commits from edge into main 2026-09-10 02:55:11 +00:00
Showing only changes of commit 1a7d2da4dd - Show all commits

View File

@@ -1879,9 +1879,110 @@ core, docs, then the kit's re-pin and `runicgateway.com`.
> a refusal releases its key, with the rule that pays for it written down — *do not answer `*.error` > a refusal releases its key, with the rule that pays for it written down — *do not answer `*.error`
> after changing the world*. > after changing the world*.
> **16b CUT OVER (2026-09-09/10) — six steps, and core before the module.** The protocol pair
> (`link#40` + `servuo-plugins#26`) is ONE step, not two: `bundle.yml`'s Gate 1 reads the protocol
> number out of both released artefacts and refuses a pair that disagrees, so whichever lands first
> leaves a compose that cannot run. Then core (`website#199`), the module carrying its own re-pin
> (`Module-uo#34`), the app (`Android-app#46`), the kit's re-pin (`Integration-kit#11`), and docs.
>
> **Core lands before the module**, which departs from the sentence above and matches what the
> engagement cutover actually did: `Module-uo`'s `ci/core-ref.json` has to name a website `main` sha
> carrying MODULE_API 1.10.0, and that sha does not exist until core has landed. Four decisions, all
> as recommended (org lead, 2026-09-09): that order; the app merges with **no `v*` tag**, so no APK
> was cut; the re-verify walks the whole delivery path; and **`edge` stays standing** in every repo
> rather than being deleted as the module-system cutover deleted its own.
>
> Releases cut: sidecar **v2.2.0**, overlay **v1.2.0**, bundle **2026.09.10 (protocol 7)**. `website`
> never releases. `MODULE_API_VERSION` and `EVENTS.md` are untouched by this leg — the cutover moves
> no contract.
>
> **`Integration-kit#10` had been merged early**, on 2026-09-08, though it was written to be held —
> so the kit's `main` was red on `checkCoreApi` for two days. That is what step 5 closes, and it is
> the reason the re-pin is a repair rather than only a date.
>
> #### The outage, and what it did not break
>
> Gitea was unreachable for about ten minutes in the middle of the window (Cloudflare 1033/530) and
> killed **both** release runs. `link`'s built every binary and wrote `SHA256SUMS`, then died pushing
> the tag: `fatal: unable to access … The requested URL returned error: 530`. `servuo-plugins`' died
> inside `Set up job` after 11m52s with no step ever executing — which is why that job's log route
> answers 500 while its predecessor's serves fine: **there is no log blob, and that absence is
> evidence.** No tag was pushed either time, so the orphaned-tag failure mode did not occur, and
> re-running both by `workflow_dispatch` published them. The first to land left the pair mismatched
> and compose run 102 failed exactly as the PRs predicted; the second dispatched it again and 103
> composed. `link`'s `rust-gates` reds on three earlier PRs were `curl: (6) Could not resolve host:
> sh.rustup.rs` inside the runner — infrastructure, not code, on all four counts.
>
> **A job's log IS readable on this instance, through the web route rather than the API:**
> `/{owner}/{repo}/actions/runs/<n>/jobs/<j>/logs` with an API token, served as `text/plain`; step
> statuses come from the UI's own POST endpoint with a `_csrf` cookie. Every earlier phase diagnosed
> CI by reproducing jobs locally, on the belief that logs were unreachable. They are not, and reading
> one is what turned four red X's into four known causes in about ten minutes.
>
> #### A seventh defect, red on every events PR since Phase 10
>
> `website`'s `server-tests` job had been failing since `#192` — eight PRs, every one reporting
> `# fail 1`, always **the same single test**, so nothing else was ever hiding behind it. The
> workstream merged over it eight times.
>
> `events/announce.js` asked `Intl.DateTimeFormat('en-GB', { …, hour12: true })`, and **that is not
> the same request as a 12-hour clock.** For a locale whose default cycle is h23 — `en-GB` is one —
> Node 20 resolves `hour12: true` to **`h11`**, whose hours run 011, so midnight renders `0:00 am`;
> Node 22 and later resolve it to `h12` and it renders `12:00 am`. **Same ICU (78.2) on both sides**,
> so this is V8's ECMA-402 behaviour and not locale data — no amount of matching the runner's locale
> would have found it.
>
> The image ships `node:20-alpine` and CI runs Node 20, while a dev machine is newer. So the mail
> every real recipient got said **"0:00 am"** beside a schedule editor saying "12:00 AM" — one
> instant, two spellings, the exact contradiction that option was added to prevent — and it rendered
> correctly in front of everyone who reviewed it. Fixed to `hourCycle: 'h12'` (`website#200`), which
> is the form `recurrence.js` had already adopted for the mirror-image case (`h23` **rather than**
> `hour12: false`); `announce.js` was the last `hour12` in either repo.
>
> **The rule: `hour12` is a request about a locale's preference, `hourCycle` is a request about the
> clock. Ask for the clock.** And the test now says so out loud, because it can only fail on Node 20:
> a green run on a dev machine is not evidence, and CI is what holds that line.
>
> #### The re-verify, from artefacts an operator would download
>
> This is the leg 16a could not do — a locally built bundle cannot go through core's module installer,
> which is https-only with a host allowlist.
>
> | | |
> |---|---|
> | installer | released `v0.1.1` binary, checksum matched against the release's own `SHA256SUMS` |
> | bundle | resolved **2026.09.10, protocol 7**; both component checksums verified by the installer |
> | overlay sync | a **first install** into a stock 57.4 tree — `add=30 change=1 unchanged=0` |
> | script build | `0 Warning(s) 0 Error(s)` — the released overlay compiles on a stock tree, which no release had ever been asked to prove |
> | shard boot | `[Bridge] enabled=True … adminWrite=True … events=True`, then `connected to 127.0.0.1:7788` |
> | sidecar | `server.hello` for **208,568 items / 42,871 mobiles**; `x-uolink-version: 7` |
> | event plane | `lease.list.ok` (config **and** targeted property leases), `item.catalog.ok` with its bounds, `GET /world/<run>` an empty list rather than a 404 |
> | core | released `main` on a throwaway database, `capabilities: ["events"]` on `/public/version` |
> | one event | published, run, **`completed` / `health: ok`**, results published |
> | the page | `/site/events` reads *"Everything scheduled, live and recently finished"* and lists a run that finished two minutes earlier |
>
> The last row is 16a's calendar fix holding on `main`: before it, a run that had already started or
> finished was absent and the page rendered `entries: []`.
>
> **A fresh `Bridge.cfg` still ships `EventsEnabled=false` and `AdminWriteEnabled=false`** — the
> operator's real first-boot state, and the released config confirms it rather than a working tree's.
>
> One thing checked and deliberately **not** reported as a defect: a **cancelled** run appears on the
> public calendar. It is meant to. The entry carries its own `status`, and the page renders a past
> cancelled run as **"Did not happen"** — the honest label, not a silent omission.
>
> **The one leg still owed is the module's own install.** `Module-uo#34` cuts the module release, and
> until that release exists there is no bundle for core's installer to fetch — the whole point of the
> leg is that the module arrives over https from a Gitea release rather than being copied into
> `MODULES_DIR` by hand. Everything above stands without it: the shard half is entirely released
> artefacts, and core is released `main`. Run it as `POST /admin/modules/install` against the
> published module tarball once step 3 lands, on the rig described here.
**Two documents that are cutover-window work by construction.** **Two documents that are cutover-window work by construction.**
- **`runicgateway.com`** — `checkFacts` reads `main`, so any claim about events is unverifiable until - **`runicgateway.com`** — `checkFacts` reads `main`, so any claim about events is unverifiable until
the cutover lands. Same 12a/12b split the engagement workstream needed. the cutover lands. Same 12a/12b split the engagement workstream needed. **16b landed it**, so both
of these are now unblocked: `main` carries the engine, the module and the app, and the bundle triple
the site quotes is sidecar **v2.2.0** / overlay **v1.2.0** / bundle **2026.09.10**.
- **`.profile`** — the org landing page is updated when the *shape* of the project changes, which a new - **`.profile`** — the org landing page is updated when the *shape* of the project changes, which a new
subsystem is. subsystem is.