v0.88.1 — v0.88.0 never deployed: a contract test with a fixed roster
- CI was red on
mainat v0.88.0, and a push tomainis the production deploy. The deploy jobs inci.ymlwait 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 onpackages/server-core/src/index.ts: it holds that the relayed roll request is built by spreading the client's payload verbatim first, so thatinvocation(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 —campaignOwnerIdandeditorUserIds— 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.
...payloadmust open the enriched object; what follows must be conditional spreads that each add one named field;campaignOwnerIdandeditorUserIdsmust be among them; andinvocationmust 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-derivesinvocationserver-side will not. - Why it reached
main. The commit that broke it touched onlypackages/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 verifyis 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.mdnow states the release gate as a rule for every commit tomain, squash or hotfix: a greenpnpm verifyagainst exactly the tree being committed — aftergit merge --squash, beforegit 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.