Skip to content
Open
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
22 changes: 20 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
run: cargo nextest run --workspace --exclude lpm-integration-tests --no-fail-fast

audit-fixtures:
name: Hoisted-mode compat audit
name: Hoisted-mode compat audit (${{ matrix.store_version }})
runs-on: ubuntu-latest
timeout-minutes: 30
# Run the audit fixtures on every push to main + every PR. The
Expand All @@ -139,6 +139,18 @@ jobs:
# package shape. Equal-outcome failures across modes (e.g. an
# ecosystem-side ESLint 9 incompat) don't gate the build — only
# asymmetric outcomes do.
#
# Phase 66 Phase 4b: matrix runs the suite under both the default
# (v1) store layout AND the new opt-in `LPM_STORE_VERSION=v2`
# virtual-store layout. Both must produce the same per-fixture
# outcomes. v2 stays opt-in until Phase 4d (default flip), so a
# green v1 row is required to merge; the v2 row gates a *separate*
# promise that nothing in the v2 code path regresses the matrix
# while it's still hidden behind the env var.
strategy:
fail-fast: false
matrix:
store_version: ["v1", "v2"]
steps:
- uses: actions/checkout@v5

Expand Down Expand Up @@ -178,14 +190,20 @@ jobs:
# auto-install layer adds non-fixture work that varies with
# the registry's response time.
LPM_NO_SKILLS: "1"
# Phase 66 Phase 4b: v2 row sets the env var; v1 row leaves
# it unset. The lpm install pipeline reads `LPM_STORE_VERSION`
# and dispatches to `~/.lpm/store/v2/{objects,links}/` when
# set to "v2", v1's `<HOME>/.lpm/store/v1/<name>@<version>/`
# otherwise.
LPM_STORE_VERSION: ${{ matrix.store_version == 'v2' && 'v2' || '' }}

# Always upload result JSONs — useful for triaging mode-asymmetric
# outcomes in CI without re-running locally.
- name: Upload audit result artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: audit-fixtures-results
name: audit-fixtures-results-${{ matrix.store_version }}
path: bench/audit-fixtures/results/
retention-days: 14

Expand Down
6 changes: 4 additions & 2 deletions bench/audit-fixtures/peer-heavy/apollo-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
"name": "audit-fixture-apollo-graphql",
"version": "1.0.0",
"private": true,
"_comment": "Phase 2 hoisted compat audit. Peer-heavy fixture: Apollo Client + GraphQL. Apollo's modular packages (`@apollo/client`, `graphql`) have a tight peer-dep coupling — the GraphQL package version must match what `@apollo/client` was built against. A hoisting bug that nests a wrong graphql version under apollo would break query parsing at runtime.",
"_comment": "Phase 2 hoisted compat audit. Peer-heavy fixture: Apollo Client + GraphQL. Apollo's modular packages (`@apollo/client`, `graphql`) have a tight peer-dep coupling — the GraphQL package version must match what `@apollo/client` was built against. A hoisting bug that nests a wrong graphql version under apollo would break query parsing at runtime. Phase 66 4b: react/react-dom were missing from this fixture; @apollo/client's index.js eager-loads `react`, so a `require('@apollo/client')` smoke check needs them in the install set. Pre-Phase-66, the audit harness's leaked `/tmp/lpm-audit-work/.lpm/wrappers/` (from sibling fixtures) accidentally satisfied the require via Node's symlink walk-up — a false positive. Real-world Apollo users always install react/react-dom themselves, so this is what a realistic install looks like.",
"dependencies": {
"@apollo/client": "^3.11.0",
"graphql": "^16.9.0"
"graphql": "^16.9.0",
"react": "^18.3.0",
"react-dom": "^18.3.0"
}
}
14 changes: 14 additions & 0 deletions bench/audit-fixtures/run-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ set -e

HERE="$(cd "$(dirname "$0")" && pwd)"

