fix(apply,rollback): reach the documented mismatch policy for singleton gem/pypi/maven variants; variant-scope the rollback before-blob gate - #176
Conversation
|
Addressed the adversarial review findings (commit e0d06aa): Major — qualified singletons no longer fall through the mismatch policy. Confirmed the finding is real: Minor — vendored awareness in Minor — stale Verified: cli lib 353/0, cli_gem_variant_mismatch_policy 6/6, in_process_gem_multi_platform 9/9 non-ignored, apply/rollback/remove/vendor/variant integration suites green, docker_e2e_gem 2/2, e2e_gem 8/8 non-ignored, core green, clippy + fmt clean. 🤖 Generated with Claude Code |
d063cb1 to
99f9cd0
Compare
…ed rollback blob gate apply (P1): for release-variant ecosystems (gem/pypi/maven) the variant loop's installed-distribution gate skipped ANY variant whose representative file mismatched — for a SINGLETON base (the common bare pkg:gem/name@ver record) that made the documented default mismatch policy (npm parity: warn content_mismatch_overwritten + apply the full verified patched content, --strict refuses, --force unchanged) unreachable: a locally-modified file failed the run with "no matching variant found" and left the file untouched. The gate now applies to multi-variant bases only — a singleton has no sibling to make a mismatch mean "different distribution", so it falls through to apply_package_patch's MismatchPolicy exactly like the npm branch. mismatch_blob_gaps mirrors the same singleton exemption so the Warn fall-through's afterHash blobs are prefetched under --download-mode diff. Multi-variant behavior is UNCHANGED (sibling mismatch = skip; no-variant-matches = fail closed), pinned by two guards. rollback/remove (P2): the before-blob gate ran over the WHOLE ecosystem-scoped manifest BEFORE select_installed_variants narrowed a broad multi-platform gem manifest to the installed platform, so an uninstalled sibling ?platform= variant's unfetchable before-blob aborted the entire run (--offline: wholesale; online: on any download failure) — and remove then refused with rollback_failed. Discovery + variant narrowing now run first and the gate covers only the (purl, path) pairs that will actually be attempted, plus in-scope purls the crawler could not resolve at all — those keep the pre-existing fail-closed gate (pinned by rollback_invariants / cli_rollback_silent / remove_network), and local-redirect purls stay excluded as before. Tests (each fails on the base commit without its fix): - cli_gem_variant_mismatch_policy.rs (new): singleton default warn+apply (human + JSON envelope), singleton --strict refusal; guards: --force unchanged, multi-variant sibling never overwritten, multi-variant no-match still fails closed. - in_process_gem_multi_platform.rs: rollback with unfetchable sibling before-blob succeeds online (404 mock, blob never requested) and --offline (installed variant's blobs only); remove ditto and clears both records. - apply.rs unit: mismatch_blob_gaps_singleton_mismatch_queued (RED vs the old gate); the sibling-skip test now uses a genuine multi-variant group. Verified: core 2460/0, cli lib 351/0, docker_e2e_gem 2/2, e2e_gem 8/8 non-ignored, gem/pypi variant + rollback/remove/apply suites green, clippy+fmt clean. Platform-gem dir layout re-validated against real RubyGems (ruby 3.4.10): gems/<name>-<version>-<platform> leaf. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he mismatch fall-through Review fixes for the singleton-variant mismatch policy: Major: the singleton exemption covered QUALIFIED singletons too. A lone `pkg:gem/name@ver?platform=x86_64-linux` record names one specific distribution, and the representative-hash gate is the ONLY platform resolution (the ruby crawler drops the gem dir's platform suffix precisely because "the installed platform is resolved later" by that hash check) — falling through let a linux-only record silently overwrite a darwin install in the Bundler plugin's `--silent` auto-apply, where the warn half of warn-and-apply is invisible, with a later rollback restoring the LINUX before-bytes. The exemption now applies to UNQUALIFIED singletons only (`variants[0] == base_purl`); qualified singletons keep the pre-existing fail-closed "no matching variant found". `mismatch_blob_gaps` mirrors the same condition (its records now retain their manifest keys so the gate can see qualifiers). Minor: `mismatch_blob_gaps` now skips vendor-owned bases. The apply loop unconditionally skips them (their results are synthesized up front), so a drifted vendored release-variant file queued a spurious "Downloading N full patched blob(s)" fetch online and a spurious "will fail to apply" warning under `--offline`. Minor: refreshed the rollback_invariants comment made stale by the gate reorder — the offline missing-blob bail now aborts after discovery, before the rollback loop produces per-package results; the pinned contentless partial_failure shape is unchanged. Tests (each fails without its fix): - cli_gem_variant_mismatch_policy::qualified_singleton_wrong_platform_fails_closed: lone ?platform= record + wrong-platform install → exit 1, "no matching variant found", file byte-identical, no overwrite warning. - apply.rs unit mismatch_blob_gaps_qualified_singleton_gated_unless_forced: qualified singleton mismatch queues nothing by default, queues under --force. - apply.rs unit mismatch_blob_gaps_vendored_base_never_queued: vendored base queues nothing (even under --force), same fixture queues without the vendor claim. Verified: cli lib 353/0, cli_gem_variant_mismatch_policy 6/6, in_process_gem_multi_platform 9/9 non-ignored, apply/rollback/remove/ vendor/variant suites green, docker_e2e_gem 2/2, e2e_gem 8/8 non-ignored, core green, clippy+fmt clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e0d06aa to
55d71cd
Compare
|
Rebased onto the new Conflicts: none — the rebase applied cleanly and Verification on the rebased tip (
🤖 Generated with Claude Code |
STACKED on PR #175 (
fix/gem-bundler-audit) — base branch isfix/gem-bundler-auditat 10c0079, notmain.Two verified findings in apply/rollback variant handling, each RED-pinned against the base commit.
P1 — the variant gate blocked the documented mismatch policy (apply.rs)
For release-variant ecosystems (gem/pypi/maven), the variant loop verified each variant's representative file and — without
--force— skipped any variant inHashMismatch/NotFoundstate. For a singleton base (the overwhelmingly common barepkg:gem/name@vermanifest record) a locally-modified file therefore failed the run withno matching variant foundand left the file untouched, while the documented default policy (npm precedent from PR #115: warncontent_mismatch_overwritten+ apply the full verified patched content,--strictopts out) was unreachable.Fix: the installed-distribution gate now applies to multi-variant bases only. A singleton has no sibling to make a mismatch mean "different distribution", so it falls through to
apply_package_patch, whose existingMismatchPolicy(Warn/Strict/Force) handles it exactly like the npm branch — same warning wording, same JSON envelope events, same--strictrefusal.mismatch_blob_gapsmirrors the singleton exemption so the Warn fall-through's afterHash blobs are prefetched under--download-mode diff.Multi-variant behavior is unchanged (sibling mismatch = skip, never overwrite; no variant matching = fail closed) and pinned by two guards.
P2 — the rollback/remove before-blob gate was variant-blind (rollback.rs)
rollback_patches_innergated the whole run onget_missing_before_blobsover the ecosystem-scoped manifest beforefind_packages_for_rollback+select_installed_variantsnarrowed a broad multi-platform gem manifest to the installed platform. An unfetchable, uninstalled sibling?platform=variant's before-blob aborted the entire rollback (--offline: wholesale; online: any download failure), andremove— which delegates here — then refused withrollback_failed.Fix: discovery + variant narrowing run first; the gate then covers only the
(purl, path)pairs that will actually be attempted, plus in-scope purls the crawler could not resolve at all — undiscovered purls deliberately keep the pre-existing fail-closed gate (that behavior is pinned byrollback_invariants.rs,cli_rollback_silent.rs, andremove_network.rs, which are outside this PR's scope). Local-redirect (local-go) purls stay excluded as before, and the manifest-key lookups keep usingfind_packages_for_rollback(qualified-purl invariant).Which test pins which change (all RED on 10c0079)
cli_gem_variant_mismatch_policy::singleton_mismatch_default_warns_and_appliescontent_mismatch_overwrittenwarning on stderr + JSONappliedevent + warning eventcli_gem_variant_mismatch_policy::singleton_mismatch_strict_refuses--strict: exit 1, file byte-identical, per-file hash-mismatch error (not the generic no-variant miss)commands::apply::tests::mismatch_blob_gaps_singleton_mismatch_queued(unit)in_process_gem_multi_platform::rollback_succeeds_when_uninstalled_sibling_before_blob_unfetchablein_process_gem_multi_platform::rollback_offline_succeeds_with_only_installed_variant_blobs_cached--offline: succeeds with only the installed variant's blobs cached, no blob endpoint hitin_process_gem_multi_platform::remove_succeeds_when_uninstalled_sibling_before_blob_unfetchableGreen-on-base guards (must NOT change):
singleton_mismatch_force_still_applies,multi_variant_mismatched_sibling_is_skipped_not_overwritten,multi_variant_none_matching_still_fails_closed; the pre-existingmismatch_blob_gaps_skips_non_installed_variant_unless_forcedwas refitted with a genuine multi-variant group (its old singleton fixture's premise is what P1 changes) while keeping its assertions.Verification
--lib351/0,in_process_gem_apply7/0,in_process_gem_multi_platform7/0,in_process_get_manifest_path5/0,in_process_get_update_count3/0,in_process_scan24/0,e2e_gem8/0 (3 ignored live),docker_e2e_gem2/2 (default non-forced apply path), plus shared-code sweep:apply_network9/0,apply_invariants6/0,rollback_invariants14/0,cli_rollback_silent5/0,cli_apply_silent4/0,remove_network2/2,remove_invariants26/0,remove_rollback_api_overrides2/0,in_process_rollback_all_ecosystems8/0,in_process_pypi_multi_release4/0,in_process_variant_apply_failure2/0,in_process_edge_cases9/0,in_process_remove_repair_lifecycle14/0,output_modes_e2e37/0,cli_dry_run_paths_e2e8/0,cli_remove_silent10/0,get_nested_apply_api_flags_e2e16/0,scan_invariants18/0,scan_sync_e2e4/0,in_process_cargo_apply3/0,in_process_pypi_apply4/0,in_process_alternate_installers12/0,in_process_remote_ecosystems_apply12/0,in_process_vendor27/0,in_process_get21/0,get_invariants22/0.cargo fmt+clippy --all-targetsclean.gem installofnokogiri-1.18.10-x86_64-linux-gnu.gemlands ingems/<name>-<version>-<platform>— the exact leaf the fixtures synthesize.Reviewer notes / deliberate scope choices
?platform=record; after this PR a mismatch there warn-overwrites (npm parity) instead of erroring. If the user switched install platforms without rescanning, the default policy would write the recorded variant's verified bytes over the other platform's file — same trade-off npm already accepts for locally-replaced packages;--strictopts out. Called out in case a qualifier-aware carve-out is preferred.rollback_invariants.rs,cli_rollback_silent.rs(tests 3–4) andremove_network.rs(both tests), which are outside this PR's owned files. The narrower fix keeps those suites green; relaxing the not-installed case would need fixture updates there first.NotFoundrepresentative on a singleton now surfaces the engine's per-file "File not found" error (Warn/Strict are hard errors on missing pre-existing files,--forceskips) instead of the genericno matching variant found; exit codes are unchanged.🤖 Generated with Claude Code
Note
Medium Risk
Behavior changes in apply/rollback for release-variant ecosystems affect real installs and rollback/remove paths, but scope is bounded to variant selection and blob gating with extensive regression tests.
Overview
Apply for gem/pypi/maven no longer treats a singleton manifest base (one record per
package@version) like a multi-platform sibling set. The installed-distribution skip now runs only whenvariants.len() > 1, so a lone variant with locally modified bytes reachesapply_package_patchand the default Warn mismatch policy (npm-stylecontent_mismatch_overwritten+ full afterHash overwrite;--strictrefuses).mismatch_blob_gapsmirrors that: singleton mismatches still queue afterHash prefetches; non-installed siblings stay excluded unless--force.Rollback/remove runs package discovery and
select_installed_variantsnarrowing before the before-blob gate. Missing before-blobs for narrowed-away sibling variants no longer abort the whole run online or under--offline; the gate still covers variants that will actually be rolled back and in-scope purls the crawler could not resolve (existing fail-closed behavior).Reviewed by Cursor Bugbot for commit a2513a6. Configure here.