feat(linker,cli,bench): Phase 66 Phase 4f — flip linker default Isolated → Hoisted (dual gate passed)#35
Open
tolgaergin wants to merge 1 commit into
Open
Conversation
…ted → Hoisted (dual gate passed)
The Phase 4 capstone. With the v2 virtual store landed in 4d and
prune in 4e, hoisted's pre-Phase-66 warm-install regression
(2.7× slower than isolated under v1's clonefile-from-store
materialization) collapses to identity: both modes now symlink
project `node_modules/<dep>` into the global
`~/.lpm/store/v2/links/<graph-key>/` and warm-install becomes pure
project-side symlink reconstruction regardless of mode.
The flip is gated on a paired warm-install bench (preplan §6.5):
```
========================================
PHASE 66 PHASE 4F DUAL-GATE RESULTS
========================================
isolated median: 110 ms
hoisted median: 110 ms
hoisted - iso : +0 ms
GATE 1 (hoisted ≤ 200 ms absolute): ✓
GATE 2 (hoisted ≤ isolated + 30 ms paired): ✓
VERDICT: BOTH GATES PASS — flip the linker default to hoisted.
```
n=20 paired samples on `bench/fixture-large` under README footnote 3
methodology (`rm -rf node_modules` only; lockfile + cache + `.lpm`
all survive). 18 of 20 paired samples landed within ±5 ms of zero;
the two outliers (+32 ms, +134 ms) look like macOS APFS page-cache
warm-up noise, not a systematic bias — median is unaffected.
## What's in this PR
**1. Bench harness** — new `bench/scripts/run-4f-gate.sh`. Round-
robin per outer iter (each pair runs back-to-back, identical state),
warm-up pass discarded, atomic timing via `date +%s%N`. Persists
raw samples to `/tmp/lpm-bench-4f-gate/<tag>-results/samples.csv`
for triage. Exits 0 only when BOTH gates pass.
**2. Default flip** — `lpm_linker::LinkerMode::default()` flipped
Isolated → Hoisted. Module-doc + `Isolated`/`Hoisted` field docs
updated to reflect the new contract. The flip is documented as the
direct consequence of the Phase 4d store-version flip and the
4f bench result.
**3. Cascading updates** to call sites that hard-coded
`LinkerMode::Isolated` as the assumed default:
- `linker_config::resolve_effective_linker` returns
`LinkerMode::default()` instead of literal `Isolated` so future
default flips don't require a second edit at this site.
- `linker_config::resolve_effective_linker_from_bytes` same change
for the malformed-manifest fallback.
- `install_state::compute_install_hash` (back-compat shim used by
test fixtures) now defaults the linker arg to
`LinkerMode::default()` — callers expecting "the hash a default
install would produce" get the post-4f value.
- `install_state::compute_install_hash_v3` same change, same
rationale.
**4. CLI help text** — `lpm install --linker` description
rewritten. Pre-4f: "isolated (default — pnpm-style isolation) or
hoisted (npm v3+ flat layout). Hoisted is faster on full-wipe / CI
cold-cache workloads, identical on everyday installs, and has
stricter peer-dep semantics — opt-in only." Post-4f: "hoisted
(default since Phase 66 4f — npm v3+ flat layout) or isolated
(pnpm-style strict isolation). Both materialize as project
node_modules/<dep> symlinks into the global virtual store, so
warm-install latency is identical between modes; the distinction
is whether transitive deps are reachable at the project root
(hoisted: yes — npm-compat) or only through each consumer's own
siblings (isolated: no — phantom-dep catcher)."
## Test scoping (the long tail)
Workflow tests + a few unit tests assert on isolated-shape behavior
(`<project>/.lpm/wrappers/...` paths, JSON envelope `symlinked`
counts, hash-pinning against a literal default). The flip would
break their contracts wholesale; pinning each test's intent
explicitly preserves what they test:
- `commands::dev::tests::auto_install_if_stale_writes_v6_install_hash_for_empty_deps` —
asserted `lines[2] == "l:isolated"`. Updated to assert against
`LinkerMode::default().as_str()` so the test stays stable across
any future default flip.
- `install_state::tests::schema_tag_is_baked_into_hash` — updated
to call `compute_install_hash_v6` with explicit
`LinkerMode::Isolated`, decoupling the schema-pin from the
default-linker-pin. The test catches schema tag drift, NOT
default-linker drift.
- `install_state::tests::populated_v1_wrappers_force_v2_migration_on_default` —
pre-flip this asserted `compute_install_hash("pkg", "lock")`
equality which now includes the new default. The shim's behavior
change handles it.
- `tests/workflows::install::install_invalidates_freshness_cache_on_lpm_linker_flip` —
the test flips from default to "the other mode". Updated to
resolve "other" as `match LinkerMode::default() { Hoisted =>
"isolated", Isolated => "hoisted" }` so the contract holds
regardless of which is the current default.
- `tests/workflows::install::install_json_envelope_with_one_package_matches_snapshot` —
envelope-shape snapshot. Pinned to `LPM_LINKER=isolated` so the
`symlinked` count stays at 2 rather than flipping to 0
(hoisted's flat real-dir shape).
- `tests/workflows::install_patches::*` — every test in this file
asserts on `<project>/.lpm/wrappers/<seg>/...` paths (v1 isolated
layout). Added a file-local `lpm_isolated()` helper that wraps
`lpm_with_registry` with `.env("LPM_LINKER", "isolated")`;
bulk-replaced the 14 `lpm_with_registry(` call sites in this
file with `lpm_isolated(`. Test contract preserved without
pinning the helper or the env globally.
- `commands::rebuild::tests::live_package_dir_falls_back_to_store_when_unlinked` —
the env-coupled `live_package_dir` was finding a stale
`esbuild@0.21.5` entry in the developer's real
`~/.lpm/store/v2/links/` from an earlier bench run, so the
fallback assertion failed (left = real-store-path, right =
fixture). Rewrote the test to call
`live_package_dir_with_v2(None)` so the v2 store walk is fully
disabled — pure unit test of the v1 wrapper-probe fallback.
## What did NOT need to change
- v1 isolated fast paths still work via `LPM_LINKER=isolated`
override.
- v1 store mode still works via `LPM_STORE_VERSION=v1` (Phase 4d
downgrade-rollback).
- `lpm_workflows::tests::support::lpm()` helper — no global
`LPM_LINKER=isolated` pin. Each test that needs isolated shape
pins it locally; tests that exercise the default chain see the
new default cleanly. This is more honest than a global pin
(which would silently mask hoisted-default regressions in
tests that don't even know they're isolated-shape).
## Pre-merge gate
- cargo clippy --workspace --all-targets -- -D warnings ✓
- cargo fmt --check ✓
- cargo nextest run --workspace --exclude lpm-integration-tests
→ 5742 tests pass, 7 skipped ✓
- cargo test -p lpm-auth (parallel-deterministic) ✓
- audit-fixtures default → 17 PASS / 1 SKIP / 0 mixed ✓
- audit-fixtures `LPM_STORE_VERSION=v1` → 17 PASS / 1 SKIP / 0 mixed ✓
- 4f dual-gate bench (n=20) → BOTH GATES PASS ✓
## What's next (post-Phase-66)
Phase 66 is complete. The next blocks on the active-roadmap are
follow-up work outside this phase:
- **README perf row refresh** with the new "hoisted default,
identical to isolated under v2" warm-install number. Single-
line edit; saves a bench rerun later.
- **CHANGELOG + release notes** for the 4d default-store flip and
the 4f default-linker flip. Single rollup entry covering the
user-visible behavior changes (silent migration, `lpm cache
prune`, default linker switch).
- **Phase 5** (Phase 63c openat) — independent perf work, queued.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Phase 4 capstone. With the v2 virtual store landed in 4d and prune in 4e, hoisted's pre-Phase-66 warm-install regression collapses to identity: both modes now symlink project
node_modules/<dep>into the global v2 store, and warm-install becomes pure project-side symlink reconstruction regardless of mode.Stacks on #34 (Phase 4e —
lpm cache prune). Will auto-rebase tomainafter #30 → #31 → #32 → #33 → #34 land.Dual-gate bench result (preplan §6.5)
n=20 paired samples on
bench/fixture-largeunder README footnote 3 methodology. 18 of 20 paired samples landed within ±5 ms of zero; two outliers (+32 ms, +134 ms) look like APFS page-cache warm-up noise — median unaffected.For context, the pre-Phase-66 numbers (active-roadmap §2.6) were:
The v2 virtual store closed that gap completely.
What's in this PR
Bench harness — new
bench/scripts/run-4f-gate.sh. Round-robin per outer iter, warm-up discarded, samples persisted to CSV for triage. Exits 0 only when both gates pass.The flip —
lpm_linker::LinkerMode::default()flipped Isolated → Hoisted with cascading updates:linker_config::resolve_effective_linkerreturnsLinkerMode::default()instead of literalIsolated(future-flip-stable).install_state::compute_install_hash+compute_install_hash_v3(back-compat shims) follow the new default.lpm install --linkerhelp text rewritten to reflect the new contract.Test scoping (the long tail) — 7 tests asserted on isolated-shape behavior. Each updated to either:
LPM_LINKER=isolatedexplicitly when testing isolated-shape (install_patches::*tests,install_json_envelope_*snapshot).LinkerMode::default()instead of literalIsolatedwhen asserting on default-linker output (hash pin tests,auto_install_if_stale_*,install_invalidates_freshness_cache_on_lpm_linker_flip).live_package_dir_with_v2(None)to disable env coupling (live_package_dir_falls_back_to_store_when_unlinkedwas finding stale entries in the dev's real~/.lpm/store/v2/links/).No global
LPM_LINKER=isolatedpin in the workflow helper — each isolated-shape test pins itself, which is more honest than a global pin (the global would silently mask hoisted-default regressions in tests that don't know they're isolated-shape).Pre-merge gate
What's next (post-Phase-66)
Phase 66 is complete with this PR. The follow-up work is outside this phase:
Test plan
LPM_STORE_VERSION=v1downgradelpm installon a fresh project produces hoisted-flatnode_modules/<dep>symlinks into~/.lpm/store/v2/links/<key>/...lpm install --linker isolatedstill produces isolated layoutLPM_STORE_VERSION=v1 lpm installstill works (downgrade rollback)audit-fixtures (v1)andaudit-fixtures (v2)rows🤖 Generated with Claude Code