Skip to content

Add the NuGet vendoring design (docs only) - #285

Merged
Mikola Lysenko (mikolalysenko) merged 8 commits into
release/v5-prereleasefrom
v5/nuget-vendoring
Sep 28, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 8 commits into
release/v5-prereleasefrom
v5/nuget-vendoring

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR adds the design for robust vendored (offline, committed) NuGet: docs/design/nuget-vendoring.md. The raw research and adversarial-review notes are in docs/design/nuget-vendoring-research/. It is docs only; no code changes. The v5 plan keeps vendored NuGet as future work, so the prototype was moved out of this PR.

Why the current layout can't be made safe. Today the patched package is vendored under the same id and version as upstream, in a local feed. Experiments with the real SDK showed:

  • The global packages folder is keyed only by id and version, and the first copy written there wins.
  • A failed --locked-mode restore still leaves the wrong copy in that cache.
  • Patched bytes leak into other projects that share the cache, and upstream bytes leak in.
  • User, CI or ancestor configs can undo source mapping.

Chosen design. It scored 76.3/100 with the judge panel; the other three candidates scored 59.6–70.5.

  • The patched package gets a Socket-only version V′: the upstream version plus a 4th part derived from the patch uuid.
  • It is committed already extracted, as a NuGet fallback package folder.
  • A generated socket-patch.targets, imported from Directory.Build.props, redirects PackageReference and CPM PackageVersion to V′.
  • packages.lock.json gets byte-exact splices. nuget.config is not edited.
  • Restore and build fail closed when the seed is tampered with, incomplete or missing, when a project still resolves the unpatched version, or when the targets file was not imported.

The doc also covers:

  • supported shapes and failure modes;
  • signing and enterprise policy;
  • migration;
  • the depscan/CLI split;
  • the test plan;
  • how each adversarial-review finding was resolved.

§0 records where the prototype changed the design.

Prototype (not in this PR)

The opt-in prototype lives on v5/nuget-vendoring-prototype and is not merged.

  • It is enabled with SOCKET_PATCH_NUGET_LAYOUT=fallback.
  • It covers SDK-style solutions with per-project locks under --locked-mode, and CPM with or without transitive pinning.
  • Its real-SDK e2e passes 4/4 on SDK 8.0.131.
  • It is ~6.4k lines and needs rebasing onto the current base before it can be proposed.

Testing

🤖 Generated with Claude Code

https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A

`cargo clippy --all-targets -- -D warnings` failed on the dead
before_hash/after_hash fields of PatchedFixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A
Setting SOCKET_PATCH_NUGET_LAYOUT=fallback vendors a patched NuGet
package under a Socket-only version V' (the upstream version plus a 4th
part derived from the patch uuid). The package is committed already
extracted, as a NuGet fallback package folder under
.socket/vendor/nuget/<uuid>/. It is wired through a generated
socket-patch.targets file (imported via CustomAfterDirectoryBuildTargets
from a Directory.Build.props block) and byte-exact packages.lock.json
splices.

Because nothing else can produce V', the patched bytes never enter or
collide with the shared global packages folder, and nuget.config and
source mapping are left alone. Restore-time and build-time guards fail
closed on a tampered, incomplete or missing seed, or an unpatched
resolution (SOCKETPATCH001/002/005/007).

Supported shapes: SDK-style solutions with per-project lock files under
--locked-mode, and Central Package Management with or without transitive
pinning. Other shapes are refused with explicit codes.

The default layout is unchanged. A repo is opted in only by the env var
or an existing nuget-fallback ledger entry.

Tests:
- unit tests;
- lock goldens captured from real dotnet;
- an ignored real-SDK e2e (sln_locked, cpm_locked, cpm_pinning,
  sln_patch_update).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A
docs/design/nuget-vendoring.md covers:
- the verified NuGet facts behind the design;
- the four candidate designs and the judge scores;
- the decision (unique-version fallback seed);
- the mechanism, supported shapes, failure modes, signing policy and
  migration;
- the server/CLI split, the test plan and the adversarial review
  dispositions;
- where the prototype overrides the design.

The raw research and review notes are committed alongside in
nuget-vendoring-research/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A
get now defaults to hosted mode (5e5f5ed), so the real-vlt
get_and_remove leg ran a hosted get and found the installed copy
unpatched. It now passes --mode agent, like the other agent-mode
fixtures that commit updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] The install-proof (ubuntu-latest, 0.0.0-*) failures in vlt compatibility come from the base branch, not from this PR. The failing test is e2e_vlt::vlt_pinned_matrix_agent_get_and_remove: left: Absent, right: Patched.

Cause: 5e5f5ed made get default to hosted mode. This #[ignore]d real-vlt test still runs a plain get <uuid> and expects agent-mode in-place patching. The same check fails on #280 and #281, which also target release/v5-prerelease.

I ported the fix into this PR as cc5f1b6: the test now passes --mode agent. It passes locally against vlt 0.0.0-18, and it has no effect once the base carries the same fix.


Generated by Claude Code

