Skip to content

resolver: widen aube-lock.yaml to every common platform#94

Merged
jdx merged 5 commits intomainfrom
claude/unruffled-tereshkova-e2f975
Apr 19, 2026
Merged

resolver: widen aube-lock.yaml to every common platform#94
jdx merged 5 commits intomainfrom
claude/unruffled-tereshkova-e2f975

Conversation

@jdx
Copy link
Copy Markdown
Contributor

@jdx jdx commented Apr 19, 2026

Summary

Fixes the cross-platform lockfile footgun reported in jdx/usage#594: an aube install on macOS used to produce an aube-lock.yaml missing @rollup/rollup-linux-*, @esbuild/linux-*, etc., and Linux CI would fail at module-resolve time. The user's workaround was hand-writing a pnpm.supportedArchitectures stanza into package.json.

What changes

  • Wide default for aube-lock.yaml. When the resolver is about to write aube-lock.yaml and the manifest has no explicit pnpm.supportedArchitectures, widen the platform filter to an explicit matrix: darwin-arm64, linux × {x64, arm64} × {glibc, musl}, win32 × {x64, arm64}. darwin-x64 is out of the baseline (Intel Macs are end-of-life, and sharp / swc have dropped those binaries) but the host's own triple is always appended, so an Intel Mac (or a freebsd / ppc64 box) still installs its own natives.
  • Foreign lockfiles stay host-only. pnpm-lock.yaml, yarn.lock, package-lock.json, and bun.lock keep pnpm's host-only default — aube shouldn't silently bake more packages into them than the native tool would have written.
  • Lockfile disabled ≠ "write aube-lock.yaml". The widening only fires when aube is actually going to write aube-lock.yaml. lockfile=false skips widening entirely — no wasted resolution for a lockfile that will never exist.
  • node_modules stays host-only. An in-memory filter_graph pass after the lockfile write trims the wide graph down to host-installable optionals, mirroring the pass that already runs on the lockfile-happy install branch.
  • Streaming fetch defers foreign-platform tarballs and catches up after filter_graph. The widened graph would otherwise pull every platform's native tarball into ~/.aube-store/ on a fresh resolve. The streaming coordinator now skips platform-mismatched registry tarballs up front; after filter_graph trims optional mismatches, a catch-up pass fetches any survivor whose store index is still missing — which handles the rare required-platform-mismatched case (broken packages that declare os/cpu without the optional marker) that filter_graph doesn't touch. Common-case fresh installs end up fetching only what the host actually links. Local file:/link: deps carry no platform constraints and always fetch inline.

Commits

  1. resolver: widen aube-lock.yaml to every common platform — wide default + post-write filter_graph on the fresh-resolve path.
  2. install: disambiguate target_lockfile_kind when lockfile disabled — fixes an overloaded None so lockfile=false doesn't needlessly widen.
  3. install: add host to wide matrix, skip mismatched tarball fetches — always include the host triple in the default matrix; first attempt at fetch-time skipping.
  4. install: revert fetch-time platform filter — filter_graph only prunes optionals — withdraws the naive fetch-time filter after Cursor Bugbot flagged that required-platform-mismatched deps would silently break.
  5. install: defer platform-mismatched fetches, catch up after filter_graph — correct two-phase design: defer in streaming, catch up for survivors.

Test plan

  • cargo test — all workspace crates (aube-resolver gains 2 new tests pinning the matrix shape and the always-include-host guarantee).
  • cargo clippy --all-targets -- -D warnings, cargo fmt --check.
  • mise run test:bats test/optional_platform.bats — three new integration tests:
    • aube-lock.yaml captures cross-platform optional natives by default
    • pnpm-lock.yaml keeps pnpm's host-only default
    • required platform-mismatched dep still gets fetched and linked (regression guard for the catch-up pass)
  • mise run test:bats across install.bats, add.bats, ignored_optional_dependencies.bats, lockfile_settings.bats, resolve.bats, dedupe.bats — no regressions.

🤖 Generated with Claude Code

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

This PR widens the resolver's platform filter when writing aube-lock.yaml to a curated set of common OS/CPU/libc triples (darwin-arm64, linux-{x64,arm64}×{glibc,musl}, win32-{x64,arm64}), plus the current host's triple unconditionally. After writing the wide lockfile the install path calls filter_graph to trim node_modules back to host-only, and a new catch-up fetch handles required (non-optional) platform-mismatched packages that survive the trim. Foreign lockfile formats and lockfile=false preserve pnpm's host-only default.

Confidence Score: 5/5

Safe to merge — logic is correct, all edge cases handled, and new behavior is covered by unit and integration tests.

All remaining findings are P2 (test portability nit for Windows runners). Core logic for wide-platform resolve, lockfile write, post-write filter_graph, and catch-up fetch is sound.

test/optional_platform.bats — two tests implicitly assume a non-Windows host and would fail on a Windows CI runner.

Important Files Changed

Filename Overview
crates/aube-resolver/src/platform.rs Adds SupportedArchitectures::aube_lock_default() with an explicit 7-triple matrix plus unconditional host-triple injection. Logic is sound: is_supported correctly skips the libc check for non-Linux combos, explicit_combinations bypasses cartesian expansion cleanly, and the unit-test suite covers the key invariants.
crates/aube/src/commands/install/settings.rs Introduces target_lockfile_kind into ResolverConfigInputs and gates aube_lock_default() on (no manifest supportedArchitectures) AND (LockfileKind::Aube). Foreign-format and lockfile=false paths correctly fall through to host-only.
crates/aube/src/commands/install/mod.rs Correctly threads target_lockfile_kind into both the --lockfile-only and the streaming resolver paths; adds filter_graph after writing the wide lockfile so node_modules stays host-only; adds a catch-up fetch for required platform-mismatched packages that survive filter_graph.
crates/aube-resolver/src/lib.rs Minimal change — re-exports SupportedArchitectures alongside is_supported for use by the install command layer.
test/optional_platform.bats Adds five integration tests covering the main feature scenarios. Two tests silently assume a non-Windows host and would fail on a Windows CI runner since win32 is in the wide default matrix.

Fix All in Claude Code

Reviews (6): Last reviewed commit: "install: defer platform-mismatched fetch..." | Re-trigger Greptile

Comment thread crates/aube/src/commands/install.rs Outdated
Fresh resolves filtered optional native deps by the host's os/cpu/libc
before writing the lockfile, so an `aube install` on macOS produced an
`aube-lock.yaml` missing `@rollup/rollup-linux-*`, `@esbuild/linux-*`,
etc. A subsequent install on Linux CI then failed because the linker
had no entry to reach for. Users had to hand-write
`pnpm.supportedArchitectures` in `package.json` just to get a
portable lockfile (see e.g. jdx/usage#594).

The fix: when the project writes `aube-lock.yaml` and the manifest
has no explicit `supportedArchitectures`, resolve across an explicit
set of (os, cpu, libc) triples — darwin-arm64, linux × {x64, arm64} ×
{glibc, musl}, and win32 × {x64, arm64}. darwin-x64 is intentionally
out of the baseline: Apple Silicon is the shipping Mac platform and
several major native ecosystems (sharp, swc) have already dropped
Intel Mac binaries. A user still on an Intel Mac widens with
`pnpm.supportedArchitectures.cpu: ["current", "x64"]` as usual.

`node_modules` stays host-only: the in-memory graph is trimmed via
`filter_graph` after the lockfile write, mirroring the pass that
already runs on the lockfile-happy install branch.

Foreign lockfile formats (pnpm-lock.yaml, yarn.lock,
package-lock.json, bun.lock) keep their current host-only default so
aube doesn't silently bake more packages into them than the native
tool would have produced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the claude/unruffled-tereshkova-e2f975 branch from 19725fe to 4fec557 Compare April 19, 2026 17:56
The prior revision mapped `source_kind_before` directly into
`ResolverConfigInputs::target_lockfile_kind`, so `None` meant both
"no lockfile on disk — we'll write aube-lock.yaml" and "lockfile=false
— we'll write nothing." `configure_resolver` then widened the
platform filter in the second case too, and the streaming fetch
coordinator downloaded optional tarballs for every common platform
just for the in-memory graph trim to discard them before linking.

Collapse the `lockfile=false` case to `None` and reserve `Some(Aube)`
for the "will actually write aube-lock.yaml" intent. The wide-default
branch now keys off a strict `Some(Aube)` match, so nothing resolves
packages that no sink would consume.

Caught by Cursor Bugbot on #94.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread crates/aube-resolver/src/platform.rs
Comment thread crates/aube/src/commands/install/mod.rs
Two follow-ups on the aube-lock widening, both caught by Cursor
Bugbot on #94:

(1) `aube_lock_default()` listed a hand-picked matrix that deliberately
omitted darwin-x64. Applied verbatim, that dropped Intel Mac users'
own native optional deps out of the resolver's graph — the
post-resolve `filter_graph` can't bring packages back that never
entered the graph. Always append the host's own triple to the
baseline matrix so an Intel Mac (or freebsd, ppc64, …) install gets
its own natives even when the user hasn't hand-rolled
`supportedArchitectures`.

(2) The streaming fetch coordinator downloaded tarballs for every
resolved package, so widening the resolver for aube-lock.yaml also
pulled every foreign-platform native tarball into the store only for
`filter_graph` to discard the graph entries before link. Carry
`os`/`cpu`/`libc` on `ResolvedPackage` and check each incoming
package against the same narrow (manifest) supportedArchitectures
the post-resolve filter uses — if the linker would drop it, skip the
fetch. Registry ignoredOptionalDependencies get the same treatment.
Local `file:`/`link:` packages have empty arrays and always pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f2f9b44. Configure here.

Comment thread crates/aube/src/commands/install/mod.rs
jdx and others added 2 commits April 19, 2026 13:26
… optionals

The previous commit skipped tarball fetches for packages that failed
the host `is_supported` check or were named in
`ignoredOptionalDependencies`. That's correct for optional deps but
unsafe in general: `filter_graph` only strips optional edges, so a
required dep with platform constraints (broken package, or one that
declares `os`/`cpu` without the `optional` marker) survives the graph
trim — and would then crash at link time when the store index is
missing because we skipped the fetch.

Revert the fetch filter. The ResolvedPackage struct goes back to its
pre-widening shape — nothing external consumed the os/cpu/libc
fields, so keeping them would be dead API surface. We accept
over-fetching foreign-platform natives into the CAS when aube-lock
widening is active (first resolve after package.json changes); the
extra tarballs land in `~/.aube-store` but never get linked into
`node_modules` — over-fetch, not over-link.

Caught by Cursor Bugbot on #94.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the perf win the previous revert left on the table without
re-introducing the correctness bug Cursor Bugbot caught.

Shape: the streaming fetch coordinator now skips tarball downloads
for registry packages whose `os`/`cpu`/`libc` don't match the host
(local `file:`/`link:` deps still always fetch — their platform
arrays are empty and `is_supported` treats them as unconstrained).
After the resolver finishes and `filter_graph` trims optional
platform mismatches out of the in-memory graph, a catch-up pass
re-reads the surviving `graph.packages` and runs
`fetch_packages_with_root` for any entry still missing a store
index. In the common case (platform-mismatched = optional, which
covers rollup / esbuild / swc / sharp native shims) the catch-up set
is empty because `filter_graph` already dropped those entries. The
rare non-empty case is a broken package that declares `os` / `cpu`
constraints without the optional marker — aube now fetches it,
matching pnpm's `packageIsInstallable` "install with warning"
behavior, instead of the previous revert's eager up-front fetch.

Net effect: first install / re-resolve into aube-lock.yaml stops
materializing ~N × platforms worth of native tarballs into
`~/.aube-store/`. Steady-state installs against a committed lockfile
were already running `filter_graph` *before* their fetch loop, so
they're unchanged.

`ResolvedPackage` regains `os` / `cpu` / `libc` — populated at each
of the three `tx.send` sites in the resolver — so the streaming
coordinator can make the skip decision without looking up the graph.

New BATS regression test `required platform-mismatched dep still
gets fetched and linked` exercises the catch-up path end-to-end with
a required dep that declares `os: ["win32"]` on a non-Windows host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 7603485 into main Apr 19, 2026
17 checks passed
@jdx jdx deleted the claude/unruffled-tereshkova-e2f975 branch April 19, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant