Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions .claude/skills/aube-bump/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,23 @@ cargo check --workspace --all-targets --message-format short # iterate until e
cargo clippy --workspace --all-targets --all-features -- -D warnings
REAL_HOME="$HOME"; mkdir -p /tmp/clean-aube-home
env HOME=/tmp/clean-aube-home RUSTUP_HOME="$REAL_HOME/.rustup" CARGO_HOME="$REAL_HOME/.cargo" \
cargo test --workspace --lib # registry/config tests read ~/.npmrc
cargo test --workspace # registry/config tests read ~/.npmrc
```

**`--workspace` with NO `--lib` — that is what CI runs** (`aube-parity.yml`, `working-directory:
vendor/aube`, `run: cargo test --workspace`). `--lib` runs only the in-crate unit tests and SKIPS
`crates/*/tests/**` entirely, so the fork-discipline integration tests — the ones that pin
default-preservation, exactly what a bump is most likely to break — never execute. The v1.35 bump
shipped a green `--lib` run and CI still failed on
`aube-settings/tests/gvs_disable_list_embedder_default.rs`, whose pinned copy of aube's built-in GVS
list had gone stale against the new upstream baseline.

**Run aube's suite from inside `vendor/aube` (or the venue), never via `--manifest-path` from the nub
root.** Cargo reads `.cargo/config.toml` from the INVOCATION directory, and aube's own config sets
`RUST_TEST_THREADS = "1"` because `aube-util`'s `concurrency` and `http::ticket_cache` tests mutate
process env and are not thread-safe. Invoking from the nub root silently drops that and those tests
flake — which reads exactly like a regression your merge caused.

For each error ask: **is this symbol nub delta or upstream?** Then apply the doctrine. Never paper
over with `.unwrap()`/`.expect()` or by deleting a capability.

Expand Down Expand Up @@ -245,12 +259,18 @@ applies. Reverting was correct. Let the tests arbitrate; don't defend a graft.
Grep after every bump — if one vanished, a resolution was wrong:

```sh
grep -rn "workspace_markers\|lockfile_basename\|EmbedderProfile\|read_branded_pnpm_config\|env_prefix\|cache_namespace\|engine_context\|env_overlay\|path_prepends\|runtime_node\|cold_path" vendor/aube/crates
grep -rn "workspace_markers\|lockfile_basename\|virtual_store_subdir\|branded_env_alias_enabled\|read_branded_pnpm_config\|env_prefix\|cache_namespace\|engine_context\|env_overlay\|path_prepends\|runtime_node\|cold_path" vendor/aube/crates
```

- **Embedder profile plumbing** — `env_prefix`, `cache_namespace`, `lockfile_basename`,
`workspace_markers`, `read_branded_pnpm_config` gating. Holds the brand + config boundary. Largely
upstreamed, so it usually converges rather than conflicts.
`workspace_markers`, `virtual_store_subdir`, `read_branded_pnpm_config` gating. Holds the brand +
config boundary. Largely upstreamed, so it usually converges rather than conflicts. The profile type
is `Embedder` (`aube-util/src/identity.rs`), reached via `aube_util::embedder()`.
`virtual_store_subdir` earns its place in the grep: the v1.35 bump auto-merged two upstream call sites
that hardcoded `aube_store::VIRTUAL_STORE_SUBDIR` (`"virtual-store"`) over nub's profile-named leaf,
with **no conflict markers** — it would have shipped silently. `branded_env_alias_enabled`
(`aube-util/src/env.rs`) is the single switch gating every `AUBE_*` alias in `settings.toml`, so each
bump's new branded settings inherit the boundary from it alone.
- **Linker** — GVS, collective hidden tree as the sole phantom mechanism, per-package
force-materialization (`diskMaterializePackages`), workspace-spanning hoisted planning, memoized
clonedir probes, whole-dir `clonefile` on macOS, direct-exec of native bins.
Expand Down
82 changes: 58 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions crates/nub-cli/src/pm_engine/vite_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,17 @@ pub(crate) fn vite_lt_8_1(version: &str) -> bool {
/// embedder-namespaced to `~/.cache/nub/pm`). This is the realpath prefix of
/// every store-resident served module, so it is the value Vite must allow. The
/// leaf name comes from the active embedder (`store` under nub), matching what
/// `aube_store::Store::virtual_store_dir` writes, so the two never drift. The
/// embedder profile is registered by the time install runs, so
/// `aube_store::dirs::cache_dir()` resolves the nub namespace.
/// `aube_store::Store::virtual_store_dir` writes. The embedder profile is
/// registered by the time install runs, so `aube_store::dirs::cache_dir()`
/// resolves the nub namespace.
///
/// Only the DEFAULT location is reproduced here. aube v1.35.0 added the
/// `globalVirtualStoreDir` / `cacheDir` settings, which relocate the real store
/// at runtime; the resolver for those (`commands::settings_context::
/// global_virtual_store_dir`) is `pub(crate)` to the aube crate, so nub cannot
/// consult it without widening that surface. A project that sets either setting
/// therefore gets a `.modules.yaml` naming the default path rather than the
/// relocated one, and Vite would not be told to allow the real store.
Comment on lines +241 to +247

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ The gap is real but the framing understates it, and the pub(crate) visibility is not the blocker. aube v1.35.0 already writes .modules.yaml itself with the settings-resolved store (install/gvs.rs::write_modules_metadata, called from install/finalize.rs:235 whenever !virtual_store_only), and when the file is absent it writes a single-key pretty-printed JSON object — which is_nub_modules_yaml accepts as nub's own stub. Since vite_compat::apply runs after the engine returns, nub does not merely fail to consult the resolver: it overwrites a correct value with the default one.

Technical details
# nub's `.modules.yaml` writer clobbers the engine's settings-resolved value

## Affected sites
- `crates/nub-cli/src/pm_engine/vite_compat.rs:248-250``global_virtual_store_dir()` reproduces only `cache_dir().join(embedder().virtual_store_subdir)`.
- `crates/nub-cli/src/pm_engine/vite_compat.rs:264-280``write_modules_yaml`; the `is_nub_modules_yaml` guard is what lets the clobber through.
- `crates/nub-cli/src/pm_engine/vite_compat.rs:286-294``is_nub_modules_yaml` returns `true` for any single-key JSON object with `virtualStoreDir`, which is exactly the shape `write_modules_metadata` emits via `serde_json::to_vec_pretty` on the absent-file path.
- `crates/nub-cli/src/pm_engine/install_family.rs:892``vite_compat::apply` runs post-engine, so nub always writes last.
- `vendor/aube/crates/aube/src/commands/install/finalize.rs:229-237` — engine-side write, using `store.virtual_store_dir()` under GVS+`Isolated` and `aube_dir` otherwise.
- `vendor/aube/crates/aube/src/commands/settings_context.rs:544` — the settings-aware resolver, which honors `globalVirtualStoreDir` then falls back to `resolved_cache_dir(cwd).join(embedder().virtual_store_subdir)`.

When the two values diverge:

| Situation | engine writes | nub overwrites with | effect |
| --- | --- | --- | --- |
| defaults | default global store | same | none |
| `cacheDir` or `globalVirtualStoreDir` set | the relocated store | the default store | Vite is told to allow a path it never serves from, and the real store stays disallowed → `403 … outside of Vite serving allow list` |
| GVS off (`next`, `react-native`, `hoisted`) | project-local `node_modules/.store` | the global store | benign; the project-local path is inside the workspace root and allowed anyway |

Also note the engine writes one file per physical importer while nub writes only the
workspace root's, so in a monorepo the root file and the member files disagree after
nub's pass.

## Required outcome
- A project that relocates its store via `cacheDir` or `globalVirtualStoreDir` ends up with a `.modules.yaml` naming the real store.
- nub never replaces a `virtualStoreDir` written by the engine with a less-informed value.

## Suggested approach
The cheapest correct change needs no visibility widening: treat an existing
`virtualStoreDir` as authoritative and leave the file alone, since the engine has
already written the resolved path by the time `apply` runs. If nub's Unit A is retired
entirely in favour of upstream's writer (see the review body), this resolves itself.

## Open questions for the human
- Is nub's Unit A still needed at all now that the engine writes the same file for every importer with the resolved path?

fn global_virtual_store_dir() -> Option<PathBuf> {
aube_store::dirs::cache_dir().map(|c| c.join(aube_util::embedder().virtual_store_subdir))
}
Expand Down
4 changes: 4 additions & 0 deletions tests/aube-bats/skips.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ install.bats|aube run auto-installs when installed package metadata is missing|'
install.bats|aube install ignores --network-concurrency=0 and falls back to default|test invokes 'aube -v' — nub's -v is --version (reserved); re-evaluate when the flag wiring lands
add.bats|aube add: refuses to add to workspace root with aube-workspace.yaml|nub-divergence: aube-workspace.yaml is not consulted (workspace-yaml toggle restricts discovery to pnpm-workspace.yaml), so it cannot define a workspace root — the pnpm-workspace.yaml variant asserts the guard
add.bats|aube add -w: errors outside a workspace|engine emits pnpm-workspace.yaml (brand-clean via workspace_markers(), PR #77); upstream test asserts aube-workspace.yaml
update.bats|aube update -r --latest updates a shared catalog entry|catalog fixture declares its workspace in aube-workspace.yaml, which workspace_markers() does not consult, so -r has no workspace root to filter against
update.bats|aube update -r --latest --no-save leaves the catalog range unchanged|catalog fixture declares its workspace in aube-workspace.yaml, which workspace_markers() does not consult, so -r has no workspace root to filter against
update.bats|aube update -r --latest updates a named catalog and preserves its prefix|catalog fixture declares its workspace in aube-workspace.yaml, which workspace_markers() does not consult, so -r has no workspace root to filter against
Comment on lines +46 to +48

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These three fixtures differ from nub only in the workspace file's name, so skipping them is right — but they were the only coverage catalogs.rs::update_workspace_yaml_catalog_entries had. It has no unit test, and the one new catalog test that still runs under nub exercises the package.json sibling instead. The YAML path is live under nub for any pnpm-incumbent workspace with a catalog: block, since read_branded_pnpm_config puts pnpm-workspace.yaml in workspace_yaml_names().

Technical details
# Workspace-YAML catalog rewrite loses its only test

## Affected sites
- `tests/aube-bats/skips.txt:46-48` — the three new skips.
- `vendor/aube/test/update.bats:164-219` — the skipped tests: default-catalog rewrite plus comment preservation, `--no-save` leaving the range untouched, and named-catalog prefix preservation (`^0.1.2``^3.0.1`).
- `vendor/aube/crates/aube/src/commands/catalogs.rs:265-296``update_workspace_yaml_catalog_entries`, reached from `update.rs:793`. Handles the `catalog` vs `catalogs.<name>` submap split and range-only replacement through `edit_workspace_yaml`.
- `vendor/aube/crates/aube/src/commands/catalogs.rs:566+` — the `tests` module covers `decide_add_rewrite`, `prune_unused_catalog_entries`, and the manifest upserts. Nothing calls either `update_*_catalog_entries`.
- `vendor/aube/crates/aube-manifest/src/workspace/config.rs:21-30``workspace_yaml_names()` appends `pnpm-workspace.yaml` whenever `engine_context().read_branded_pnpm_config` is set, which is what makes the YAML branch reachable under nub.
- `vendor/aube/test/update.bats:221-246``aube update -r --latest updates a package.json catalog source`, correctly left unskipped; it covers `update_manifest_catalog_entries` only.

## Required outcome
- nub CI exercises the workspace-YAML catalog rewrite — default catalog, named catalog with a preserved range prefix, and `--no-save` — against a workspace filename nub actually reads.

## Open questions for the human
- Is this path pnpm-compat-only in practice? If a nub-identity project can never reach `update_workspace_yaml_catalog_entries`, the natural home for the replacement is the compat harness rather than `tests/aube-bats/`.
- If neither home is worth the fixture, is a `known-gaps.txt` entry the honest record, given `skips.txt` self-describes as permanent *intended divergences*?


# ── pnpm-parity divergences (decision C: nub extends aube's behavior to match pnpm) ──
add.bats|aube add --save-peer writes only peerDependencies and does not install|nub dual-writes --save-peer to devDependencies+peerDependencies for pnpm parity (decision C); aube asserts peerDependencies-only
Expand Down Expand Up @@ -80,6 +83,7 @@ update.bats|aube update --lockfile-only: refreshes lockfile without populating n
update.bats|aube update --lockfile-only --latest: bumps direct deps without linking|fixture's committed aube-lock.yaml is invisible: nub's canonical lockfile name is nub.lock
update.bats|aube update preserves time: entries for direct deps (time-based mode)|fixture's committed aube-lock.yaml is invisible: nub's canonical lockfile name is nub.lock
update.bats|aube update drops a stray time: block under default resolution (pnpm parity)|fixture's committed aube-lock.yaml is invisible: nub's canonical lockfile name is nub.lock
update.bats|aube update: workspace update.ignoreDeps takes precedence|update itself succeeds; the assertions grep aube-lock.yaml, and nub's canonical lockfile name is nub.lock

# ── virgin projects self-pin nub via devEngines.packageManager ──────────────
# On the FIRST package.json-modifying verb in a project nub is first to touch,
Expand Down
6 changes: 6 additions & 0 deletions vendor/aube/.github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
"matchManagers": ["cargo"],
"matchPackageNames": ["/^sigstore-/"],
"groupName": "sigstore crates"
},
{
"description": "decmpfs 0.1.2 fails to compile for musl targets (FICLONE ioctl request typed c_ulong, which is c_int on musl). Hold at 0.1.0 until upstream fixes it.",
"matchManagers": ["cargo"],
"matchPackageNames": ["decmpfs"],
"allowedVersions": "<=0.1.0"
}
]
}
2 changes: 1 addition & 1 deletion vendor/aube/.github/workflows/auto-merge-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
fetch-tags: true
Expand Down
Loading
Loading