Repository navigation
Fix Hatch takeover leaving direct-ref permission (#674) - #680
Mikola Lysenko (mikolalysenko) merged 4 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
After a hosted -> vendored takeover of two or more Hatch packages, rollback, remove and vendor --revert left allow-direct-references = true (plus empty [tool] / [tool.hatch] tables) in pyproject.toml, silently disabling Hatchling's direct-reference guard. The takeover unwinds hosted pins one at a time, so when the first package is vendored the other packages' hosted references still hold the permission hosted mode added, and the vendored ledger recorded that as the permission's original value. The ledger now records the value the permission has once those live non-vendored references are gone, which is the rule the hosted unwind already applies. Both lanes share one helper for dropping the permission and its emptied tables. Fixes #674 Assisted-by: Claude Code:claude-opus-5-5
Covers #674 through the hosted rewrite, discovery and the takeover's per-pin restore_upstream, rather than a simulated unwind, and checks both revert orders come back to the pre-hosted pyproject bytes. Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d3076b5. Configure here.
|
Ready for review — head
Generated by Claude Code |
|
Reviewed The first vendored permission record now excludes permission held by remaining hosted references. Shared ownership survives selective removal, and final cleanup restores Hatchling’s direct-reference guard. The extracted hosted cleanup helper preserves its existing behavior. Validation:
A separate exploratory case found existing hosted rewriting removes a comment attached to the permission value. This reproduced before any vendoring with both this binary and a control whose Hatch code matches main; it is preserved separately from the eight passing cases and is not introduced here. |
Hatch hosted mode (#680, #743) rewrites pyproject.toml and hatch.toml in place, with no lockfile, through utils::hatch::plan. No scenario exercised that rewriter: hatch.toml is a HOSTED pypi input, and a hatch project with no lock fell through every existing pypi fixture. The fixture is a lockless hatchling app. Direct deps go in [project], and a hatch.toml default env (in-project .venv) pins every patched transitive, since hosted Hatch only redirects deps a Hatch table declares. A scan rewrites both files and adds [tool.hatch.metadata] allow-direct-references. It is sized at 1000 packages / 25 patched so a scan takes about 75-85 ms. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Bench: add gradle hosted and rescan scenarios #646 gave Gradle builds a hosted mode: scan crawls Gradle's modules-2/files-2.1 cache, pins suffixed versions in gradle.lockfile and wires the build through an owned settings script and index under .socket/gradle/. None of that was benchmarked; the maven scenarios only reach the pom.xml + ~/.m2 path. The gradle fixture is a single-project Groovy build with dependency locking (1000 locked artifacts, 25 patched direct deps), its cache under the fixture's GRADLE_USER_HOME with jar and pom in separate sha1 dirs. The Maven-coordinate generator, pom writer and maven2 grant builder are shared with the maven fixture, whose bytes are unchanged. The grant's indexUrl is https because the Gradle planner refuses anything else; scan never fetches it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) * Bench: add hatch hosted and rescan scenarios Hatch hosted mode (#680, #743) rewrites pyproject.toml and hatch.toml in place, with no lockfile, through utils::hatch::plan. No scenario exercised that rewriter: hatch.toml is a HOSTED pypi input, and a hatch project with no lock fell through every existing pypi fixture. The fixture is a lockless hatchling app. Direct deps go in [project], and a hatch.toml default env (in-project .venv) pins every patched transitive, since hosted Hatch only redirects deps a Hatch table declares. A scan rewrites both files and adds [tool.hatch.metadata] allow-direct-references. It is sized at 1000 packages / 25 patched so a scan takes about 75-85 ms. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
LLM Description written by Claude Code:claude-opus-5-5
Fixes #674
Summary
After a hosted→vendored takeover of two or more Hatch packages,
rollback,removeandvendor --revertused to leaveallow-direct-references = true(plus empty[tool]/[tool.hatch]tables) behind. That silently turned off Hatchling's direct-reference guard and kept the pyproject dirty. With this change the pyproject comes back byte-exact.Root cause
The takeover in
commands/vendor.rscallsrestore_upstreamwith one hosted pin per candidate inside the vendoring loop. When the first Hatch package is vendored, the other packages' hosted direct references are still live, and so is the permission hosted mode added for them. The vendoredhatch_permissionrecord snapshotted that state as itsoriginal. Later packages clone the same record. So once the last reference was unwired, the revert "restored" a permission the user never had.Fix
vendor/pypi_hatch.rs::wire: a new permission record now checks the project for a direct reference the vendored ledger doesn't own (anything that isn't a{root:uri}/.socket/vendor/…reference). If there is one, the permission it finds is held for those references, so the record'soriginalis the document with that permission removed, along with any tables that leaves empty. The hosted unwind already applies this rule: it drops the permission once no project direct reference is left. Because the decision is made once when the record is created, every package that clones the record inherits it, whatever the revert order.utils/hatch.rs:drop_direct_reference_permissionandpermission_keysnow live here. The hosted unwind (redirect/upstream/pypi.rs) and the vendored ledger share them, so both lanes produce the same bytes.revertis unchanged. A permission the user set themselves (with no non-vendored direct reference live at wiring time) is still recorded and restored verbatim.I didn't restructure the generic per-pin takeover loop. It carries many per-candidate refusal gates, and the pnpm (#636) and uv (#670) leftovers have different mechanisms; #672 covers those.
Tests (red → green)
vendor::pypi_hatch::tests::takeover_through_hosted_unwind_reverts_byte_exactdiscover_patched_refs, per-pinrestore_upstreamand vendoredwire, reverted in both orders[tool]…allow-direct-references = true)vendor::pypi_hatch::tests::takeover_snapshot_of_hosted_permission_is_not_restored[metadata](sibling key kept)vendor::pypi_hatch::tests::user_permission_survives_vendored_revertPer-issue checklist:
vendor --revert(all three go throughpypi_hatch::revert) → byte-exact pyproject. Covered by the first two tests above.Local validation
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --lib -- pypi_hatch hatch upstream: 87 passed.cargo test --workspace --all-features --no-fail-fast: everything passes except 12 tests that simulate write or removal failures withchmod/ read-only dirs. The sandbox runs as root, which bypasses those permissions. None of them touch Hatch code. CI runs unprivileged.SOCKET_PATCH_HATCH_E2E_REQUIRED=1 SOCKET_PATCH_HATCH_E2E_VERSION=1.18.1 cargo test -p socket-patch-cli --all-features --test e2e_vex_build -- hatch:: --ignored: 4 passed.rustfmt --checkis clean on the touched files. Note thatcargo fmt --all -- --checkfails on main itself (about 130 files, from045d7ec), and CI doesn't run rustfmt. I kept those unrelated reformats out of this PR; only one existing line inutils/hatch.rswas reflowed.Behaviour note
If a user had both their own direct reference and
allow-direct-references = truebefore vendoring, the permission is kept as long as any project direct reference remains. It is dropped only once none is left, which is what hosted mode already does.🤖 Generated with Claude Code
Note
Medium Risk
Changes vendored Hatch permission ledger semantics and shared TOML cleanup used on revert; well-covered by new tests but affects dependency wiring rollback paths.
Overview
Fixes #674: after a hosted→vendored Hatch takeover with multiple packages, revert/rollback no longer leaves
allow-direct-references = true(and empty[tool]/[tool.hatch]tables) when the project should return to its pre-hosted bytes.Vendored wiring now treats
hatch_permission“original” as the file without the direct-reference permission when non-vendored project direct refs are still live at wire time (e.g. another package’s hosted pin during per-pin takeover). User-set permissions are still snapshotted verbatim.Shared Hatch helpers —
permission_keysanddrop_direct_reference_permission— moved intoutils/hatch.rsso hosted unwind (restore_hatch) and the vendored ledger apply the same cleanup rule.Adds regression tests for takeover/revert order, full hosted→vendor lane, and user-owned permissions in
pyproject.toml/hatch.toml.Reviewed by Cursor Bugbot for commit d3076b5. Configure here.
Generated by Claude Code