# Wipe the work-dir parent ONCE at suite start. `run-audit.sh` already
# wipes `/tmp/lpm-audit-work/<fixture>-<mode>/` per fixture, but it
# does not clean the parent — so a previous suite run that ever
# populated `/tmp/lpm-audit-work/.lpm/wrappers/` (e.g., a fixture's
# `lpm install` cwd-relative behavior, or a manual debug session)
# leaves leftover wrappers that Node's symlink walk-up resolves
# through. That produced false-positive PASSes for fixtures missing
# real peer deps in their package.json — see Phase 66 4b's
# `peer-heavy/apollo-graphql`, `tooling/eslint-flat-config`, and
# `workspace/monorepo-basic` archaeology. Wiping the parent makes
# every suite run fixture-isolated and the baseline trustworthy.
WORK_PARENT="${LPM_AUDIT_WORK_BASE:-/tmp/lpm-audit-work}"
rm -rf "$WORK_PARENT"

# All fixtures, deterministically ordered.
FIXTURES=(
"peer-heavy/react-ssr"
Expand Down
5 changes: 3 additions & 2 deletions bench/audit-fixtures/tooling/eslint-flat-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
"name": "audit-fixture-eslint-flat-config",
"version": "1.0.0",
"private": true,
"_comment": "Phase 2 hoisted compat audit. Tooling fixture. ESLint 9 with flat config + two popular plugins. Risk: ESLint walks node_modules to discover plugins; some plugins import their own peer (eslint-plugin-react-hooks needs eslint as peer). Hoisted layout has plugins at top level — should work. Isolated layout has plugins behind a symlink — also should work. Failure mode would be plugin not finding its peer, or a plugin's transitive dep missing.",
"_comment": "Phase 2 hoisted compat audit. Tooling fixture. ESLint 9 with flat config + two popular plugins. Risk: ESLint walks node_modules to discover plugins; some plugins import their own peer (eslint-plugin-react-hooks needs eslint as peer). Hoisted layout has plugins at top level — should work. Isolated layout has plugins behind a symlink — also should work. Failure mode would be plugin not finding its peer, or a plugin's transitive dep missing. Phase 66 4b: typescript was missing from this fixture; @typescript-eslint/typescript-estree's index.js eager-loads `typescript` (required by `getWatchProgramsForProjects.js`), so loading the parser fails without it. Pre-Phase-66, the audit harness's leaked `/tmp/lpm-audit-work/.lpm/wrappers/` (from sibling fixtures) accidentally satisfied the require via Node's symlink walk-up — a false positive. Real-world TypeScript-ESLint users always install typescript themselves.",
"type": "module",
"dependencies": {
"eslint": "^9.17.0",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"eslint-plugin-react-hooks": "^5.1.0"
"eslint-plugin-react-hooks": "^5.1.0",
"typescript": "^5.5.0"
}
}
5 changes: 3 additions & 2 deletions bench/audit-fixtures/workspace/monorepo-basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
"name": "audit-fixture-workspace-monorepo",
"version": "1.0.0",
"private": true,
"_comment": "Phase 2 hoisted compat audit. Workspace fixture (no other coverage exists). Three-member monorepo with internal cross-deps via workspace:* and external deps. Verifies (1) workspace resolution under both linker modes, (2) internal symlink creation per-member, (3) hoisting of shared external deps without breaking workspace identity. The root workspace:* references are needed because lpm's bare `install` only processes member deps when the root references them — different shape than npm/bun which auto-recurse, but it's what's wired today.",
"_comment": "Phase 2 hoisted compat audit. Workspace fixture (no other coverage exists). Three-member monorepo with internal cross-deps via workspace:* and external deps. Verifies (1) workspace resolution under both linker modes, (2) internal symlink creation per-member, (3) hoisting of shared external deps without breaking workspace identity. The root workspace:* references are needed because lpm's bare `install` only processes member deps when the root references them — different shape than npm/bun which auto-recurse, but it's what's wired today. Phase 66 4b: lodash is also declared at root because lpm's v1 resolver does NOT recurse into workspace member `dependencies` (a separate v1 bug, tracked outside Phase 66's v2 store work). Real-world monorepos commonly hoist shared external deps to the root anyway, so this is a realistic shape. Pre-Phase-66, the audit harness's leaked `/tmp/lpm-audit-work/.lpm/wrappers/` from sibling fixtures accidentally satisfied the require via Node's symlink walk-up — a false positive.",
"dependencies": {
"audit-utility": "workspace:*",
"audit-core": "workspace:*",
"audit-consumer": "workspace:*"
"audit-consumer": "workspace:*",
"lodash": "^4.17.21"
},
"workspaces": ["packages/*"]
}
Loading