d9a3f53 removed PatchedFixture's before_hash/after_hash as dead code,
but a macOS-only test reads them, which broke the macOS build. Restore
the fields under #[cfg(target_os = "macos")], so they exist where they
are read and clippy stays clean elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] CI status for e8755a3:

  • test (macos-latest): caused by this PR. My clippy cleanup removed two PatchedFixture fields that a macOS-only test reads. Fixed in 49dd215, which keeps the fields under #[cfg(target_os = "macos")].
  • coverage (e2e_redirect_cargo_build: cargo_get_uuid_hosted_fresh_checkout_fetch and cargo_hosted_fresh_checkout_fetch_pulls_patched_crate_and_vex_verifies), Pipenv matrix (… marker-excluded extras …) and PDM native (ubuntu-latest, 2.17.3): these are not caused by this PR. They are hosted cargo/pypi paths that this diff doesn't touch, and the two cargo tests fail the same way on clean release/v5-prerelease locally. The Pipenv and PDM workflows pass on v5 WS1+WS2: ledger-free hosted mode, upstream-restore rollback, vendor ejects hosted projects #280 (WS1 ledger-free hosted), whose hosted rewrite changes, e.g. keeping Pipenv's index, appear to be what fixes them. That fix is the WS1 rework itself, not a small patch, so I'm not porting it here. These checks should go green once v5 WS1+WS2: ledger-free hosted mode, upstream-restore rollback, vendor ejects hosted projects #280 lands and this branch merges the base.
  • vlt install-proof: fixed in cc5f1b6 (see the comment above).

Generated by Claude Code

Vendored NuGet is future work for v5, so the opt-in fallback layout
prototype (9 new modules, routing hooks and a real-dotnet e2e) moves
to v5/nuget-vendoring-prototype, unmerged. This branch keeps the
design doc, its research notes and the two test fixes. The doc now
points at the prototype branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A
…uget-vendoring

# Conflicts:
#	crates/socket-patch-cli/tests/covgap_commands_rollback.rs
@mikolalysenko Mikola Lysenko (mikolalysenko) changed the title NuGet vendoring v2: design + opt-in unique-version fallback layout prototype Add the NuGet vendoring design (docs only) Sep 28, 2026
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review September 28, 2026 11:33
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] ready to land (docs/opt-in only)

This PR is now the design doc and research notes plus a one-line e2e_vlt test fix; there are no product code changes. It is merged with the current release/v5-prerelease (#280), and clippy is clean. The opt-in prototype is kept, unmerged, on v5/nuget-vendoring-prototype.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] vlt patch compatibility / install-proof fails on 2f49451. The cause is on the base branch, not this PR.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] test (windows-latest) fails on 2f49451, in -p socket-patch-cli --test covgap_commands_scan_mod only. It comes from the base branch, not this PR. #280 last changed that test file, and this PR's diff against release/v5-prerelease is docs plus one line in e2e_vlt.rs. The Linux and macOS test jobs don't fail it. I know of no fix yet; it belongs in a follow-up to #280.


Generated by Claude Code

Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
get defaults to hosted mode since 5e5f5ed, so the real-vlt
get_and_remove leg ran a hosted get and found the installed copy
unpatched (Absent, expected Patched). Pass --mode agent, as the other
agent-mode fixtures already do. Same change as cc5f1b6 on #285; it
blocked the e2e tier's e2e_vlt jobs now that they run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

#283 landed on release/v5-prerelease as 06437d2; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Merged release/v5-prerelease at 06437d2 (#283) into this branch as a1f7082. There were no conflicts, and clippy is clean. The base already carries the same e2e_vlt --mode agent fix, so this PR is now docs only: 12 files under docs/design/. The PR stays ready for review.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] CI on a1f7082 has two red checks. Neither comes from this PR: its diff is only docs/design/, so the tested code is byte-identical to release/v5-prerelease at 06437d2.

No fix exists for either failure, so there is nothing to port. I'm re-running the failed jobs once. If e2e_vlt fails again, I'll treat it as real and fix it on the base.


Generated by Claude Code

Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Sep 28, 2026
* Cancel only superseded PR runs in CI

A CI or compatibility run is now cancelled only when a newer push to
the same pull request replaces it. Push, dispatch and scheduled runs
always finish, so a manually dispatched run on the v5 base branch (its
only CI verdict, since push CI runs on main alone) is no longer killed
by a later dispatch or by the non-main cancel rule, and main keeps
finishing its rust-cache saves.

CI now groups PR runs by PR number, like the compatibility workflows
already do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp

* Expect native path separators in scan headers

scan_hosted_paths_run_once_per_project_directory compared the
per-directory `== apps/a ==` header against a literal forward-slash
path, but scan prints the directory glob matched, which Windows
spells `apps\a`. The Windows test leg failed on this since 62f07c7,
and because every e2e job waits on `test`, the whole e2e tier was
skipped on v5 PRs. Build the expected header from path components.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp

* Run the vlt agent get test in agent mode

get defaults to hosted mode since 5e5f5ed, so the real-vlt
get_and_remove leg ran a hosted get and found the installed copy
unpatched (Absent, expected Patched). Pass --mode agent, as the other
agent-mode fixtures already do. Same change as cc5f1b6 on #285; it
blocked the e2e tier's e2e_vlt jobs now that they run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp

* Run the pnpm safety e2e gets in agent mode

get defaults to hosted mode since 5e5f5ed, so the three pnpm safety
tests ran a hosted redirect and found proj_a's installed copy
unpatched and no pnpm-layout note. They test the in-place apply
path, so pass --mode agent. These e2e-tier tests had not run since
that change because a red base test skipped the tier.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp

* Let the NuGet hosted e2e run without .socket/

v5 hosted mode writes no `.socket/` directory (the lock pins are the
whole hosted state), so the hosted leg's fresh_checkout panicked with
NotFound copying a tree that no longer exists. Copy it only when the
run left one; the vendored leg still carries its ledger through.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp

---------

Co-authored-by: Claude <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit c02ccf8 into release/v5-prerelease Sep 28, 2026
126 of 129 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the v5/nuget-vendoring branch September 28, 2026 16:36
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
…etup-and-ui

Take the base's configuration.md deferred-defaults paragraph, which
already accounts for `setup` being removed in v5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
…ed-engine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju
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