Skip to content

fix(gem): hosted grant rotation refreshes the source block in place — no more nested Gemfile sources - #211

Merged
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
fix/gem-hosted-grant-rotation
Aug 19, 2026
Merged

fix(gem): hosted grant rotation refreshes the source block in place — no more nested Gemfile sources#211
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
fix/gem-hosted-grant-rotation

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

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 "..." do blocks in the Gemfile. On gems.rb/Gemfile twin spellings the same blindness tripped the redirect_gem_gemfile_spellings_diverge trap 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 on DepOverride.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 shipped token: "".

Fix

Two-sided, so the guard is caller-independent:

  • Core: the recognizer/refresh pattern now wildcards the path level before the patch uuid unconditionally (gem_index_url_pattern), and a rotated grant refreshes the URL in place (ledger edit redirect_gemfile_source_url, original = old URL) — never nests. New helper grant_token_path_segment (mod.rs:2979) derives the token from the URL shape.
  • CLI: hosted.rs now populates a truthful token for all ecosystems via the same generic path-segment-before-uuid derivation, so any future dep.token consumer is safe. (Audit: gem_index_url_pattern was 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-rerun leg: 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's run.log is pinned in the red commit. The lane's run.sh scripts pass as-written on the fixed binary (their green path is the fix).

Tests (red → green)

  • Red (8bd8960, test-only): unit gemfile_rerun_with_rotated_grant_and_cli_empty_token_never_nests failed with the exact nested-2-blocks Gemfile; gems_rb_twins_rotated_grant_with_cli_empty_token_refreshes failed on the divergence trap; new e2e capstone gem_hosted_rotated_grant_rescan_refreshes_source_block_and_installs failed through the real binary + real bundler 4.0.15 with the campaign's production failure shape.
  • Green (1def4d0): both units pass; full e2e suite 5/5 — the rotation leg proves exactly one source block, the redirect_gemfile_source_url ledger edit (original=token-A URL, new=token-B URL), and a green fresh install of byte-verified patched gems. Plus grant_token_path_segment_shapes (7 URL-shape edges incl. uuid-as-host and uuid-in-first-segment) and Gemfile source:-option refusal units.
  • Style (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 -- --ignored 5/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-rewrite gem.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 "…" do blocks around existing Socket blocks. The rewriter refreshes the index URL in place, records a redirect_gemfile_source_url ledger edit, and stays byte-idempotent when the grant is unchanged.

Core: New grant_token_path_segment recovers the token from URL shape (path segment before the patch uuid). gem_index_url_pattern now wildcards that segment from DepOverride.token or from the URL itself, so idempotency does not break when callers leave token empty.

CLI: scan/hosted populates DepOverride.token from registry override or artifact URLs via the same helper.

Also: When a gem line blocks redirect because of path: into .socket/vendor/, the warning prescribes socket-patch vendor --revert instead of a generic skip message.

Tests: Unit cases for empty-token rotation, gems.rb twins, grant_token_path_segment edges, and vendored-path warnings; e2e capstone gem_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.

…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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

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.

Comment thread crates/socket-patch-core/src/patch/redirect/mod.rs
Comment thread crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs Outdated
…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>
@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit 6bd05da into main Aug 19, 2026
62 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the fix/gem-hosted-grant-rotation branch August 19, 2026 15:14
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.

2 participants