Repository navigation
fix(ci): the future-spec-major gate and the artifact-door pins stop moving with the version label - #23155
Merged
objectstack-fleet[bot] merged 2 commits intoOct 13, 2026
Conversation
…oving with the version label On the generated Version Packages tree the spec label rolls to 18.0.0-next.0 and two pins that hold on `main` broke, with no byte of their own changed: - check-future-spec-major counted its own self-test fixtures against the tree's major (they cite 18 as "above 17" by construction), so the roll read as a gutted instrument; and its two major-18 quotation entries, whose sentences became legitimate citations at 18, reported "matched NOTHING". The floor now counts above the fixtures' own major, an entry at or below the published major is dormant (it can excuse nothing), and a self-test row drives the real corpus one major up under a prerelease-shaped label so the class reds on `main` first. - plugin-artifact-forward-conversion.test.ts claimed "the current surface" as `^<installed label>`, which at M.0.0 (and M.0.0-next.N) is the floor the door's major-step half opens on by design. It now derives the floor the sibling door tests use: the first x.y.z past the label and every retiredAfter, never an M.0.0. Claude-Session: https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Oct 13, 2026
objectstack-fleet
Bot
deleted the
claude/issue-23135-v18-version-pr-red
branch
October 13, 2026 05:23
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #23135
Clause-②: no
The generated Version Packages PR #21988 (head
63eb607ad1, spec label18.0.0-next.0) is red on four checks. Two of them are failures in their own right, and the other two are aggregators that roll those two up. Both real failures are pins onmainthat are stated against the spec package's version label, so they flip when the version pass rolls the label to 18, even though none of their own bytes changed. This PR fixes both onmain, at their producers. It changes nothing onchangeset-release/main, on #21988, in any release workflow or on any governed surface.Every red on #21988's current head
Read after the last of them completed at 2026-10-13T03:23Z:
GET /repos/objectstack-ai/objectstack/pulls/21988returned head63eb607ad15b883fee70224f20afbf3c89a18e46, and/commits/{sha}/check-runsreturned 44 runs: 32 success, 8 skipped, 4 failure, 0 still running. The five Test Core shards andType Check · workspacethat were running when the card was filed have all finished green, except shard 1 below.Type Check · source gates(job 115105083441). Failing line:✗ check-future-spec-major: 3 problem(s). This is red 1 below.TypeScript Type Check(the aggregator, job 115108695957). Failing line:type-check lane `typecheck-source-gates` concluded `failure` -- expected `success`.It has no failure of its own; it is red 1 rolled up.Test Core (1/6)(job 115105339463). Failing line:@objectstack/metadata:test: Tests 2 failed, 878 passed (880), plusFailed: @objectstack/metadata#testin the turbo summary. This is red 2 below.Test Core(the aggregator, job 115113050333). Failing lines:Test Core: 1 of 6 declared shard(s) of test published no positive attestation (test-1-of-6).andTest Core: leg test reported result 'failure'. It has no failure of its own; it is red 2 rolled up.Red 1:
check-future-spec-majoron the 18 treeThe three problems, verbatim, from the job and reproduced locally:
scripts/check-future-spec-major.mjs holds 10 citation(s) above the published major, below the floor of 12.QUOTATION_EXEMPTIONS entry 0 (docs/adr/0087-metadata-protocol-upgrade-contract.md, major 18) matched NOTHINGQUOTATION_EXEMPTIONS entry 1 (docs/v17-docs-sweep.md, major 18) matched NOTHINGDoes a prerelease of the next major count as published? Yes. The gate's own text decides it.
@objectstack/specnpm major ABOVE the one this tree publishes". It also says: "The rule is 'an@objectstack/specversion whose MAJOR is above the major inpackages/spec/package.json', read at runtime. A gate hardcoding18goes dormant the day the tree reaches 18".The generated tree publishes
18.0.0-next.0, which is an npm release of major 18 on thenextdist-tag. On that tree, a citation of 18 names the release that carries the change, so it is legitimate there. Reading 18 as the published major is therefore correct, and this PR leaves the class itself (majors above the published one) exactly as it was.What was wrong: two pins stated against the label instead of against what they pin
above spec.major. The self-test fixtures cite 18 as "above 17", because they are written against a synthetic17.4.0manifest. At 18, those fixtures read as "at the major", so the floor saw a gutted instrument (10 of 12) although nobody had touched it. Fix: the floor now counts aboveSELF_FIXTURE_MAJOR = 17, the major the fixtures are written against. The two synthetic manifests now derive from that same constant.major: 18quotation entries. At 18, the sentences they excuse are legitimate citations. Such a citation iscontinued before the ledger is ever consulted, so the entries matched nothing and the self-invalidation rule reported each one as a hole. An entry at or below the published major can excuse nothing: it is neither a hole nor over-broad. Fix: such an entry is now DORMANT. It is listed on the pass line ("delete oncemainitself carries that major") and is never judged. Live entries keep both refusals unchanged. The version branch cannot be edited, andmainstill needs these entries until it carries 18 itself, so a red there could never be fixed.docs/adr/0087-metadata-protocol-upgrade-contract.mdis not edited. Entry 0 still points at it unchanged.There are four new self-test rows (the roster floor moves from 29 to 33):
.0.0-next.0). It fails when that run reports more problems than the run at the published major. A tree only ever gains majors, and a citation that is legitimate at M is legitimate at M plus one, so the count can never rise unless some pin moves with the label. That is this card's class, and the row now catches it onmain, before the roll reaches the version branch.Proofs
changeset-release/mainat63eb607ad15b883fee70224f20afbf3c89a18e46, label18.0.0-next.0.node scripts/check-future-spec-major.mjs --self-testprinted✓ check-future-spec-major self-test: 29 cases pass.(exit 0).node scripts/check-future-spec-major.mjsprinted✗ check-future-spec-major: 3 problem(s), the three lines above (exit 1).dbe4fc5e0f, identical to the committed one). The self-test printed✓ check-future-spec-major self-test: 33 cases pass., with the roll row reading(0 at 18, 0 at 19.0.0-next.0)(exit 0). The gate printed✓ check-future-spec-major: no @objectstack/spec citation names a major above 18, then3 live witnessed ledger entr(y|ies), then2 DORMANT ledger entr(y|ies) at or below major 18 … docs/adr/0087-metadata-protocol-upgrade-contract.md (major 18), docs/v17-docs-sweep.md (major 18)(exit 0).mainstays green. Worktree atf7ec15171: the self-test printed 33 cases pass, with the roll row reading(0 at 17, 0 at 18.0.0-next.0), and the gate printedno … citation names a major above 17,5 live witnessed ledger entr(y|ies).pnpm check:future-spec-majorexited 0.main, restored. Each mutation went throughnode scripts/ablation-replace.mjsin WRAP mode, with my own EXIT/INT/TERM trap on absolute paths. Each restore was proven by blob equal to HEAD andgit diff HEADempty.f.majorwithspec.majoragain). The self-test went red on 3 rows; the roll row read0 problem(s) at 17, 1 at 18.0.0-next.0 … holds 10 citation(s).0 problem(s) at 17, 2 at 18.0.0-next.0.main's real corpus, exactly the three problems CI read on the version branch.Red 2: the two
plugin-artifact-forward-conversion.test.tsfailuresVerbatim, from the job and reproduced locally:
AssertionError: expected 'expected "the strict parse must refus…' to contain 'allowRestore'. The received message wasexpected "the strict parse must refuse" not to be reached.AssertionError: expected [ Array(1) ] to have a length of +0 but got 1.The producer is the test's fixture, not the conversion source
^followed by the installed label. On the 18 tree that is^18.0.0-next.0.parseRangeFloordrops the prerelease tag, so the floor reads as18.0.0.M.0.0at or above the label is exactly where the door's major-step half opens. This is documented in the module doc ofpackages/metadata-core/src/artifact-forward-conversion.ts(verdictconverted-major-step): a prerelease build of M may predate any retirement in step M, so every retired entry of step M is replayed. It is pinned at the labels17.7.0,18.0.0-next.0and18.0.0(AT_OR_BELOW_THE_FLOOR) inpackages/metadata-core/src/artifact-forward-conversion.test.ts.permission-allow-restore-purge-removedhastoMajor: 18, so at the roll the "current" artifact was converted on purpose, and both pins read that documented rescue as an amnesty.@objectstack/metadata-corewithapplyArtifactForwardConversions:{"installed":"18.0.0-next.0","range":"^18.0.0-next.0","verdict":"converted-major-step","authoredFloor":"18.0.0","notices":["permission-allow-restore-purge-removed","permission-allow-restore-purge-removed"]}{"range":"^18.0.1","verdict":"authored-current","notices":[]}.main(label17.7.0), restored and proven as above, forced'^18.0.0'into both pins. It reproduced both CI assertion texts verbatim (Tests 2 failed, 4 passed (6)). So the M.0.0 floor alone is the mechanism, and the pins still go red when the window opens.The source therefore behaves as designed, and the fixture's own comment was the false premise: "
^X.Y.Zfloors at exactly the installed version, so this pin survives every future spec release without edits" does not hold at any major's first release. Fix: the test now uses the floor derivation that the siblingplugin-unbound-form-predicate-roots.test.tsalready uses for "the current surface": the firstx.y.zpast both the installed label and every retired entry'sretiredAfter. That floor has a non-zero patch, so it is never anM.0.0. No assertion changed. One premise guard was added: the derived floor must be resolvable and have a non-zero patch.Proofs
pnpm turbo run build --filter='@objectstack/metadata^...' --filter='@objectstack/service-settings^...'(15 of 15 tasks),pnpm --filter @objectstack/metadata exec vitest run --maxWorkers=2 src/plugin-artifact-forward-conversion.test.tsprintedTests 2 failed, 4 passed (6), with the two assertion texts above.a436ff248b, identical to the committed one):Test Files 1 passed (1),Tests 6 passed (6).mainstays green. Worktree atf7ec15171: the file alone gaveTests 6 passed (6); the whole package (pnpm --filter @objectstack/metadata exec vitest run --maxWorkers=2) gaveTest Files 61 passed (61),Tests 880 passed (880); andpnpm --filter @objectstack/metadata run typecheck(tsc --noEmit) exited 0.--listFilesshows that the program does include this test file (61 test files in total).The other shard-1 ELIFECYCLE lines are collateral
@objectstack/spec:test:repo,@objectstack/spec:testand@objectstack/service-settings:testall endELIFECYCLEbetween 03:00:06.39Z and 03:00:06.45Z. That is within 0.42 s of metadata's ownELIFECYCLE, and none of the three printed a failing test. Turbo's summary names onlyFailed: @objectstack/metadata#test(Tasks: 37 successful, 42 total). Run to completion on the generated tree (the clone, with this PR's two files present, neither of which those suites read for their verdicts), the suites measured as follows:@objectstack/service-settings:Test Files 42 passed (42),Tests 755 passed (755);@objectstack/spec,--project repo:Test Files 57 passed (57),Tests 997 passed (997);@objectstack/spec,--project local:Test Files 660 passed (660),Tests 20021 passed, 1 todo (20022).The pin
The seat reads that run.
That refresh will also carry everything else that lands on
mainbefore it. The commits onmainafter this branch's merge base are outside what this PR measured.Local gates (CI runs the full farm)
I derived the gates with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this branch atf7ec15171, after mergingorigin/maind206d08ca. It listed 73 commands. I ran each one and recorded its exit code before any pipe.pnpm check:lean-entry-closurefirst refused withPREREQUISITE NOT MET(nopackages/objectql/dist); I built@objectstack/objectqland re-ran it, and it exited 0 (2 published condition(s) measured from a real load).--ranthen reconciled the record as73 derived famil(ies) accounted for — 72 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3). All 72 that ran exited 0, includingpnpm check:future-spec-major,pnpm check:nul-bytes,pnpm check:cross-package-test-inputs,pnpm check:test-source-alias,node scripts/check-position-name-fold-loaders.mjs,pnpm check:type-check-coverageandpnpm check:type-check-debt.pnpm check:pm-dispatch-gatesran detached and exited 0 (1833 cases pass, fast tier).NOT MEASURED locally:
pnpm check:dual-build-cjs-loads(PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/; it needs the whole workspace built, and 73 packages are unbuilt in this worktree). It reads builtdist/, and this diff changes no built byte of any package, so it cannot move it. CI's lint jobs build and run it.Changeset
None: the
skip-changesetlabel. The diff is a rootscripts/file (the root package@objectstack/spec-monorepoisprivate) and a test file in@objectstack/metadata, whosefilesisdist,README.mdandCHANGELOG.md. Greppingpackages/metadata/distfor the new test helpercurrentSurfaceFloorfinds 0 files, against a positive control_parseAndRegisterArtifactthat finds 6.Acceptance notes
mainitself carries 18.0.0-next.N or later, which happens after the maintainer lands the Version Packages PR. The gate's pass line names both from then on. Nobody currently owns that cleanup.mainsits in Changesets pre modenextwith 24majorchangesets queued, a change landing onmainnow first ships in18.0.0-next.N. The gate's prescription onmain("Not shipped yet ⇒ the bare published major, which ADR-0087 guarantees is the carrier (a pre-GA retirement shipsminor)") still names 17. That is the same pre-mode family as spec(conversions): under a pre-mode label (18.0.0-next.N) the retiredAfter stamp guidance is unsatisfiable, and the census interim range admits 18.0.0, which its own PUBLISHED rule rejects after GA #23042, which remains open, and it is a decision, not a fix for this card.--project reporun of@objectstack/spectook 20m24s under the shared lock on this 4-CPU container (--maxWorkers=2). This is a shared-box reading.Generated by Claude Code