docs(modules): close Phase 5 — the acceptance run, and the page shell it found
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>
This commit is contained in:
139
modules/kit-acceptance.md
Normal file
139
modules/kit-acceptance.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Integration Kit — the acceptance run
|
||||
|
||||
**Phase 5's fifth decision** ([`../website/MODULE_SYSTEM.md`](../website/MODULE_SYSTEM.md) §2.11.1 d5).
|
||||
The [Integration Kit](https://gitea.whitlocktech.com/RunicGateway/Integration-kit) is finished when
|
||||
someone outside this project builds a working module for a new game by following it alone. That
|
||||
cannot be manufactured, so this is the **proxy**: a cold agent given the kit and the documents it
|
||||
links to — never core's source, never `module-uo` — asked to build a module for a second game, whose
|
||||
result was then installed into a real core and loaded in a browser.
|
||||
|
||||
Run **2026-08-12**, against `Integration-kit` at slice 2 (PR #3) and core at website `edge` 1b692bf.
|
||||
|
||||
> The verdict below does **not** clear the draft banner. Decision 32 makes that a human's to remove,
|
||||
> and this exercise is aimed at the same target with a machine. It is worth what it found, not more.
|
||||
|
||||
---
|
||||
|
||||
## 1. Verdict
|
||||
|
||||
> **Yes, with caveats.** A competent developer who had never seen this platform can build a working
|
||||
> module from this kit alone.
|
||||
|
||||
It did, in one pass, without reading core's source and **without opening three of the four normative
|
||||
documents** — `MODULE_SYSTEM.md` and both `link/` specs were never touched, correctly, since the
|
||||
deliverable was a website module with no sidecar. `MODULE_API.md` was opened only at the end, to
|
||||
audit the kit rather than to build against it.
|
||||
|
||||
Its own summary of what carried the run:
|
||||
|
||||
> What raises this to "yes" rather than "yes but barely" is the template. The four suites plus the
|
||||
> two guard scripts constitute a machine-checkable specification of a valid module, and they are
|
||||
> better documentation than any chapter could be — every rule they enforce comes with a comment
|
||||
> explaining the bug that motivated it and how it looked when it went wrong.
|
||||
|
||||
And the caveat that mattered most, which is a process one rather than a comprehension one: **the
|
||||
first command the kit tells a reader to run fails on a pristine copy** (F1).
|
||||
|
||||
## 2. Method, and the one thing it could not enforce
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Given** | `Integration-kit` at slice 2, tracked files only (so the template's chunk had to be built, as a reader's would); local mirrors of the five documents the kit links out to, at the same relative paths, because the kit's links point at a Gitea host a credential-less agent cannot reach |
|
||||
| **Denied** | core's source, `module-uo`, the web beyond the npm registry |
|
||||
| **Asked for** | a trivial but real module for **Rust** — valid `module.json`, a public route backed by its own table, a prebuilt chunk rendering it, a purge fragment, and whatever guards the kit says a module needs, passing |
|
||||
| **Told to log** | findings *as they happened*, into a running file, on the grounds that being stuck is worth more than remembering being stuck |
|
||||
| **Model** | Claude Opus 5 |
|
||||
|
||||
Rust was chosen deliberately over an unfamiliar game: it is the subject of
|
||||
[`rust-dryrun.md`](rust-dryrun.md), which the kit's front page names as the first thing to read, so
|
||||
the run also measures whether that document is followable.
|
||||
|
||||
**The honest limitation.** A subagent on this machine inherits the workspace as its shell's default
|
||||
directory, so isolation is a clean tree plus explicit instructions plus a self-reported list of every
|
||||
path read — not a sandbox. The agent reported no read outside its root and disclosed one unrelated
|
||||
process slip unprompted, which is some evidence of good faith but is not proof. A future run should
|
||||
put the agent somewhere it *cannot* reach core.
|
||||
|
||||
## 3. What was built, and what it proved
|
||||
|
||||
`module-rust` — id `rust`, `coreApi ^1.4.0`, two public routes over two tables (`rust_servers` +
|
||||
`rust_wipes`, FK, child-first purge), a 8.0 kB chunk with two pages and a nav row. **44 server + 18
|
||||
client tests, none skipped**; `check:imports`, `check:swagger`, `check:externals` all green.
|
||||
|
||||
Installed into a real core on `edge` and booted **against an empty database** — the condition Phase 4
|
||||
slice 3 established as the only one that can see a schema-ordering defect:
|
||||
|
||||
- loader scanned it, schema fragment replayed (4 statements), `onBoot` ran, state `started`
|
||||
- `/api/v1/public/modules` published it; both routes served correct JSON
|
||||
- the chunk registered under enforced `script-src 'self'` with **zero CSP reports and zero console
|
||||
errors**, and a **deep-linked param route rendered on a cold load** — the `readyState` ordering
|
||||
path [`../website/MODULE_API.md`](../website/MODULE_API.md) §7.7 exists for
|
||||
|
||||
Two things it did *not* do are worth recording as evidence the contract held for a stranger: it never
|
||||
reached into core (`check:imports` green), and it wrote no connection to a game server — in fact it
|
||||
wrote a test asserting that about itself, having read in chapter 2 that §2.7 is the one rule with no
|
||||
CI behind it.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
Eight from the agent, one from the browser. **B1 could not have been found by the agent** — it had no
|
||||
core to render against, which is the structural gap this two-stage method exists to cover, and it is
|
||||
the most valuable single result of the run.
|
||||
|
||||
| # | Class | Where | What | Disposition |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **B1** | **WRONG** | `MODULE_API.md` §3.4 | A module page built exactly as the kit teaches **renders outside the site**. `PublicLayout` gives the chrome, not the body; core's nine public pages each wrap content in `shell-… page-body`, whose class names appear in no contract. Content at x=0, no padding, footer riding up under it | **Fixed** — `PublicLayout` takes an opt-in `shell` prop, `MODULE_API_VERSION` **1.5.0** (website#148); §3.4 rewritten; template and chapter 2 updated |
|
||||
| F1 | WRONG | `template/README.md`, `book/02` | `npm run check:swagger` **fails on a pristine, unedited copy** on Windows: the fragment compares byte-for-byte and a default Windows clone is CRLF. The message asserts a false cause — "the routes or their annotations changed" | **Fixed** — `template/.gitattributes` (`eol=lf`) *and* the comparison normalises line endings |
|
||||
| F2 | WRONG | `rust-dryrun.md` §1 | `coreApi: "^1.3.0"` while everything else said 1.4.0 — in the only complete `module.json` in the kit's reading path | **Fixed** — `^1.5.0`, as a dated correction |
|
||||
| F3 | MISSING | `template/README.md` | The rename checklist claims to name every file carrying a placeholder. Both `release.yml` flavours carry `gitea.example.com` and `your-org/your-module` under a literal `# CHANGE THESE` and are absent — and `checkRenameSites.js`'s pattern cannot match them, so CI is silent **by construction** | **Fixed** — rows added, pattern widened |
|
||||
| F4 | FRICTION | `template/.gitea/workflows/release.yml` | The bundle's include list is hardcoded (`for d in boot.js core.js index.js db model router`). Add any top-level directory under `server/` and it is **silently dropped from every release**; the post-check only resolves the five paths in `module.json` | **Fixed** — inverted to an exclusion list |
|
||||
| F5 | AMBIGUOUS | `world.router.js`, `book/02` | The comment warns that a backtick in a single-quoted `#swagger` description ends the string early — but the same file uses two backtick spans and they survive verbatim | **Fixed**, and sharpened: see below |
|
||||
| F6 | AMBIGUOUS | `template/` | No `.gitignore`. The kit's own covers the template's paths, but a reader who copies the directory and runs `git init` inherits nothing — `node_modules/` included | **Fixed** — `template/.gitignore` ships |
|
||||
| F7 | WRONG | `MODULE_API.md` §3.4 | "The kit is those **seven** members" over a table publishing **eight** exports (five rows; `PageState` contributes three). Propagated faithfully into the template's comment and core's `shared.js` — the kit's never-re-specify discipline carrying a miscount *out* of the contract | **Fixed** in all three |
|
||||
| F8 | note | — | Three of the four normative documents were never opened, and never needed to be | Recorded above |
|
||||
|
||||
### F5 is worse than reported, and the correction is the useful part
|
||||
|
||||
Measured directly rather than reasoned about. A **backtick** inside a single-quoted `#swagger`
|
||||
description is harmless — it survives verbatim into the fragment. A **double quote** is not, and it
|
||||
does not fail the way the comment predicts:
|
||||
|
||||
```
|
||||
// #swagger.summary = 'A "quoted" world status'
|
||||
→ "summary": "A \"" ← silently truncated
|
||||
→ swagger-autogen prints Success, and swaggerFragment.js's error capture sees nothing
|
||||
```
|
||||
|
||||
The template told a reader that the tool would tell them. For the one case that actually bites, it
|
||||
does not: the annotation is truncated, the generator reports success, and the only signal is a
|
||||
`check:swagger` diff whose message blames the routes. **A chapter that predicts the wrong debugging
|
||||
heuristic is worse than one that predicts none** (§2.11.1) — restated here for a comment inside the
|
||||
template, which no check in that repo can see either.
|
||||
|
||||
### Two things the run recommends that are not defects
|
||||
|
||||
- **"Run every check on the untouched copy before you edit anything."** The agent did this off its
|
||||
own bat, and it is what found F1; without a baseline it would have blamed its own edits. Chapter 1
|
||||
now says so.
|
||||
- **The template should ship the §2.7 self-check.** Chapter 2 correctly says the no-game-connection
|
||||
rule has no CI behind it, because an outbound socket is not statically detectable *in general*. But
|
||||
a module can make a decidable claim about **itself**, and the agent wrote that check, with a
|
||||
positive control (it catches a real RCON dial) and a negative one (prose about RCON does not trip
|
||||
it). It called it "the check I would most want the template to ship", noting that for Rust
|
||||
`new WebSocket(rconUrl)` in `boot.js` is ten lines away. Adopted into the template, from its code.
|
||||
|
||||
## 5. What this does and does not settle
|
||||
|
||||
**Settles:** the kit is sufficient for the website-module half of the job. Someone can start from
|
||||
nothing and finish with a module that loads into a real core, serves, renders in the site's chrome,
|
||||
and passes every rule the contract has a check for — without reading core.
|
||||
|
||||
**Does not settle:** the sidecar and the game-plugin halves. This module has neither, so chapters 3
|
||||
and 4 were never exercised and remain proved only by the fact that they describe systems that exist
|
||||
(`link/`, `servuo-plugins/`). Nor does it settle anything about a *person's* experience — an agent
|
||||
does not get frustrated, does not skim, and does not give up, which are three of the failure modes
|
||||
the banner is really about.
|
||||
|
||||
**So the banner stays.** The next run of this exercise should either be a person, or an agent placed
|
||||
somewhere it genuinely cannot reach core's source, and should be asked for a module **with** a
|
||||
sidecar.
|
||||
Reference in New Issue
Block a user