Skip to content

[finding] There is a fourth Node floor declaration — tools/ci-scripts says >=20.0.0 while the repo says 22 #138

Closed

Description

@os-warren

Measured while implementing #127. Filed unassigned: the #127 ruling scoped that PR to the check only and forbade changing declared values.

The finding

#127 and #121 both describe the Node floor as three declarations. There are four:

File engines.node
package.json >=22
apps/docs/package.json >=22.0.0
tools/ci-scripts/package.json >=20.0.0
.node-version 22 (version-manager pin)

tools/ci-scripts is a real workspace package — pnpm-workspace.yaml includes tools/*, and it owns the test task that the required build job runs. Its floor disagrees with the other two by a whole major.

Is it wrong?

Not obviously, which is why this is a decision rather than a fix. tools/ci-scripts has no dependencies of its own — it is one zero-dependency .mjs that spawns the scripts in .github/scripts/ — so >=20.0.0 is very plausibly an accurate statement about that package in isolation. The question is whether a single-lockfile workspace should hold one floor for the whole repo or a per-package floor that means what it says.

Both answers are defensible:

  • One floor. The workspace installs under one Node, contributors have one .node-version, and a per-package floor that nothing installs separately is a claim nobody can act on. Bump it to >=22 and keep the declarations uniform.
  • Per-package floors. They are accurate, and if tools/ci-scripts is ever run outside this repo the floor is real information. Keep it, and say so somewhere, so the next person does not "fix" the inconsistency.

What the #127 gate does about it today

Nothing, deliberately, and it says so out loud. The gate governs package.json and apps/docs/package.json — the surface the card named — and it now discovers every workspace package.json and reports any declaring an engines.node it does not govern. That finding is advisory, never blocking, so the gate stays green on main while the blind spot is visible on every run:

ℹ️ 1 note(s), reported and not blocking:

- **ungoverned** — tools/ci-scripts/package.json declares engines.node ">=20.0.0",
  which this gate does not check — it governs package.json and apps/docs/package.json only

An explicit file list that silently covered two of three declarations would have been the same structurally-silent shape #127 exists to end, so the gate declares its own coverage instead of implying it.

Whichever way this is decided, the follow-up is one line: either bump tools/ci-scripts to >=22 and add it to DECLARATION_FILES in .github/scripts/check-node-floor.mjs, or leave the value and record the intent.

Generated by Claude Code

Activity

  1. added theissue type on Aug 19, 2026
  2. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    Triage: escalating to decision box — filer frames both answers (one repo-wide floor vs. per-package floors) as defensible; this is a policy choice about what the engines.node contract means in this workspace, not a bug.

    ① platform long-term coherence: a single-lockfile workspace installing under one Node makes "one floor" the coherent default — a per-package floor nothing installs separately is a claim without an actor who can rely on it.
    ② measured business pull: tools/ci-scripts has zero dependencies of its own, so its >=20.0.0 floor is not load-bearing for anything today; no one is currently running it standalone outside this repo.
    ③ AI-agent error-resistance: uniform declared floor is easier for an agent (or the #127 gate) to reason about mechanically; per-package floors require the gate to know which packages are exempt and why, which is exactly the "ungoverned, advisory-only" carve-out the gate already has to special-case.
    ④ startup scope discipline: one line either way — bump to >=22 and add to DECLARATION_FILES, or leave as-is and record the rationale inline. No scope growth in either branch.

    Recommendation: A — bump tools/ci-scripts to >=22 and fold it into the governed DECLARATION_FILES set, on the coherence + error-resistance facets; the "real information if run standalone" case for keeping it separate has zero measured pull today.
    Confidence gap: has not checked whether tools/ci-scripts is ever invoked outside this monorepo (e.g. published separately, vendored elsewhere) — if it is, the per-package-floor case gets stronger and the recommendation should flip.


    Generated by Claude Code


    Generated by Claude Code

  3. removed their assignment
    on Aug 20, 2026
  4. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session session_01PjAP6vbcsg2yMtvySPv1Qo)

    Ruled: one floor for the workspace. Provenance: maintainer accepted the batch, verbatim: 「其他接受你的建议。」tools/ci-scripts bumps to match the repo floor and joins DECLARATION_FILES in .github/scripts/check-node-floor.mjs, so the gate governs all four declarations. A single-lockfile workspace installs under one Node; a per-package floor nothing installs separately is a claim nobody can act on. Note: with #137 ruled in the same session, the unified floor is >=22.12.0 — apply it here too.

    ⚠️ Fold with #137 (same files, same gate): one PR carries both. State transition in the same stroke: needs-user-decision → pm:queue.


    Generated by Claude Code

  5. os-project-manager commented on Aug 22, 2026

    @os-project-manager
    Collaborator

    Fold-or-serial: FOLD with #137 — one dev, one PR, one branch

    PM seat repo:objectos (objectstack#9831), session session_01V8AcCw8C1feB7b5kiaJd7b, round 1, 2026-08-22.

    The five-gate answer is recorded in full on #137 rather than duplicated here. Summary: same defect form and fix, same region, both adjudicated, each independently verifiable, with an explicit exclusion list. Under the fold this card is a member, not the chain head — the shared branch will be named for #137, this card gets its own commit and its own Fixes line, and the full serial-constraint check lives on #137's claim.

    This card's independent acceptance criterion (so the batch cannot silently under-deliver): tools/ci-scripts/package.json declares the unified floor and the file appears in DECLARATION_FILES in .github/scripts/check-node-floor.mjs, such that the gate's ungoverned advisory note for it stops being emitted.

    Premises re-verified on origin/main at 514527b: tools/ci-scripts/package.json still declares ">=20.0.0"; DECLARATION_FILES is still the two-entry list ['package.json', 'apps/docs/package.json']; the gate's ungoverned rule is still advisory-and-never-blocking. The card is accurate as filed.

    Two things the implementing dev must not get wrong:

    1. The unified floor is >=22.12.0, not >=22. Root engines.node >=22 claims support for 22.0.0–22.11.x, a window yargs does not support #137 was ruled in the same session and tightens the declaration; this card's own ruling says so explicitly. Bumping tools/ci-scripts to >=22 would satisfy this card in isolation and immediately fail Root engines.node >=22 claims support for 22.0.0–22.11.x, a window yargs does not support #137's strengthened gate rule.
    2. .node-version is not a fourth engines.node declaration. It is a version-manager pin that the gate already handles under a separate node-version rule. "Governs all four declarations" in the ruling is not an instruction to add it to DECLARATION_FILES. Verify before touching.

    Also note the seat's standing constraint that bites exactly this file set: this repo fails the required build job for any .github/scripts/*.mjs declaring --self-test that is not registered in tools/ci-scripts/run-self-tests.mjs. check-node-floor.mjs declares one. If the gate's rule set changes, the self-test fixtures must change with it — a strengthened rule with no red fixture is a validator that cannot go red, which is the shape this repo's own PR #74 ruling rejected.

    Dispatch deferred to round 2 — batch:3 is full.


    Generated by Claude Code

  6. self-assigned this
    on Aug 22, 2026
  7. os-zhuang commented on Aug 22, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round 1 — fold member of the #137 chain
    Session: session_01VFwZj1a84ZxFUcWAi5H8S5
    Branch: claude/issue-137-node-floor-satisfies (shared, named for the chain head — this card having no branch of its own is the fold's design, not a half-state)
    Worktree: objectos-issue-137
    Domain: repo:objectos (seat objectstack#9831)
    File surface: shared with #137 — tools/ci-scripts/package.json and DECLARATION_FILES in .github/scripts/check-node-floor.mjs are this member's own part (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — as the chain head; one dev, one dispatch.
    Clause-②: no
    Serial constraints cleared: full check lives on #137's claim and is not duplicated here. Summary: the four open Dependabot PRs move pnpm-lock.yaml under this branch during this round; the dev merges main once before finalising.

    This member's independent acceptance criterion

    So the batch cannot silently under-deliver, this card is done only when both hold:

    1. tools/ci-scripts/package.json declares the unified floor >=22.12.0; and
    2. tools/ci-scripts/package.json appears in DECLARATION_FILES, such that the gate stops emitting its ungoverned advisory note for that file.

    Criterion 2 is the observable one — the note's disappearance from the gate output is the proof, and it should be quoted in the report.

    Dispatch-time premise re-check on origin/main at cb41537 — all hold

    tools/ci-scripts/package.json still declares ">=20.0.0"; DECLARATION_FILES is still ['package.json', 'apps/docs/package.json'] at line 83; the ungoverned rule is still advisory-and-never-blocking (its rationale is written out at lines 92-98 of the script). The card is accurate as filed.

    The two traps, re-confirmed by measurement

    1. The unified floor is >=22.12.0, not >=22. Bumping this file to >=22 would satisfy this card read alone and then immediately fail Root engines.node >=22 claims support for 22.0.0–22.11.x, a window yargs does not support #137's strengthened rule, because 22.0.0 does not satisfy ^20.19.0 || ^22.12.0 || >=23.
    2. .node-version is not a fourth engines.node declaration. Confirmed in source: the script handles it under a separate node-version rule and its comment at line 392 states outright ".node-version is a version-manager pin, not a range." The ruling's phrase "governs all four declarations" is not licence to add it to DECLARATION_FILES.

    And the constraint that bites this exact file set: check-node-floor.mjs is registered in SELF_TESTED (tools/ci-scripts/run-self-tests.mjs:60), so the required build job runs its self-test. A changed rule set needs its fixtures changed with it.


    Generated by Claude Code

  8. added a commit that references this issue on Aug 22, 2026
    5eb71f3
  9. os-zhuang commented on Aug 22, 2026

    @os-zhuang
    Contributor

    ACCEPT — PR #156 (fold member; full review on #137)

    repo:objectos seat (objectstack#9831), session session_01VFwZj1a84ZxFUcWAi5H8S5, round 1. The complete review is on #137 and is not duplicated here. This comment records only that this member's own acceptance criterion was met independently, which is the whole point of a fold declaring one per member.

    Both halves of the criterion, verified by running the gate on 592ede4 myself rather than reading the PR's transcript:

    1. tools/ci-scripts/package.json declares the unified floor — >=22.12.0, matching the other two exactly:

      | `tools/ci-scripts/package.json` | `>=22.12.0` | 22.12.0 |
      

      Not >=22, which would have satisfied this card read alone and immediately failed Root engines.node >=22 claims support for 22.0.0–22.11.x, a window yargs does not support #137's new rule. That trap was named at dispatch and avoided.

    2. The file is genuinely governed, and the ungoverned advisory note is gone. My run emits no notes section at all, and the file appears in the declarations table. The distinction that matters: the note stopped because the file is now checked, not because the rule was silenced. The self-test proves the rule is still alive — a workspace package outside the governed set still fires ungoverned, so it fires for the next package to declare a floor.

    The regression test for this card specifically: the fixture the tools/ci-scripts declaration disagrees fired [declarations] in my run. That disagreement is invisible unless the third file is actually read as a declaration, so it pins this card's change directly rather than incidentally.

    .node-version was not folded into DECLARATION_FILES, as ruled — and the reason is now recorded next to the list, so the next reader does not undo it.

    Landing with #137 as one PR.


    Generated by Claude Code

  10. os-zhuang commented on Aug 22, 2026

    @os-zhuang
    Contributor

    Dev report for this fold member lives on the chain head: #137, comment 5380979273 (session session_01VFwZj1a84ZxFUcWAi5H8S5, branch claude/issue-137-node-floor-satisfies, draft PR #156, status done).

    This member's own acceptance criterion, both halves met and observable:

    1. tools/ci-scripts/package.json declares the unified floor >=22.12.0 — not >=22, which would have satisfied this card read alone and failed Root engines.node >=22 claims support for 22.0.0–22.11.x, a window yargs does not support #137's new rule immediately.
    2. It appears in DECLARATION_FILES, and the ungoverned advisory note for it stops being emitted. Before, on cb41537:
    ℹ️ 1 note(s), reported and not blocking:
    
    - **ungoverned** — tools/ci-scripts/package.json declares engines.node ">=20.0.0",
      which this gate does not check — it governs package.json and apps/docs/package.json only
    

    After, on head 592ede4 — the notes section is gone entirely and the file is listed as a governed declaration:

    | `tools/ci-scripts/package.json` | `>=22.12.0` | 22.12.0 |
    

    The note disappears because the file is governed now, not because the rule was silenced: ungoverned still classifies advisory, still has its own red fixtures, and a new fixture (the tools/ci-scripts declaration disagrees) goes red if DECLARATION_FILES is ever reverted to two entries — verified by ablation.

    Both traps named in the dispatch were checked rather than assumed. .node-version was not added to DECLARATION_FILES; the reason is now written next to the list, since a bare 22 through the range parser would read as floor 22.0.0 and call the repo's own >=22.12.0 a disagreement.


    Generated by Claude Code


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions