Skip to content

v0.88.1 — v0.88.0 never deployed: a contract test with a fixed roster

  • CI was red on main at v0.88.0, and a push to main is the production deploy. The deploy jobs in ci.yml wait on the test job, so the release built, failed one test, and stopped. Nothing reached prod; every release behind it would have queued up the same way until something green landed.
  • The failing test was apps/web/src/utils/requestRollInvocation.test.ts, a source-text guard on packages/server-core/src/index.ts: it holds that the relayed roll request is built by spreading the client's payload verbatim first, so that invocation (the executing action's id, added in v0.33.0) survives the trip through the server. It asserted that with a regex naming exactly the two server-side fields the relay added at the time — campaignOwnerId and editorUserIds — and a closing brace right after them. v0.88.0's last commit added a third, characterOwnerId (so the bound character's owner is prompted whoever placed the token row), and the guard read that as the spread being gone.
  • The guard now reads the shape rather than the roster. ...payload must open the enriched object; what follows must be conditional spreads that each add one named field; campaignOwnerId and editorUserIds must be among them; and invocation must not be — the one addition that would actually break the contract the test exists to keep. A fourth field will pass; a rewrite that drops the verbatim spread or re-derives invocation server-side will not.
  • Why it reached main. The commit that broke it touched only packages/server-core, and only that package was re-tested before the squash. Guards in one package read the source of another, so a package-scoped run is not the gate CI runs. This is the "fixed window" trap of source-text guards, and the second half of the lesson is procedural: pnpm verify is CI's whole gate in CI's order and takes about 75 seconds, and it was not run on the tree that was committed.
  • CLAUDE.md now states the release gate as a rule for every commit to main, squash or hotfix: a green pnpm verify against exactly the tree being committed — after git merge --squash, before git commit, and again after any late fix — and no tag until it is green. The instruction existed before under the per-TODO workflow; it now sits in the merge checklist where a release is actually cut, with this release as the reason.