# 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.

**Every finding was re-derived before it was acted on**, and that was not ceremony: one of them (F3)
was wrong in a detail, and another (F5) turned out to understate the problem in a way that changed
the fix. A report from a run like this is evidence, not a verdict — the same standard the rest of
this plan applies to a passing test suite.

| # | 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. `.gitea/workflows/release.yml` carries `gitea.example.com` and `your-org/your-module` under a literal `# CHANGE THESE` and is absent — and `checkRenameSites.js`'s pattern cannot match them, so CI is silent **by construction** | **Fixed** — row added, pattern widened. *Corrected: the report said both workflow flavours; only the Gitea one is affected, since GitHub supplies `GITHUB_REPOSITORY` and friends* |
| 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.
