feat(kit): the engagement contract, taught and built (cutover 5 of 7)
All checks were successful
PR Checks / prose (pull_request) Successful in 7s
PR Checks / template (pull_request) Successful in 31s

The kit was pinned to website 963d734 -- MODULE_API 1.6.0, the Teams cutover --
and the platform is on 1.9.0. Three registrations and two calls arrived in
between, and a reader building against this book would have found no mention of
any of them: a module can now declare what its game can announce, and never who
is told.

Moving `ci/core-ref.json` is the mechanism for exactly this. The pin is now
66bb3b9a (website `main`, the engagement cutover) and `template/module.json`
declares `^1.9.0`.

What chapter 2 gained, under "Telling core something happened":

  * a TRIGGER is a payload contract, not a notification stream -- the two share
    one id namespace and are constantly confused;
  * `ceiling` is required, has no default, and is a CONTAINMENT tree rather than
    a size ladder (a `staff` ceiling does not permit `owner`);
  * an AUDIENCE resolver returns user ids and nothing else, resolves to NOBODY
    on failure, and takes CONSTANT params -- the constraint worth knowing before
    you design around it;
  * templates re-ensure per seedVersion, rule groups are offered ONCE per group
    key, so a rule appended to an existing group reaches fresh installs only;
  * `ctx.events.emit` binds the owner and is fire-and-forget; `ctx.inbox.push`
    is the direct write, for when there is nothing for an operator to decide.

The template builds all of it: one trigger, one audience over the clan roster it
already had, one seeded body and one seeded rule group, and an emitter in
`boot.js` that fires on the TRANSITION rather than on the poll. Seven new tests,
including the audience that resolves to nobody when its query throws.

Three claims were wrong and are corrected here rather than shipped:

  * core validates `subjectKey` against the declared variables and refuses the
    module; the draft taught a cooldown keyed on `undefined`, which the check
    exists to prevent and a reader will never see.
  * `emit` throws OUTSIDE production and only drops-and-logs inside it. Teaching
    the second half alone leaves a developer meeting a throw the book says
    cannot happen.
  * the seeded body itself was malformed -- heading `level: 2` where the block
    registry takes 'h2', and no block ids at all.

The third is the one worth keeping: `registerEngagementSeeds` checks that
`blocks` is a non-empty array and stops, so that body would have registered,
seeded, and failed the first time an operator opened it. Found by running the
template's `register()` through core's real registry at the pinned ref -- which
CI does not do, and cannot: the template job checks the version and runs the
template against fakes. A fake accepts what core refuses. The gap is now named
in the chapter, beside the code, and in the pin's own comment, and the rule that
bit has a test that fails on it.

Also: `checkLinks` skipped `.core/`. Bumping this pin means cloning core into
that directory first, and the walk then reported nine broken links in someone
else's README. CI never saw it -- the clone happens in the `template` job and
the check runs in `prose` -- so it was a failure only a person could meet.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-01 12:57:55 -05:00
parent 39736f8448
commit a8fa524263
12 changed files with 597 additions and 27 deletions

View File

@@ -1,34 +1,47 @@
{
"repo": "https://gitea.whitlocktech.com/RunicGateway/website.git",
"branch": "main",
"ref": "963d734dcc09580a7d8bb676370b4faf9b8727b2",
"ref": "66bb3b9a3fad01112c06f32d931c9bae56d22de6",
"why": [
"The core this kit is written against, pinned to a commit rather than a branch.",
"This one is the Teams cutover, the commit MODULE_API_VERSION 1.6.0 reached",
"`main` on, and 1.6.0 is what template/module.json declares. It moved here from",
"the 1.5.0 bump because Teams expanded the contract the book teaches: the",
"template now registers a Team provider and declares slots for core to fill,",
"and both are members that exist only at this ref and later.",
"This one is the engagement cutover, the commit MODULE_API_VERSION 1.9.0 reached",
"`main` on, and 1.9.0 is what template/module.json declares. It moved here from",
"1.6.0 (the Teams cutover) because engagement expanded the contract the book",
"teaches by three registrations and two calls: a module now declares what its",
"game can announce and never who is told.",
"",
"Moving this pin is the moment someone re-reads the chapters: CI asserts the",
"version template/module.json declares still equals this core's",
"MODULE_API_VERSION, so a contract bump turns this repo red on purpose",
"(MODULE_SYSTEM.md 2.11.1 d2, 2.10).",
"(MODULE_SYSTEM.md 2.11.1 d2, 2.10). Note what that means in the other",
"direction, because it is easy to misread as a safety net: the check clones",
"THIS ref, so a core that has moved past it does not turn the repo red on its",
"own. Nothing goes red until someone moves the pin. Between cutovers the kit is",
"not wrong, it is DATED - and this file is where the date is written down.",
"",
"That mechanism earned its keep this time. Writing the chapters against 1.6.0",
"found that core's inverted-slot fills named three of module-uo's slots",
"literally, so the direction worked for that one module and silently did",
"nothing for any other game - an empty page with nothing logged. That is the",
"exact class of thing a book written for an audience outside this org is meant",
"to catch, and it was fixed in core before this pin moved.",
"The mechanism earned its keep again here. Writing chapter 2's engagement",
"section against 1.9.0 found that the seeded body a module ships is the one",
"thing registerEngagementSeeds does not validate - it checks that `blocks` is a",
"non-empty array and stops - so the template's own example body had a heading",
"level of 2 where the block registry takes 'h2', and no block ids at all. It",
"would have registered, seeded, and failed the first time an operator opened it.",
"Caught by running the template's register() through core's real registry at",
"this ref, which is what a re-read is for; both the fix and the gap are now in",
"the chapter and beside the code.",
"",
"That gap is also why this file's own instruction is not enough on its own. The",
"template job builds and tests the template against fakes and checks this",
"number; it does not load the module into core. A declaration a fake accepts",
"and core refuses would ship green, so a pin move is a run against a real core,",
"not just an edit here.",
"",
"The branch said `edge` until 2026-08-12, when the module system cut over and",
"that branch was deleted (MODULE_SYSTEM.md 2.9). Teams cut a second `edge` and",
"this pin skipped it entirely: the kit is written against what shipped, never",
"against what is in flight. Nothing in CI reads the branch field - it clones",
"the repo and checks out the sha - which is why a wrong label here would sit",
"unnoticed. It is for the person deciding whether a newer core is worth",
"re-reading the book for.",
"that branch was deleted (MODULE_SYSTEM.md 2.9). Two later workstreams cut an",
"`edge` of their own and this pin skipped both: the kit is written against what",
"shipped, never against what is in flight. Nothing in CI reads the branch field",
"- it clones the repo and checks out the sha - which is why a wrong label here",
"would sit unnoticed. It is for the person deciding whether a newer core is",
"worth re-reading the book for.",
"",
"Same convention as Module-uo's ci/core-ref.json, deliberately - one file, one",
"sha, reviewable in a diff."