Repository navigation
Commit 68fb6ac
test(spec): the retired-after census reads a prerelease package label on its release triple (#23041)
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`
```text
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](https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1959aac commit 68fb6ac
1 file changed
Lines changed: 67 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
35 | 52 | | |
36 | 53 | | |
37 | 54 | | |
| |||
69 | 86 | | |
70 | 87 | | |
71 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
72 | 105 | | |
73 | 106 | | |
74 | 107 | | |
| |||
91 | 124 | | |
92 | 125 | | |
93 | 126 | | |
94 | | - | |
| 127 | + | |
95 | 128 | | |
96 | 129 | | |
97 | 130 | | |
| |||
102 | 135 | | |
103 | 136 | | |
104 | 137 | | |
105 | | - | |
| 138 | + | |
106 | 139 | | |
107 | 140 | | |
108 | 141 | | |
| |||
126 | 159 | | |
127 | 160 | | |
128 | 161 | | |
129 | | - | |
| 162 | + | |
130 | 163 | | |
131 | 164 | | |
132 | 165 | | |
133 | 166 | | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
138 | 171 | | |
139 | 172 | | |
140 | 173 | | |
141 | 174 | | |
142 | 175 | | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
0 commit comments