Repository navigation
repo: make the root nub-identity on nub.lock, and bootstrap CI with nub - #807
Conversation
The root carried both `package-lock.json` and `bun.lock`. Two candidate
lockfiles and no declaration is an ambiguous project, so `nub install` refused
nub's own repo with ERR_NUB_LOCKFILE_AMBIGUOUS. Incumbency is already covered
where it belongs: tests/conformance/frontdoor/ runs all seven incumbents,
including a nub-identity row that round-trips nub.lock, so the root pair was
testing nothing the fixtures do not.
Both lockfiles go, nub.lock replaces them, and the `devEngines` declaration
added to break the tie goes with them — one lockfile needs no tie-break.
Every CI leg installed the root deps with `npm ci`, and every one of those runs
BEFORE nub is built, so each needed a bootstrap:
- ci.yml, busybox-run-probe.yml, compile-native.yml (both legs) install a
released nub through this repo's own ./setup action, then
`nub install --frozen-lockfile`.
- wpt-worker.yml already builds nub two steps earlier, so it uses that binary
and depends on no published release at all.
- release.yml builds a HOST nub and installs with it. The publish path must
never bootstrap from a published nub: a broken release would otherwise wedge
the very workflow that publishes its fix.
`--node-linker hoisted` everywhere, not a stylistic choice: the runtime staging
and nub-core's build script copy real directories out of node_modules and
panic on the symlinked store the default isolated linker produces.
`cache: npm` is dropped from the setup-node steps that no longer install with
npm — it keys on a lockfile npm can read, and the root no longer has one.
The oxc lockstep gate follows the lockfile. nub.lock spells the helper-runtime
version in three independent places (the importer's `specifier:` and `version:`,
and the `<name>@<version>:` package key), so the check gains a source over the
two it had; each spelling was broken individually and observed to fail.
One consequence, stated rather than buried: ci.yml's root install no longer
passes through the Socket Firewall shim, which is wired by shimming `npm` and
`pnpm` by NAME on PATH. nub carries no such shim. nub.lock still pins every
tarball by integrity hash, `--frozen-lockfile` refuses to re-resolve, and
lifecycle scripts run under nub's build jail; the shims stay in place for the
pnpm/npm surfaces the tests themselves drive.
The tool was renamed fray -> frizz and only the old name was listed, so new-worktree.ts printed "source missing, skipping '.fray'" and every scripted worktree came up with no orchestration surface at all. Both names are listed now, so an older checkout still resolves.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Important
Dependency caching is dropped across the whole CI matrix rather than moved, and the comment added here asserts the opposite. The devEngines range added to package.json also excludes this tree's own version.
Reviewed changes — the full diff at d18383b, plus the workflow jobs, setup/action.yml, and the lockfile-identity code paths surrounding it.
- Root lockfile identity —
package-lock.jsonandbun.lockare deleted in favor of a singlenub.lock(pnpm-v9 YAML), making the root unambiguously nub-identity. devEngines.packageManager— switched from{name: npm, onFail: warn}to{name: nub, version: ^0.7.4, onFail: ignore}; this is the declaration that breaks lockfile ties.- CI bootstrap — all eight root
npm cicalls becomenub install --frozen-lockfile --node-linker hoisted, sourced from./setup(a published nub) inci.yml/busybox/compile-native, and from a locally built./target/debug/nubinrelease.yml/wpt-worker. cache: 'npm'removed from everyactions/setup-nodein the five touched workflows.scripts/check-oxc-lockstep.mjs— rewritten to checknub.lock's three independent version spellings instead of the npm and bun locks. Verified passing locally..worktreeinclude— addssymlink .frizzbeside the pre-rename.fray, an unrelated but correct drive-by fix.
I checked and cleared several things worth recording so they don't get re-litigated: cargo build -p nub-cli does land at ./target/debug/nub on the cross legs (no CARGO_BUILD_TARGET, no build.target in any .cargo/config.toml); the toolchain and metadata primer both precede the new release.yml step; and both bare nub and ./target/debug/nub resolve correctly under pwsh on the Windows legs. The devEngines tie-break also genuinely works in code — identity.rs:155 sets self_names: &["nub"] and aube-lockfile/src/detect.rs:264-290 catches AmbiguousLockfiles and prefers nub.lock rather than erroring.
⚠️ mac-build.yml's root install was not converted, and now resolves unpinned
The sweep targeted npm ci, but .github/workflows/mac-build.yml:87 is npm install --no-audit --no-fund --loglevel=error, running at the repo root with no working-directory. With package-lock.json gone it has nothing to read, so it resolves fresh from package.json's semver ranges and writes a brand-new package-lock.json back into the root. That job never invokes nub, so the stray file is inert there — but it is a second root lockfile in a PR whose entire purpose is having exactly one, and the versions that job's build sees can now drift from what every other leg installs.
Technical details
# Convert or neutralize the last root install
## Affected sites
- `.github/workflows/mac-build.yml:87` — `npm install --no-audit --no-fund --loglevel=error` at the repo root; not matched by the `npm ci` sweep, now lockfile-free and writes a fresh `package-lock.json`.
## Required outcome
- The root deps this job installs come from `nub.lock` at the same pinned versions every other leg uses, and the job leaves no second lockfile in the working tree.
## Suggested approach (optional)
- Mirror the other legs: `- uses: ./setup` followed by `nub install --frozen-lockfile --node-linker hoisted`. The comment above the step (lines 80-86) explains the deps exist so `aube-resolver/build.rs` can find a real primer and so tests can import `@oxc-project/runtime` — both are satisfied by the nub install.
- If bootstrapping a published nub is undesirable in this workflow, `npm install --no-package-lock` at minimum stops the stray lockfile, though it leaves the versions unpinned.⚠️ Four workflows now bootstrap on an unpinned @nubjs/nub@latest
./setup defaults nub-version to latest and installs it with npm install -g "@nubjs/nub@${spec}". None of the five new call sites override it, so ci.yml, busybox-run-probe.yml and compile-native.yml are all pinned to whatever nub published most recently — in workflows where every other action is pinned by SHA. The PR body articulates precisely this hazard as the reason release.yml self-builds instead ("a broken release cannot wedge the workflow that publishes its fix"), but the same failure mode now reds the entire test matrix rather than one job. Worth a deliberate decision either way; this PR is also the first consumer of ./setup anywhere in this repo's CI, so there is no prior green run to lean on.
Technical details
# Decide the CI bootstrap pin
## Affected sites
- `.github/workflows/ci.yml:395`, `.github/workflows/ci.yml:958`
- `.github/workflows/busybox-run-probe.yml:52`
- `.github/workflows/compile-native.yml:87`, `.github/workflows/compile-native.yml:233`
All are bare `- uses: ./setup`, taking `nub-version: latest` from `setup/action.yml`.
## Required outcome
- A conscious answer to: should a bad `@nubjs/nub` publish be able to red the entire test matrix?
- If no, the bootstrap nub is pinned to a known-good version that moves only when someone bumps it.
- If yes (dogfooding the newest release is the point), that intent is stated in a comment so the next person debugging a matrix-wide red does not have to infer it.
## Open questions for the human
- Is bootstrapping CI on `latest` intentional dogfooding, or an unexamined default? The asymmetry with `release.yml`'s explicit self-build reasoning suggests the latter, but only you can say.ℹ️ Nitpicks
.github/workflows/lat-check.yml:39still says "every ci.yml test leg runs the rootnpm cithrough the Socket Firewall shim" — both halves are now false. The surrounding rationale (keep the checker onnpxrather than in the root install) still stands and is worth preserving; only the mechanism sentence needs rewording..github/workflows/compile-native.yml:229still says "Host Node only runsnpm cifor the compiler's bootstrap deps"; the step below it is nownub install.scripts/check-oxc-lockstep.mjs:105—if (!found.length)is satisfied by any one of the three spellings matching, so if the importer block's shape ever drifts (say the specifier gains a caret, which\d+\.\d+\.\d+will not match) the package-key regex alone keeps the check green and the "no entry found" guard never fires. Not worth restructuring, but the three-place claim in the comment above is stronger than what the code enforces.
Claude Opus | 𝕏
| version: 10 | ||
| # No `cache: npm` — setup-node's npm cache keys on a lockfile npm can read, | ||
| # and the root now carries only nub.lock. nub's own store cache rides on | ||
| # the `./setup` step that installs the root deps further down. |
There was a problem hiding this comment.
This claim does not hold: ./setup is used bare at line 958, and setup/action.yml defaults the cache input to "false" while gating its cache step on if: inputs.cache == 'true'. So cache: 'npm' was removed and nothing replaced it — every leg of the test matrix now does a cold network install, as do busybox, both compile-native legs, and both release.yml legs.
Technical details
# Restore dependency caching, or correct the comment
## Affected sites
- `.github/workflows/ci.yml:881-883` — comment asserts the store cache "rides on the `./setup` step"; it does not.
- `.github/workflows/ci.yml:958`, `.github/workflows/ci.yml:395` — bare `- uses: ./setup`, no `cache` input.
- `.github/workflows/busybox-run-probe.yml:52`, `.github/workflows/compile-native.yml:87`, `.github/workflows/compile-native.yml:233` — same.
- `.github/workflows/release.yml:512`, `.github/workflows/release.yml:657` — dropped `cache: "npm"` and use `./target/debug/nub` rather than `./setup`, so they have no caching path available at all.
- `setup/action.yml:145` — `if: inputs.cache == 'true'`, default `"false"`.
- `setup/action.yml:134` — lockfile auto-detect list is `pnpm-lock.yaml package-lock.json bun.lock bun.lockb yarn.lock`; it does not know `nub.lock`, so even with caching enabled the key silently degrades to hashing `package.json`.
## Required outcome
- Either the touched legs restore warm dependency caching keyed on `nub.lock`, or the comment stops claiming a cache that isn't configured.
- If caching is restored, the cache key tracks `nub.lock` rather than falling back to `package.json`.
## Suggested approach (optional)
- Pass `with: { cache: 'true' }` on the `./setup` call sites, and add `nub.lock` to the head of `setup/action.yml:134`'s auto-detect list so every nub-identity consumer of the shared action gets a correct key. Passing `cache-dependency-path: nub.lock` at the call sites would fix this repo only and leave the action's own contract broken.| "devEngines": { | ||
| "packageManager": { | ||
| "name": "nub", | ||
| "version": "^0.7.4", |
There was a problem hiding this comment.
^0.7.4 on a 0.x version means >=0.7.4 <0.8.0 under npm semver, so it excludes 0.8.0 — the version this very tree builds (Cargo.toml:95, npm/nub/package.json:3). Combined with onFail: "ignore" and ./setup installing latest, this declaration is already unsatisfiable at merge and will never say so.
| "version": "^0.7.4", | |
| "version": ">=0.7.4", |
| # shim, so the interception that covered the old `npm ci` does not cover | ||
| # this step. What still holds: nub.lock pins every tarball by integrity | ||
| # hash, `--frozen-lockfile` refuses to re-resolve, and lifecycle scripts | ||
| # run under nub's build jail rather than unconfined. The shims stay in |
There was a problem hiding this comment.
The build jail is #[cfg(target_os = "linux")] (landlock + seccomp) and #[cfg(target_os = "macos")] only — vendor/aube/crates/aube-scripts/src/lib.rs:19,377,454 has no Windows arm. This matrix includes windows-latest, where the mitigation set for losing sfw shrinks to the integrity-pinned lockfile plus --frozen-lockfile, with no confinement at all. Worth narrowing the sentence, since it is the stated justification for accepting the coverage loss.
Review findings on #807, all three confirmed against the code before acting. Dependency caching was dropped rather than moved. `setup/action.yml` defaults its `cache` input to "false" and gates both cache steps on `inputs.cache == 'true'`, and every `./setup` call site was bare — so removing `cache: npm` from the setup-node steps left every touched leg doing a cold install, while the comment claimed the store cache "rides on the ./setup step". All five call sites now pass `cache: 'true'`, and the comment says what is actually configured. The action's lockfile auto-detect did not know `nub.lock`, so even with caching on, the key degraded to hashing `package.json` — verified by running its loop both ways against this tree. `nub.lock` now leads the list. Fixed in the action rather than at the call sites: a nub-identity project has no other lockfile, so every consumer of this shared action hit the same silent degradation. `devEngines.packageManager.version` was `^0.7.4`, which on a 0.x version means >=0.7.4 <0.8.0 and so excludes 0.8.0 — the version this tree builds. `nub install` wrote that declaration itself when the field was removed; it only writes when the field is ABSENT, so a corrected `>=0.7.4` survives reinstall (verified). release.yml and wpt-worker still have no store cache. That is deliberate and now says so: caching rides on ./setup, and using it there would put a published nub on the publish path. The sfw note claimed lifecycle scripts run under the build jail. That does not hold on the windows-latest cell — aube-scripts has Linux and macOS arms only — so the sentence now names the platforms and states what remains on Windows.
The comment explained the npx pin by pointing at the root `npm ci` running through the Socket Firewall shim. This branch removes that install, so the sentence names a mechanism that no longer exists. The reason for the pin is unchanged and stays.
|
Your Claude subscription has hit its usage limit. It resets at 12:40am (UTC). Re-trigger Pullfrog after the reset, or add an Add repo secret → · Model settings → · Setup docs → · Ask in Discord →
|
# Conflicts: # .github/workflows/lat-check.yml
|
Your Claude subscription has hit its usage limit. It resets at 12:40am (UTC). Re-trigger Pullfrog after the reset, or add an Add repo secret → · Model settings → · Setup docs → · Ask in Discord →
|
|
Shipped in v0.8.1: https://github.com/nubjs/nub/releases/tag/v0.8.1 |

Two root lockfiles with no declaration made the repo ambiguous, so
nub installrefused nub's own repo.package-lock.jsonandbun.lockgo;nub.lockreplaces them.tests/conformance/frontdoor/still covers all seven incumbents.All eight
npm cicalls ran before nub is built.ci.yml,busyboxandcompile-nativebootstrap a released nub via./setup;wpt-workeruses the nub it already built;release.ymlbuilds a host nub, so a broken release cannot wedge the workflow that publishes its fix.--node-linker hoistedis required: the build script panics on a symlinked store.ci.yml's root install no longer passes through the Socket Firewall shim.