From 23fe0d46ad0d81843e30c2346cc7285223e9a074 Mon Sep 17 00:00:00 2001 From: Jeff Musgrave <1578618+JeffMusgrave@users.noreply.github.com> Date: Tue, 20 Oct 2026 03:32:19 -0400 Subject: [PATCH] Compile governed Cargo replacements through their consumers Prepare 0.29.4. Real-Cargo regression checks preserve the consumer lock, reject replacement type errors, avoid dependency development targets, and keep the Rust/Go check execution aligned with its plan. The Rust module suite and new quality regression pass; broader verification is in progress. --- CHANGELOG.md | 7 +++ VERSION | 2 +- docs/design/cargo-facts.md | 14 +++++- internal/quality/cargo_test.go | 26 ++++++++++ internal/rustfacts/compilation.go | 4 +- .../rustfacts/compilation_patches_test.go | 49 +++++++++++++++++++ internal/rustfacts/discovery.go | 5 ++ 7 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 internal/rustfacts/compilation_patches_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c637c8..53eb918d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## 0.29.4 - 2026-10-20 + +- Compile governed Cargo replacements through their actual consumer, including + when only replacement source is selected. Retain source discovery and checks + without requiring dependency-owned locks or compiling dependency development + targets. Keep mixed Rust/Go execution aligned with the gate's command plan. + ## 0.29.3 - 2026-10-18 - Resolve cyclic Rust glob exports and root `extern crate` aliases without diff --git a/VERSION b/VERSION index 5540b6e0..35b1b3d1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.29.3 +0.29.4 diff --git a/docs/design/cargo-facts.md b/docs/design/cargo-facts.md index 7e64dd03..90d4c7f3 100644 --- a/docs/design/cargo-facts.md +++ b/docs/design/cargo-facts.md @@ -290,8 +290,18 @@ invitation for the check to resolve dependencies. Checks select the manifest's ordinary Cargo packages, default features and all enabled targets, using the governed native host explicitly. They do not enable -every feature or establish coverage of other target configurations. Compiler -warnings, Clippy's default lint group and unused-code lints use `--force-warn` at +every feature or establish coverage of other target configurations. + +Compiler selection maps explicitly governed replacement packages to their consumers, +using the same ownership boundary as locked resolution. Discovery retains the +replacement's own metadata for source analysis and also discovers its consumer. +Selecting only replacement source still checks the consumer; selecting both +does not compile it twice. Replacement development targets and their independent +locks are not part of the consumer build. Source analysis remains selected for +the replacement, while compiler evidence covers only dependencies active in the +consumer's default feature and host configuration. + +Compiler warnings, Clippy's default lint group and unused-code lints use `--force-warn` at the invocation boundary. Source `allow`, `expect`, `deny` and `forbid` attributes cannot hide these diagnostics or turn policy lint collection into a compiler failure. Code Polishy treats the resulting typed findings as blocking errors, diff --git a/internal/quality/cargo_test.go b/internal/quality/cargo_test.go index 0a1feda5..6533e7e7 100644 --- a/internal/quality/cargo_test.go +++ b/internal/quality/cargo_test.go @@ -80,6 +80,32 @@ func TestCargoAndGoCheckPlanMatchesObservedCommandOrder(t *testing.T) { } } +func TestCargoReplacementCheckKeepsGoCommandsInsideThePlan(t *testing.T) { + t.Parallel() + repo := cargoQualityRepository(t) + repo.Config.SupplyChain.DependencyOverridePolicies = []policy.DependencyOverridePolicy{{Ecosystem: "cargo", Path: "Cargo.toml", Field: "patch.crates-io"}} + writeQualityFile(t, repo.Root, "Cargo.toml", "[package]\nname='app'\nversion='1.0.0'\nedition='2024'\n[workspace]\nexclude=['vendor/library']\n[dependencies]\nlibrary='=1.0.0'\n[patch.crates-io]\nlibrary={path='vendor/library'}\n") + writeQualityFile(t, repo.Root, "Cargo.lock", "version=4\n[[package]]\nname='app'\nversion='1.0.0'\ndependencies=['library']\n[[package]]\nname='library'\nversion='1.0.0'\n") + writeQualityFile(t, repo.Root, "src/lib.rs", "pub fn app() {\n library::call();\n}\n") + writeQualityFile(t, repo.Root, "vendor/library/Cargo.toml", "[package]\nname='library'\nversion='1.0.0'\nedition='2024'\n") + writeQualityFile(t, repo.Root, "vendor/library/src/lib.rs", "pub fn call() {}\n") + writeQualityFile(t, repo.Root, "go.mod", "module example.test/mixed\n\ngo 1.26.6\n") + writeQualityFile(t, repo.Root, "main.go", "package main\n\nfunc main() {}\n") + selection := repository.Selection{Files: []string{"main.go", "src/lib.rs", "vendor/library/src/lib.rs"}} + planned := CheckCommands(repo, selection, "check") + boundary := &cargoRecordingRunner{OSRunner: runner.OSRunner{}} + if findings := Check(t.Context(), repo, selection, boundary, "check"); len(findings) != 0 { + t.Fatalf("replacement prevented the mixed-language check: %+v", findings) + } + want := []string{} + for _, command := range planned { + want = append(want, command.Name) + } + if !reflect.DeepEqual(boundary.names, want) { + t.Fatalf("executed commands = %v, planned = %v", boundary.names, want) + } +} + func TestCargoNativeDeadCodePreservesPublicAPIAndChecksSelectedFeatures(t *testing.T) { t.Parallel() repo := cargoQualityRepository(t) diff --git a/internal/rustfacts/compilation.go b/internal/rustfacts/compilation.go index 1a3a43df..8d35f743 100644 --- a/internal/rustfacts/compilation.go +++ b/internal/rustfacts/compilation.go @@ -26,7 +26,9 @@ func PlanCompilation(repo repository.Repository, discovery DiscoveryPlan, allFil if len(discovery.Commands) == 0 { return plan } - for _, manifest := range discovery.Manifests { + manifests, _, findings := cargoResolutionRoots(repo, discovery.Manifests) + plan.Findings = append(plan.Findings, findings...) + for _, manifest := range manifests { files := compilationSources(repo, manifest, allFiles) native, err := repo.NativeUnit(files, "lint", "typecheck", "dead-code") if err != nil { diff --git a/internal/rustfacts/compilation_patches_test.go b/internal/rustfacts/compilation_patches_test.go new file mode 100644 index 00000000..b5608b05 --- /dev/null +++ b/internal/rustfacts/compilation_patches_test.go @@ -0,0 +1,49 @@ +package rustfacts + +import ( + "bytes" + "os" + "path/filepath" + "slices" + "testing" + + "github.com/riteofstring/code-polishy/internal/policy" + "github.com/riteofstring/code-polishy/internal/runner" +) + +func TestRustCompilationUsesReplacementConsumerAndPreservesDependencyTargets(t *testing.T) { + t.Parallel() + for _, selected := range []string{"Cargo.toml", "vendor/patch-source/src/lib.rs", "vendor/leaf/src/lib.rs"} { + t.Run(selected, func(t *testing.T) { + repo := cargoPatchRepository(t) + manifest := string(readRustFile(t, repo, "Cargo.toml")) + manifest = string(bytes.ReplaceAll([]byte(manifest), []byte("default=[]"), []byte("default=['desktop']"))) + write(t, repo.Root, "Cargo.toml", manifest) + write(t, repo.Root, "src/lib.rs", "pub fn value() -> u32 { patch_source::value() }\n") + write(t, repo.Root, "vendor/patch-source/src/lib.rs", "pub fn value() -> u32 { patch_leaf::value() }\n") + write(t, repo.Root, "vendor/patch-source/build.rs", "fn main() {}\n") + write(t, repo.Root, "vendor/patch-source/tests/unconsumed.rs", "compile_error!(\"dependency development targets must not compile\");\n") + write(t, repo.Root, "vendor/leaf/src/lib.rs", "pub fn value() -> u32 { 42 }\n") + before := readRustFile(t, repo, "Cargo.lock") + plan, discovery := compilationDiscovery(t, repo, selected) + if findings := Compile(t.Context(), repo, plan, discovery, runner.OSRunner{}); len(findings) != 0 { + t.Fatalf("consumer compilation failed: %+v", findings) + } + if !bytes.Equal(before, readRustFile(t, repo, "Cargo.lock")) { + t.Fatal("compilation changed the consumer lock") + } + for _, path := range []string{"vendor/patch-source/Cargo.lock", "vendor/leaf/Cargo.lock"} { + if _, err := os.Stat(filepath.Join(repo.Root, filepath.FromSlash(path))); !os.IsNotExist(err) { + t.Fatalf("compilation created a replacement lock at %s: %v", path, err) + } + } + write(t, repo.Root, "vendor/leaf/src/lib.rs", "pub fn value() -> u32 { \"wrong\" }\n") + findings := Compile(t.Context(), repo, plan, discovery, runner.OSRunner{}) + if !slices.ContainsFunc(findings, func(f policy.Finding) bool { + return f.Check == "quality.rustCompiler" && f.Subject == "E0308" && f.Path == "vendor/leaf/src/lib.rs" + }) { + t.Fatalf("consumer failed to check the replacement source: %+v", findings) + } + }) + } +} diff --git a/internal/rustfacts/discovery.go b/internal/rustfacts/discovery.go index 11997ec5..e2426dda 100644 --- a/internal/rustfacts/discovery.go +++ b/internal/rustfacts/discovery.go @@ -29,6 +29,11 @@ func PlanDiscovery(repo repository.Repository, selected, allFiles []string) Disc if len(manifests) == 0 { return plan } + consumers, _, findings := cargoResolutionRoots(repo, manifests) + plan.Findings = append(plan.Findings, findings...) + manifests = append(manifests, consumers...) + slices.Sort(manifests) + manifests = slices.Compact(manifests) if err := configurationProblem(repo); err != nil { plan.Findings = append(plan.Findings, projectFinding(manifests[0], "configuration", err.Error())) return plan