Fix four vendored/hosted correctness bugs found while profiling - #268
Merged
Mikola Lysenko (mikolalysenko) merged 14 commits intoSep 27, 2026
Merged
Conversation
…bucket `filter_to_installed_releases` buckets every release-variant purl (PyPI `?artifact_id=`, RubyGems `?platform=`, Maven `?classifier=`) into a `HashMap` keyed by base purl and then drains it. That drain is the function's OUTPUT order, which is the order the download loop walks — so `download.patches` (and `apply.patches`, and the per-patch stderr lines) came out in `HashMap` bucket order: two identical runs of the same project emitted the same records in different orders. Sort the multi-variant bases before resolving them (stable warnings) and sort the kept selection by purl before returning it, matching how every sibling collection in the same envelope is ordered (scan's `packages`, the agent flow's `skip_records`). The `--all-releases` pass-through gets the same order so both arms of the function share one contract. Two tests: one on the narrowing itself and one on the emitted `download.patches` array. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The patch view serves `blobContent` only for the files a patch CHANGES. A zero-delta file — `beforeHash == afterHash` — comes back with hashes and no content, which the in-memory vendor stager counts as a failed fetch. One such patch made the WHOLE run bail `no_local_source`: exit 1, `status: error`, zero events, and every other package in the manifest left unvendored without a word. Live example: `pkg:npm/tar-fs@2.1.1`, patch `8ff3e0c7-6855-4224-924b-3e1151744ed4` — seven zero-delta fixture files plus one changed `package/index.js`. A three-package project (tar-fs, braces, minimist) downloaded all three records and then vendored none. A package whose patch content cannot be obtained is an unsatisfiable package like any other (`vendor_fetch_failed`, `redirect_revert_failed`, the Bun refusals …): it gets its own `failed` event and the run carries on. `stage_vendor_sources_in_memory` now hands those purls back in `MemStagedSources::unavailable()`; `vendor`, `scan --vendor` / `get --mode vendored` and `repair` report them one by one and run the engine over the rest. The pre-event `no_local_source` bail stays for the case it was written for — NOTHING in the manifest can be staged, so there are no events to report — which is the shape every existing test pins. The stager's own stderr summary is unchanged, so `--silent` still gets exactly one error line per arm. Repro on the live API (same project, built binaries): before, exit 1 / `status: error` / `no_local_source` / 0 events; after, exit 1 / `partial_failure` with `failed pkg:npm/tar-fs@2.1.1 no_local_source` plus `applied` braces and minimist, both in the ledger. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…inventory The hosted rewriter turns `name==X` into the PEP 508 direct reference `name @ <patch-server url> --hash=sha256:…`. The requirements.txt inventory only reads exact `==` pins, so every line a hosted run had already rewritten vanished from it — and the second hosted run over a wet requirements.txt reported `packagesWithPatches: 1` instead of 12, having "lost" the eleven packages it had just wired. uv.lock keeps its `[[package]]` name/version through the same rewrite, and Pipfile.lock's reader already keeps a Socket-written reference as the package it replaces (`socket_reference_coords`); requirements.txt now does too, through that same reader and for both shapes it writes (the hosted url and the vendored `.socket/vendor/pypi/…` path). The recovered entry is discovery-only — `resolved: None`, `integrity: None`, exactly what a `==` pin beside it yields — so the PATCHED artifact the line points at can never be fetched as a pristine source, and VEX ledger liveness keeps reading it as the "proves nothing" entry it reads a hosted uv.lock/Cargo.lock entry as. A user's own file/url reference is still not ours to resolve and stays out; a reference whose coordinates contradict the requirement's own name is skipped fail-closed. Live repro on the phase-3 `req-big` fixture (391 pins, 11 redirected): before, run 1 reported 12 packages with patches and run 2 reported 1 with 0 redirected; after, both runs report 12 / 11 with the same single pre-existing warning and a byte-identical requirements.txt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ing it
A bundler path source will not load without the eval-able stub gemspec
rubygems writes into `<gem home>/specifications/` when the gem is
INSTALLED, and a downloaded `.gem` carries its gemspec only as YAML in
`metadata.gz` — which is precisely why the vendoring service converts it
and serves a separate `gem-stub-gemspec` artifact. So a local build can
never vendor a fetched gem. The auto-fetch rung downloaded the `.gem`
anyway and only then hit the backend's `gem_spec_missing`: a wasted
registry round trip on every `--vendor-source build` run, ending in a
message whose remedy ("use --vendor-source=service") did not name the
mode that actually works from here.
Refuse before the fetch, for gem purls only, only when the run cannot use
the patch service at all (`--vendor-source build`, or no service config),
and only for the purls a fetch would actually be attempted for — the ones
`fetch_pristine_package` resolves from the lockfile or recovers from the
ledger. A gem that resolves from nowhere has nothing to say about
gemspecs and keeps its calm `package_not_installed` skip. The refusal is
the same `gem_spec_missing` code and the same `failed` event, with a
detail that says why a fetched gem is unusable and points at
`bundle install` or `--vendor-source=auto`. `auto` and `service` still
fetch: the service path needs the staged pristine copy, and that is the
mode that CAN vendor this gem. The backend keeps its own refusal as the
backstop for every other route into it (including the security case where
a staging dir must never yield a stub).
Repro: a lockfile-only gem against a mock rubygems host — before, the
`.gem` was downloaded and the run then failed `gem_spec_missing`; after,
the host sees no request at all and the same failure arrives with the
real remedy. Two guard twins pin the scope: `auto` still downloads, and a
gem no lockfile resolves still reports `package_not_installed`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ownload
The pre-fetch `gem_spec_missing` gate keyed off "the lockfile resolves this
purl, or the ledger knows it" — mere RESOLVABILITY. The download it exists
to save needs VERIFIABILITY, and the already-vendored case needs no local
build at all, so the gate fired in two cases where `main` never fetched
anything and never failed:
* A bundler < 2.6 `Gemfile.lock` (no `CHECKSUMS` section — the majority of
real locks) resolves the gem but records no verifier.
`registry_fetch::fetch_and_stage` refuses a `LockIntegrity::None` entry
before any network I/O, so CLI_CONTRACT's documented pair fires instead
(`vendor_fetch_unverifiable` warning + the calm `package_not_installed`
skip). The gate replaced that with `failed`/`gem_spec_missing` — for a
download that never existed, and with a remedy that cannot work: the same
fixture under `--vendor-source auto` still yields the skip pair, because
the purl never reaches the gem backend in any mode.
* An already-vendored gem on a fresh clone (committed `.socket/vendor/gem/`
copy + wired lock, `bundle install` not yet run) has a ledger entry, which
is exactly the case `fetch_pristine_package`'s ledger-recovery rung exists
for ("an already-vendored lock-only checkout re-scans green"). The
recovered fetch feeds the gem backend's idempotent hot path, which
re-confirms the wired lock and returns `already_vendored` without ever
needing a stub gemspec. Measured on `main`: exit 0, `status: success`,
events `[skipped/vendor_fetched_missing, skipped/already_vendored]`. With
the gate: exit 1, `partialFailure`, `failed`/`gem_spec_missing` — a green
idempotent re-run turned into a failure, with nothing wrong with the
project. `scan --vendor` / `get --mode vendored` under
`--vendor-source build` broke the same way.
Mirror `fetch_pristine_package`'s own `fetchable` filter instead: an
inventory entry whose integrity is not `LockIntegrity::None`, and no ledger
entry. GEM-4's real case — a not-installed gem a bundler >= 2.6 lock CAN
verify — still refuses before the download, unchanged.
Two regression tests, both green on `origin/main` and red on the gate as
written: the unverifiable lock keeps its documented skip pair with zero
registry requests, and a vendor-then-`rm -rf vendor/` re-run stays exit 0
with `already_vendored`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ventory The hosted half of this fix landed in 11de805; the vendored half did not, although both docstrings claimed it. The hosted rewriter emits a PEP 508 direct reference (`name @ <patch-server url>`), but the VENDORED requirements writer emits something else entirely — a bare path line, `./<rel wheel>[ ; marker] --hash=sha256:<hex> # socket-patch vendor: <name>==<ver>` (`vendor::pypi_requirements::vendor_line`) — with no `name @` at all. `direct_reference` splits on `@` and so never matched it, and `inventory_requirements_txt` discarded the comment part where the `socket-patch vendor:` tag lives, so a vendored requirements.txt kept the exact symptom the hosted arm fixed: its packages drop out of `lock_inventory`, which is what `scan/discovery.rs::lockfile_supplement` counts, so a re-scan of an already-vendored lockfile-only checkout under-reports them. The `.socket/vendor/pypi/` arm of `socket_reference_coords` was only ever reachable from Pipfile.lock. Read the vendored shape too: keep the logical line's comment, and when the code part is a bare path `socket_reference_coords` recognizes, take the requirement name from the `socket-patch vendor:` tag — the same `utils::requirements::vendor_tag` reader `vex::discover::pypi_other` already uses — and cross-check it against the path's own coordinates, the same fail-closed rule the hosted arm applies to its url. The recovered entry stays discovery-only (`resolved: None`, `integrity: None`), so the PATCHED wheel it points at can never be fetched as a pristine source. A user's own wheel path is still not ours to resolve and stays out. Two tests, both red before: the vendored shape (with and without an env marker, beside a `==` pin and a user's own wheel path), and a round trip through the writer's own `vendor_line` formatter — the twin of the hosted `the_hosted_rewriters_own_output_reinventories` guard. Both docstrings now describe the two shapes they actually read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The JS-7 package is still unvendorable. a34bb9d stopped one contentless view from killing a whole run, but the run's own example package — `pkg:npm/tar-fs@2.1.1`, patch `8ff3e0c7-…`, one changed file plus seven zero-delta fixture files — still fails on every run, and nothing in it actually needs the bytes the view withholds. `covered()` demanded the after-blob for EVERY file of a record. A zero-delta file (`beforeHash == afterHash`) is already at its patched content in the pristine copy: `verify_file_patch` answers `AlreadyPatched` as soon as the on-disk hash equals `afterHash` (`patch/apply.rs`), which is exactly why the view serves such a file with hashes and no `blobContent`. Requiring it made every patch that carries one permanently unvendorable — measured on origin/main as the original JS-7 symptom (exit 1, `status: error`, `no_local_source`, zero events) and on a34bb9d as a per-package `failed`/`no_local_source` on every run. `needs_blob()` is now the one rule, used by `covered()` (which decides what to fetch) and by the fetch loop (which decides whether a view came back complete), so the two can never disagree about which files a fetch must bring back. The loop also collects every genuinely contentless file instead of breaking at the first one: `patch.files` is a `HashMap`, so "the first file with no content" was bucket order, and a partial view abandoned its remaining files at random. Tests: `a_view_whose_only_contentless_files_are_zero_delta_vendors` is the live JS-7 shape — red on unmodified main (`status: error`) and on a34bb9d, green now, with the vendored tarball asserted to carry the changed file at its patched bytes AND the zero-delta file at the bytes it always had. `contentless_patch_view_fails_only_its_own_package` and `every_patch_unstageable_keeps_the_run_level_error` (both added by a34bb9d) encoded the wrong classification: they made their package unstageable with a zero-delta file, i.e. they asserted that the JS-7 package must fail. Every assertion in both is unchanged; only the fixture's view changed, so the package they exercise is now unsatisfiable for a reason that really is unsatisfiable — the file the patch CHANGES is served with no content, so its patched bytes exist nowhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`drop_unstageable` recorded every dropped purl with one verbatim run-level string, "patch artifacts unavailable (offline or download failure)". For the case the per-package report was written for — the view is served fine, 200, but a file the patch changes carries no `blobContent` — the run is neither offline nor a download failure, so the one machine-readable explanation named two causes that are both false. The stager knew the real one (it formats `[error] <purl>: no blob content served for <file>`), but every human channel in that block is gated on `if !common.json`, so a `--json` consumer — depscan, CI — saw only the misleading detail and never learned which file was contentless. The whole point of the per-package report is per-package diagnostics, and the per-package slot was the one place the specific reason was dropped. Carry the reason out of the fetch loop with its purl and put it in that package's `failed` event: which file was served without content (and how many others), which file carried a malformed or undecodable blob, that no view is served for the uuid at all, or the transport error. `no_local_source` stays the stable `errorCode`; only the free-text `error` changes. The `[error]`/summary stderr lines are untouched. Pinned by an added assertion on the existing e2e: the failed event's `error` must read "the patch view served no blob content for package/index.js". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
acae5db made `filter_to_installed_releases` sort both arms by (purl, uuid) but left the contract sentence "With `--all-releases` set this is a verbatim pass-through" in place. Harmless today — `select_patches` hands this function one patch per purl, so the uuid tiebreak never decides which record survives the purl-keyed `records` map in `download_patch_records_preflighted` — but a future caller would read a sentence that is no longer true. Say what the arm does now: nothing is narrowed away and no view is fetched, and the output order is the same one the narrowed arm returns. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`drop_unstageable` has three callers — `vendor`, `scan`/`get --mode vendored` (`scan::vendor_flow`) and `repair` — and every test that reached it drove `vendor`. The suites that touch the vendored scan (`scan_vendor_step_error_e2e`, `covgap_commands_scan_vendor_flow`, `covgap_commands_get`) all mount single-patch manifests, so they only ever exercised the preserved whole-run bail: a regression in the vendored scan's `Ok(staging_errors || engine_errors)` fold — dropping the staging-error bit, or reporting a stuck package twice — passed the whole suite. One mixed-selection case for `scan --mode vendored`, manifest-free the way vendored mode really runs (discovery + the download phase's blob seed, no `.socket/` on disk): one package whose view serves the file it changes without content and one it serves complete. The stuck package is reported exactly once as `failed`/`no_local_source` with the reason naming the file, the other still vendors, and the envelope is `partialFailure`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four user- and consumer-visible behavior changes shipped on this branch with no `[Unreleased]` entry, although the file's own header states the Release workflow refuses to publish a version that does not appear in it (`scripts/release-lint.sh`) and every recent merge to main updates it. CHANGELOG `[Unreleased]` → `Fixed` now carries one entry per fix, each calling out what a JSON consumer sees change: the vendor envelope's shift from `status: "error"` + top-level `no_local_source` to `partialFailure` + per-package `failed` events, the purl ordering of `download.patches` / `apply.patches`, the requirements.txt inventory recovery, the zero-delta staging fix, and the gem build-mode refusal — including the `vendor_fetched_missing` warning that disappears from a build-mode run that no longer downloads. CLI_CONTRACT's error-code table gains the two facts a consumer needs and could not previously read anywhere: * `no_local_source` is now reported at TWO levels, and which one arrives depends on whether anything else in the manifest staged (so a one-patch manifest still gets the run-level shape). The table says so explicitly rather than leaving the inconsistency undocumented, and `json_envelope`'s `error_code` doc points at it. * `gem_spec_missing` was never in the table at all. Its row states the pre-fetch refusal, its exact scope (the lock both resolves AND verifies the gem, and no ledger entry already vendors it), what is deliberately unaffected (`vendor_fetch_unverifiable`'s documented pair, an already-vendored re-run, `auto`/`service`), and the dropped warning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`plural()` already carries the count ("2 files"), so the multi-file arm of
the staging reason read "(and 2 other 2 files)". Say "(and 2 more files)",
and pin all three arms — none, one, several — with a unit test, since that
string is the only machine-readable explanation a `--json` consumer gets
for a per-package `no_local_source`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#257 landed the X1b deferred pristine fetch, which already refuses a not-installed gem in `--vendor-source build` with `gem_spec_missing` BEFORE downloading it, under the same scope this branch's gate had (verifiable lock entry, no ledger entry). Take main's gate and drop this branch's copy; keep the scope-guard suite, adapted to main's behavior (the backend's own detail text, and the already-vendored fresh-clone re-run now makes no registry request at all). The other three fixes still apply and are ported onto #257's code: * js7: the in-memory stager's view fetches now run through `ordered_concurrent`; the per-file `needs_blob` rule and the per-package drop reasons fold into that loop unchanged. The dropped records never reach `vendor_records`, so they stay out of the exact service-download plan and the group commit too (new test). * order: `filter_to_installed_releases` still drains a HashMap on main; the purl sort now also orders #257's view prefetch plan, which is built from the same selection. * req: untouched by #257. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
September 27, 2026 13:10
Mikola Lysenko (mikolalysenko)
enabled auto-merge (squash)
September 27, 2026 14:40
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Sep 27, 2026
#276 moved the lock inventory onto `ProjectView` (disk or the in-memory hosted engine's `MemoryProject`). The requirements.txt reader keeps the rewired-line fix on main's new signature, so it reaches the in-memory engine through the same function; the redirected-lines test now also reads the file through a `ProjectView::Memory` and expects the same packages. The order and js7 fixes touch `get` and vendored staging, neither of which the hosted-only in-memory engine copies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
merged commit Sep 27, 2026
0b4e645
into
main
100 of 101 checks passed
Mikola Lysenko (mikolalysenko)
deleted the
fix/profiling-correctness-bugs
branch
September 27, 2026 15:03
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Sep 27, 2026
Brings in #268 (four vendored/hosted correctness fixes found while profiling). The only overlap with vlt is repair: #268 moved the no-local-source reporting into report_no_local_source, and that now passes the whole vendor entry to soft_restore_without_fingerprint, so the vlt-specific remedy text survives. The CHANGELOG, CLI_CONTRACT and lock-inventory test conflicts keep both sides. Assisted-by: Claude Code:claude-opus-5-5
This was referenced Sep 27, 2026
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.
Correctness bugs recorded while profiling the vendored and hosted scan
paths (perf-plan-phase3 §6). This is not a performance change.
Originally opened on
1bfe5326. After #257 (8c381ecf, the hosted/vendoredperformance PR) merged,
origin/mainwas merged in (00d9224e, a mergecommit so the PR needs no force-push). #257 had already fixed one of the four
bugs; the other three are ported onto its new code paths. Every remaining
fix's regression test was re-run against
main's sources (the fix fileschecked out from
8c381ecf, the tests kept) and fails there.Also merged: #276 (
afa72533, the napi in-memory hosted engine), in56aae018. #276 moved the lock inventory ontoProjectView, which can bethe disk or the in-memory engine's
MemoryProject. The requirements.txt fix(bug 2) now sits on main's new
inventory_requirements_txt(view)signature,so the in-memory engine gets it through the same function. The
redirected-lines test also reads the file through a
ProjectView::Memoryand expects the same packages.
hosted_memoryis hosted-only: it has nocopy of
get's release narrowing (bug 1) or of vendored staging (bug 3),so those fixes have nothing to reach there.
download.patchesinHashMapordermainmaindid not touch it--vendor-source builddownloaded a gem it could never use (GEM-4)main's gate1.
download.patchescame out inHashMaporder (acae5db3,b8f08b07)filter_to_installed_releasesgroups every release-variant purl (PyPI?artifact_id=, RubyGems?platform=, Maven?classifier=) into aHashMapkeyed by base purl and then drains it. The drain order is what thefunction returns, so it is the order the download loop walks. As a result
download.patches,apply.patchesand the per-patch stderr lines came outin bucket order, and two identical runs gave different JSON. #257 did not
change this. Its concurrent view prefetch in the download loop is planned
from the same
selectedlist, so it follows the sorted order.Change. Sort the multi-variant bases, and sort the kept selection by
(purl, uuid)before returning. The--all-releasesarm uses the sameorder.
Tests.
release_narrowing_keeps_a_stable_purl_order,download_patches_json_is_purl_ordered. Both fail onmain.2. A hosted- or vendored-rewired requirements.txt dropped out of the inventory (
11de805c,45425953)The requirements.txt inventory read only exact
==pins, so every line thisCLI had already rewritten disappeared from it. A hosted wet re-run of a
391-pin requirements.txt reported
packagesWithPatches: 1instead of 12.#257 did not touch
lock_inventory/pypi.rsorutils/requirements.rs.Change. Read back both shapes we write, as the package they replace:
the hosted
name @ <patch-server url>direct reference, and the vendoredbare
./.socket/vendor/pypi/<uuid>/<wheel> … # socket-patch vendor: <name>==<ver>line. These entries are for discovery only (resolved: None,integrity: None). A user's own file, URL or path reference, and areference whose name contradicts its line, still stay out.
Tests. Four
lock_inventorytests, including a round trip through eachwriter's own formatter. All four fail on
main.3. One contentless patch view killed the whole vendored run (
a34bb9d0,7dbc90a1,ec2c1c7d,5f598436)The patch view serves
blobContentonly for the files a patch changes. Azero-delta file (
beforeHash == afterHash) comes back with no content. Thein-memory vendor stager counted such a view as a failed fetch, and a single
failed fetch made the whole run bail with
no_local_source: exit 1,status: "error", zero events, and every other package left unvendored.Live example:
pkg:npm/tar-fs@2.1.1, patch8ff3e0c7-…. #257 made the viewfetches concurrent (
ordered_concurrent+hold_back_debug, consumed inorder) but kept both the all-files-need-content rule and the whole-run bail.
Change (merged into #257's concurrent loop; the loop's fold order is
unchanged):
needs_blob()rule is shared bycovered()(what to fetch) and the fetch loop (whether a view came backcomplete).
package, with a
failed/no_local_sourceevent that names the realreason (the file served with no content, a malformed blob, or the fetch
error). The rest of the run continues, in
vendor,scan/get --mode vendoredandrepair.drop_unstageableremoves adropped record before
vendor_records. Speed up hosted and vendored scans: concurrent API requests, parallel crawl, single-pass rewriters #257's exact service-download plan(
plan_service_downloads) and its group commit are both built insidevendor_recordsfrom the records it is given, so a dropped package isnever granted a download (a grant can start a server-side build and counts
against quota) and never enters the commit. A zero-delta-only view is now
complete, so that package is planned and vendored like any other.
the manifest can be staged, there are no per-package events to report.
Tests (
vendor_partial_staging_e2e):a_view_whose_only_contentless_files_are_zero_delta_vendors,contentless_patch_view_fails_only_its_own_package,scan_vendored_reports_an_unstageable_package_and_vendors_the_rest, and newsince the merge
a_dropped_package_is_never_granted_a_service_download.That last test uses
--vendor-source autowith two stageable packages, sothe plan attaches, plus the unstageable one. The mock service answers
not_found, and the test asserts that both stageable uuids asked for a grantand the dropped uuid never did. All four fail on
main.every_patch_unstageable_keeps_the_run_level_errorpasses on both, as itshould.
4. GEM-4: already fixed by #257
#257's X1b deferred fetch added a
MissingRung::GemBuildRefusedgate. Itrefuses
gem_spec_missingbefore downloading, under the same scope thisbranch had arrived at after its own scope correction: a not-installed gem,
service off, lock entry resolvable and verifiable, and no ledger entry.
On top of that it leaves
--dry-runalone, and #257's CHANGELOG alreadyrecords it. The merge takes
main's gate, so this branch'svendor.rsgate(
25756641,66a6e4b9) is gone and its CHANGELOG entry is dropped so thetwo do not duplicate.
vendor_gem_lockfile_only_e2eis kept as regression coverage of the gate'sscope.
mainalready pins the gate itself invendor_rerun_no_network_e2e. The suite is adapted tomain's behavior:gemspec … install the gem or use --vendor-source=service");
main, so itreports only
already_vendoredand makes no registry request. It usedto report
vendor_fetched_missingtoo, and the test now asserts zerorequests.
All five tests pass on
main, as expected for a bugmainalready fixed.Docs
[Unreleased]→Fixed: entries for bugs 1–3 only. Nothingduplicates Speed up hosted and vendored scans: concurrent API requests, parallel crawl, single-pass rewriters #257's entries.
no_local_sourceis documented at bothlevels (per package, or run-level when nothing stages).
gem_spec_missinggets the row it never had, describing
main's X1b gate (the backend'sdetail, no
vendor_fetched_missing,--dry-rununaffected) and pointingto the "Vendor auto-fetch § Gem, local build only" paragraph Speed up hosted and vendored scans: concurrent API requests, parallel crawl, single-pass rewriters #257 wrote.
Deliberately not changed
no_local_sourcebail when nothing is stageable, includinga one-patch manifest. Five existing suites pin that shape.
repair's candidate partition still has no mixed-manifest test; that is afollow-up.
Verification (macOS, on the merged tree)
cargo clippy --workspace --all-targets -- -D warnings: clean.cargo test --workspace --no-fail-fast: 9430 passed, 0 failed, 136 ignored across 270 suites (CARGO_INCREMENTAL=0). Thedocker_e2e_*suites self-skip without docker on this host.main: fix sources checked out from8c381ecfwith the testskept. Bug 1: 2/2 fail. Bug 2: 4/4 fail. Bug 3: 4/4 fail (the run-level
guard stays green). Bug 4: 5/5 pass (already fixed on
main).🤖 Generated with Claude Code