Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
474fc59
feat(phase-46): P1 schema extensions — StaticTier, ProvenanceSnapshot…
tolgaergin Apr 20, 2026
a15cbd3
feat(phase-46): P1 ScriptPolicyConfig loader + --policy/--yolo/--tria…
tolgaergin Apr 20, 2026
403a041
fix(phase-46): P1 audit findings 1 + 2 — honest help text + surface s…
tolgaergin Apr 20, 2026
665e74b
fix(phase-46): P1 audit v3 Finding 1 — warning names actually-effecti…
tolgaergin Apr 20, 2026
107fde5
feat(phase-46): P1 helper migration — show_install_build_hint + all_s…
tolgaergin Apr 20, 2026
f13541c
feat(phase-46): P1 metadata plumbing — published_at + behavioral_tags…
tolgaergin Apr 21, 2026
8c03c55
feat(phase-46): P1 trust-snapshot persistence + new-bindings diff notice
tolgaergin Apr 21, 2026
f4baed8
feat(phase-46): P1 lpm trust diff + lpm trust prune subcommands — P1 …
tolgaergin Apr 21, 2026
120fcc3
fix(phase-46): P1 audit v4 — accurate trust prune --json mutated flag…
tolgaergin Apr 21, 2026
648e0ae
feat(phase-46): P2 chunk 1 — static_gate classifier (layer 1)
tolgaergin Apr 21, 2026
1886a2b
fix(phase-46): hygiene — drop let-and-return in lpm-auth test helper
tolgaergin Apr 21, 2026
eda35fc
feat(phase-46): P2 chunk 2 — static_gate fixture corpus + stats-only …
tolgaergin Apr 21, 2026
028b9f3
feat(phase-46): P2 chunk 3 — populate static_tier + render in approve…
tolgaergin Apr 21, 2026
8a3b925
feat(phase-46): P2 chunk 4 — approve-builds --yes refuses amber/red
tolgaergin Apr 21, 2026
9e6e5d6
feat(phase-46): P2 chunk 5 — triage-mode install summary line
tolgaergin Apr 21, 2026
d6b66f4
feat(phase-46): P2 chunk 6 — corpus locked at 500 + bijection check +…
tolgaergin Apr 21, 2026
a4d469a
feat(phase-46): P3 chunk 1 — release-age resolver + parser + strict c…
tolgaergin Apr 21, 2026
53a4a8f
feat(phase-46): P3 chunk 2 — wire --min-release-age through install s…
tolgaergin Apr 21, 2026
dc66f71
feat(phase-46): P3 chunk 3 — ship-criteria E2E tests + §12.3 pin-bypa…
tolgaergin Apr 21, 2026
97d19c7
feat(phase-46): P4 chunk 1 — type scaffolding + ProvenanceSnapshot re…
tolgaergin Apr 21, 2026
7f57d0b
feat(phase-46): P4 chunk 2 — Sigstore attestation fetch + cache + SAN…
tolgaergin Apr 21, 2026
5379ada
fix(phase-46): P4 chunk 2 — enforce body-size cap during streaming, n…
tolgaergin Apr 21, 2026
7436c74
fix(phase-46): P4 chunk 2 — replace oversized-CL test harness with ra…
tolgaergin Apr 21, 2026
917239c
feat(phase-46): P4 chunk 3 — drift gate + producer fix + approval wri…
tolgaergin Apr 21, 2026
eec6312
fix(phase-46): P4 chunk 3 — split workflow identity; deterministic re…
tolgaergin Apr 21, 2026
fd88e1a
feat(phase-46): P4 chunk 4 — --ignore-provenance-drift[-all] override…
tolgaergin Apr 21, 2026
30ae5f9
feat(phase-46): P4 chunk 5 — wiremock E2E ship-criteria suite (8 tests)
tolgaergin Apr 22, 2026
7153e59
fix(phase-46): P4 chunk 5 — tighten unblocked assertions + honest sho…
tolgaergin Apr 22, 2026
8f673c3
feat(phase-46): P5 chunk 1 — lpm-sandbox crate scaffolding
tolgaergin Apr 22, 2026
493b762
feat(phase-46): P5 chunk 2 — macOS Seatbelt backend + CLI wiring
tolgaergin Apr 22, 2026
ba47450
fix(phase-46): P5 chunk 2 — reject --sandbox-log at the CLI until Chu…
tolgaergin Apr 22, 2026
8475836
feat(phase-46): P5 chunk 3 — Linux landlock backend + unified build.r…
tolgaergin Apr 22, 2026
fb389ca
fix(phase-46): P5 chunk 3 — make landlock pre_exec closure async-sign…
tolgaergin Apr 22, 2026
1fdd56e
feat(phase-46): P5 chunk 4 — macOS LogOnly via (with report), Linux r…
tolgaergin Apr 22, 2026
7c4ccd5
feat(phase-46): P5 chunk 5 — §12.5 escape + green corpora, P4-P5 orde…
tolgaergin Apr 22, 2026
8bf418a
fix(phase-46): P5 chunk 5 — escape corpus paired-control + canonicali…
tolgaergin Apr 22, 2026
d7c5221
feat(phase-46): P5 chunk 6 — tighten CI clippy to --all-targets + lan…
tolgaergin Apr 22, 2026
9392112
feat(phase-46): P6 chunk 1 — effective_policy plumbing + triage pointer
tolgaergin Apr 22, 2026
59c4cbd
feat(phase-46): P6 chunk 2 — shared trust helper + green-tier promoti…
tolgaergin Apr 22, 2026
e4fda68
feat(phase-46): P6 chunk 3 — migrate all_scripted_packages_trusted on…
tolgaergin Apr 22, 2026
be81841
feat(phase-46): P6 chunk 4 — post-auto-build §5.3 pointer + JSON stat…
tolgaergin Apr 22, 2026
dbe913c
feat(phase-46): P6 chunk 5 — reference-fixture integration tests + tw…
tolgaergin Apr 22, 2026
d52283d
feat(phase-46): P7 chunk 1 — pure version-diff core + schema extensions
tolgaergin Apr 22, 2026
e5e751d
feat(phase-46): P7 chunk 2 — install render path (terse hints + pre-a…
tolgaergin Apr 22, 2026
0f2c8dc
feat(phase-46): P7 chunk 3 — approve-builds TUI diff card + AcceptNew…
tolgaergin Apr 22, 2026
b2f57d4
feat(phase-46): P7 chunk 4 — JSON enrichment (SCHEMA_VERSION 2→3 + ve…
tolgaergin Apr 22, 2026
1a89e77
feat(phase-46): P7 chunk 5 — reference-fixture integration tests for …
tolgaergin Apr 22, 2026
90993ac
feat(phase-46-close): Chunk 2 — fix script-policy=allow selection gap
tolgaergin Apr 22, 2026
4646d04
fix(phase-46-close): Chunk 2 — sync --policy/--yolo/--triage help text
tolgaergin Apr 22, 2026
5d331cd
feat(phase-46-close): Chunk 3 — lpm approve-builds --dry-run
tolgaergin Apr 22, 2026
46cac40
fix(phase-46-close): Chunk 3 — dry_run on every JSON envelope
tolgaergin Apr 22, 2026
6d08037
feat(phase-46-close): Chunk 4 — lpm doctor entries for sandbox + scop…
tolgaergin Apr 22, 2026
41ef0e8
feat(phase-46-close): Chunk 5 — cold-install-triage bench + deny base…
tolgaergin Apr 22, 2026
4a660d3
fix(phase-46-close): Chunk 5 — narrow Axis 2 claim to control-path ov…
tolgaergin Apr 22, 2026
ed001fa
chore(bench): honor BENCH_WORK_DIR + BENCH_PROJECT_DIR env overrides
tolgaergin Apr 23, 2026
f19d23e
perf(phase-46): fan out build_blocked_set_metadata concurrently
tolgaergin Apr 23, 2026
4607f4f
docs(bench): Phase 46.0 tag-cut baseline — macOS arm64
tolgaergin Apr 23, 2026
cb01216
chore: release v0.23.0
tolgaergin Apr 23, 2026
53cd126
fix(lpm-cert): drop unused `use super::*` in Linux clippy builds
tolgaergin Apr 23, 2026
10e75ac
fix(lpm-sandbox): move escape-corpus probes to /var/tmp (Linux CI den…
tolgaergin Apr 23, 2026
24e3a17
fix(lpm-task, bench): best-of-N perf measurement + rustfmt 1.94.0 reflow
tolgaergin Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ jobs:
key: ${{ runner.os }}-cargo-lint-${{ env.RUST_TOOLCHAIN }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-lint-${{ env.RUST_TOOLCHAIN }}-

# --all-targets covers library, binary, test, example, and bench
# targets. The bare `--workspace` variant misses lints in test
# modules (Chunk 4 caught two pre-existing `assert_eq!(x, false)`
# lints in build_state.rs that this stricter invocation surfaces).
- name: Clippy
run: cargo clippy --workspace -- -D warnings
run: cargo clippy --workspace --all-targets -- -D warnings

- name: Check formatting
run: cargo fmt --check
Expand Down
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"crates/lpm-extractor",
"crates/lpm-workspace",
"crates/lpm-security",
"crates/lpm-sandbox",
"crates/lpm-runner",
"crates/lpm-runtime",
"crates/lpm-task",
Expand All @@ -35,7 +36,7 @@ members = [
default-members = ["crates/lpm-cli"]

[workspace.package]
version = "0.22.0"
version = "0.23.0"
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/lpm-dev/rust-client"
Expand All @@ -53,6 +54,7 @@ lpm-lockfile = { path = "crates/lpm-lockfile" }
lpm-extractor = { path = "crates/lpm-extractor" }
lpm-workspace = { path = "crates/lpm-workspace" }
lpm-security = { path = "crates/lpm-security" }
lpm-sandbox = { path = "crates/lpm-sandbox" }
lpm-runner = { path = "crates/lpm-runner" }
lpm-runtime = { path = "crates/lpm-runtime" }
lpm-task = { path = "crates/lpm-task" }
Expand Down Expand Up @@ -157,6 +159,12 @@ filetime = "0.2"
# Pure-Rust, GNU patch format, no fuzzy matching by default.
diffy = "0.4"

# Phase 46 P2: POSIX shell tokenizer for the Layer 1 static-gate
# classifier. Pure-Rust, no dependencies, understands standard shell
# quoting so `lpm-security::static_gate` can split script bodies into
# argv without spawning a real shell.
shlex = "1.3"

[profile.release]
opt-level = 3
lto = true
Expand Down
213 changes: 213 additions & 0 deletions bench/baselines/2026-04-23-46.0-macos-arm64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Baseline: 2026-04-23 — Phase 46.0 tag cut — macOS arm64

> Phase 46.0 close-out bench readings on the release-manager
> machine, captured during the 46.0 tag-cut validation.
> Companion to the [Next.js validation report](../../../../a-package-manager/DOCS/new-features/37-rust-client-phase46-nextjs-validation.md).

## Environment

| | |
|---|---|
| Date | 2026-04-23 |
| Machine | arm64, Darwin 25.3.0 (Apple Silicon) |
| LPM binary | `/tmp/lpm-rs-46-tag/release/lpm-rs` built from `phase-46` tip `f19d23e` |
| LPM version string | `lpm 0.22.0` (pre-`v0.23.0` tag-bump) |
| Methodology | `bench/run.sh` medians, plus a custom A/B runner (`/tmp/lpm-ab-bench.sh`) for cross-binary comparison |
| Harness note | Bench workdir redirected to `/tmp/lpm-ab-work` via `BENCH_WORK_DIR` (Chunk 7 commit `ed001fa`) so VS Code's `--no-ignore` rg search doesn't storm the process table during cold-install cycles |

## §12.7 cold-install-triage bench (Axis 1 + Axis 2)

Captured 2026-04-22 23:45 via `bench/run.sh cold-install-triage`
against the in-tree `bench/project/` fixture (17 direct deps → 51
packages). RUNS=3, median per axis:

| Axis | Policy | autoBuild | Wall-clock median | Delta vs deny | Target |
|------|--------|-----------|-------------------|---------------|--------|
| 1 | deny | off | 820 ms | — | — |
| 1 | triage | off | 696 ms | **−15 %** | ≤ 5 % regression |
| 2 | deny | on | 658 ms | — | — |
| 2 | triage | on | 680 ms | **+3 %** | ≤ 5 % regression |

Both axes inside the §12.7 v2.11 budget. Axis 1's −15 % reading
is noise-dominated at RUNS=3 (the meaningful claim is "triage is
no slower than deny"). Axis 2 is +3 % control-path overhead.

v2.11 caveat preserved: **Axis 2 does not exercise P5 sandbox
spawn or P6 tier auto-execution on this fixture.** The 51-package
tree's scripts are all `prepare` / `prepublishOnly`, neither of
which is in `EXECUTED_INSTALL_PHASES`
([lpm-security/src/lib.rs:70][executed]), so `build::run`'s
scriptable set is empty, the sandbox never spawns, and Axis 2
measures only the tier-evaluation control-path walk. True
execution-path benchmarking needs a pinned script-bearing fixture
and is deferred.

[executed]: ../../crates/lpm-security/src/lib.rs

## §13.1 A/B cross-binary (main vs phase-46 under deny, 277 pkgs)

A/B between `origin/main @ 5a4c33f` (v0.22.0) and `phase-46 @ f19d23e`
on a larger 277-package fixture at `/tmp/lpm-large-fixture`
(webpack-adjacent mix: lodash / axios / express / zod / eslint /
typescript / vitest / … — representative of a real team project,
roughly 5× the scale of `bench/project/`). Both binaries invoked
with bare `lpm install --allow-new` (phase-46's default
`script-policy = "deny"` kicks in through package.json /
config.toml precedence, same output as pre-phase-46). Wipe
sequence between iters: `node_modules lpm.lock lpm.lockb
~/.lpm/cache ~/.lpm/store`.

RUNS=10 with order alternation per pair (A→B on odd iters,
B→A on even) so neither binary gets a systematic CF-edge-warm
advantage:

| Iter | Order | main (ms) | phase-46 (ms) |
|------|-------|-----------|---------------|
| 1 | A→B | 2491 | 2754 |
| 2 | B→A | 2600 | 2771 |
| 3 | A→B | 2993 | 2765 |
| 4 | B→A | 2624 | 2719 |
| 5 | A→B | 2740 | 3148 |
| 6 | B→A | 2712 | 2915 |
| 7 | A→B | 2389 | 2915 |
| 8 | B→A | 2563 | 2748 |
| 9 | A→B | 2437 | **8044 ⚠** |
| 10 | B→A | 2593 | 2803 |

Iter 9's phase-46 outlier (8044 ms) is an APFS `extract`
scheduling hiccup, not a phase-46 regression — per-stage JSON
from an earlier run with a similar outlier showed
`extract_sum ≈ fetch_ms` (1:1 ratio, meaning extracts ran nearly
serial vs the usual 5× parallelism). Median-of-10 discards it
cleanly.

### Median of 10

| | median wall-clock | delta |
|---|---|---|
| main (v0.22.0 / `5a4c33f`) | **2600 ms** | — |
| phase-46 (`f19d23e` tip) | **2803 ms** | **+7.8 %** |

Per the 2026-04-10 baseline doc limitations:
> "Numbers can fluctuate by upstream registry conditions. …
> variations under ~10 % should be ignored."

**+7.8 % sits at the noise floor for this class of measurement.**
Process-count delta across 10 iterations was ±5 (normal OS jitter),
confirming no per-install process leak under either binary.

### What the fix achieved

The pre-fix reading of the same bench on the same binary-phase-46
tip (before commit `f19d23e`) was **+32 %** — ~770 ms of serial
per-package overhead in `build_blocked_set_metadata`, which
`.await`ed `get_package_metadata` + `fetch_provenance_snapshot`
one package at a time. `f19d23e` fans the loop out via
`futures::future::join_all`, saving ~570 ms / 277 packages and
dropping the cross-binary delta onto the noise floor.

### Tree parity

| | Packages | Store size | node_modules top-level |
|---|---|---|---|
| main | 277 | 25 MB | 18 |
| phase-46 | 277 (**identical set**) | 25 MB | 18 |

Resolved trees verified byte-identical via JSON envelope
comparison (`set(main["packages"]) == set(phase46["packages"])`,
diff = 0). Phase-46 does not skip or short-circuit any work.

## Per-stage JSON breakdown (post-fix, cold edge)

Single-iteration `--json` capture after the fix, same 277-pkg
fixture, A→B pair (main cold, phase-46 on warm edge):

```
run count total resolve fetch link initial_batch_ms
main_cold 277 2376 1932 378 44 885
p46_warm 277 3335 1962 973 54 884
p46_cold 277 2583 1887 295 52 844
main_warm 277 2560 2153 342 43 997
```

Cold-vs-cold (apples-to-apples):
- `total_ms` delta: **+207 ms (+8.7 %)**
- `resolve_ms` delta: **−45 ms (−2.3 %)** — phase-46 ≥ main
- `fetch_ms` delta: **−83 ms (−22.0 %)** — phase-46 ≥ main
- `link_ms` delta: **+8 ms (+18.2 %)** — within jitter on a 44 ms base
- `sum(stages)` delta: phase-46 −120 ms vs main
- `UNACCOUNTED (total − sum_stages)` delta: **+327 ms**

The +327 ms "unaccounted" sits in the post-stage
`capture_blocked_set_after_install_with_metadata` pass:
per-package `compute_script_hash` + `read_install_phase_bodies`
on the store tree, plus the trust-snapshot write. That's real
Phase 46 security work (classifier output, `static_tier`
annotation, published_at / behavioral_tags_hash persistence,
trust-delta fingerprinting) — not in main at all. **Acceptable at
this scale and inside the 10 % noise floor.** If it ever shows up
as a user complaint on large monorepos (1000+ packages where
300 ms becomes 1 s+), the per-package `package.json` parse is
easily parallelizable via `rayon::par_iter` or
`tokio::task::spawn_blocking` fanout.

## Other phase-46 serial-await hotspots (for the backlog)

Not triggered on the standard `lpm install --allow-new` invocation
measured above, but same serial-await pattern as the
`build_blocked_set_metadata` hot spot:

- [`install.rs:1674`](../../crates/lpm-cli/src/commands/install.rs#L1674)
— minimum-release-age cooldown gate. Only fires on
`!allow_new && !used_lockfile` (first install with cooldown, no
lockfile present). Users hit this once, either bypass with
`--allow-new` or set `minimumReleaseAge: 0` in package.json.
- [`install.rs:1803`](../../crates/lpm-cli/src/commands/install.rs#L1803)
— provenance-drift gate. Short-circuits when
`trustedDependencies` has no rich-form entries (the common case
today — very few projects have written rich approvals yet).

Both would benefit from a fanout but neither regresses the common
path. Queued for a follow-up perf pass if usage patterns ever
exercise them.

## How to reproduce

```bash
# 1. Build both binaries (separate target dirs so neither trashes
# the other's incremental cache).
cd /Users/tolgaergin/Documents/Projects/tolgaergin/lpm/rust-client
CARGO_TARGET_DIR=/tmp/lpm-rs-46-tag cargo build --release
git worktree add /tmp/lpm-rs-main origin/main
(cd /tmp/lpm-rs-main && CARGO_TARGET_DIR=/tmp/lpm-rs-main-target cargo build --release)

# 2. Drop the larger fixture.
mkdir -p /tmp/lpm-large-fixture
# … package.json contents inline in the bench commit message …

# 3. Run the A/B. The script is /tmp/lpm-ab-bench.sh — not
# committed to the repo because it duplicates
# `median_ms_ab_with_setup` for cross-binary use. Paste inline
# from the commit or re-derive from bench/run.sh.
RUNS=10 /tmp/lpm-ab-bench.sh
```

## Limitations

- **Single machine.** Apple Silicon / Darwin 25.3.0. Linux x64
numbers still to come from a CI-class runner; the 10 % noise
floor observation holds across platforms per prior baselines.
- **No isolated network.** Bench hits `lpm.dev` + `registry.npmjs.org`
over the release-manager's ISP. DNS was normal at measurement
time (verified via `dig`: 20–50 ms / query). Prior tag-cut
attempts on this same day were distorted by a local DNS / VS
Code rg-process-leak interaction — see the commit message on
`ed001fa` for the harness-side fix.
- **RUNS=10 for the A/B.** Enough to resolve the +7.8 % signal
above the noise floor but not enough to publish sub-percent
precision. Acceptable for the release-gate check.
- **Fixture is synthetic** (webpack-adjacent mix). Real-world
installs with Next.js + Tailwind + many workspace members may
have different profiles; the Next.js 16.2.4 reference install
captured in the validation doc complements this data at the
small-tree end (32 packages, 1 amber-by-design postinstall).
Loading
Loading