Repository navigation
根 engines 声明比工具链实际要求宽:node ">=22" 放行 22.0–22.10 / 23.x / 25.x,pnpm ">=9" 放行 pnpm 9 —— @changesets/cli@3 全部拒绝 #5306
Description
Activity
os-support-ai commented
on Aug 20, 2026 CollaboratorMore actionsNot dispatched — flagged as a suspected mislabel, by the
repo:objectuiexecution seat (PM round 19, sessionsession_01RV6yuVCxymHYE16PL9vQkE). ⛔ I am not changing the label and ⛔ not adjudicating; both belong to the triage seat.This card carries
pm:queue, but it reads as a decision card:- It offers three options (A:
>=22.11/>=10; B: copy^22.11 || ^24 || >=26; C:.npmrcengine-strict=trueafter A or B) and does not choose. - Its own body says so in as many words: "收紧
engines.node/engines.pnpm影响的是每一个消费者与每一条 CI 通道,是一个决定,不是工具升级的搭车项" — which is also exactly why it was split out of Migrate to @changesets/cli v3 — one PR carrying the bump plus the config changes v3's defaults require #5296 rather than done there. - Tightening
engineschanges what environments are accepted for every consumer of this workspace and every downstream, and option C would make the declaration start refusing environments that install fine today. A seat picking A over B is making that call for everyone.
The measured facts are not in dispute and are worth keeping intact for whoever grades it: root declares
node: ">=22", pnpm: ">=9";@changesets/cli@3.0.1declaresnode: "^22.11 || ^24 || >=26", pnpm: ">=10.0.0"; neither root range is a subset. Nothing enforces the declaration today — noengine-strictin.npmrc,packageManagerpinned topnpm@10.31.0, workflows onnode-version: '22.x'— so this is a false declaration, not a live failure. That is what makes it safe to leave queued while it waits, and also what makes it easy to forget: nothing will go red to remind anyone.If triage grades it and picks an option, it becomes a genuinely small mechanical card and I will take it in the next round.
Generated by Claude Code
- It offers three options (A:
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 21, 2026 yinlianghui-tw commented
on Aug 24, 2026 CollaboratorMore actionsClaim: PM loop round 8
Session:session_019b5UBNMtTzKbVtZZGvFuxe
Branch:claude/issue-5306-engines-match-toolchain
Worktree:objectui-issue-5306
Domain:domain:devx(@ objectui, seat #5748)
File surface: rootpackage.json(engines), plus whatever pins/docs state the supported versions
Container & model: S,mode:subagent,model: sonnet
Clause-②: no
Serial constraints cleared: in-flight 0 at round open. Co-dispatched: #5174 final batch, #5391, #5007, #4902 — all disjoint.⚠️ Rootpackage.jsonis a hot file; if anything else has touched it sinced8b48f495, merge before pushing.PM-verified premise on
origin/main@d8b48f495"engines": { "node": ">=22", "pnpm": ">=9" }
Confirmed verbatim. The card's claim is that this admits versions the toolchain provably rejects — node 22.0–22.10 / 23.x / 25.x, and pnpm 9, which
@changesets/cli@3refuses outright.This is declared-≠-enforced on the contributor onboarding surface: a range that admits a version where
pnpm installor the release tooling fails is worse than no range, because it reads as a checked guarantee.Zone 2 — ⛔ do NOT take the card's version numbers on faith
The card's specific boundaries (22.10, 23.x, 25.x,
@changesets/cli@3vs pnpm 9) are the thing to verify, not the spec. Derive the real minimums from the toolchain itself:- what the installed
@changesets/cliactually declares in its ownengines - what other pinned devDependencies require
- what CI actually runs on (
.github/workflows/**setup-node/pnpm/action-setupversions) —⚠️ the declared range must not exclude the version CI itself uses, and must not admit one CI never exercises without saying so - whether
packageManageris set in rootpackage.json, and whether it agrees
Report the measured minimum for each, with the source. If the card's numbers are wrong, use yours and say which differed.
Direction and its limit
Tighten
enginesto what the toolchain actually requires — restoring declared = enforced. ⛔ This is not licence to bump to "latest": the new floor must be justified by a measured requirement, not by preference. A range that excludes a working version is the same defect mirrored.⚠️ Stop and report rather than deciding if tightening would exclude a version CI currently runs, or if the honest floor turns out to be a major bump for contributors (e.g. requiring pnpm 10 when the repo has never said so). That is a contributor-facing product decision, ⛔ not a config hygiene fix, and it is maintainer-floor.Gates
Derive from your diff.
enginesis enforced at install time, so state whetherpnpm installstill succeeds under the declared floor. Check for any test pinning the engines block (this repo pins a lot of its own config). Runcheck-changeset-presence.mjsand follow its verdict; ⛔ noskip-changesetlabel (#4912/#3724).
Generated by Claude Code
- what the installed
yinlianghui-tw commented
on Aug 24, 2026 CollaboratorMore actions{ "issue": 5306, "status": "needs_decision", "branch": "claude/issue-5306-engines-match-toolchain", "pr": null, "premise_still_valid": true, "summary": "No code changed. Premise confirmed and worse than the card assumed: root `engines` (`node: \">=22\"`, `pnpm: \">=9\"`) is looser than every direct devDependency's own declared floor, not just @changesets/cli's. The binding constraint is `jsdom@^30.0.1` (a direct devDependency vitest needs for its DOM test environment), whose own `package.json` `engines.node` is `^22.22.2 || ^24.15.0 || >=26.0.0` -- a hard, deliberate breaking change in jsdom 30 (jsdom's own Changelog: 'Breaking Changes: Node.js minimum version raised to ^22.22.2 || ^24.15.0 || >=26.0.0'). That is 11 Node-22 releases stricter than @changesets/cli's own floor (^22.11) and stricter than eslint@10.8.1's own floor (^22.13.0). Escalating per the dispatch's STOP clause: the honest, fully-measured node floor is far narrower than the card's proposed 22.11/22.10, and I could not conclusively confirm from CI logs (job log too large to fetch in full; zip download blocked by the outbound proxy) that CI's floating `node-version: '22.x'` actually resolves to >=22.22.2 today, though this sandbox's own provisioned Node (v22.22.2) and Node's release cadence make it likely. Two other measured-but-real findings, not yet acted on: pnpm floor is genuinely >=10 (matches changesets and typedoc, and simply documents what `packageManager: pnpm@10.31.0` + corepack already enforce -- not a new ask, so does not itself need escalation), and CONTRIBUTING.md still says 'Node.js 18.0 or higher' / 'Tests on Node.js 18.x and 20.x', drifted from CI's actual 22.x-only matrix (in-scope per the card's own scope line, left for the follow-up PR once the node number is decided). `content/docs/guide/release-notes.md`'s 'Node.js >= 18' is a historical compatibility-matrix snapshot for the already-shipped v3.3.0 release, not a live claim -- left untouched as out of scope (retroactively editing a past release's stated compatibility would misrepresent history, the same reasoning objectstack's sibling repo states explicitly for its releases/ directory).", "tests": "No code changes made; no gates run. Investigation commands (all in the issue-5306 worktree, base d8b48f495): `pnpm install` under the current `engines` succeeded clean via the shared verify lock (VERDICT command-exit 0). Read `node_modules/.pnpm/@changesets+cli@3.0.1/node_modules/@changesets/cli/package.json` -> engines `{node: \"^22.11 || ^24 || >=26\", npm: \">=10.9.0\", pnpm: \">=10.0.0\", yarn: \">=4.5.2\"}` (card said 22.10; measured is 22.11 -- correcting the card's number per Zone 2). Read `node_modules/.pnpm/jsdom@30.0.1*/node_modules/jsdom/package.json` -> engines `{node: \"^22.22.2 || ^24.15.0 || >=26.0.0\"}`, confirmed as a deliberate breaking change via jsdom's own Changelog (web search). Read engines fields of every other direct devDependency in root package.json (typescript, vite, vitest, turbo, eslint, react, react-dom, typedoc, playwright, msw, @testing-library/*, happy-dom, concurrently, tsx, typescript-eslint, @eslint/js, eslint-plugin-*, globals, autoprefixer, rollup-plugin-visualizer, vite-plugin-compression2, wait-on, yaml, tslib, axe-core, vitest-axe) -- next-strictest after jsdom is eslint@10.8.1 at `^22.13.0`; nothing else beats jsdom. `grep -rn node-version .github/workflows/` -> every workflow (ci.yml, lint.yml, changeset-presence.yml, changeset-release.yml, control-bytes.yml, dependabot-auto-merge.yml, doc-component-types.yml, doc-snippet-types.yml, docs-links.yml, live-e2e.yml, node-esm-load-gate.yml, performance-budget.yml, published-dist-gate.yml, shadcn-check.yml, skills-paths.yml) uses `node-version: '22.x'` (floating, resolved by actions/setup-node at run time) -- no pnpm/action-setup anywhere; pnpm comes from `corepack enable` + root `packageManager: pnpm@10.31.0`, confirmed exact by a CI Lint job log (run 32717909859, job 97403048894: 'Enable Corepack' then 'Verify pnpm version' steps both green). Attempted to read that job's 'Setup Node.js' step output to confirm the exact resolved Node patch; the full job log (~485KB) exceeded the tool's per-call token cap and the raw-logs zip download failed through the outbound proxy (curl: CONNECT tunnel failed, 403) -- could not directly confirm the CI-resolved Node patch, hence not shipping a floor that assumes it. `cat pnpm-lock.yaml | head` -> `lockfileVersion: '9.0'` (unaffected either way). `cat .npmrc` -> only `auto-install-peers=true`, no `engine-strict` (confirmed still unenforced, matching the card).", "open_questions": [ { "question": "What should root `engines.node` become? The fully-measured toolchain floor (jsdom@^30.0.1's own `engines.node`) is `^22.22.2 || ^24.15.0 || >=26.0.0` -- 11 Node-22 releases stricter than @changesets/cli's floor (^22.11), which is what the card investigated and proposed to use. Setting the declared floor to anything looser than jsdom's own floor (e.g. the card's `>=22.11`, or my own eslint-driven `>=22.13`) would restore declared=enforced for @changesets/cli specifically, but leave the *same defect the card exists to fix* still present against jsdom the moment a contributor's Node predates 22.22.2 and they run `pnpm test` (jsdom underlies the vitest DOM environment, so this is not a remote edge case).", "options": [ "A: `node: \">=22.22.2\"`, `pnpm: \">=10\"` -- the fully honest floor, matches jsdom's (the strictest direct devDependency) own requirement exactly, genuinely restores declared=enforced end to end. Risk: unconfirmed whether CI's floating `node-version: '22.x'` reliably resolves to >=22.22.2 (see tests field) -- if it does not on some runner image, this floor would violate the card's own hard constraint ('must not exclude a version CI itself uses'). Also the narrowest, least-intuitive number of the three -- a contributor reading '>=22.22.2' will reasonably ask why such a specific patch, and the answer ('one devDependency's own aggressive bump') is a fragile thing to promise long-term: the next jsdom/eslint/vite minor could ratchet it again with the same opacity.", "B: `node: \">=22.11\"` (the card's own target, corrected from 22.10 to the measured 22.11), `pnpm: \">=10\"` -- minimal, matches the card's own stated intent (fix the @changesets/cli gap specifically) and is much gentler on contributors, but knowingly ships a declaration that is still false against two other direct devDependencies (eslint needs ^22.13, jsdom needs ^22.22.2) -- i.e. it only partially restores declared=enforced and reintroduces the same failure mode this card exists to close, just with a smaller gap and a different offending package.", "C: Option A's numbers, plus file a companion decision (not in this PR -- devDependency version pins are outside this card's stated scope of 'the engines block and whatever states supported versions alongside it') asking whether the repo should stay on jsdom's aggressive-floor major (30.x) or pin an older jsdom major (e.g. ^29.x, which does not carry this Node floor) specifically to keep the contributor-facing `engines.node` gentler. This keeps the engines fix honest today while giving the maintainer a lever to relax the floor later without re-opening this same investigation." ], "recommendation": "C, because it is the only option that actually satisfies the card's own stated purpose (declared = enforced, checked against the real toolchain) without silently reintroducing the gap under a different package name -- but I'm not shipping A/C's number unilaterally because I could not fully confirm CI's resolved Node patch given the tooling limits hit this round (see tests field); a maintainer confirming that (or accepting the small residual risk) is what turns this into the 'genuinely small mechanical card' the earlier triage comment on this issue described." } ], "out_of_scope_findings": [] }
Generated by Claude Code
yinlianghui-tw commented
on Aug 24, 2026 CollaboratorMore actionsos-dev-report (marker HTML comment above was stripped by the GitHub body sanitizer; reposting with the marker as literal text per instructions)
{ "issue": 5306, "status": "needs_decision", "branch": "claude/issue-5306-engines-match-toolchain", "pr": null, "premise_still_valid": true, "summary": "No code changed. Premise confirmed and worse than the card assumed: root `engines` (`node: \">=22\"`, `pnpm: \">=9\"`) is looser than every direct devDependency's own declared floor, not just @changesets/cli's. The binding constraint is `jsdom@^30.0.1` (a direct devDependency vitest needs for its DOM test environment), whose own `package.json` `engines.node` is `^22.22.2 || ^24.15.0 || >=26.0.0` -- a hard, deliberate breaking change in jsdom 30 (jsdom's own Changelog: 'Breaking Changes: Node.js minimum version raised to ^22.22.2 || ^24.15.0 || >=26.0.0'). That is 11 Node-22 releases stricter than @changesets/cli's own floor (^22.11) and stricter than eslint@10.8.1's own floor (^22.13.0). Escalating per the dispatch's STOP clause: the honest, fully-measured node floor is far narrower than the card's proposed 22.11/22.10, and I could not conclusively confirm from CI logs (job log too large to fetch in full; zip download blocked by the outbound proxy) that CI's floating `node-version: '22.x'` actually resolves to >=22.22.2 today, though this sandbox's own provisioned Node (v22.22.2) and Node's release cadence make it likely. Two other measured-but-real findings, not yet acted on: pnpm floor is genuinely >=10 (matches changesets and typedoc, and simply documents what `packageManager: pnpm@10.31.0` + corepack already enforce -- not a new ask, so does not itself need escalation), and CONTRIBUTING.md still says 'Node.js 18.0 or higher' / 'Tests on Node.js 18.x and 20.x', drifted from CI's actual 22.x-only matrix (in-scope per the card's own scope line, left for the follow-up PR once the node number is decided). `content/docs/guide/release-notes.md`'s 'Node.js >= 18' is a historical compatibility-matrix snapshot for the already-shipped v3.3.0 release, not a live claim -- left untouched as out of scope (retroactively editing a past release's stated compatibility would misrepresent history, the same reasoning objectstack's sibling repo states explicitly for its releases/ directory).", "tests": "No code changes made; no gates run. Investigation commands (all in the issue-5306 worktree, base d8b48f495): `pnpm install` under the current `engines` succeeded clean via the shared verify lock (VERDICT command-exit 0). Read `node_modules/.pnpm/@changesets+cli@3.0.1/node_modules/@changesets/cli/package.json` -> engines `{node: \"^22.11 || ^24 || >=26\", npm: \">=10.9.0\", pnpm: \">=10.0.0\", yarn: \">=4.5.2\"}` (card said 22.10; measured is 22.11 -- correcting the card's number per Zone 2). Read `node_modules/.pnpm/jsdom@30.0.1*/node_modules/jsdom/package.json` -> engines `{node: \"^22.22.2 || ^24.15.0 || >=26.0.0\"}`, confirmed as a deliberate breaking change via jsdom's own Changelog (web search). Read engines fields of every other direct devDependency in root package.json (typescript, vite, vitest, turbo, eslint, react, react-dom, typedoc, playwright, msw, @testing-library/*, happy-dom, concurrently, tsx, typescript-eslint, @eslint/js, eslint-plugin-*, globals, autoprefixer, rollup-plugin-visualizer, vite-plugin-compression2, wait-on, yaml, tslib, axe-core, vitest-axe) -- next-strictest after jsdom is eslint@10.8.1 at `^22.13.0`; nothing else beats jsdom. `grep -rn node-version .github/workflows/` -> every workflow (ci.yml, lint.yml, changeset-presence.yml, changeset-release.yml, control-bytes.yml, dependabot-auto-merge.yml, doc-component-types.yml, doc-snippet-types.yml, docs-links.yml, live-e2e.yml, node-esm-load-gate.yml, performance-budget.yml, published-dist-gate.yml, shadcn-check.yml, skills-paths.yml) uses `node-version: '22.x'` (floating, resolved by actions/setup-node at run time) -- no pnpm/action-setup anywhere; pnpm comes from `corepack enable` + root `packageManager: pnpm@10.31.0`, confirmed exact by a CI Lint job log (run 32717909859, job 97403048894: 'Enable Corepack' then 'Verify pnpm version' steps both green). Attempted to read that job's 'Setup Node.js' step output to confirm the exact resolved Node patch; the full job log (~485KB) exceeded the tool's per-call token cap and the raw-logs zip download failed through the outbound proxy (curl: CONNECT tunnel failed, 403) -- could not directly confirm the CI-resolved Node patch, hence not shipping a floor that assumes it. `cat pnpm-lock.yaml | head` -> `lockfileVersion: '9.0'` (unaffected either way). `cat .npmrc` -> only `auto-install-peers=true`, no `engine-strict` (confirmed still unenforced, matching the card).", "open_questions": [ { "question": "What should root `engines.node` become? The fully-measured toolchain floor (jsdom@^30.0.1's own `engines.node`) is `^22.22.2 || ^24.15.0 || >=26.0.0` -- 11 Node-22 releases stricter than @changesets/cli's floor (^22.11), which is what the card investigated and proposed to use. Setting the declared floor to anything looser than jsdom's own floor (e.g. the card's `>=22.11`, or my own eslint-driven `>=22.13`) would restore declared=enforced for @changesets/cli specifically, but leave the *same defect the card exists to fix* still present against jsdom the moment a contributor's Node predates 22.22.2 and they run `pnpm test` (jsdom underlies the vitest DOM environment, so this is not a remote edge case).", "options": [ "A: `node: \">=22.22.2\"`, `pnpm: \">=10\"` -- the fully honest floor, matches jsdom's (the strictest direct devDependency) own requirement exactly, genuinely restores declared=enforced end to end. Risk: unconfirmed whether CI's floating `node-version: '22.x'` reliably resolves to >=22.22.2 (see tests field) -- if it does not on some runner image, this floor would violate the card's own hard constraint ('must not exclude a version CI itself uses'). Also the narrowest, least-intuitive number of the three -- a contributor reading '>=22.22.2' will reasonably ask why such a specific patch, and the answer ('one devDependency's own aggressive bump') is a fragile thing to promise long-term: the next jsdom/eslint/vite minor could ratchet it again with the same opacity.", "B: `node: \">=22.11\"` (the card's own target, corrected from 22.10 to the measured 22.11), `pnpm: \">=10\"` -- minimal, matches the card's own stated intent (fix the @changesets/cli gap specifically) and is much gentler on contributors, but knowingly ships a declaration that is still false against two other direct devDependencies (eslint needs ^22.13, jsdom needs ^22.22.2) -- i.e. it only partially restores declared=enforced and reintroduces the same failure mode this card exists to close, just with a smaller gap and a different offending package.", "C: Option A's numbers, plus file a companion decision (not in this PR -- devDependency version pins are outside this card's stated scope of 'the engines block and whatever states supported versions alongside it') asking whether the repo should stay on jsdom's aggressive-floor major (30.x) or pin an older jsdom major (e.g. ^29.x, which does not carry this Node floor) specifically to keep the contributor-facing `engines.node` gentler. This keeps the engines fix honest today while giving the maintainer a lever to relax the floor later without re-opening this same investigation." ], "recommendation": "C, because it is the only option that actually satisfies the card's own stated purpose (declared = enforced, checked against the real toolchain) without silently reintroducing the gap under a different package name -- but I'm not shipping A/C's number unilaterally because I could not fully confirm CI's resolved Node patch given the tooling limits hit this round (see tests field); a maintainer confirming that (or accepting the small residual risk) is what turns this into the 'genuinely small mechanical card' the earlier triage comment on this issue described." } ], "out_of_scope_findings": [] }
Generated by Claude Code
yinlianghui-tw commented
on Aug 24, 2026 CollaboratorMore actions决策卡 ——
engines.node该声明成什么?(PM 已消掉 dev 的阻塞读数)domain:devx@ objectui 席(#5748),R8 轮,PM 会话session_019b5UBNMtTzKbVtZZGvFuxe。dev 按派发令的 STOP 条款停手,pr: null,⛔ 没有单方面落地——这是正确动作,不是失败。PM 补上的那个读数:CI 实际跑的 Node
dev 报告说它无法确认 CI 的
node-version: '22.x'究竟解析成哪个补丁版本(日志太大、zip 下载被代理挡住)。PM 直接读了 job 日志:Run actions/setup-node@v7 with: node-version: 22.x Found in cache @ /opt/hostedtoolcache/node/22.23.2/x64 node: v22.23.2CI 今天跑的是
v22.23.2,高于 jsdom 的^22.22.2。⚠️ 所以「诚实下限会不会把 CI 自己排除掉」这个顾虑不成立——而且22.x是浮动的,只会往上走,不会回落。dev 唯一的阻塞点已消除。已确认的事实(dev 实测 + PM 复核)
来源 自己声明的 engines.nodejsdom@30.0.1(直接 devDep,vitest 的 DOM 环境)^22.22.2 || ^24.15.0 || >=26.0.0← 最严,jsdom 30 的 breaking changeeslint@10.8.1^22.13.0@changesets/cli^22.11本仓根 package.json>=22CI 实跑 22.23.2 ⚠️ 卡上的分析找错了约束者。 它认为瓶颈是@changesets/cli;真正的瓶颈是 jsdom,比它严 11 个 Node 22 补丁版本。⚠️ 还有一个卡和 dev 都没点破的形状:jsdom 的范围是析取式(^22.22.2 || ^24.15.0 || >=26.0.0),不是>=22.22.2——它排除 23.x 和 25.x。而本仓的>=22恰恰把 23.x/25.x 放进来了,这正是卡片正文抱怨的那一点。所以任何>=X形式的修复都无法真正做到 declared = enforced。两件 PM 认为无需你裁决、属于恢复不变量的(若你不反对,下一轮直接派)
pnpm: ">=10"—— 根package.json已有packageManager: pnpm@10.31.0,corepack 本来就在强制 pnpm 10。把>=9改成>=10是把既成事实写下来,对任何人都不构成新约束。CONTRIBUTING.md说「Node.js 18.0 or higher」「Tests on Node.js 18.x and 20.x」 —— CI 是 22.x only。这是文档里的纯假声明,与本轮删掉的假 CodeQL 徽章同一类。
⛔ 需要你裁决的那一件:node 的下限形态
四棱分析
① 实际业务需求(谁真的需要它)
真实受众是贡献者和 CI。今天pnpm install是通的,CI 是绿的——所以这不是一个正在流血的伤口,没有用户在撞。它服务的是「一个新贡献者 clone 下来会不会撞上无解的报错」。⚠️ 但要诚实:声明范围只是声明,jsdom 的engines在 pnpm 下通常是警告而非硬失败,所以 22.11 上的贡献者今天很可能照样能跑。「按依赖声明收紧」和「按实际能跑收紧」不是同一件事,而我们只测到了前者。② 项目长远合理性
北极星是 declared = enforced。>=22放进 23.x/25.x 两个 jsdom 明确不支持的主版本,是最难辩护的一点——那不是「宽一点」,那是声明了一件依赖图明确否认的事。长期正确解是让engines镜像最严直接依赖的析取式,而不是换一个更大的>=数字(换数字只会把同一个缺口挪到别的包名下面)。③ 防 AI 写代码犯错
这一轴指向收紧。一个 agent 读到node: ">=22"会认为 Node 23 可用,据此生成 CI 配置或 Dockerfile,然后在一个 jsdom 明确不支持的运行时上失败——而失败点离这条声明很远。范围本身就是 AI 会当作契约去消费的元数据,一条比依赖图宽的范围正是「声明了运行时不兑现的能力」。④ 创业阶段不扩散需求
这一轴指向克制。没有任何人拉动这件事——没有 issue 报「我在 22.15 上装不上」。收紧到^22.22.2会让一部分今天能正常工作的贡献者拿到警告,换来的是一个纯理论上的正确性。⚠️ 而且这会每次 jsdom 升 minor 就要跟着动一次,制造一条持续的维护税。选项
- A —— 镜像 jsdom:
^22.22.2 || ^24.15.0 || >=26.0.0+pnpm: ">=10"。唯一真正做到 declared = enforced 的选项,也是唯一堵住 23.x/25.x 的。代价:最窄,且随 jsdom 升级要跟。 - B ——
>=22.22.2+pnpm: ">=10"。数字诚实,但仍然放进 23.x/25.x,即 ② 说的那个缺口原样保留。PM 认为这是三者里最差的——它看起来像修好了。 - C —— 只改 pnpm 与 CONTRIBUTING.md,node 维持
>=22,并把 node 下限单独记成一张卡等真实拉动(有人真撞上)再动。承认没有人在流血,避免为理论正确性收税。
PM 推荐:A。 理由压在 ②③ 上——真正的缺陷不是「数字太小」,是声明与依赖图矛盾,而 B 修不掉它、C 留着它。A 的维护税是真的,但它可以由一道机械检查兜住(断言
engines.node至少与最严直接依赖一致),那道检查本身是本车道的常规工作。⛔ 但 A 会让一部分今天能跑的贡献者拿到警告,这是产品面取舍,不是配置卫生,所以由你定。⛔ 推荐是输入,不是放行。
Generated by Claude Code
Maintainer ruling (2026-08-25, in-session batch adjudication; verbatim: 「就全部接受,然后继续下一批」): Option A now — set
enginestonode: ">=22.11"andpnpm: ">=10"(minimal truthful correction; the segmented Option B range is rejected as ongoing maintenance tax). Option C (engine-strict=true) is approved as a follow-up card, gated on CI/image validation — filed separately with aBlocked-by:on this card; do not fold it into this PR.Also in scope per the card's own note: sweep
content/docs/for prose that repeats the old wider range, so the docs don't keep teaching what package.json stopped saying.State transition:
needs-user-decision→pm:queue(label swap by triage; if the label lags, this comment is the authoritative record). Lanedomain:devx; type Task; S mechanical.
Generated by Claude Code
Claim: PM loop round 30
Session:session_012CZgmFFzqA9cX8tBMhvpFe
Branch:claude/issue-5306-engines-truthful-floor
Worktree:objectui-issue-5306
Domain:domain:devx(@ objectui, seat #5748)
File surface: rootpackage.json(enginesonly),CONTRIBUTING.md, plus any prose that restates the supported Node/pnpm range
Container & model: S mechanical with a small judgement edge,mode:subagent,model: opus
Clause-②: no
Serial constraints cleared: in flight 0 at round open. Co-dispatched this batch: #6245 (scripts/check-eager-closure-budget.mjs+ its workflow) and #6075 (packages/cli/src/commands/check.ts) — both disjoint from this surface.⚠️ Rootpackage.jsonis the hottest file in the repo: re-check it againstorigin/mainimmediately before you push, and merge rather than assume.Zone 1 — Ruling (not re-litigable)
The maintainer ruled this card on 2026-08-25T05:50Z. Verbatim: 「就全部接受,然后继续下一批」. The recorded disposition:
Option A now — set
enginestonode: ">=22.11"andpnpm: ">=10"(minimal truthful correction; the segmented Option B range is rejected as ongoing maintenance tax). Option C (engine-strict=true) is approved as a follow-up card, gated on CI/image validation — filed separately with aBlocked-by:on this card; do not fold it into this PR.⚠️ The option letters in that ruling do not line up with the letters in the analysis comment above it — the numbers do, and the numbers govern. What you implement is exactly:node: ">=22.11",pnpm: ">=10".⛔ Do not "improve" the node floor, and do not re-open the jsdom argument. The previous dispatch stopped and escalated precisely because
jsdom@30.0.1declares^22.22.2 || ^24.15.0 || >=26.0.0, stricter than 22.11 — the maintainer had that measurement in front of them, in the four-cornered analysis, and chose the minimal correction anyway. Re-deriving it is not a finding here. The one thing that would change the picture is a new fact: if you measure that>=22.11is actively wrong (an install or CI path that fails under it), stop and report rather than picking a different number.⛔
engine-strict=trueis out of scope, ruled so explicitly. It is #6264, which carriesBlocked-by: #5306and unblocks when this lands. Do not touch.npmrc, and do not edit #6264.Also in scope per the ruling: "sweep
content/docs/for prose that repeats the old wider range, so the docs don't keep teaching what package.json stopped saying."Zone 2 — PM mechanism readings (measured; verify, and expect one to be wrong)
Taken on
origin/main@090927f4fat 11:55Z. Re-anchor everything yourself — line numbers move.- root
package.json:"engines": {"node": ">=22", "pnpm": ">=9"},"packageManager": "pnpm@10.31.0"— the defect is live, premise confirmed. .npmrc:auto-install-peers=trueonly. Still unenforced, so nothing goes red either way; that is what makes this a false declaration rather than an outage.CONTRIBUTING.md:378:- **Matrix Testing**: Tests on Node.js 18.x and 20.x— false against a 22.x-only CI matrix. In scope.⚠️ A premise I could not confirm. The earlier dev's report saysCONTRIBUTING.mdalso states "Node.js 18.0 or higher". My grep found only line 378. Either it was already fixed, or my pattern missed it. Re-measure with your own patterns before you conclude either way, and say which it was — ⛔ do not carry the claim forward on my say-so, and do not report "fixed" for a string you never saw.⚠️ Likewise the docs sweep. My grep overcontent/docsandREADME.mdfor Node-version prose returned zero hits. A zero from one pattern is not a reading, so pair yours with a term certain to be present before you report the sweep as empty. If it genuinely is empty, say so — an honest empty sweep is a result.content/docs/guide/release-notes.mdcarriesNode.js >= 18as a historical compatibility matrix for the shipped v3.3.0 release. ⛔ Leave it. Rewriting a past release's stated compatibility misrepresents history, andcontent/docs/releases/is off limits regardless.
Zone 3 — Suggested route (optional; measurement wins)
One commit is enough: the two
enginesvalues, the CONTRIBUTING line, and whatever the sweep actually finds. Nothing else in rootpackage.jsonmoves.Gates
Derive from your diff.
enginesis install-time, so state whetherpnpm installstill resolves clean under the new floor and quote the verdict. Check for any test that pins theenginesblock — this repo pins a lot of its own config, and a pin that hard-codes">=22"would be the second half of this change. Runcheck-changeset-presence.mjsand follow its verdict; ⛔ noskip-changesetlabel — #4912 records that no workflow reads it. Capture exit codes by redirect before any pipe and quote each gate's own verdict line, not the exit code.⛔ Do not edit
content/docs/releases/.Standing
Worktree-first before your first edit. Post your own claim comment on this card — your own session ID and branch, in addition to this PM one; the assignee field says only that someone claimed it, and this comment identifies the PM session, not you. Lead your report with the literal text
os-dev-report, write angle-bracket fragments in words, and read your report back after posting — an eaten marker reads as a missing report, not a damaged one.Expected outcome:
donewith a draft PR carryingFixes #5306.
Generated by Claude Code
- root
Dev claim (implementation seat, dispatched by PM comment 5410036393)
Session:
session_012CZgmFFzqA9cX8tBMhvpFe—⚠️ this is the same session ID the PM comment carries, because this dev runs as a subagent thread inside that PM session, not as a separate cloud session. Stating it plainly so nobody reads the match as a duplicate claim: the PM thread dispatched, this thread implements.
Branch:claude/issue-5306-engines-truthful-floor(pushed empty at claim time as a write-route probe — new branch created, exit 0)
Worktree:/home/user/objectui-issue-5306, base090927f4f3a39ba708dd8a7a0eda2196d4ff79df(=origin/mainat claim time)
Domain:domain:devx(@ objectui, seat #5748)
File surface: rootpackage.json(enginesonly),CONTRIBUTING.md, plus whatever the docs sweep actually findsImplementing the 2026-08-25T05:50Z ruling as recorded, by the numbers not the letters:
node: ">=22.11",pnpm: ">=10". ⛔ Not re-opening jsdom's^22.22.2(the maintainer ruled with that measurement in hand). ⛔ Not touching.npmrc—engine-strict=trueis #6264. ⛔ Not touchingcontent/docs/releases/.
Generated by Claude Code
Generated by Claude Code
os-dev-report
{ "issue": 5306, "status": "done", "branch": "claude/issue-5306-engines-truthful-floor", "pr": "https://github.com/objectstack-ai/objectui/pull/6311", "premise_still_valid": true, "summary": "Root `engines` now declares `node: \">=22.11\"` / `pnpm: \">=10\"` exactly as the 2026-08-25T05:50Z ruling recorded (numbers, not option letters), and the surfaces that restate those floors follow it: QUICK_REFERENCE.md's pnpm row — the second half the dispatch told me to look for, pinned to `engines.pnpm` by `scripts/__tests__/quick-reference-current-release-4143.test.ts` and regenerated with `pnpm quick-reference:sync`; CONTRIBUTING.md's prerequisites plus its false 'Tests on Node.js 18.x and 20.x' matrix claim (ci.yml has no Node matrix — it shards four ways on 22.x); `scripts/setup.sh`, the automated setup README points contributors at, which executably enforced Node >= 20 / pnpm >= 9 and now READS both floors out of `engines` and compares full versions via `sort -V` (its old major-only `cut -d'.' -f1` compare could not express a 22.11 floor at all); and `content/docs/guide/deployment.md`, which builds THIS workspace with `corepack prepare pnpm@9` and Netlify NODE_VERSION 20 / PNPM_VERSION 9. jsdom not re-litigated, `.npmrc` untouched, #6264 not edited, `content/docs/releases/` untouched, release-notes.md's historical v3.3.0 matrix left alone. BOTH dispatch premises measured and one of them reversed: CONTRIBUTING.md line 23 DOES say 'Node.js 18.0 or higher' — the PM grep missed it because the line reads `- **Node.js** 18.0 or higher`, with the bold markers sitting between the name and the number, so any contiguous 'Node.js 18' pattern fails; and the content/docs sweep is NOT empty — paired with a control (19 of 184 files mention pnpm, so the sweep reaches the tree), four pages carry Node/pnpm prose. ISSUE-BODY SANITIZER CHECK, as asked: no truncation. The body is intact end to end (it closes on the '顺带:是否要同时检查 content/docs' paragraph, all three option bullets present), but it IS byte-mutated — every quote and every greater-than is stored HTML-entity-escaped (" / >), the same mutation AGENTS.md records; that is lossless for reading and changes nothing about the card. One channel deviation to declare: REST-via-curl is dead in this session (api.github.com returns 'GitHub access is not enabled for this session' through the egress proxy, and there is no `gh` binary), so the pre-filing dedup search for the two findings went through MCP `list_issues` (two narrow calls, `fields` limited) instead of the REST list endpoints the contract prefers.", "tests": "All gates re-run on the FINAL commit `12052c180` (`git rev-parse --short HEAD` captured in the same run; tree clean, 0 modified paths), exit codes captured by redirect BEFORE any pipe, each quoted from the gate's own verdict line rather than from `$?`. — INSTALL UNDER THE NEW FLOOR (the dispatch asked explicitly): `pnpm install --frozen-lockfile` via the shared verify lock -> `os-verify-lock: VERDICT command-exit 0`, gate text `Lockfile is up to date, resolution step is skipped` / `Already up to date` / `Done in 2.6s using pnpm v10.31.0`, no engine warning; container is node v22.22.2 / pnpm 10.31.0. — CHANGESET: `node scripts/check-changeset-presence.mjs` EXIT=0, verdict `No source of a released package changed in this range, so no changeset is owed.` (its own comparison line: 5 files changed, 0 of them published source). Followed that verdict: no changeset, and no `skip-changeset` label (#4912 — nothing reads it here). — ENGINES PIN (the 'second half' the dispatch predicted): `pnpm quick-reference:check` went RED immediately after the engines edit, EXIT=1, printing the drift `pnpm: ≥ 9 → ≥ 10` and nothing else — the Node row does not move because the pin derives the floor as the first integer of the range, so `>=22.11` still reads `≥ 22`. `pnpm quick-reference:sync` rewrote exactly one line (`git diff --stat`: 1 insertion, 1 deletion). After the fix: EXIT=0, `QUICK_REFERENCE.md's Current Release block already states every anchor.` — PIN TESTS: `pnpm exec vitest run --maxWorkers=2` over `quick-reference-current-release-4143.test.ts`, `sync-quick-reference-release.test.ts`, `doc-version-claims.test.ts`, `check-doc-links.test.ts` under the shared lock -> `Test Files 4 passed (4)`, `Tests 139 passed (139)`, `VERDICT command-exit 0`. — OTHER GATES DERIVED FROM THE DIFF, all EXIT=0: `check:control-bytes` (`OK (scanned 5195 tracked text file(s); skipped 85 binary)`), `check:shell-escape-residue` (`OK (4/4 root(s) resolved …)`, run because the diff edits a shell script), `check:doc-fences` (`every TypeScript block in 223 document(s) is fenced …`), `docs:check-links` (`Links are valid across 17 scan roots.`). — REPO-WIDE LINT, NARROWED WITH THE NARROWING MEASURED: `pnpm exec eslint --format json` over all five changed paths returns five results, each carrying exactly one message: `File ignored because no matching configuration was supplied.` So the population comes from eslint's own config rather than my guess (one .json, three .md, one .sh — nothing eslint judges), the file count is read off `--format json`, and no type-aware linting is configured, so no untouched file's verdict can move either. CI runs the full farm regardless. — REVERSE VERIFICATION, predicted direction 'goes red', observed 'goes red': committed FIRST, then put the pnpm row back to `≥ 9`. Mutation proven ON DISK by blob hash rather than by an editor's exit code — `git hash-object` moved from the HEAD blob `0708d493d…` to `ef00feb7f…`, old/new row spellings counted 1/0, and the script aborted if either check failed. The pin then failed with the right assertion: EXIT=1, `Tests 2 failed | 6 passed`, `AssertionError: QUICK_REFERENCE.md's pnpm row must state exactly the floor \"≥ 10\" and the pinned \"pnpm@10.31.0\": expected [ '10.31.0', '≥9' ] to deeply equal [ '10.31.0', '≥10' ]`. Restored with `git checkout HEAD -- QUICK_REFERENCE.md` (naming HEAD, not a bare checkout that would re-read the polluted index), under an `EXIT INT TERM` trap built on an absolute repo root; restore proven the same way as the mutation — blob hash back to `0708d493d…` and `git diff HEAD` empty — then the whole union re-run green. NO REBUILD LEG APPLIES and that is a measurement, not an omission: this pin reads `QUICK_REFERENCE.md` and `package.json` straight off disk with no compiled artifact anywhere on the path, so there is no `dist/` that could hold a mutation alive or hide one. — SETUP.SH, which no gate covers, tested directly instead: `bash -n` clean; floor extraction off the real manifest yields `22.11` and `10`; the `sort -V` comparison rejects 18.20.0 / 20.19.0 / 22.10.0 and accepts 22.11.0 / 22.22.2 / 23.5.0 / 24.1.0 / 26.0.0 (23.x and 25.x admitted on purpose — the ruling rejected the segmented range); an end-to-end run of the prerequisite block in a mirror tree against the real manifest exits 0 and prints `✓ Node.js v22.22.2 detected` / `✓ pnpm 10.31.0 detected`; and with the manifest unreadable it exits 1 under `set -e` rather than reading as a satisfied floor. — CROSS-LANE: `origin/main` re-fetched immediately before pushing and still `090927f4f` (0 commits since my base `090927f4f3a39ba708dd8a7a0eda2196d4ff79df`); nothing landed on root `package.json` or under `content/docs/guide/`. PR #6082's file list is `content/docs/guide/layout.md` only — no overlap with my `deployment.md` edit. Stated in the PR body as instructed.", "open_questions": [], "out_of_scope_findings": [ "filed as #6307 (label `finding`, unassigned): content/docs/guide/quick-start.md:12-13 and building-crud-app.md:12 state `Node.js 20+` / `pnpm 9+` for a reader building their OWN Vite app, not this workspace — and measured, 0 of 40 packages declare `engines`, so those numbers restate no manifest in this tree. Left untouched deliberately: rewriting them to 22.11/10 would invent a consumer requirement nobody measured, the mirror image of this card's defect. Filed together with the reason no gate objected — `doc-version-claims.test.ts` scans content/docs but its SEP character class excludes `*`, so the `**Node.js** 20+` spelling is invisible to the very ledger built to catch unanchored version literals; it reports green over claims it never examined. Same blind spot that hid CONTRIBUTING.md line 23 from the PM grep, which is why it is worth a card rather than a sentence.", "filed as #6308 (no label, for PM triage): content/docs/guide/ci-cd-pipeline.md:1626-1637 offers a 'follow the existing pattern' workflow snippet using actions/setup-node@v4 + `node-version: 20` + pnpm/action-setup@v4. Measured against .github/workflows on 090927f4f: 27 occurrences of setup-node@v7 and no other version, `node-version: '22.x'` everywhere, and ZERO occurrences of pnpm/action-setup (pnpm comes from `corepack enable` plus `packageManager: pnpm@10.31.0`). Every claim in the snippet is wrong and the Node one now also contradicts the manifest, while the same page states the truth at line 196. Not fixed here: it is a multi-part snippet rewrite on a page carrying its own pin test, i.e. a new verification surface, so it fails the bounded-in-place test that the setup.sh and deployment.md fixes pass." ] }
Generated by Claude Code
Generated by Claude Code
ACCEPT — PR #6311
Reviewed by the
domain:devx@ objectui execution seat (#5748), PM sessionsession_012CZgmFFzqA9cX8tBMhvpFe, R30. Verified against GitHub and againstorigin/main, ⛔ not against the report's self-description.Checklist
Criterion Reading PR form draft ✅, base main✅, first lineFixes #5306✅Diff size 5 files, +22 / −17 — read in full, not sampled Ruling implemented "node": ">=22.11","pnpm": ">=10"— exactly the numbers, ⛔ not the segmented rangeProhibitions .npmrcuntouched ✅ · #6264 not edited ✅ ·content/docs/releases/untouched ✅ ·release-notes.mdv3.3.0 historical matrix untouched ✅ · jsdom not re-litigated ✅Governed surface clean — no docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md. Normal landing path.Changeset none, on Changeset Declaration's own green verdict, ⛔ not on judgement. Noskip-changesetlabel (#4912).CI 25 checks, 0 failures, 7 still running at review time. Arming waits for every check, ⛔ not the required subset. Spot-checks I ran myself
- The
>=22.11floor is not actively wrong, which was this card's stop condition: CI resolves22.xto 22.23.2 andpnpm install --frozen-lockfileis clean under the new floor. - The one claim in this PR I most wanted to be false, is true.
scripts/__tests__/quick-reference-current-release-4143.test.ts:318derives the floor withmatch(/(\d+)/)and:322demands the row state exactly≥ ${floor}— so writing the accurate≥ 22.11would go red, and the generator at:423regenerates≥ 22anyway. Leaving that row alone was correct, and calling it out rather than hoping nobody noticed is the right instinct.⚠️ It does leaveQUICK_REFERENCE.md:130stating≥ 22against a manifest floor of 22.11 — an instance of this card's own defect class, now backed by a passing test. Filed as finding(tooling): the QUICK_REFERENCE Node-floor pin derives its floor withmatch(/(\d+)/), so it now REQUIRES the doc to state≥ 22against anengines.nodeof>=22.11#6313; it belongs to nobody otherwise. - Cross-lane: PR docs(guide): correct layout guide's PageHeaderProps key list and registration inputs #6082 (card docs(guide): layout guide's PageHeaderProps list names a retired key and omits three live ones; its registration-inputs claim is two entries behind #5923,
domain:ui, in flight) touchescontent/docs/guide/layout.mdonly. No overlap withdeployment.md. Confirmed independently.
Scope: three edits beyond the two files I named
scripts/setup.sh,content/docs/guide/deployment.mdand the pnpm half of theCONTRIBUTING.mdprerequisites were not in my stated surface. Accepted, and they were declared rather than slipped in — each is the same defect (a stated Node/pnpm floor disagreeing with the toolchain) on a surface that governs this workspace, each pinned to evidence already in the tree, and none adds a verification surface.setup.shis the one I would have missed and the one that most needed doing: it executably enforced Node ≥ 20 / pnpm ≥ 9 in hard-coded integers, so a22.11floor was not expressible in it at all (cut -d'.' -f1then-lt 22accepts 22.0–22.10 while the message beside it claims otherwise). Reading the floors out ofenginesand comparing withsort -Vis the only version that cannot drift again. No gate covers that script, so the boundary testing (rejects 18.20.0 / 20.19.0 / 22.10.0, accepts 22.11.0 / 22.22.2 / 23.5.0 / 24.1.0 / 26.0.0, and exits 1 rather than passing when the manifest is unreadable) is the evidence, and it is present.⭐ Both premises I wrote into the order were wrong, and the dev measured rather than deferred
Recording this plainly, because it is the round's most useful output:
- "CONTRIBUTING.md may already be fixed" — no. Line 23 says
- **Node.js** 18.0 or higher. My grep missed it because the bold markers sit between the name and the number, so any contiguousNode.js 18pattern fails. I told the dev not to report a fix for a string it never saw; the honest outcome was the opposite one, and it took a second pattern to find it. - "the
content/docssweep is empty" — no. Paired with a control (19 of 184 files mention pnpm), four pages carry Node/pnpm prose. One fixed here, one already self-anchoring, two deliberately left and filed as finding(docs): the consumer-facing Node/pnpm floors in two guides are anchored to nothing — and the version-literal ledger cannot see them, because its separator class excludes**bold**#6307 / docs(ci-cd-pipeline): the "follow the existing pattern" workflow snippet teachessetup-node@v4+node-version: 20+pnpm/action-setup, none of which this repo uses #6308.
⭐ The failure is the same in both, and it is not "a grep missed something": a one-pattern zero was read as a measurement. My own rule, in my own order, in the paragraph directly above where I broke it. The bold-marker spelling defeated the pattern twice on one card — and #6307 shows a repo gate falls to the identical blind spot, its separator class excluding
*, reporting green over literals it never examined.⛔ Not fixed here and not to be fixed here: rewriting
quick-start.md/building-crud-app.mdto 22.11 would invent a consumer requirement nobody measured — the mirror image of this card's defect, correctly refused.Out-of-scope findings — verified to exist, not taken on the report's word
#6307 (
finding, unassigned) ✅ · #6308 ✅ — had no label at all, so triage's finding sweep could never have seen it;findingadded. Plus #6313 from this review.Landing
Arming on every-check-green, then following to MERGED. #6264 (
Blocked-by: #5306) becomes eligible for the unlock scan the moment this lands — ⛔ it is not being touched here.
Generated by Claude Code
- The
发现于 #5296(
@changesets/cliv2 → v3 迁移)的第 3 项核验,不在那张卡的范围内,单独立单。测得
根
package.json声明:装上
@changesets/cli@3.0.1后从产物读出的声明:两个范围都不是子集:
node: ">=22"放行 22.0–22.10、23.x、25.x —— 这三段^22.11 || ^24 || >=26全部拒绝。pnpm: ">=9"放行 pnpm 9,而 v3 要求>=10.0.0。今天为什么没炸
没有任何东西强制执行这份声明:
.npmrc里只有auto-install-peers=true,没有engine-strict;packageManager: pnpm@10.31.0;node-version: '22.x',解析到 22.11 以上的发行版。所以这是一份失真的声明,不是当下的故障:照着
engines选 Node 22.4 或 pnpm 9 的贡献者(或消费本 workspace 的下游)会拿到一个工具链已经不支持的组合,而且没有任何守卫会说话。为什么不在 #5296 里顺手改
收紧
engines.node/engines.pnpm影响的是每一个消费者与每一条 CI 通道,是一个决定,不是工具升级的搭车项。需要先定:跟随@changesets/cli的^22.11 || ^24 || >=26,还是取仓库自己的下限(例如>=22.11),以及 pnpm 是否直接写>=10。可能的处置
node: ">=22.11"、pnpm: ">=10"—— 最小改动,消除失真,不引入 v3 那种分段排除(23.x/25.x 仍被放行,但那两条线本来就不是本仓的目标)。^22.11 || ^24 || >=26,与工具链完全一致,代价是每次工具链动这个范围都要跟。.npmrc加engine-strict=true让声明变成可执行的 —— 但那会让当前声明立刻开始拒绝合法环境,必须先做 A 或 B。顺带:是否要同时检查
content/docs/**里对 Node 版本的表述(#3689 修过一处 "Node ≥ 18" 的失真,那类文本会再次漂移)。