NEC-107: Take the pending action majors, and stop naming pnpm twice #169

Merged
nectenda-agent merged 6 commits from worktree-renovate-batch-node26 into main 2026-09-25 14:59:42 +01:00
Collaborator

Task: NEC-107 — https://projectron.nerchure.com/tasks/107

Six action majors that Renovate has been holding on the dependency dashboard
(#18), plus the three things Renovate cannot do for them.

The references. Every actions/*, pnpm/action-setup and docker/*
reference under .forgejo/ moves to its current major, and so do the three in
the public mirror's generated GitHub workflow. The census is 9 checkout@v7,
6 setup-node@v7, 4 action-setup@v6, 4 cache@v6, 2 buildx@v4,
1 qemu@v4 — 26 sites, none left at an older major. The count is the thing to
check rather than the diff: this branch was written against 16 sites, main
added three workflows carrying 7 more, and merging it unchanged would have left
Renovate to re-offer the stragglers.

version: goes with the bump, at 4 sites. pnpm/action-setup@v6 reads
packageManager and fails a run that names pnpm twice — we found that the
expensive way, on a red run. The mirror's generator is the one that mattered
most: it writes the mirror's packageManager from this repository's, so
bumping it without dropping version: would have failed in the public
repository rather than here.

Two claims that had stopped being true. pnpm.onlyBuiltDependencies is
gone from the root package.json — pnpm 12 prints that it ignores the field on
every install, and it had drifted, naming sqlite3 (not a dependency) and
omitting the three packages allowBuilds denies. And "Node 26 cannot build
better-sqlite3" is withdrawn from deploy/inventory.mjs,
deploy/UPGRADES.md, docs/plan-master.md and docs/plan-phase0.md. Two
tests now read that wording from POLICY.node.lineNote instead of copying it;
a grep for the wording missed one of them twice, and CI caught what the grep
did not. Both are mutation-checked in each direction.

What this run does not cover. checkpoint-soak.yml and e2e-soak.yml are
dispatch-only, so their bumps are unexercised here. The mirror's workflow
reaches GitHub only on the next plugin publish; build-mirror --check proves
it stages consistently, not that GitHub accepts it.

Changelog

NONE

Task: NEC-107 — https://projectron.nerchure.com/tasks/107 Six action majors that Renovate has been holding on the dependency dashboard (#18), plus the three things Renovate cannot do for them. **The references.** Every `actions/*`, `pnpm/action-setup` and `docker/*` reference under `.forgejo/` moves to its current major, and so do the three in the public mirror's generated GitHub workflow. The census is 9 `checkout@v7`, 6 `setup-node@v7`, 4 `action-setup@v6`, 4 `cache@v6`, 2 `buildx@v4`, 1 `qemu@v4` — 26 sites, none left at an older major. The count is the thing to check rather than the diff: this branch was written against 16 sites, `main` added three workflows carrying 7 more, and merging it unchanged would have left Renovate to re-offer the stragglers. **`version:` goes with the bump, at 4 sites.** `pnpm/action-setup@v6` reads `packageManager` and fails a run that names pnpm twice — we found that the expensive way, on a red run. The mirror's generator is the one that mattered most: it writes the mirror's `packageManager` from this repository's, so bumping it without dropping `version:` would have failed in the *public* repository rather than here. **Two claims that had stopped being true.** `pnpm.onlyBuiltDependencies` is gone from the root `package.json` — pnpm 12 prints that it ignores the field on every install, and it had drifted, naming `sqlite3` (not a dependency) and omitting the three packages `allowBuilds` denies. And "Node 26 cannot build better-sqlite3" is withdrawn from `deploy/inventory.mjs`, `deploy/UPGRADES.md`, `docs/plan-master.md` and `docs/plan-phase0.md`. Two tests now read that wording from `POLICY.node.lineNote` instead of copying it; a grep for the wording missed one of them twice, and CI caught what the grep did not. Both are mutation-checked in each direction. **What this run does not cover.** `checkpoint-soak.yml` and `e2e-soak.yml` are dispatch-only, so their bumps are unexercised here. The mirror's workflow reaches GitHub only on the next plugin publish; `build-mirror --check` proves it stages consistently, not that GitHub accepts it. ## Changelog NONE
Moving to Node 26 corrected the sentence in CLAUDE.md and left four copies of
it standing. The claim was in five places, which is the thing worth recording:
three citations read as corroboration rather than as one unchecked sentence
quoted twice, and that is most of why it lasted.

  CLAUDE.md                     corrected when the move happened
  deploy/UPGRADES.md:55         "we are on 22 because CLAUDE.md records..."
  deploy/UPGRADES.md:451        the same, as the reason toolchain majors are off
  deploy/inventory.mjs:83       lineNote, printed beside the node row
  deploy/test/console.test.ts   asserting that text

inventory.mjs is the one that mattered. It is not prose about a decision; it is
the sentence the tool prints next to the row while somebody is deciding, and it
would have gone on giving a withdrawn reason as the reason. It now says what is
actually true and load-bearing: 26 is Current rather than LTS, so moving again
is a decision and not a row to action.

The UPGRADES.md paragraph keeps the example rather than deleting it, since what
it now demonstrates is more useful than what it used to assert.

The test was worse than stale. It asserted /better-sqlite3/ against a note its
own fixture supplied, so it kept passing after the reason had been withdrawn --
it was checking that a string it had just written down was still written down.
It now takes the wording from POLICY and asserts the property instead: whatever
standing reason exists rides along with the item. Mutation-checked both ways --
emptying POLICY.node.lineNote fails it by name, restoring passes 40/40.

Found by grepping for the claim after the release, which is later than it
should have been looked for. A commit whose subject is "stop repeating a dead
claim" should have started by counting how many times it was repeated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5JXvAgQ27c4dMFDB2Wu5B
The seven items Renovate had pending approval, less the Obsidian pin, which is
held deliberately: bumping OBSIDIAN_VERSIONS changes what the e2e gate runs
against and moves the cache key with it, so it wants a run where it is the only
variable.

  actions/checkout             v4 -> v7   (5 sites)
  actions/setup-node           v4 -> v7   (3 sites)
  actions/cache                v4 -> v6   (3 sites)
  pnpm/action-setup            v3 -> v6   (2 sites)
  docker/setup-buildx-action   v3 -> v4   (2 sites)
  docker/setup-qemu-action     v3 -> v4   (1 site)

Checked against code.forgejo.org rather than github.com before changing
anything, because that is where the runner actually fetches them:
DEFAULT_ACTIONS_URL points there and Renovate only ever consults github to
answer whether something newer exists. All six tags resolve, so none of these
is a reference to something the runner cannot get.

actions/cache is the one that was genuinely uncertain and the reason this
waited. Versions 5 and above use GitHub's Cache Service v2, which a 2026
self-hosted runner may not serve, and the cache here is not decorative --- the
last green run restored 44 MB of pnpm store and 411 MB of Obsidian binaries.
Forgejo's runner went to 13.2.0 first, so this is now a reasonable thing to
try; if it turns out the runner still cannot serve v2, the symptom will be a
red branch run and the answer is to put that one back to v4.

Three cache sites, not two. The shared e2e action caches the pnpm store *and*
the Obsidian binaries, and a grep that showed one of them was a grep with a
head limit on it.

pnpm/action-setup v6 still declares `version` as an optional input, so the
`version: 12` in both call sites stays valid. It is now redundant --- v6 reads
packageManager from package.json --- but removing it is a separate question
about how many places pnpm is pinned, not part of a version bump.

Nothing here is verified by the diff. Every one of these six runs in ci.yml, so
pushing the branch exercises all of them in a single run, which is the point of
doing them together rather than as six pull requests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5JXvAgQ27c4dMFDB2Wu5B
v6 refuses a run that names pnpm twice — "Multiple versions of pnpm
specified", naming both the action's `version` input and packageManager — even
when the two agree. v3 tolerated it, so the previous commit carried `version:
12` forward and CI failed at the first step.

Checking that v6 still declared a `version` input was not the same as checking
the combination was allowed, and the schema could not have said so. The branch
run said so in one line.

packageManager is the survivor of the pair by preference as well as necessity:
pnpm enforces it itself and it carries the integrity hash. pnpm is now named in
four places rather than six, all of them tracked — packageManager by the npm
manager, the three Dockerfile lines by the custom manager added in 78d4230.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5JXvAgQ27c4dMFDB2Wu5B
7feee10 said it had withdrawn the better-sqlite3 reason from every place it
was still being asserted. It had not: this test quoted a fragment of the
sentence rather than the sentence, so a grep for the wording found five sites
and missed the one that mattered — the only one that was an assertion rather
than prose. CI run 446 found it instead, which is the wrong order.

Both note assertions now read POLICY.node.lineNote, so there is no second copy
of the text to go stale. Each first asserts the note exists, because a note
that silently disappears from POLICY would otherwise turn `toContain('')` into
a test that cannot fail — which is how the original got here.

Mutation-checked in both directions: stubbing the push in compare() fails the
contains, and emptying POLICY.node fails the guard, in this file and in
console.test.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5JXvAgQ27c4dMFDB2Wu5B
Two leftovers from the pnpm 12 and Node 26 moves, both found by reading the CI
log for run 447 rather than by grepping again — which is the point, since
grepping for the wording is what missed them twice already.

pnpm 12 prints "the pnpm field in package.json is no longer read" on every
install and lists `pnpm.onlyBuiltDependencies` as ignored. It had also drifted:
it named sqlite3, which is not a dependency, and omitted the three packages
allowBuilds denies. Removing it leaves pnpm-workspace.yaml as the only place
that says which install scripts may run. Nothing else in the tree reads the
field; pnpm's own warning is the evidence it was inert.

plan-phase0.md still gave "better-sqlite3 will not compile against Node 26" as
a hard constraint and engines.node as >=22, and plan-master.md put Node 22 in
the architecture diagram, the stack table and the runtime paragraph. The
decision entries are marked superseded rather than rewritten, because the
constraint was cited as real for a year and deleting it loses why.

Checked, not assumed: scripts/build-mirror.mjs derives the mirror workflow's
node-version from engines.node and now emits 26; its '>=22' fallback only
fires if engines disappears.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5JXvAgQ27c4dMFDB2Wu5B
Take the ten references added since this branch was written, and the mirror's
All checks were successful
Release note / release-note (pull_request) Successful in 13s
e2e / multi (push) Successful in 4m30s
CI / build (pull_request) Successful in 4m43s
CI / e2e (pull_request) Successful in 5m14s
CI / promote (pull_request) Has been skipped
bbc92e28ab
This branch bumped 16 references on 21 September. `main` has added three
workflows since — checkpoint-soak, e2e-soak, release-note — carrying 7 more, so
merging it as it stood would have landed 16 of 26 and left Renovate to re-offer
the rest. The count is the thing worth checking here, not the diff: a partial
toolchain bump is worse than none, because CI and the image then disagree about
which toolchain built the lockfile.

checkpoint-soak also arrived with `pnpm/action-setup@v3` and `version: 12`. That
pair is exactly what v6 refuses, so the version goes with the bump rather than
being discovered by a red run later.

The last three are `scripts/build-mirror.mjs`, which generates the *public*
mirror's GitHub workflow. Leaving those at v4 looked like the conservative
choice and is the opposite: the generator writes the mirror's `packageManager`
from this repository's, so the next publish would name pnpm in both places, and
whoever bumped the action later would hit the duplicate-version failure in the
public repository rather than here. `packageManager` is no longer destructured
in `workflow()` either — it existed only for the line that has gone, and an
unused binding fails `pnpm lint`.

Verified rather than assumed: checkout v7.0.1, setup-node v7.0.0 and
action-setup v6.1.0 all exist on GitHub, so the mirror's references resolve
there. checkout v7's one behavioural change blocks fork checkouts on
`pull_request_target` and `workflow_run`, and neither appears anywhere in this
repository.

The census now reads 9 checkout@v7, 6 setup-node@v7, 4 action-setup@v6,
4 cache@v6, 2 buildx@v4, 1 qemu@v4 — 26, with no `version:` beside any of them.

Also drops two counts from this branch's own new UPGRADES.md paragraph, which
said `node-version` lived in three workflows. It is five now, and the paragraph
would have shipped a number that was wrong the day main added a workflow. The
group matches by manager rather than by filename, so it needs no count at all —
the same reason the note in POLICY is read rather than copied.

Task: NEC-107

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5JXvAgQ27c4dMFDB2Wu5B
cruelacid force-pushed worktree-renovate-batch-node26 from bbc92e28ab
All checks were successful
Release note / release-note (pull_request) Successful in 13s
e2e / multi (push) Successful in 4m30s
CI / build (pull_request) Successful in 4m43s
CI / e2e (pull_request) Successful in 5m14s
CI / promote (pull_request) Has been skipped
to c30b165c00
All checks were successful
Release note / release-note (pull_request) Successful in 13s
CI / build (pull_request) Successful in 4m31s
CI / e2e (pull_request) Successful in 5m6s
CI / promote (pull_request) Has been skipped
Deploy site / deploy (push) Successful in 58s
e2e / multi (push) Successful in 4m36s
CI / build (push) Successful in 5m1s
CI / e2e (push) Successful in 5m17s
CI / promote (push) Successful in 31s
2026-09-25 14:53:42 +01:00
Compare
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Nectenda/nectenda!169
No description provided.