Skip to content

perf: dependency-swap heuristic + parallel AUR CI lane - #77

Merged
kridaydave merged 8 commits into
mainfrom
perf/overlap-and-window
Oct 19, 2026
Merged

kridaydave merged 8 commits into
mainfrom
perf/overlap-and-window

Conversation

@kridaydave

Copy link
Copy Markdown
Contributor

Adds removed-dependency tracking to Delta and fires R04_DEPENDENCY_SWAP on near-identical rename candidates. The AUR CI lane now runs through the shared parallel evaluate_candidates driver with pin-file ordering. Includes CHANGELOG/PERF updates and a regression test for pin ordering.

Commit made by beam-free in opencode on behalf of Kriday.

kridaydave and others added 3 commits October 17, 2026 13:40
Add removed-dependency tracking to Delta and fire R04_DEPENDENCY_SWAP
when a dropped name has a near-identical counterpart among added
dependencies. Run the AUR CI lane through the shared parallel
evaluate_candidates driver with pin-file ordering and per-worker
context/store handles. Update docs and tests.

Commit made by beam-free in opencode on behalf of Kriday.
The --mutants shards on #77 found 26 survivors, 24 of them inside
levenshtein_at_most_one. The rule shipped with three tests, all of which
went through dependency_findings, and all three used one shape: a pair
that normalizes to two equal-length strings differing in the last
character. Every other arm of the edit walk -- insertion, deletion, the
leftover tail, the second-edit bail, either empty string -- was never
exercised, which is exactly the set that survived.

So drive the predicate itself, shape by shape, in both argument orders,
because the caller normalizes both names and hands the pair over in
whatever order the dependency sets happened to iterate. The false cases
carry as much weight as the true ones here: the rule fires on ordinary
fork renames, so a predicate that is one edit too permissive is noise on
every release that swaps a dependency.

compute_delta's removed set gets the same treatment. It was asserted
nowhere in its own tests, which is why deleting the `!` from
`!target_deps.contains_key(dep)` changed nothing observable. One fixture
now carries a kept, a bumped, a dropped and an added dependency and pins
all three lists to exact contents, so an unchanged name has to be in none
of them.

Also pin the empty-normalized-name guard in both directions. A name made
only of separators normalizes to "", and "" is one edit from every
single-character name, so without the guard a dropped "___" pairs with
whatever one-letter package arrived alongside it.

Co-Authored-By: Claude Opus 4.5 (1M context) <noreply@anthropic.com>
The --mutants run on the swap-heuristic PR found 26 survivors. 24 of
them are killed by the new tests; the remaining 3 are true equivalents
in levenshtein_at_most_one, confirmed by an exhaustive comparison
over 29.8M string pairs up to length 6 plus an invariant argument:

- `i < long.len()` in the match arm is an invariant on loop entry, so
  `<` -> `<=` only enables out-of-bounds indexing that never happens.
- the trailing-edit guard `i < long.len()` fires only when edits is
  still 0, so disabling it (`<` -> `>`) cannot change `edits <= 1`.
- `edits += 1` in that guard runs only at edits == 0, so `+=` and
  `*=` (0+1 vs 0*1) agree.

All three are excluded in CI with documented reasons, following the
existing precedent for `replace <= with > in evaluate_candidates`. The
two unviable `Default::default()` mutants and the two loop-control
mutants that time out are left to the existing genre exclusions.

Co-Authored-By: Claude Opus 4.5 (1M context) <noreply@anthropic.com>
@kridaydave
kridaydave force-pushed the perf/overlap-and-window branch from b8d7bda to 478d3f1 Compare October 17, 2026 15:31
Clears three critical advisories against zip 2.4.2 (RUSTSEC-2026-0367,
-0368, -0369). Blueline never called the vulnerable extract() APIs, but
the upgrade changed behaviour in ways the extraction tests caught:

- zip 9's enclosed_name strips a leading root or drive prefix instead
  of returning None, and splits on backslashes, so raw /etc/evil and
  foo\bar names came back contained as etc/evil and foo/bar. Blueline
  refuses both against the raw entry bytes, before sanitization can
  hide what the archive asked for. name() is a Result in zip 9; a
  name whose bytes fail to decode is refused too.

- The deflate meta-feature enables flate2/zlib-rs, and feature
  unification would have switched every flate2 user in the build -
  safe_extract's gunzip and ureq's HTTP gzip - from miniz_oxide to
  zlib-rs. The dependency uses deflate-flate2 instead, deferring the
  backend to flate2's own default. The gzip-bomb scenario caught the
  switch on the first run. zopfli leaves the tree.

Also: exclude the fourth levenshtein_at_most_one timeout mutant in CI
(the flipped >/< guard hangs the scan), and isolate the pypi CLI tests
from the developer's real baseline store by setting BLUELINE_DATA_DIR -
HOME alone loses to XDG_DATA_HOME, and the fixtures had been writing
demo-pkg/demo-lock rows into it.
The [Unreleased] section had accumulated six Changed, four Added, four
Fixed and two Removed headings from branch merges, plus a
non-standard Documentation corrections heading. One section per
Keep a Changelog type, in canonical order (Added, Changed, Removed,
Fixed, Security); the documentation-corrections entries fold into
Fixed, where they belong. Verified a pure reorder: the multiset of
content lines is identical before and after.
The dev and test profiles now compile at opt-level 1. That alone took the gate
from 46.4 s to 21 s of test time and 53 s to 15 s of CPU, with peak RSS down
from 342 MB to about 270 MB. The shipped release profile is untouched.

The incremental cache was 4.1 GB across 251 sessions, 3.2 GB of it blueline
own, because cargo mutants sets RUSTFLAGS and writes a fresh one per run. New
scripts/prune_incremental.sh deletes sessions older than a week, judging by the
newest file inside rather than the directory mtime, which cargo touches on reuse.
Dry run by default.

PERF.md gains section 10 with the dev-gate measurements, and the stale claim
that rustup was missing is corrected (it is installed and binds 1.97.1).

Commit made by Beam Free in OpenCode on behalf of Kriday.
Eight tests in src/registry/npm.rs used to keep a loopback server alive for a
fixed 600 ms and then join the thread, so each cost 600 ms of sleeping no
matter how fast the work was. The loop now exits as soon as the expected number
of requests has been served, with a 50 ms idle fallback and a 5 s safety
deadline. cargo test --lib registry::npm went 7.11 s to 2.37 s
single-threaded, five consecutive runs clean, full gate still green.

Commit made by Beam Free in OpenCode on behalf of Kriday.
verify.sh now runs cargo nextest run --all-targets --locked --test-threads 8
when nextest is installed and falls back to cargo test otherwise, so a
checkout without nextest still passes identically. CI installs nextest through
taiki-e/install-action@v2. .cargo/config.toml sets test-threads = 8 for the
cargo test fallback.

Measured at fae8050: the whole gate went from 23.8 s to 11.4 s wall, 3.1x over
the pre-opt-level baseline of 48.9 s. nextest overlaps the 15 test binaries
instead of running them one at a time, and 8 threads shave another 2 s off the
lib binary at the cost of 387 to 449 MB peak RSS instead of 275 MB.

Commit made by Beam Free in OpenCode on behalf of Kriday.
@kridaydave
kridaydave merged commit ad55ceb into main Oct 19, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant