The book, written out of the tree slice 1 proved. Four chapters in the order the
work happens: the first module in twenty minutes, the website module, the sidecar,
and the game-side plugin.
Shape, settled with the org lead:
* template/README.md stays the REFERENCE — it travels with a copied template and
CI holds it against the tree — and chapter 1 is the narration: what you should
see after each step, the state your module lands in, and the four ways it fails.
The chapter links to the checklist rather than restating it.
* chapters 3 and 4 cite link/ and servuo-plugins/ by FILE AND IDENTIFIER, never by
line. Those repositories move for their own reasons and checkLinks already
forbids commit permalinks, so a line number in this book is wrong the moment
they do. The template stays the only code quoted verbatim.
* one PR: the outline's status table and the link check are only coherent when the
whole set lands.
scripts/checkChapterPaths.js is the anti-rot half a machine can answer: every path
a chapter names in backticks must exist. None of those mentions is a markdown link,
so checkLinks never looked at them, and none is code, so nothing else did either —
renaming one template file would have left four chapters quietly pointing at
nothing. Its anchor list is STATED rather than derived from the tree, for the reason
the template's own build guard states it: a list derived from what exists cannot
fail when what exists changes, and an anchor that stops matching is a check that has
silently stopped checking. So each anchor must exist or the check fails. Eleven
tests, every "must not catch" case a span that really appears in the book.
stripFences moved to scripts/lib/markdown.js and both checks use it — shared code,
not a shared description.
CHAPTER 1 WAS RUN, NOT REASONED ABOUT. The template was copied into a real core on
edge, booted against the dev database, and every claim in "what you should see"
checked: the five log lines, /examplegame/status with its injected
<script type="module" src="/modules/examplegame/entry.js">, the chunk served
no-cache while module.json 404s, /api/v1/public/world/status, the capabilities in
/api/v1/public/modules, and the route in the merged /api/docs.json. Then the three
failures the chapter tells a reader to cause on purpose, because a chapter that
predicts the wrong debugging heuristic is worse than one that predicts none:
* an undeclared prefix -> stage `register`, "declared public/extra but never
registered it", routes 404 and absent from /public/modules;
* a table without the id prefix -> stage `schema`, at LOAD time, before mounting;
* a throwing onBoot -> after mounting, so the same route answers 503 "Module
unavailable" rather than vanishing.
All three came out exactly as written, and the messages in the chapter are that
core's own. Two small corrections fell out of the run: the log sample now shows the
real interleaving of core's three lines with the module's two, and the section on
failure adds that a module disappears from /api/v1/public/modules in every failure
case — a check that needs no login.
MODULE_SYSTEM.md 2.11.1 slice 2. Docs half: docs#146.
Co-Authored-By: Claude <noreply@anthropic.com>
184 lines
8.3 KiB
YAML
184 lines
8.3 KiB
YAML
# Gate every pull request into `main`. This repo is documentation plus a template
|
|
# module, so the checks are about whether the documentation is still TRUE rather
|
|
# than whether software works.
|
|
#
|
|
# ── What each job is really asking ───────────────────────────────────────────
|
|
#
|
|
# • `prose` — the documentation, checked as far as documentation can be. Every
|
|
# relative link resolves, and no link pins a reader to a commit snapshot of a
|
|
# document that moves. Nothing is fetched: this project's Gitea is self-hosted,
|
|
# so an HTTP check would fail on a runner without credentials and teach
|
|
# everyone to ignore red. What breaks in practice is a relative path after a
|
|
# file moves, and that is answerable offline.
|
|
#
|
|
# It also holds `template/README.md`'s rename checklist against the template
|
|
# tree, in both directions — an unlisted file that still carries the
|
|
# placeholder, and a listed file that no longer does, are both failures. That
|
|
# checklist is the only instruction a reader has for the first thing they do
|
|
# with the template, and it is prose, so it rots the way prose does.
|
|
#
|
|
# And it checks that every path the book names in backticks still exists. The
|
|
# chapters teach out of `template/`, none of those mentions is a markdown link,
|
|
# and nothing else in this repo would ever look at them — so renaming one
|
|
# template file would leave four chapters quietly pointing at nothing. That is
|
|
# the cheap half of "is the book still true"; the other half is a reviewer's.
|
|
# All three checks in `scripts/` have their own unit tests, run in the same job.
|
|
#
|
|
# • `template` — the interesting one, and the anti-rot mechanism of the whole
|
|
# repo (MODULE_SYSTEM.md §2.11.1 d2). It clones CORE at the ref pinned in
|
|
# `ci/core-ref.json` and asks three things:
|
|
#
|
|
# 1. does `template/module.json`'s `coreApi` still EQUAL that core's
|
|
# `MODULE_API_VERSION`? Equality, not "satisfies" — a range check would
|
|
# stay green across a contract bump, and green would then mean "the
|
|
# template still loads" when we need it to mean "someone has re-read the
|
|
# book since the contract changed". This failing is the system working.
|
|
# 2. does the template still build? A kit whose examples do not compile is
|
|
# worse than no kit, because the reader trusts it first.
|
|
# 3. do the template's own boundary guards still pass? They are the same
|
|
# checks a real module ships (MODULE_API.md §5.1, §3.6), and the template
|
|
# is what teaches a newcomer that they exist.
|
|
#
|
|
# ── The guard, and why the template job can report green with no template ────
|
|
#
|
|
# Slice 0 is this scaffold; the template lands in slice 1. Rather than leave the
|
|
# repo ungated in between, or land a workflow that red-Xes every docs PR until
|
|
# there is something to build, the template steps are conditional on
|
|
# `template/module.json` existing. Before it lands the job prints why it did
|
|
# nothing; the moment the file appears the job arms itself with no edit here.
|
|
# Same guard Module-uo#1 used through its own planning phase.
|
|
#
|
|
# Enforcement (one-time, in the Gitea UI):
|
|
# Repository Settings → Branches → Branch Protection (rule for `main`)
|
|
# • Enable Status Check
|
|
# • Status check patterns: PR Checks / *
|
|
# Gitea only lists a context in its dropdown after it has reported once, so let
|
|
# this run on one PR first. The glob keeps matching as jobs are added.
|
|
#
|
|
# Runner: the shared self-hosted `ubuntu-latest` runner. Node only — no database,
|
|
# no Docker socket.
|
|
|
|
name: PR Checks
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
concurrency:
|
|
group: pr-checks-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
NPM_CONFIG_FETCH_RETRIES: 5
|
|
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: 20000
|
|
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: 120000
|
|
|
|
jobs:
|
|
prose:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
# No dependencies on purpose — every step in this job has to run on a clone
|
|
# with nothing installed, which is also how a reader will run them.
|
|
- name: Check every link in the book
|
|
run: node scripts/checkLinks.js
|
|
|
|
- name: Check the rename checklist against the template
|
|
run: node scripts/checkRenameSites.js
|
|
|
|
- name: Check every path the book names still exists
|
|
run: node scripts/checkChapterPaths.js
|
|
|
|
# The checks, checked. A check that has never been shown to fail is a check
|
|
# nobody knows the state of — and these gate the instructions for the first
|
|
# thing a reader does. Named file by file rather than `node --test scripts/`:
|
|
# directory mode is not portable across the Node versions people run this on.
|
|
- name: Test the checks themselves
|
|
run: |
|
|
node --test scripts/checkRenameSites.test.js
|
|
node --test scripts/checkChapterPaths.test.js
|
|
|
|
template:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Is there a template yet?
|
|
id: guard
|
|
run: |
|
|
if [ -f template/module.json ]; then
|
|
echo "present=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "present=false" >> "$GITHUB_OUTPUT"
|
|
echo "No template/module.json — the template lands in Phase 5 slice 1."
|
|
echo "The steps below are skipped until it does; see this file's header."
|
|
fi
|
|
|
|
# Anonymous HTTPS, and a full clone rather than a shallow one: the pin is a
|
|
# commit sha, and `--depth 1` can only fetch a branch tip.
|
|
- name: Clone core at the pinned ref
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: |
|
|
REPO=$(node -p "require('./ci/core-ref.json').repo")
|
|
REF=$(node -p "require('./ci/core-ref.json').ref")
|
|
echo "core: $REPO @ $REF"
|
|
git clone --quiet "$REPO" .core
|
|
git -C .core checkout --quiet "$REF"
|
|
|
|
- name: Is the kit still written against this core? (MODULE_SYSTEM.md §2.11.1 d2)
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: node scripts/checkCoreApi.js --core .core
|
|
|
|
- name: Install the template's deps
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: |
|
|
npm ci --prefix template/server
|
|
npm ci --prefix template/client
|
|
|
|
- name: Check the template's module boundary (MODULE_API.md §5.1)
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: npm run check:imports --prefix template/server
|
|
|
|
# The build comes before the externals check because that check reads the
|
|
# BUILT chunk: whether `import { useState } from 'react'` became core's React
|
|
# or a bare specifier no browser can resolve is decided by vite.config.js, and
|
|
# is invisible in source.
|
|
- name: Build the template's client chunk
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: npm run build --prefix template/client
|
|
|
|
- name: Check the built chunk's externals (MODULE_API.md §3.6)
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: npm run check:externals --prefix template/client
|
|
|
|
- name: Run the template's tests
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: npm test --prefix template/server
|
|
|
|
# After the build, and that ordering is the point: two of the client tests
|
|
# read the BUILT chunk and SKIP when there is none. Run before the build,
|
|
# this job would report green while asking nothing about the artifact that
|
|
# ships — which is exactly how the first real module's two artifact tests sat
|
|
# green and inert.
|
|
- name: Run the template's client tests
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: npm test --prefix template/client
|
|
|
|
# The committed OpenAPI fragment, regenerated and compared. Core merges that
|
|
# file verbatim into its own spec, so a stale one documents a URL surface the
|
|
# module does not serve — and nothing at runtime will ever say so.
|
|
- name: Check the template's OpenAPI fragment is current (MODULE_API.md §2.8)
|
|
if: steps.guard.outputs.present == 'true'
|
|
run: npm run check:swagger --prefix template/server
|