Slice 3 of Phase 5 (MODULE_SYSTEM.md 2.11.1), and the phase's last slice.
docs/modules/kit-acceptance.md is decision 5's deliverable: a cold agent given the
Integration Kit and the documents it links to — never core's source, never
module-uo — built a working module for a second game, which was then installed
into a real core and taken through MODULE_API.md 7.7's browser smoke. Verdict
recorded whichever way it went, and it went **yes, with caveats**: one pass, no
core source, and three of the four normative documents never opened.
The finding that justifies the two-stage shape is the one the agent structurally
could not reach, because it had no core to render against. A module page built
exactly as the kit teaches renders OUTSIDE the site: PublicLayout supplies the
chrome and not the body, and the `shell-... page-body` wrapper every core public
page writes for itself is two class names that appear in no contract. That is
3.4's own stated failure — "a module page that does not look like the site it is
installed in" — reached by following 3.4.
Fixed in core rather than documented at the reader, so the class names stay
core's private business and the theming workstream keeps its freedom to rename
them: PublicLayout takes an opt-in `shell` width, MODULE_API_VERSION 1.5.0
(website#148, merges first).
- MODULE_API.md 1.1: 1.5.0's entry, and a new bump-table row — adding an
OPTIONAL prop or argument is minor. "A member's signature changes" is major
because a call already written changes meaning, and an optional prop changes
none; the table now says what it means rather than leaving it to be argued.
- MODULE_API.md 3.4: the shell prop, why a module names a width and never a
class, and the eight-vs-seven miscount the run also turned up — the kit had
faithfully carried it out of the contract into the template, which is the
never-re-specify rule working exactly as designed on a wrong input.
- rust-dryrun.md: coreApi ^1.3.0 -> ^1.5.0, as a dated correction per decision 33.
It is the only complete module.json in the kit's reading path and nothing
checks a JSON block inside a Markdown file, which is the reusable half.
- MODULE_SYSTEM.md 2.11.1: slice 3 recorded, plus the third finding worth
generalising — a check whose failure message asserts a diagnosis has to be
right about it. `check:swagger` failed on a pristine template on Windows
(CRLF) while blaming the routes, green on the Linux runner forever.
- Decision 34: core owns the page body as well as the chrome.
The banner does not come off. Decision 32 makes that a person's to remove, this
run exercised the website-module half only (the module has no sidecar, so
chapters 3 and 4 were never tested), and an agent does not skim or give up.
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 4 closed; this is the plan for Phase 5, the Integration Kit, settled with
the org lead on six decisions.
MODULE_SYSTEM.md
- §2.11.1 (new): Phase 5's shape — measured starting state, the six decisions,
the four slices, the acceptance mechanism.
- §2.11: layer 2 rewritten. The sidecar is the default and only path to a game,
and it is a non-blocking dumb forwarder that persists before it forwards. A
game that already ships a remote-control surface gets a thin sidecar, not none.
- §2.10: a MODULE_API_VERSION bump now carries a pass over the kit.
- Part 3: decisions 28–33.
- The Phase 5 line in Part 2's phase list: STARTED 2026-08-12.
MODULE_API.md
- §2.7 gains one prohibition: a module does not open a connection to a game
server from the website process — a game socket, RCON, a query port, an
engine admin API — with the three reasons it exists (the website is the
internet-facing process, the sidecar owns the durable copy, neither side can
stall the other). Normative prose with no CI behind it, stated so that every
second module does not re-decide it.
- §1.1: the 1.4.0 entry. No member changed; minor deliberately.
modules/rust-dryrun.md
- §2 "Talking to the game" rewritten from "No sidecar" to a thin RCON sidecar,
with a dated Correction note recording what the exercise originally concluded
and why it was overruled. Corrected in view rather than silently rewritten.
Pairs with website#… (the 1.4.0 bump, into edge); either order.
Co-Authored-By: Claude <noreply@anthropic.com>
Records the slice that closes the extraction, and adds the two documents phase 3
owed: docs/modules/uo/ and the module-rust dry run.
**The slice found an obligation neither repo had built.** MODULE_API.md §2.8 and
§6.1a settle the OpenAPI fragment in detail — a module ships one, core merges the
fragments of started modules into /api/docs.json. Neither half existed, so the 72
URLs module-uo serves were in no spec at all. §2.8 and §6.1a now record what was
built, including the four things settled while building it: the filename is fixed
rather than declared, a module namespaces what it DEFINES and references core's
shared schemas by core's name, the generator derives its prefixes from the
module's own register() call, and swagger-autogen's diagnostics have to be
captured because it reports a broken annotation and then prints Success.
**§5.3 gains the design decision the frozen manifest actually made:** it is a
SUBTRACTION, not a prefix filter. Generating the manifest without the module and
then with it answers "what does the module serve" AND "did core lose anything",
and the second is the one §1.2 promises to the shipped Android app. A module that
shadowed a core route cannot appear as an addition anywhere.
**BACKEND_DESIGN.md §4.0.1** is new: /api/docs.json is assembled per request, the
two generated artifacts are core's alone, and the route count was still 228.
**docs/modules/** is new, per §2.10 (module documentation aggregates here, not in
module repos): docs/modules/uo/README.md orients a reader on what module-uo
serves, owns and needs from an operator, and links out to the feature docs that
already existed rather than restating them.
**docs/modules/rust-dryrun.md** is phase 3's fourth acceptance criterion. A
written, deliberately unimplemented module for Rust — chosen because it wipes
monthly, runs several servers rather than one shard, identifies by Steam, and
ships RCON so there is no sidecar to write. The contract generalises: same
manifest, same seven registration calls, same schema rules, and six of the UI
kit's seven members wanted by a game with nothing in common with the one the kit
was curated from.
It found one real gap — **a module cannot register an identity provider**, and
"Sign in with Steam" is what a Rust community expects. Recorded as the first
candidate for a future MODULE_API_VERSION bump rather than bolted on: an identity
provider participates in session creation, and §2.7's link-only SSO policy has to
survive it.
Also: website-README.md refreshed from the repo (it was several changes stale),
and three settled decisions added (18-20).
Pairs with Module-uo#6 and website#141.
Co-Authored-By: Claude <noreply@anthropic.com>