Skip to content

test(spec): the retired-after census reads a prerelease package label on its release triple - #23041

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23025-census-prerelease-label
Oct 12, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23025-census-prerelease-label

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23025
Clause-②: no

The rule

The retired-after census (packages/spec/src/conversions/retired-after.census.test.ts) now reads @objectstack/spec's package label on its release triple:

  • a stable label x.y.z is its own release triple;
  • a prerelease label x.y.z-PRERELEASE drops the suffix, so 18.0.0-next.N compares as 18.0.0;
  • a label that is neither still throws, naming the label.

Every comparison against the label uses that triple: the census is never ahead of it, every retiredAfter is at or below it, and the release-pending range for unpublished stamps ends at it. Only the label gets this reading. A census version and a retiredAfter stamp stay strict stable x.y.z (triple() is unchanged), so a prerelease written into the census or onto an entry still throws.

Why the release triple: it is how the artifact door already reads the runtime label (parseVersion in packages/metadata-core/src/artifact-forward-conversion.ts compares x.y.z triples and tolerates a suffix; PR #23023, now on main, keeps that reader). On a stable label the triple is the label, so no assertion changes meaning there.

Pins, in the same file:

  • the package label is read on its release triple (new block, 4 cases): a stable label is its own triple; 18.0.0-next.0, 18.0.0-next.12 and 18.1.0-rc.1 read as their release triples and compare at, never above, the release; nine malformed labels still throw, each naming the label; triple() and cmp() still throw on a prerelease, so the reading stays the label's alone.
  • The census suite itself, run under the label 18.0.0-next.0 through scripts/ablation-replace.mjs (below).

What this PR does not settle (part 2 of triage's direction)

The legal retiredAfter for a retirement landing under 18.0.0-next.N was routed to #23001's window rule. PR #23023 has landed and #23001 is closed, but neither states a stamp rule for a retirement under a prerelease label. Its dev's report says the door's new major-step half "does not read retiredAfter for step-18 entries". Its module docblock describes the prerelease while the label "still names the last release of M-1". This PR takes no position on the question.

What the census admits today, read off this diff: under a prerelease label the release-pending range is [last censused release, release triple], today [17.4.0, 18.0.0]. So it admits both candidate stamps, the last 17.x release and 18.0.0. Two facts for whoever decides it:

  • The PUBLISHED rule re-judges every stamp once the stable release is censused. A retirement that lands under 18.0.0-next.N ships retired in 18.0.0, so the census will then require the stable release before 18.0.0. A stamp of 18.0.0 would pass now and go red at that refresh.
  • .claude/skills/spec-property-retirement/SKILL.md and the retiredAfter type doc (packages/spec/src/conversions/types.ts) tell an author to stamp the current package label. Under pre mode that label is 18.0.0-next.N, which tsc's ${number}.${number}.${number} type and this census both refuse, so the author has to guess.

If the rule becomes "below the release triple under a prerelease label", the census change is one comparison in this file. #23001 is not addressed here.

Reproduction, at base 3071d3e98

node scripts/ablation-replace.mjs --file packages/spec/package.json \
  --anchor '"version": "17.7.0"' --replacement '"version": "18.0.0-next.0"' \
  -- pnpm --filter @objectstack/spec exec vitest run --project local src/conversions/retired-after.census.test.ts
  • Mutation landed: anchor 1 -> 0, blob 9eba400e -> 73244d5e.
  • Tests 3 failed | 2 passed (5). Each failure is Error: not a stable x.y.z version: '18.0.0-next.0', thrown by triple() in the three cases that compare against the label.
  • Restore: blob 9eba400e == HEAD, git diff HEAD empty.

Sweep: every read of the package label in packages/spec tests and scripts

git grep over packages/spec/src/**/*.test.ts, packages/spec/scripts/** and the vitest configs, for package.json reads, .version reads, version.split('.'), parseInt of a version, strict x.y.z regex literals and semver imports (none). Each hit, and what it does under 18.0.0-next.0:

Site How it reads the label Under 18.0.0-next.0 Verdict
src/conversions/retired-after.census.test.ts strict x.y.z regex in triple() throws, 3 cases fixed here
src/kernel/protocol-version.test.ts:24-:42 split('.')[0]; includes('-') major 18 equals PROTOCOL_MAJOR 18, so the equality branch passes prerelease-aware; measured green below
scripts/build-schemas-check-mode.test.ts:996 (repo project) Number.parseInt(label, 10) 18, identical to a 18.0.0 label never sees the suffix; see the not-measured note
scripts/openapi-self-consistency.test.ts:303 string equality with the generated info.version both sides read the same label no parse; measured green below
scripts/lib/upgrade-guide-docs.ts:55 (releaseStatus, from build-upgrade-guide.ts:194) MAJOR.MINOR.PATCH[-PRE] regex status prerelease already prerelease-aware
scripts/build-schemas.ts:300, :767 interpolated into URLs and x-spec-version; parseInt major no throw no strict parse
scripts/build-openapi.ts:30 written into info.version no throw no parse
scripts/build-spec-changes.ts:101 passed through as toVersion no throw no parse
scripts/analyze-bundle-size.ts:176 a report field no throw no parse
scripts/build-retired-after-census.ts:92 parseStable over packument versions, not the label n/a stable-only by design: the census holds stable releases

No shipped parser. Every hit is a test or a script, and packages/spec's files[] ships neither (measured below), so the change stays tests-only.

The measured half: the whole spec local project under 18.0.0-next.0, on the fixed tree, is green (below). Since this diff touches only the census file, that run is also every other local test's base reading: the census was the only red. The spec files main brought in since add no label read (re-swept after the merge).

Verification, at head ee6874ff9 (the fix with origin/main 0ede0f8ab merged in)

  • Census file, real label 17.7.0: pnpm --filter @objectstack/spec exec vitest run --project local src/conversions/retired-after.census.test.ts gives Test Files 1 passed, Tests 9 passed (9).
  • Census file, label 18.0.0-next.0, the same command wrapped by scripts/ablation-replace.mjs as in the reproduction: the mutation landed (anchor 1 -> 0, blob 9eba400e -> 73244d5e), and Tests 9 passed (9). Restore: blob 9eba400e == HEAD, git diff HEAD empty. With the reproduction this is the reverse verification: the same command at base gives 3 failed | 2 passed.
  • Neither run is vacuous on the UNPUBLISHED rule. The census trails at 17.4.0, so both labels take the release-pending branch over the 44 unpublished stamps: [17.4.0, 17.7.0] on the real label, [17.4.0, 18.0.0] on the prerelease label.
  • The whole spec local project under 18.0.0-next.0, through the same tool, at 7c75f2c49 (the fix before the merge): Test Files 650 passed | 1 skipped (651), Tests 19470 passed | 1 skipped | 1 todo. Restored, blob == HEAD. vitest list --project local confirms src/kernel/protocol-version.test.ts and scripts/openapi-self-consistency.test.ts are in that project.
  • pnpm --filter @objectstack/spec build: exit 0. pnpm --filter @objectstack/spec typecheck: exit 0. Its check:test-typecheck compiles tsconfig.test.json. That program lists the census test (tsc --listFilesOnly: 2368 files), and test-typecheck-debt.json has no row for it.
  • Declared narrowing: the whole local project on the real label at the merged head is left to CI. The diff against main is this one file, run on both labels at that head above.
  • Publish surface (skip-changeset): npm pack --dry-run lists 2080 files, none of them *.test.ts; positive control src/kernel/manifest.zod.ts is listed. releaseTriple has 0 hits in dist/; positive control retiredAfter is found in dist/shared/index.js.
  • Narrowed lint, a measurement rather than CI's full pnpm lint: eslint --no-inline-config --format json over the one touched file reports 1 file, 0 errors, 0 warnings. The file is inside the config's population (--print-config resolves 4 rules for it, and no ignored notice is reported). The config enables no type-aware linting (no parserOptions.project or projectService), so the diff cannot move a verdict on an untouched file.

Gates, at head ee6874ff9

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 75. 74 were run, each exit captured before any pipe, and 72 exited 0. --ran reconciliation: 75 derived, 72 run, 2 NOT-MEASURED, 1 UNRUN.

  • NOT MEASURED: check:dual-build-cjs-loads and check:lean-entry-closure exited 3 (PREREQUISITE NOT MET: no built output for 83 packages, and for objectql). This diff changes no build input.
  • UNRUN by instruction: check:type-check-debt, which the dispatch marks NOT MEASURED.
  • check:doc-formula-expressions first exited 3 (@objectstack/formula unbuilt). After @objectstack/formula and @objectstack/lint were built, it exited 0.

Acceptance notes

  • Part 2 has no open carrier. See the section above. Routing it is the seat's call.
  • scripts/build-schemas-check-mode.test.ts (the repo project's only label reader) is NOT MEASURED under the prerelease label. One attempt was cut by a foreground timeout mid-run, and a second never got the verify lock (exit 99). Its read is parseInt, which gives 18 for 18.0.0-next.0 and for 18.0.0 alike, so any red there is the 17 to 18 major move, not the prerelease suffix.
  • The !pending branch of the UNPUBLISHED rule (exact label) is not exercised by any current tree: the census trails at 17.4.0, so both labels take the pending branch. Under the release triple it requires the stamp to equal the triple, which is unreachable while the label is a prerelease (the census never records a prerelease tarball).

Generated by Claude Code

… on its release triple

Changesets pre mode writes `18.0.0-next.0` into packages/spec/package.json
on the Version Packages PR. The census parsed the label with the same
strict x.y.z reader it uses for census versions and stamps, so three of
its five cases threw `not a stable x.y.z version` and spec's local test
project went red.

The census now compares against the label's release triple
(`18.0.0-next.N` reads as `18.0.0`), the reading the artifact door gives
the runtime label. Census versions and retiredAfter stamps stay strict
stable x.y.z, and a label that is neither x.y.z nor x.y.z-PRERELEASE
still throws. A unit pin covers the rule.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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): node scripts/docs-audit/affected-docs.mjs --json 9f4b652e3a488f4351b47efdea93202033c064f7 → packageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants