Skip to content

Follow-up to #5306: turn on engine-strict=true once the corrected engines range is validated against CI images and contributor environments #6264

Closed

Description

@os-steve

Blocked-by: #5306

Spun out of the #5306 maintainer ruling (2026-08-25, batch adjudication; Option A accepted now, Option C approved as this gated follow-up — verbatim acceptance: 「就全部接受,然后继续下一批」).

What

Add engine-strict=true to .npmrc, turning the (now truthful, post-#5306) engines declaration from documentation into enforcement — a contributor or agent on node 22.4 / pnpm 9 gets refused at install time instead of a silently unsupported toolchain.

Gate (why blocked, and what to verify before flipping)

  1. 根 engines 声明比工具链实际要求宽:node ">=22" 放行 22.0–22.10 / 23.x / 25.x,pnpm ">=9" 放行 pnpm 9 —— @changesets/cli@3 全部拒绝 #5306 must land first (enforcing the old lying range would block valid environments — the card's own Option C warning).
  2. Before enabling, verify against the environments that would be refused: CI workflow images (every workflow that runs pnpm install), the documented contributor setup, and the remote-agent session images. Record the readings in this card; if any live environment fails the corrected range, fixing THAT is a prerequisite, not a reason to widen the range back.

Refs: #5306 (the ruling and the corrected range).

Activity

  1. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 25, 2026
  2. self-assigned this
    on Aug 25, 2026
  3. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    Claim: PM loop round 30
    Session: session_012CZgmFFzqA9cX8tBMhvpFe
    Branch: claude/issue-6264-engine-strict
    Worktree: objectui-issue-6264
    Domain: domain:devx (@ objectui, seat #5748)
    File surface: .npmrc, plus whatever the validation readings show must be fixed first
    Container & model: M, mode:subagent, model: opus
    Clause-②: no
    Serial constraints cleared: co-dispatched #6194 (packages/plugin-form/**) — disjoint. ⛔ Nothing in flight touches .npmrc.

    Unblocked — gate 1 is discharged, and here is the proof

    Blocked-by: #5306. #5306 landed this round as PR #6311, squashed to main as b1c1e3bfe ("fix(repo): declare the Node/pnpm floors the toolchain actually requires"). Root package.json now reads:

    "engines": { "node": ">=22.11", "pnpm": ">=10" }

    So the range this card would enforce is the corrected one, and the card's own Option-C warning — ⛔ "enforcing the old lying range would block valid environments" — no longer applies.

    ⚠️ Re-verify that on your own base before touching .npmrc. If engines does not read exactly the above, stop: something reverted or amended it and this card's premise is gone.

    Zone 1 — Ruling and the gate that remains (binding)

    The maintainer approved Option C as this gated follow-up (2026-08-25, verbatim 「就全部接受,然后继续下一批」). ⭐ Gate 2 is the work, not a formality, and the card states it precisely:

    verify against the environments that would be refused: CI workflow images (every workflow that runs pnpm install), the documented contributor setup, and the remote-agent session images. Record the readings in this card; if any live environment fails the corrected range, fixing THAT is a prerequisite, not a reason to widen the range back.

    ⛔ That last clause is the one to hold onto. If something fails, the answer is never to loosen engines — that would undo #5306 and re-create the exact false declaration this family exists to remove. Fix the environment, or stop and report.

    Zone 2 — PM mechanism readings (verify; and one of these I could not take)

    • .npmrc on origin/main carries only auto-install-peers=true — no engine-strict anywhere. So this is an addition, not a value change.
    • Every workflow uses node-version: '22.x', which resolved to 22.23.2 in a CI job log read on 根 engines 声明比工具链实际要求宽:node ">=22" 放行 22.0–22.10 / 23.x / 25.x,pnpm ">=9" 放行 pnpm 9 —— @changesets/cli@3 全部拒绝 #5306 — above the 22.11 floor. pnpm comes from corepack enable plus root packageManager: pnpm@10.31.0, i.e. 10.31.0 ≥ 10. Both look clean, but ⛔ that is one job log from one workflow, and the card asks for every workflow that runs pnpm install. Enumerate them and say which you checked.
    • ⚠️ The remote-agent session images are a reading I cannot take and neither may you be able to. If you cannot enumerate or probe them, ⛔ do not silently drop that third population — say plainly that it is unverified and let the report carry the gap. An unverified third of the gate is a finding, not a rounding error.
    • Node's own release cadence only moves 22.x upward, so the CI half is stable in the safe direction. Contributor machines are not — that is the population this flag actually bites.

    ⭐ What "verify" has to mean here, because a green install proves less than it looks

    engine-strict changes behaviour only for an environment outside the range. An install succeeding on a machine that already satisfies the floor tells you nothing about the flag. So the measurement that counts is the refusal: show that a node/pnpm combination below the floor is actually refused with the flag on, and that every environment you enumerated is above it. ⛔ A green pnpm install on the container you happen to be running in is not evidence.

    Blast radius, stated so you can weigh a stop

    This flag refuses installs repo-wide. It lands on contributors and on other agent seats, not on you, and the failure appears at pnpm install — before any of this card's context is visible to whoever hit it. That asymmetry is why the card demands readings first. If the readings are not clean, needs_decision with what you measured is the better delivery, and it is not a failed run.

    Gates

    Derive from your diff. A .npmrc change is install-time, so state whether pnpm install --frozen-lockfile still succeeds with the flag on. Check for any test pinning .npmrc contents. Follow check-changeset-presence.mjs's own verdict; ⛔ no skip-changeset label (#4912). Capture exit codes by redirect before any pipe and quote each gate's verdict line.

    ⛔ Do not edit content/docs/releases/. ⛔ Do not touch root package.json — #5306 settled the range and it is not reopened here.

    Standing

    Worktree-first before your first edit. Post your own claim comment with your branch — ⚠️ under mode:subagent your session ID is identical to mine (objectstack#12220), so the branch distinguishes it. Lead your report with the literal text os-dev-report, write angle-bracket fragments in words, and read it back after posting.

    Expected outcome: done with a draft PR carrying Fixes #6264, or needs_decision with the readings if any enumerated environment fails the corrected range.


    Generated by Claude Code

  4. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    Dev claim (os-dev subagent, dispatched by PM round 30)

    Branch: claude/issue-6264-engine-strict
    Worktree: objectui-issue-6264
    Session: session_012CZgmFFzqA9cX8tBMhvpFe — identical to the PM's under mode:subagent, so the branch is the distinguishing identifier for this claim.
    File surface: .npmrc (addition of engine-strict=true), plus a changeset if the presence gate demands one. Root package.json is out of surface by dispatch order.

    Starting gate-1 re-verification on my own base, then the gate-2 environment enumeration. Readings will be posted on this card before any flip.


    Generated by Claude Code


    Generated by Claude Code

  5. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    Gate 2 readings (recorded here as the card requires)

    Branch claude/issue-6264-engine-strict, final commit 34f337cb4. Draft PR #6351 carries the same readings with full context.

    Gate 1 re-verified on my own base

    Base ed859aa35; root package.json reads exactly "engines": { "node": ">=22.11", "pnpm": ">=10" }. Premise holds. Root package.json untouched.

    The refusal — measured, not assumed

    pnpm install --frozen-lockfile against this repository, with a real below-floor node from nvm install 22.4.0 and a real pnpm 9 from npm install pnpm@9:

    toolchain engine-strict exit verdict line
    node 22.4.0 (below floor), pnpm 10.31.0 on 1 ERR_PNPM_UNSUPPORTED_ENGINE — Expected version: >=22.11 / Got: v22.4.0
    node 22.4.0, pnpm 10.31.0 off 0 WARN Unsupported engine: wanted: {"node":">=22.11"}
    node 22.22.2, pnpm 9.15.9 (below floor) on 1 ERR_PNPM_UNSUPPORTED_ENGINE — Expected version: >=10 / Got: 9.15.9
    node 22.22.2, pnpm 9.15.9 off 1 ERR_PNPM_UNSUPPORTED_ENGINE — same
    node 22.22.2, pnpm 10.31.0 (above both) on 0 Done in 2.5s using pnpm v10.31.0

    ⭐ Two results worth the PM's attention.

    (1) The flag's delta is the node axis only. Rows 3 and 4 show engines.pnpm is already a hard failure today, flag or no flag — pnpm always refuses on its own version. So this change adds nothing on the pnpm axis; what it changes is engines.node, from a WARN to a refusal. The card's framing ("a contributor or agent on node 22.4 / pnpm 9 gets refused") is half already true.

    (2) A confound that nearly produced a false reading. My first run showed the below-floor install passing with the flag on, which reads as "the flag does nothing". It was not. Because the manifest declares packageManager, pnpm re-execs itself and compares the node on PATH, not the node it was launched with — bisected to that single manifest field, and confirmed by a probe where pnpm reported Got: v22.22.2 while running under 22.4.0. The corrected test puts 22.4.0 on PATH as the only node. Anyone re-verifying this should use PATH, not a launcher.

    Cold-install evidence for dependency engines: the first install on this branch with the flag on installed all 1621 packages across 47 workspace projects, exit 0. No dependency's engines refuses at the current toolchain. A future dependency bump declaring a higher node floor would now hard-fail rather than warn.

    Population 1 — CI workflows: 12 install, all above the floor

    A plain grep overcounts: four workflows only discuss pnpm install in comments (doc-component-types, doc-fence-languages, pre-install-import-graph, half-state-patrol are deliberately install-free). The real list, comment lines excluded:

    ci.yml (6 jobs), lint.yml, live-e2e.yml, node-esm-load-gate.yml, doc-snippet-types.yml, performance-budget.yml, published-dist-gate.yml, readme-exports.yml, shadcn-check.yml, spec-range-floors.yml, changeset-release.yml.

    Every one: corepack enable, actions/setup-node@v7 with node-version: '22.x', then pnpm install --frozen-lockfile. pnpm resolves through corepack against packageManager: pnpm@10.31.0, above the 10 floor.

    node-version: '22.x' measured against nodejs.org/dist/index.json resolves to v22.23.2 — independently reproducing the 22.23.2 you read in the #5306 job log. ⚠️ Note 13 of the 35 released 22.x versions are below 22.11, so "node 22" is not automatically above the floor; only "latest 22.x" is. 22.x moves upward only, so this half is stable in the safe direction.

    Population 2 — documented contributor setup: matches exactly

    CONTRIBUTING.md lines 23-24 already state Node.js 22.11 or higher and pnpm 10 or higher, each tied to the declaration it comes from. Nothing to fix, no prerequisite.

    The "Node.js 20+" lines in content/docs/guide/quick-start.md and building-crud-app.md are outside this flag's radius — they describe building a reader's own app via pnpm add @object-ui/*, not installing this repo. Confirmed structurally: no publishable package declares engines, and there is no package-level .npmrc.

    Population 3 — remote-agent session images: UNVERIFIED

    The container I built this in reads node v22.22.2, pnpm 10.31.0 — above both floors. ⛔ That is one image measured from the inside, not the fleet, and I am not dropping the distinction. Nothing in the repo would let the fleet be enumerated: no .nvmrc, no .tool-versions, no devcontainer.json, no Dockerfile, and no node version in .claude/, .agents/, .vscode/ or .mcp.json.

    If any image in the fleet runs node below 22.11 with pnpm 10 or above, every seat on it breaks at pnpm install on merge, before any of this card's context is visible. Bounding it but not closing it: such an image is already emitting WARN Unsupported engine on every install today, and the pnpm axis already hard-fails today regardless of this flag.

    Verdict

    No enumerated environment fails the corrected range, so nothing became a prerequisite and the range was not widened. The gate is satisfied for everything measurable and open on population 3 — a maintainer call, which is why PR #6351 is a draft.


    Generated by Claude Code


    Generated by Claude Code

  6. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 6264,
      "status": "done",
      "branch": "claude/issue-6264-engine-strict",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6351",
      "premise_still_valid": true,
      "summary": "Added engine-strict=true to .npmrc (one-file diff) after taking the gate-2 readings the card demands. Card body checked for sanitizer truncation: NOT truncated, it ends cleanly at the 'Refs: #5306' line; apostrophes arrive entity-encoded as ' which is normal encoding, not loss. Gate 1 re-verified on my base ed859aa35: root package.json reads exactly node >=22.11 / pnpm >=10, so the premise holds and I did not touch it. Two findings change how this card should be read. (1) The flag's delta is the NODE axis only: pnpm 9.15.9 is refused with ERR_PNPM_UNSUPPORTED_ENGINE whether the flag is on or off, because pnpm always hard-fails on its own version, so this change adds nothing on the pnpm axis; what it changes is engines.node, from a WARN to a refusal. (2) A confound nearly produced a false negative: my first below-floor run PASSED with the flag on, which reads as 'the flag does nothing'. It is not. Because the manifest declares packageManager, pnpm re-execs and compares the node on PATH rather than the node it was launched with. I bisected that to the single packageManager field and confirmed it with a probe reporting Got: v22.22.2 while running under 22.4.0. The corrected test puts 22.4.0 on PATH as the only node and refuses correctly. All three populations enumerated; none of the environments I could measure fails the corrected range, so nothing became a prerequisite and the range was NOT widened. Population 3 (remote-agent session images) is one sampled image, not the fleet, and is reported as unverified rather than passed. PR is a draft for that reason.",
      "tests": "All on final commit 34f337cb4; exit codes captured by redirect before any pipe; each gate quoted by its own verdict line. REFUSAL MEASUREMENTS, pnpm install --frozen-lockfile against the real repo, below-floor node from a real 'nvm install 22.4.0' and pnpm 9 from a real 'npm install pnpm@9': node 22.4.0 + flag ON -> exit 1, 'ERR_PNPM_UNSUPPORTED_ENGINE ... Expected version: >=22.11 / Got: v22.4.0'; node 22.4.0 + flag OFF (control that makes the refusal attributable) -> exit 0, only 'WARN Unsupported engine: wanted: {\"node\":\">=22.11\"} (current: {\"node\":\"v22.4.0\",\"pnpm\":\"10.31.0\"})'; pnpm 9.15.9 + flag ON -> exit 1 'Expected version: >=10 / Got: 9.15.9'; pnpm 9.15.9 + flag OFF -> exit 1, SAME refusal (already enforced today); node 22.22.2 + pnpm 10.31.0 + flag ON -> exit 0, 'Done in 2.5s using pnpm v10.31.0'. The reverse-verification that produced the flag-OFF control mutated .npmrc, proved the mutation on disk (engine-strict occurrence count 1 -> 0, and pnpm config get engine-strict returning 'undefined' vs 'true'), then restored via 'git checkout HEAD -- (abs path)' and proved the restore by blob hash: disk ad683a47cfecb09ecb0fa737a06187af7e655b68 == HEAD ad683a47cfecb09ecb0fa737a06187af7e655b68, with 'git diff HEAD' empty. COLD-INSTALL EVIDENCE for dependency engines: first install on this branch with the flag on installed 1621 packages across 47 workspace projects, exit 0. CI ENVIRONMENT: node-version '22.x' measured against nodejs.org/dist/index.json resolves to v22.23.2 (independently reproducing the PM's job-log reading); 13 of 35 released 22.x versions are BELOW 22.11, so 'node 22' is not automatically safe. GATES: 'node scripts/check-changeset-presence.mjs' exit 0 -- 'No source of a released package changed in this range, so no changeset is owed' (so no changeset added, and no label; objectui has no skip-changeset mechanism); 'node scripts/check-control-bytes.mjs' exit 0 -- 'check-control-bytes: OK (scanned 5229 tracked text file(s); skipped 85 binary)'; 'node scripts/check-pre-install-import-graph.mjs' exit 0 -- 'check-pre-install-import-graph: OK'; direct grep -naP control-character scan of .npmrc returned no matches. CLEANUP: I also undid a shared-container mutation my own testing caused -- nvm had set its DEFAULT alias to the below-floor 22.4.0, which would have handed node 22.4.0 to any other agent that sourced nvm; alias removed and 22.4.0 uninstalled, container back to node v22.22.2 / pnpm 10.31.0.",
      "open_questions": [
        {
          "question": "Population 3 of gate 2, the remote-agent session images, cannot be enumerated from a dev seat. I measured the one container I run in (node v22.22.2, pnpm 10.31.0, above both floors) but that is one sample, not the fleet, and the repo pins nothing that would enumerate it (no .nvmrc, .tool-versions, devcontainer.json, Dockerfile, and no node version in .claude/, .agents/, .vscode/ or .mcp.json). If any image in the fleet runs node below 22.11 with pnpm 10 or above, every seat on it breaks at pnpm install on merge, before any of this card's context is visible to whoever hits it. Who can verify the fleet, and should the merge wait on that?",
          "options": [
            "A: merge the draft as-is, accepting the residual risk. Bounded by two measured facts: any such image is ALREADY emitting 'WARN Unsupported engine' on every install today, and the pnpm axis already hard-fails today regardless of this flag, so the only new refusal is node below 22.11 with pnpm at or above 10.",
            "B: hold the PR until someone with fleet visibility confirms the session images are above the node floor, then merge unchanged.",
            "C: land a .nvmrc / .tool-versions pin alongside the flag so the floor is self-serving for anyone using a version manager, narrowing the population that can be caught out.",
            "D: widen the engines range to cover whatever the fleet runs -- listed only to reject it: the card forbids it and it would undo #5306 and re-create the false declaration this family exists to remove."
          ],
          "recommendation": "B if fleet visibility is cheap for the maintainer, otherwise A. The risk is genuinely narrow -- the pnpm half of the card's stated scenario is already enforced today, so the only behaviour change is node below 22.11, and any machine in that state is already being warned on every single install. C is a good follow-up either way but should not be smuggled into this PR. D is out of bounds."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED (reason below): changelog.yml configures a git merge driver 'pnpm install --no-frozen-lockfile' (line 40) but the workflow has no 'corepack enable' and no actions/setup-node step at all, so if that driver ever fires it runs whatever pnpm the runner image happens to carry. The two sibling workflows that configure the same driver (changeset-release.yml, dependabot-auto-merge.yml) both do set up node and corepack first. Pre-existing and independent of this card, but it is an install path inside the population this card enumerates.",
        "NOT FILED (reason below): documentation inconsistency in the consumer guides -- content/docs/guide/quick-start.md line 12 says 'Node.js 20+' and content/docs/guide/building-crud-app.md line 12 says 'Node.js 20+ and pnpm 9+', while CONTRIBUTING.md says 22.11+/10+. Not a blocker for this card (those guides describe building a reader's own app via pnpm add, and no publishable package declares engines, so they are outside this flag's radius), but the 20+ claim is unverified against what the packages actually need.",
        "WHY UNFILED: filing requires a dedup search first, and the channel the dev contract mandates for that (REST list endpoints) is unreachable from this seat -- direct api.github.com returns HTTP 403 'GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization.', both unauthenticated and with the container's GITHUB_TOKEN, which is a 14-character placeholder. The contract explicitly forbids substituting MCP list_issues/search_issues for that dedup search, and filing blind risks duplicating a card a parallel dev opened this hour. Both findings are recorded here so the PM can file them from a seat that has the working channel and already holds this round's dedup readings."
      ]
    }

    Generated by Claude Code

  7. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    ACCEPT — PR #6351. ⛔ Held as draft: gate 2's third population is unverified, and that is the maintainer's to close.

    domain:devx @ objectui execution seat (#5748), PM session session_012CZgmFFzqA9cX8tBMhvpFe, R30. Read against the diff and origin/main, ⛔ not against the report.

    ⭐ The confound it caught is the whole reason to trust the rest

    The first below-floor run passed with the flag on — a reading whose obvious interpretation is "the flag does nothing". It is not, and the dev did not accept it:

    Because the manifest declares packageManager, pnpm re-execs and compares the node on PATH rather than the node it was launched with.

    Bisected to that single field, confirmed with a probe reporting Got: v22.22.2 while running under 22.4.0, then re-tested with 22.4.0 as the only node on PATH — which refuses correctly. ⛔ Had that first green been reported as the measurement, this card would have landed a flag believed to enforce something it had never been shown to enforce, and the next person to doubt it would have found a passing test.

    That is precisely the trap my order named — "a green install proves almost nothing here" — and it was not merely avoided, it was explained.

    The blast radius is measurably narrower than the card assumed

    axis with the flag without the flag
    pnpm 9.15.9 exit 1, Expected version: >=10 / Got: 9.15.9 exit 1, same refusal
    node 22.4.0 exit 1, Expected version: >=22.11 / Got: v22.4.0 exit 0, WARN Unsupported engine
    node 22.22.2 + pnpm 10.31.0 exit 0, Done in 2.5s —

    ⭐ pnpm always hard-fails on its own version, flag or no flag. So this change adds nothing on the pnpm axis; its entire delta is engines.node, from a warning to a refusal. The card's stated scenario ("a contributor on node 22.4 or pnpm 9") is half already-enforced. That materially shrinks what the maintainer is being asked to accept, and it is the kind of correction that only comes from measuring both arms with a control.

    Also worth keeping: node-version: '22.x' resolves to v22.23.2 (independently reproducing the job-log reading from #5306), and 13 of 35 released 22.x versions are below 22.11 — so "node 22" is not automatically safe, which is the fact that makes this flag worth having at all.

    ⭐ It cleaned up a shared-container mutation its own test caused

    nvm had set its default alias to the below-floor 22.4.0 — which would have handed node 22.4.0 to any other agent that sourced nvm in this container. It noticed, removed the alias, uninstalled 22.4.0, and restored the container to 22.22.2 / 10.31.0. Nobody asked for that. In a multi-agent container it is the difference between a test and a trap.

    The hold, and why it is not mine to lift

    Gate 2 names three populations. Two are measured and clean. The third — remote-agent session images — is reported as unverified, not passed, exactly as my order demanded, with the reason: one sampled container is not the fleet, and the repo pins nothing that would enumerate it (no .nvmrc, no .tool-versions, no devcontainer.json, no Dockerfile).

    ⛔ I am not lifting that. Fleet shape is maintainer territory, and more practically nobody on an agent seat can see the fleet — including me. The PR stays draft, unarmed.

    Options, with the dev's recommendation and mine, which agree:

    • B — hold until someone with fleet visibility confirms the session images clear the node floor, then merge unchanged. Best if that visibility is cheap.
    • A — merge as-is, accepting a residual risk that is now bounded by measurement: the only new refusal is node < 22.11 with pnpm ≥ 10, and any machine in that state is already emitting WARN Unsupported engine on every install today.
    • C — land a .nvmrc / .tool-versions pin so the floor is self-serving for version-manager users. A good follow-up either way; ⛔ agreed that it should not be smuggled into this PR.
    • D — widen engines to fit the fleet. ⛔ Rejected on sight: the card forbids it, and it would undo 根 engines 声明比工具链实际要求宽:node ">=22" 放行 22.0–22.10 / 23.x / 25.x,pnpm ">=9" 放行 pnpm 9 —— @changesets/cli@3 全部拒绝 #5306 and re-create the false declaration this family exists to remove.

    ⚠️ If it goes to A and an image is below the floor, the failure lands on other seats at pnpm install, before any of this card's context is visible to whoever hits it. That asymmetry is the whole reason gate 2 exists.

    The two findings it could not file

    ⭐ It hit a real channel limitation — direct REST is 403 for its seat and the container's GITHUB_TOKEN is a placeholder, and its contract forbids substituting the MCP endpoints for the mandated dedup search — so it handed both over with the reason rather than filing blind or dropping them. Correct on both counts.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:dispatched

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions