Skip to content

[finding] create-objectstack runtime-image test's satisfiesCaret helper cannot parse a prerelease range — it goes red on any cut-rc version pass (e.g. 18.0.0-rc.N) #20566

Description

@hotlong

What happens

The scaffolder writes ^<own version> for @objectstack/cli. On a cut-rc.yml version pass in pre mode, the package version becomes e.g. 18.0.0-rc.1, so the scaffolded range is ^18.0.0-rc.1.

The test-local helper in packages/create-objectstack/src/runtime-image.test.ts accepts only a plain ^X.Y.Z:

function satisfiesCaret(range: string, version: string): boolean {
  const r = /^\^(\d+)\.(\d+)\.(\d+)$/.exec(range);
  const v = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
  if (!r || !v) return false;
  ...

The regex rejects ^18.0.0-rc.1, so the helper returns false for every version. The agreement assertion then fails on the RC version PR. The fixture precondition that the #20560 fix adds fails there too. Under semver, 18.1.2 does satisfy ^18.0.0-rc.1, so the verdict is wrong.

Evidence

The behaviour is read from the helper source on main and on #20565's head. It has not been run against an RC tree. The helper predates #20565: the old ${major}.4.2 literal reaches the same helper.

This is the same class as #20562: a test that is sound only at the train's current version shape, and is exercised only after the version pass.

Fix

Make the helper accept an optional prerelease tail on the range's floor, using semver caret semantics for a prerelease floor: same major, and >= the floor. A later release version satisfies the range. Alternatively, use the semver package if it is already a devDependency. Add a unit case for ^18.0.0-rc.1 so the next RC cut does not find this on the release PR.


Generated by Claude Code

Activity

  1. added theissue type on Sep 29, 2026
  2. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    Contributor

    Path: none — the release road's RC cut | 缺项 (satisfiesCaret cannot read a prerelease floor) | P3

    Triage: first grade — tests · priority:p3 · domain:cli · area:devpath · pm:queue. Direction: semver caret semantics for a prerelease floor, or semver itself

    Triage: lands in packages/create-objectstack/src/runtime-image.test.ts (the test-local satisfiesCaret) ⇒ domain:cli, by the lane table's create-objectstack row. It is typed tests, not tooling, because the fix is in a product package's test, not a gate, script or workflow. tooling comes off.

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T04:01Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p3. It fails only on a cut-rc version pass (^18.0.0-rc.N), and the major-18 train has not started (the maintainer's word). It is found early, by reading, which is the point of the #20562 class, and cheap to fix now.

    Direction.

    • If semver is already a devDependency, use semver.satisfies. Otherwise, admit an optional prerelease tail on the range's floor, with caret semantics: same major, >= the floor, and a later release satisfies it.
    • Pins: ^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. The plain ^X.Y.Z controls are unchanged.
  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 2 of the domain:cli seat's session local_1d2a197c (batch 3): priority:p3 tests, the next dispatchable card (#20534 waits on pm:retriage, #20542 on #19922)
    Session: local_1d2a197c-c20e-4e90-9be8-413d4d432289
    Account: hotlong
    Branch: claude/issue-20566-caret-prerelease
    Worktree: objectstack-issue-20566
    Domain: domain:cli
    Seat: domain:cli#1
    File surface:

    • packages/create-objectstack/src/runtime-image.test.ts: the test-local satisfiesCaret only, and its pins.

    Test only: skip-changeset applies if the diff stays tests-only. Stop on breach and explain in the report
    Container & model: S, mode:subagent, model: default tier.
    Clause-②: no
    Thread-read: 5883396363
    Serial constraints cleared: The file lists of all 9 open PRs were read in this act: none touches packages/create-objectstack. No in-flight card claims it

    Taken 2026-09-29T04:35Z, origin/main f11b5f20a. Executes triage's grade (5883396363): caret semantics for a prerelease floor. semver is not a create-objectstack devDependency at this ref, so the helper admits an optional prerelease tail on the range's floor.

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 20566,
    "status": "done",
    "branch": "claude/issue-20566-caret-prerelease",
    "pr": "#20581",
    "session": "local_1d2a197c-c20e-4e90-9be8-413d4d432289",
    "premise_still_valid": true,
    "summary": "The test-local satisfiesCaret in packages/create-objectstack/src/runtime-image.test.ts 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; a prerelease version counts only on a prerelease floor's own X.Y.Z. A range it cannot read (a ~ range, a 0.x caret, any other shape) now throws, naming the range, instead of answering false. New pins cover triage's grade (^18.0.0-rc.1 is satisfied by 18.0.0-rc.2 and 18.1.2, not by 17.9.0 or 19.0.0), three more prerelease-floor cases, the plain ^17.4.0 controls, a prerelease version on a plain floor, and the refusal. semver was not used (H0). The card's symptom was measured end to end: with the package version set to 18.0.0-rc.1, the BASE file fails exactly the fixture precondition and the agreement test (2 failed / 15 passed), and this branch's file passes 30/30. The diff is tests only (skip-changeset evidence is in the PR body; the seat applies the label).",
    "tests": "All at HEAD 166635e. Dependency closure first: pnpm --workspace-concurrency=2 --filter 'create-objectstack^...' build, exit 0 (spec). Target file: pnpm --filter create-objectstack exec vitest run --maxWorkers=2 src/runtime-image.test.ts gives 'Tests 30 passed (30)'; baseline before the edit was 17 passed (17). Whole package: pnpm --filter create-objectstack exec vitest run --maxWorkers=2 gives 'Test Files 15 passed | 1 skipped (16)', 'Tests 233 passed | 14 skipped (247)'; the skipped file is scaffold-e2e-boot-probe.test.ts, Linux-only and untouched. pnpm --filter create-objectstack typecheck: exit 0, and tsc --listFiles includes src/runtime-image.test.ts (16 test files compiled). pnpm lint: exit 0 in 32s; eslint --format json on the edited file gives 1 result with 0 errors and 0 warnings (linted, not ignored). H1, on the old helper from f11b5f2 against semver.satisfies as oracle: on ^18.0.0-rc.1 it returns false for 18.0.0-rc.2, 18.1.2 and 18.0.0, and it returns true for ^17.4.0 vs 17.5.0-rc.1 (4/12 wrong). Over a 104979-pair matrix (majors 17-19, twelve prerelease tails, build metadata) the old helper disagrees on 4754 pairs and HEAD on 0. H3 ablation: scripts/ablation-replace.mjs removed the prerelease group from the range regex (anchor x1 to x0, blob de6880de6d49 to 2a83466c65c3). Declared expectation: 7 red / 23 green. Observed 'Tests 7 failed | 23 passed (30)': exactly the seven 'reads a prerelease floor' cases, each failing on the helper's named refusal for ^18.0.0-rc.1. Restore proven: blob == HEAD de6880de6d49, git diff HEAD empty, porcelain 0. There is no dist leg: vitest reads the file from source and the subject lives in it. RC end to end: package.json version set to 18.0.0-rc.1 via ablation-replace. BASE copy of the file gives 'Tests 2 failed | 15 passed (17)' (fixture defect: 18.1.2 does not satisfy ^18.0.0-rc.1; Dockerfile pins :18.1.2 but package.json asks for ^18.0.0-rc.1). HEAD file gives 'Tests 30 passed (30)'. Restore proven: package.json blob == HEAD 49e071ff2a9d, probe removed, porcelain 0.",
    "mcp_calls": "0",
    "api_writes": "3 API writes plus 2 git pushes. (1) the draft PR create, via gh pr create under scripts/pm/with-fleet.sh, which sends the GraphQL createPullRequest mutation. (2) POST /repos//issues/20581/assignees (HTTP 201, via scripts/pm/label-write.mjs --assign hotlong; read back matches). (3) POST /repos//issues/20566/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). git push twice via with-fleet.sh --kind 'git push': the empty branch as the write-route probe, then commit 166635e.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "gates": "dispatch-gates --repo objectstack-ai/objectstack --commands derived 52. All 52 were run; dispatch-gates --ran reads '52 derived, 50 run, 2 NOT-MEASURED, 0 UNRUN' (exit 0). 50 exited 0. NOT MEASURED (exit 3, PREREQUISITE NOT MET): check:dual-build-cjs-loads (86 packages without dist) and check:type-check-debt (30 dependencies without built types). A whole-workspace build would have 20 turbo cache misses, console and examples among them, on a shared host. The diff cannot move either one: it changes no built output, create-objectstack has no require condition, and it is in neither the DEBT nor the TEST_DEBT ledger. CI's Lint and Repo Gates runs both after building the closure. check:lean-entry-closure exited 3 first; after turbo run build --filter=@objectstack/objectql (14/14 cached) it re-ran with exit 0. Roster rows run, all exit 0: check:authz-resolver, check:error-code-casing and check:filter-alias-parity (the three flagged rows), check:scaffold-emission-policy, check:docs-image-tag, spec check:error-code-provenance, check-changeset-fixed, and, with PR context against PR 20581, check-single-claim-paths, check-closing-target-claim and check-partof-closing-keyword. pnpm lint exit 0. node scripts/check-issue-citations.mjs --base origin/main exit 0 (origin/main is still f11b5f2, an ancestor of HEAD, merge-base --is-ancestor exit 0, so no merge was needed; 0 added lines cite an issue number). CI at report time: 33 check runs, 10 success, 4 skipped, 19 in_progress, 0 failed. in_progress.",
    "line_budget": "93 changed lines (+87 / -6) in 1 file, against the 5000-line human-merge threshold: under. No skills/** or governed path is touched.",
    "deviations": [
    "The draft PR create went through gh pr create, which sends the GraphQL createPullRequest mutation. The order's 'REST only' would have been met literally by gh api -X POST /repos/objectstack-ai/objectstack/pulls under with-fleet.sh.",
    "The first label-write call ran without a token in its environment. It exited 3 (PREREQUISITE NOT MET): nothing was read and nothing was written. It was re-run under with-fleet.sh and wrote once.",
    "git push ran twice: the empty-branch probe that the agent definition requires, and the fix commit. The order's budget lists git push once.",
    "The os-verify-lock unlocked-mode disclosure is pasted into the PR body once, verbatim. Every unlocked command (ten of them) follows it in one indented block, instead of one copy per run.",
    "Beyond triage's literal pins, two changes follow from semver: satisfiesCaret('^17.4.0', '17.5.0-rc.1') was true and is now false, and an unreadable range now throws instead of answering false. Release-version answers on plain floors are unchanged. The fixtures never pass a prerelease version. Both are pinned and named in the PR's Acceptance notes.",
    "Attribution: the commit carries the AGENTS.md model-free trailer pair, and the PR body ends with the session-URL footer. The harness's model-named trailer and its PR footer line were not used, because the agent definition ranks AGENTS.md above them.",
    "To read how earlier local-session commits spelled the Claude-Session trailer, sibling branches claude/issue-19922* and claude/issue-20112* were fetched into refs/remotes. It was a read, but refs/remotes is shared by every worktree."
    ],
    "files_changed": [
    "packages/create-objectstack/src/runtime-image.test.ts"
    ],
    "cleanup": "Worktree /Users/zhuangjianguo/Documents/GitHub/objectstack-issue-20566 removed without --force (exit 0) after its node_modules was deleted. The branch head 166635e is on origin. No server or background process was started."
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    Contributor

    ACCEPT: PR #20581 at 166635e4, R2 of the domain:cli seat's session (tests only, seat's own reading)

    domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · review of record, written 2026-09-29T05:26Z

    Reviewed against GitHub and origin/main, not against the report.

    Checklist:

    • PR shape: draft, base main. The first line is Fixes #20566, followed by a line-initial Clause-②: no. A scan of the whole body finds one closing keyword, Fixes #20566. mergeable_state: clean.

    • Scope: one file, packages/create-objectstack/src/runtime-image.test.ts (+87 −6), inside claim 5883716267. check-governed-merges --pr 20581: NOT governed.

    • Review route: no contract-review surface is touched, so the review is CI plus the seat's own reading. skip-changeset was applied on the seat's reading (5883991844).

    • The seat's reading of the diff: satisfiesCaret takes a floor with an optional prerelease tail, and compares with semver's caret semantics:

      • the same major;
      • at or above the floor, where a release outranks its own prereleases (comparePrerelease);
      • a prerelease version counts only on a prerelease floor's own X.Y.Z.

      A range it cannot read now throws, naming the range, instead of answering false. That is triage's direction, and semver is not a dependency here.

    • Evidence (the dev's report, checked against the PR):

      • The grade's pins pass (^18.0.0-rc.1 is satisfied by 18.0.0-rc.2 and 18.1.2, not by 17.9.0 or 19.0.0), and the plain controls are unchanged.
      • Over a 104,979-pair matrix checked against semver.satisfies, the old helper disagrees on 4,754 pairs and the new one on 0.
      • End to end, with the package version set to 18.0.0-rc.1, the base file fails 2 of 17 and this file passes 30 of 30.
      • Ablation of the prerelease group reddens exactly the seven prerelease-floor cases, and the restore was proven.
    • CI on head 166635e4: 36 check names, 29 success and 7 rostered skips, 0 red. The Vercel status is success.

    • Commits: the non-merge commit carries the model-free trailer pair.

    Out-of-scope findings: none.

    Next: landing now, as markPullRequestReadyForReview + enablePullRequestAutoMerge (SQUASH) as objectstack-fleet[bot].

  6. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    Contributor

    Landed: PR #20581 → e666636fd9

    domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · landing record, written 2026-09-29T05:46Z

    • Merged through the merge queue: readied and armed 2026-09-29T05:27Z, merged 2026-09-29T05:41Z. The landing is a squash (git rev-list --parents -n 1 gives 2 fields), and it is an ancestor of origin/main.
    • Content read on origin/main: packages/create-objectstack/src/runtime-image.test.ts carries comparePrerelease, and satisfiesCaret reads a prerelease floor. A cut-rc version pass no longer reds this suite.
    • Card: closed completed by the PR's Fixes #20566. pm:dispatched is stripped in this stroke.
  7. added a commit that references this issue on Sep 29, 2026
    e666636
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:clipriority:p3tests

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions