resolver: widen aube-lock.yaml to every common platform#94
Conversation
Greptile SummaryThis PR widens the resolver's platform filter when writing Confidence Score: 5/5Safe 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.
|
| 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. |
Reviews (6): Last reviewed commit: "install: defer platform-mismatched fetch..." | Re-trigger Greptile
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>
19725fe to
4fec557
Compare
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>
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
… 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>

Summary
Fixes the cross-platform lockfile footgun reported in jdx/usage#594: an
aube installon macOS used to produce anaube-lock.yamlmissing@rollup/rollup-linux-*,@esbuild/linux-*, etc., and Linux CI would fail at module-resolve time. The user's workaround was hand-writing apnpm.supportedArchitecturesstanza intopackage.json.What changes
aube-lock.yaml. When the resolver is about to writeaube-lock.yamland the manifest has no explicitpnpm.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.pnpm-lock.yaml,yarn.lock,package-lock.json, andbun.lockkeep pnpm's host-only default — aube shouldn't silently bake more packages into them than the native tool would have written.aube-lock.yaml.lockfile=falseskips widening entirely — no wasted resolution for a lockfile that will never exist.node_modulesstays host-only. An in-memoryfilter_graphpass 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.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; afterfilter_graphtrims 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 declareos/cpuwithout the optional marker) thatfilter_graphdoesn't touch. Common-case fresh installs end up fetching only what the host actually links. Localfile:/link:deps carry no platform constraints and always fetch inline.Commits
resolver: widen aube-lock.yaml to every common platform— wide default + post-write filter_graph on the fresh-resolve path.install: disambiguate target_lockfile_kind when lockfile disabled— fixes an overloadedNonesolockfile=falsedoesn't needlessly widen.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.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.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-resolvergains 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 defaultpnpm-lock.yaml keeps pnpm's host-only defaultrequired platform-mismatched dep still gets fetched and linked(regression guard for the catch-up pass)mise run test:batsacrossinstall.bats,add.bats,ignored_optional_dependencies.bats,lockfile_settings.bats,resolve.bats,dedupe.bats— no regressions.🤖 Generated with Claude Code