Repository navigation
Commit e666636
test(create-objectstack): satisfiesCaret reads a prerelease floor, so the runtime-image agreement holds on a cut-rc version pass (#20581)
Fixes #20566
Clause-②: no
## What changed
One file, tests only:
`packages/create-objectstack/src/runtime-image.test.ts`.
The scaffolder writes `^OWN_VERSION` for `@objectstack/cli`. A `cut-rc`
version pass in pre mode makes that version `18.0.0-rc.N`, so the
scaffolded range is `^18.0.0-rc.N`. The test-local helper
`satisfiesCaret` accepted only a plain `^X.Y.Z`. On such a range it
answered `false` for every version. So the fixture precondition and the
agreement test would both go red on the RC version PR, and the failure
would blame the fixture and the scaffolder.
- **`satisfiesCaret` now follows semver caret semantics for a prerelease
floor.** The version must have the same major and be at or above the
floor. A release outranks its own prereleases, so `18.1.2` and `18.0.0`
satisfy `^18.0.0-rc.1`. A prerelease version counts only on a prerelease
floor's own `X.Y.Z`, so `18.1.0-rc.1` does not satisfy it. Prerelease
precedence is in a small `comparePrerelease` beside it.
- **A range the helper cannot read now throws instead of answering
`false`.** The thrown message names the range. A `false` here reads as
the scaffolder's or the fixture's fault, and that is how this defect
would have presented. Majors of 1 and up only: `syncObjectStackDeps`
writes no range for a `0.x` version.
- **New pins** in `describe('satisfiesCaret')`:
- Triage's grade: `^18.0.0-rc.1` is satisfied by `18.0.0-rc.2` and
`18.1.2`, and not by `17.9.0` or `19.0.0`.
- Three more prerelease-floor cases: `18.0.0` yes, `18.0.0-rc.0` no,
`18.1.0-rc.1` no.
- The plain-floor controls on `^17.4.0` (`17.4.0`, `17.5.2` yes;
`17.3.9`, `18.0.0` no).
- One case for a prerelease version against a plain floor.
- The refusal: `~18.0.0` and `^0.2.3` throw, naming the range.
`semver` was not used. It is not a `create-objectstack` devDependency at
this ref (see H0 below), and triage's grade takes the "otherwise" branch
in that case.
## Measurements
All of these ran at `166635e4c0` (branch head), which the
test/typecheck/lint/gate readings below cite.
- **H0: `semver` is not usable from the package without adding a
dependency.** `packages/create-objectstack/package.json` devDependencies
are `@objectstack/spec`, `@types/node`, `tsup`, `typescript`, `vitest`
and `yaml`. `semver` is a ROOT devDependency (`package.json`, used by
two root scripts). From the package directory,
`import.meta.resolve('semver')` does resolve at runtime, but only as a
phantom through the root `node_modules/semver` link. It has no types:
there is no `@types/semver` in the store and no `.d.ts` in
`semver@7.8.5`, and the package's `strict` NodeNext `tsconfig.json`
compiles this test file. So the import would resolve at runtime and fail
the typecheck. That falls under triage's "otherwise" branch. (The
typecheck failure is inferred from the absent declarations, not run.)
- **H1: the helper as it stood answers the prerelease range wrongly.**
The helper was extracted from `f11b5f20a` and compared with
`semver.satisfies` (the root devDependency, used only as an oracle in a
scratch script):
- On `^18.0.0-rc.1` it returned `false` for `18.0.0-rc.2`, `18.1.2` and
`18.0.0`, where semver says `true`. It also returned `true` for
`^17.4.0` vs `17.5.0-rc.1`, where semver says `false`. That is 4 of 12
cases.
- Over a 104,979-pair matrix (majors 17–19, twelve prerelease tails,
plus build metadata), the old helper disagrees with semver on **4,754**
pairs. The new helper at `166635e4c0` disagrees on **0**.
- **The card's symptom, measured end to end.** It had not been run
against an RC tree before. `packages/create-objectstack/package.json`
was set to `"version": "18.0.0-rc.1"` through
`scripts/ablation-replace.mjs`, and the suite ran twice:
- The BASE copy of this test file: **2 failed | 15 passed**, exactly the
two the card predicted. The precondition failed with `fixture defect:
the planted @objectstack/cli 18.1.2 does not satisfy ^18.0.0-rc.1`, and
the agreement test with `scaffolded Dockerfile pins :18.1.2 but the
scaffolded package.json asks for "^18.0.0-rc.1"`.
- This branch's file: **30 passed (30)**.
- Restore proven: `package.json` blob equals HEAD (`49e071ff2a9d`), `git
diff HEAD` is empty, the probe file is removed, and `git status
--porcelain` shows 0 lines.
- **H2: the suite is not platform-gated.** `runtime-image.test.ts` has
no `skipIf`/`runIf` and ran on darwin here: 30/30. The one skipped file
in the package run is `scaffold-e2e-boot-probe.test.ts`, which is
Linux-only and untouched here. In CI this suite runs in `ci.yml` Test
Core (`create-objectstack#test`).
- **H3: ablation.** The prerelease branch was removed from the range
regex (anchor hit x1 → x0, blob `de6880de6d49` → `2a83466c65c3`). The
expected direction was declared before the run: the seven
prerelease-floor pins go red because the range becomes unreadable, and
the other 23 stay green. Observed: **7 failed | 23 passed (30)**. They
are exactly the seven `reads a prerelease floor` cases, each failing on
the helper's named refusal (`satisfiesCaret reads
^MAJOR.MINOR.PATCH[-PRERELEASE] with MAJOR >= 1, got "^18.0.0-rc.1"`).
The plain-floor controls stayed green. Restore proven: blob
`de6880de6d49` equals HEAD and `git diff HEAD` is empty. There is no
`dist/` leg, because vitest reads this test file from source and the
helper lives in it.
## Verification
- `pnpm --filter create-objectstack exec vitest run --maxWorkers=2
src/runtime-image.test.ts`: **30 passed (30)** (17 before this change).
- `pnpm --filter create-objectstack exec vitest run --maxWorkers=2`
(whole package): **15 passed | 1 skipped (16) files, 233 passed | 14
skipped (247) tests**.
- `pnpm --filter create-objectstack typecheck`: exit 0. `tsc
--listFiles` includes `src/runtime-image.test.ts` (16 test files
compiled).
- Dependency closure first: `pnpm --filter 'create-objectstack^...'
build` (builds `@objectstack/spec`), exit 0.
- `pnpm lint`: exit 0 in 32 s. `eslint --format json` on the edited
file: 1 result, 0 errors, 0 warnings (the file is linted, not ignored).
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 52 commands. All 52 were run and reconciled with
`--ran`: **52 derived, 50 run, 2 NOT MEASURED, 0 UNRUN**.
- NOT MEASURED: `check:dual-build-cjs-loads` (86 packages without
`dist/`) and `check:type-check-debt` (30 dependencies without built
types). Both exited 3, PREREQUISITE NOT MET. A whole-workspace build
would have 20 turbo cache misses (console and examples among them) on a
shared host.
- Why this diff cannot move either one: the diff changes no built output
(dist grep below). `create-objectstack` has no `require` condition, and
it is in neither the `DEBT` nor the `TEST_DEBT` ledger. CI's Lint & Repo
Gates runs both after building the closure.
- `check:lean-entry-closure` first exited 3. After a `turbo run build
--filter=@objectstack/objectql` (a full cache hit) it re-ran green.
- Roster rows that could apply: `check:authz-resolver`,
`check:error-code-casing`, `check:filter-alias-parity` (the three
rosters flagged under `packages/`), `check:scaffold-emission-policy`,
`check:docs-image-tag`, spec `check:error-code-provenance` and
`check-changeset-fixed`. All exit 0. `check-single-claim-paths`,
`check-closing-target-claim` and `check-partof-closing-keyword` need PR
context and were run once this PR existed; readings are in the report on
the card.
- `node scripts/check-issue-citations.mjs --base origin/main`: exit 0.
`origin/main` is still `f11b5f20a`, an ancestor of HEAD (exit 0), so no
merge was needed.
- **Nothing ships (skip-changeset evidence).** `files[]` is `dist`,
`README.md` and `CHANGELOG.md`. After `pnpm --filter create-objectstack
build`, a grep of those paths finds `satisfiesCaret`,
`comparePrerelease` and `reads a prerelease floor` in 0 files. The
positive controls `pinRuntimeImage` and `readResolvedCliVersion` are
each found in 1 file. The seat applies the label.
**Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
could not take the shared verify lock on this host: no usable `flock`.
The shared
verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
not ship it), so the command below was run directly, without the lock —
a declared narrowing, not a silent one. No serialization guarantee held
for this
run, nor for any sibling agent in this container while it ran.
NODE_OPTIONS=--max-old-space-size=4096 pnpm --workspace-concurrency=2
--filter 'create-objectstack^...' build
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter create-objectstack
exec vitest run --maxWorkers=2 src/runtime-image.test.ts
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter create-objectstack
exec vitest run --maxWorkers=2 --reporter=verbose
src/runtime-image.test.ts
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter create-objectstack
typecheck
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter create-objectstack
exec vitest run --maxWorkers=2
bash ablation-h3.sh (the H3 ablation above, wrapped whole)
bash rc-e2e.sh (the RC end-to-end proof above, wrapped whole)
NODE_OPTIONS=--max-old-space-size=4096 pnpm exec turbo run build
--filter=@objectstack/objectql --concurrency=2
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter create-objectstack
build
NODE_OPTIONS=--max-old-space-size=4096 pnpm lint
(The helper printed the wording above once per run, each time naming
that run's command. It is pasted once here, with every unlocked command
in the block.)
## Acceptance notes
- **One answer changes on a plain floor, and it is unreachable from the
fixtures.** `satisfiesCaret('^17.4.0', '17.5.0-rc.1')` was `true` and is
now `false`. That is semver's answer: a prerelease version is admitted
only on a prerelease floor's own `X.Y.Z`. The suite's version argument
is always a release (`OWN_MAJOR.(OWN_MINOR+1).2`, or the tag pinned from
it), so no existing assertion read the old answer. It is pinned as
`admits a prerelease version only on a prerelease floor`.
- **The refusal is new behaviour.** An unreadable range (a `~` range, a
`0.x` caret, or any shape outside `^MAJOR.MINOR.PATCH[-PRERELEASE]`)
used to return `false` and now throws, naming the range. An unparseable
VERSION still answers `false`, because a `latest` tag really does
disagree with a range.
- **The fixture derivation needed no change for RC.**
`String('18.0.0-rc.1').split('.').map(Number)` gives `ownMinor` 0, so
the planted version is `18.1.2`. That lies inside `^18.0.0-rc.1` and
above its floor, as the RC end-to-end run shows.
- The oracle matrix, the H1 probe and the two mutation scripts were
one-time proofs run from the session scratchpad. No permanent test
depends on `semver`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_
Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>1 parent c876a74 commit e666636
1 file changed
Lines changed: 87 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
46 | 69 | | |
47 | 70 | | |
48 | 71 | | |
49 | 72 | | |
50 | 73 | | |
51 | 74 | | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
52 | 88 | | |
53 | 89 | | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
59 | 102 | | |
| 103 | + | |
60 | 104 | | |
61 | | - | |
| 105 | + | |
| 106 | + | |
62 | 107 | | |
63 | 108 | | |
64 | 109 | | |
| |||
261 | 306 | | |
262 | 307 | | |
263 | 308 | | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
264 | 345 | | |
265 | 346 | | |
266 | 347 | | |
| |||
0 commit comments