fix(gem): hosted grant rotation refreshes the source block in place — no more nested Gemfile sources - #211
Merged
Mikola Lysenko (mikolalysenko) merged 5 commits intoAug 19, 2026
Conversation
…ocks with the CLI's empty token Regression tests for the P0: the CLI's only production DepOverride construction site (scan/hosted.rs) leaves token empty, so the gem rewriter's grant-rotation idempotency guard (which wildcards only non-empty rotating segments) never recognizes the previous grant's source block. A re-scan under a rotated grant wraps the old block's indented gem line in a NEW nested source block, keeps the stale token URL live, corrupts the ledger revert chain, and reports success. - core unit: rotated re-run with CLI-shaped (empty-token) overrides must refresh the URL in place — currently nests (2 blocks). - core unit: gems.rb/Gemfile identical twins + rotated grant + empty token must not be trapped behind spellings-diverge — currently is. - e2e (real host bundler): scan A -> re-scan A (byte-idempotent, holds today) -> re-scan B rotated — currently nests exactly like the campaign repro (hosted-b1/b2/b4 idempotent-rerun), then fresh install of the rotated pair. All three captured red at this commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pulating DepOverride.token
Defense in depth for the P0 rotated-grant nesting corruption:
- core: gem_index_url_pattern now derives the grant-token path level
from the index URL itself (the segment immediately preceding the
patch uuid — production's /patch-registry/gem/{token}/{uuid}/ shape)
and wildcards it unconditionally, so the block recognizer, the
refresh-in-place branch, and gem_spelling_residue's footprint
erasure all survive a caller that leaves token empty.
- cli: scan/hosted.rs (the only production DepOverride construction
site) now recovers the token via the new pub
grant_token_path_segment helper from the registryOverride indexUrl
(artifact URL fallback) instead of hard-coding String::new() —
every path-token'd ecosystem gets a truthful token.
- polish: the redirect_gem_source_option refusal prescribes
'socket-patch vendor --revert' when the blocking path: option is
socket-patch's own .socket/vendor wiring.
Red tests from the previous commit now pass; the rotated-grant e2e leg
(token A -> A -> B, same uuid, real host bundler) proves exactly one
source block, a redirect_gemfile_source_url ledger edit, and a green
fresh-checkout install of the patched bytes.
NOTE: the depscan TS twin (registry-rewrite gem.ts) must be ported to
match — cross-repo follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewer P2: the new code introduced 5 rustfmt diffs on its own lines (main's versions of both files are fmt-clean). Scoped rustfmt run on the two touched files only; no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9a47426. Configure here.
…t blast radius; expect() in new capstone reads Bugbot round: (1) the socket-vendored-wiring refusal now prescribes the per-gem eject (remove <purl>) first and says vendor --revert reverts EVERY vendored dependency, not just the blocking gem; unit test pins both statements. (2) the rotation capstone's new file reads carry step-labeled expect()s so a CI failure names the step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on + dependency pin, frozen-installable (#212) * test(gem): red — hosted CHECKSUMS lock must converge, not leave the exit-37 mixed state Red half of KL1 (bundler-4 DEFAULT lock => mainstream hosted-gem path): - 4 new core unit tests pinning the fully converged rewrite on a CHECKSUMS lock: patch-registry GEM section holding the moved spec (+sublines), '<name> (= <ver>)!' DEPENDENCIES pin (rewritten, or added sorted for a transitive dep), patched CHECKSUMS sha, no redirect_gem_frozen_install caveat, converged re-run a no-op, and rotated-grant refresh of the converged lock's GEM remote (redirect_gemfile_lock_source_url). - e2e canary FLIPPED per its own header: was gem_hosted_checksums_lock_pins_patched_sha_but_bundler_refuses_mixed_state (pinning exit 37), now gem_hosted_checksums_lock_converges_and_installs_frozen_and_unfrozen — keeps the ledger-original rewrite-half asserts and now demands the converged lock plus green FROZEN (BUNDLE_FROZEN=true, lock byte-identical — the exit-16 two-step gone) and UNFROZEN fresh installs of the patched bytes. All five captured red at this commit (unit: mixed-state lock output; e2e: converged-GEM-section assert against the real 4.0.15 lock). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(gem): hosted CHECKSUMS locks converge — patch-registry GEM section + dependency pin, frozen-installable Implements the verified fix shape for KL1 (exit 37 'mismatched checksums' on the bundler-4 DEFAULT lock, exit 16 two-step under frozen/deployment): when a CHECKSUMS pin lands (or is already at target), converge_gem_lock_source rewrites the lock into what bundler itself writes after an install from the redirected Gemfile — - the dep's spec entry (+ dependency sublines) moves out of the upstream GEM section into a patch-registry GEM section (remote: <index-url>), ledger edit redirect_gemfile_lock_gem_source with the upstream remote as revert original; - DEPENDENCIES pins '<name> (= <version>)!' (rewritten, or added in bundler's sorted position for a transitive dep), ledger edit redirect_gemfile_lock_dependency_pin; - rotation-aware and idempotent: a section whose remote matches the token-wildcard pattern is recognized as ours (never duplicated) and refreshed in place under a rotated grant (redirect_gemfile_lock_source_url), CRLF preserved throughout; - fail-soft: an unattributable spec (absent, duplicated, legacy multi-remote section, no DEPENDENCIES) leaves today's mixed state. redirect_gem_frozen_install now fires only on a genuinely MIXED pair (pre-CHECKSUMS locks) — a converged pair is frozen-installable as written, so the caveat is dropped there. Flipped e2e canary (real host bundler 4.0.15) proves the converged pair fresh-installs patched bytes both FROZEN (BUNDLE_FROZEN=true, lock byte-identical) and unfrozen. NOTE: the depscan TS twin (registry-rewrite gem.ts) must be ported to match — cross-repo follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style(gem): rustfmt the lock-convergence code and its units Reviewer P2 (second half): the convergence work introduced 3 more rustfmt diffs on its own lines. Scoped rustfmt run on the touched file only; no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(gem): red — a hand-edited lock with DEPENDENCIES before GEM must fail soft, not converge on stale indices Reviewer nit: converge_gem_lock_source runs the DEPENDENCIES pin first on the premise those lines sit after the GEM sections (so the later spec-move indices never shift). Bundler always writes sources first, but a hand-edited lock with DEPENDENCIES before GEM breaks the premise — the transitive-dep pin INSERT would shift the parsed spec/remote/end indices before the spec move reads them. Pin the fail-soft contract: checksum pinned, GEM attribution untouched, frozen-install caveat, no convergence edits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(gem): lock convergence bails when DEPENDENCIES precedes the dep's GEM section One-line ordering guard (reviewer nit): converge_gem_lock_source edits DEPENDENCIES first because bundler writes source sections before it — a hand-edited lock violating that order would leave the spec-move splicing on indices the pin insert had already shifted. Guard: deps_start before the spec section's end routes to the existing fail-soft mixed path (checksum pin + frozen-install caveat, lock shape untouched). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(gem): bless the shared golden fixture to the converged lock shape The converged-lock rewrite changes gem/bundler/basic's expected output: the lock now carries a patch-registry GEM section, the `rails (= 7.0.0)!` DEPENDENCIES pin, and two new ledger edit kinds (redirect_gemfile_lock_dependency_pin, redirect_gemfile_lock_gem_source) with faithful originals. Caught by the workspace coverage job; the shape matches what the bundler-matrix campaign verified frozen-installs clean on bundler 4.0.18. Cross-repo: depscan's TS gem.ts twin must land the same convergence and re-bless its copy of this fixture in lockstep (shared golden contract). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(gem): gate service-supplied index URLs before any Gemfile/lock write Bugbot round: converge_gem_lock_source wrote ov.index_url verbatim into lock remote: lines (and the Gemfile source string always did the same) with no grammar check. New is_valid_gem_index_url — http(s) scheme, no quote/backslash/whitespace/control chars — gates the gem arm at intake, twin of is_valid_cargo_index_url; malformed URLs skip the dependency with redirect_gem_invalid_index_url. Unit test covers quote, backslash, newline-injection, space, and non-http schemes. Cross-repo: the depscan gem.ts twin needs the same intake gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
enabled auto-merge (squash)
August 19, 2026 14:54
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Aug 19, 2026
Mikola Lysenko (mikolalysenko)
deleted the
fix/gem-hosted-grant-rotation
branch
August 19, 2026 15:14
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.

Defect
A hosted gem re-scan under a rotated grant (production rotates the grant-token path segment of the patch-registry URL per request) did not recognize the Socket source block a previous run wrote — it wrapped it in a new one, leaving nested
source "..." doblocks in the Gemfile. On gems.rb/Gemfile twin spellings the same blindness tripped theredirect_gem_gemfile_spellings_divergetrap instead of refreshing. Silent Gemfile corruption on the exact re-scan path production exercises.Root cause
gem_index_url_pattern(crates/socket-patch-core/src/patch/redirect/mod.rs:3001): the rotation-idempotency wildcard was keyed onDepOverride.token— and the CLI never populated that field, so the pattern degraded to an exact-URL match. A block written under grant token A was invisible to a run under token B, and the rewriter appended a fresh block around it.crates/socket-patch-cli/src/commands/scan/hosted.rs:286: the grant token is not a top-level reference-API field, so the CLI shippedtoken: "".Fix
Two-sided, so the guard is caller-independent:
gem_index_url_pattern), and a rotated grant refreshes the URL in place (ledger editredirect_gemfile_source_url, original = old URL) — never nests. New helpergrant_token_path_segment(mod.rs:2979) derives the token from the URL shape.hosted.rsnow populates a truthful token for all ecosystems via the same generic path-segment-before-uuid derivation, so any futuredep.tokenconsumer is safe. (Audit:gem_index_url_patternwas the only production consumer repo-wide; bun/npm/cargo/maven/nuget rotation idempotency does not use it.)Campaign repro
Hosted+vendored real-data sweep,
hosted-b2/idempotent-rerunleg: token A → A → B on the same patch uuid. Run 2 was byte-idempotent; run 3 (rotated grant) nested two source blocks — the exact Gemfile shape from that lane'srun.logis pinned in the red commit. The lane'srun.shscripts pass as-written on the fixed binary (their green path is the fix).Tests (red → green)
8bd8960, test-only): unitgemfile_rerun_with_rotated_grant_and_cli_empty_token_never_nestsfailed with the exact nested-2-blocks Gemfile;gems_rb_twins_rotated_grant_with_cli_empty_token_refreshesfailed on the divergence trap; new e2e capstonegem_hosted_rotated_grant_rescan_refreshes_source_block_and_installsfailed through the real binary + real bundler 4.0.15 with the campaign's production failure shape.1def4d0): both units pass; full e2e suite 5/5 — the rotation leg proves exactly one source block, theredirect_gemfile_source_urlledger edit (original=token-A URL, new=token-B URL), and a green fresh install of byte-verified patched gems. Plusgrant_token_path_segment_shapes(7 URL-shape edges incl. uuid-as-host and uuid-in-first-segment) and Gemfilesource:-option refusal units.9a47426): scoped rustfmt on the two touched files (reviewer P2 — main's versions were fmt-clean, the drift was introduced here).Final gate at head: core lib redirect units green,
e2e_redirect_gem_build -- --ignored5/5 against real host bundler 4.0.15,cargo clippy -p socket-patch-core -p socket-patch-cli --all-targets— zero warnings in touched files (5 pre-existing warnings live in untouched test files, present on main).Reviewer sign-off
Adversarial review approved, red→green independently reproduced at unit and e2e level. Disclosed gaps riding this PR: in-container docker-matrix legs (bundler 1.17/2.7/4.0.18) were not re-run (disk headroom fell below the 15 GB stop threshold; no images were built); maven/golang rotation idempotency was not deep-audited (recommend a cross-lane check).
Cross-repo follow-up
The depscan TS twin (
registry-rewritegem.ts) must be ported to match: token-independent grant-rotation recognition — wildcard the path level before the patch uuid unconditionally. Shared golden suite should pin the nested-block red case.🤖 Generated with Claude Code
Note
Medium Risk
Changes hosted gem Gemfile rewrite and idempotency logic on a production re-scan path; scope is gem redirect + CLI override construction, with strong unit and e2e coverage but no broad cross-ecosystem rotation audit in this PR.
Overview
Hosted gem re-scans under a rotated grant (production rotates the grant-token segment in patch-registry URLs per request) no longer nest new
source "…" doblocks around existing Socket blocks. The rewriter refreshes the index URL in place, records aredirect_gemfile_source_urlledger edit, and stays byte-idempotent when the grant is unchanged.Core: New
grant_token_path_segmentrecovers the token from URL shape (path segment before the patch uuid).gem_index_url_patternnow wildcards that segment fromDepOverride.tokenor from the URL itself, so idempotency does not break when callers leavetokenempty.CLI:
scan/hostedpopulatesDepOverride.tokenfrom registry override or artifact URLs via the same helper.Also: When a
gemline blocks redirect because ofpath:into.socket/vendor/, the warning prescribessocket-patch vendor --revertinstead of a generic skip message.Tests: Unit cases for empty-token rotation,
gems.rbtwins,grant_token_path_segmentedges, and vendored-path warnings; e2e capstonegem_hosted_rotated_grant_rescan_refreshes_source_block_and_installs(token A → A → B through real bundler).Reviewed by Cursor Bugbot for commit 9a47426. Configure here.