Skip to content

Stop vendored Maven liveness attesting unpatched builds - #2006

Merged
Mikola Lysenko (mikolalysenko) merged 6 commits into
mainfrom
fix/vendored-maven-liveness
Nov 7, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 6 commits into
mainfrom
fix/vendored-maven-liveness

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Vendored JVM (Maven reactor) liveness attested in cases where hosted mode and VEX discovery don't. The owner rule says hosted's stricter behavior is ground truth, so this PR brings vendored mode in line.

What attested wrongly, and the fix

J2: executable classifier and type copies. The project could also declare an executable copy of the patched GA: <classifier>tests</classifier>, a native classifier, or <type>test-jar</type>. The planner only warned (or, for typed copies, skipped silently), so the entry still attested. The classifier-less pin doesn't retarget the copy, and the vendored tree serves only the main jar, so Maven fetches the public release and its code runs.

  • Now a used (non-dependencyManagement) executable copy is uncertain, and so not attestable, unless every pom that reads it resolves the suffixed version. That check uses the same inheritor/profile override check as the main jar.
  • The rule is shared in formats::maven:
    • is_main_jar: untyped or jar, no classifier.
    • is_executable_variant: a type counts as the classifier it implies (test-jar → tests, ejb-client → client, java-source / javadoc → non-executable). Every other type except pom is executable.
    • is_executable_classifier: everything except sources / javadoc. Hosted mode and discovery use it too.
  • Vendored mode is stricter than discovery in one case: a copy inside a <profile> also blocks attestation, because the profile may be active.

J8: unscoped "wired" check. maven_reactor::wired_checked searched every reactor pom's text for the suffixed version. A suffixed version that appeared only in a build plugin's dependencies, a <profile>, an unused property, another artifact's declaration or a classifier copy kept the entry wired.

  • Now "wired" needs a <dependencies> or <dependencyManagement> declaration outside <profiles> that names the main jar at the suffixed version. Doc::declarations already excludes <build> and <reporting>.
  • ${...} is interpolated in the declaring pom and in every pom that inherits it, as Maven interpolates after inheritance.
  • A version-less declaration must also read the pin from the nearest local <dependencyManagement> on its chain. One pinned local root no longer proves an unpinned sibling root.
  • vex, vendor --check and the planner's refuse_unwired use this check.

J15: fail-open on read errors. entry_wired was unwrap_or(true), so an unreadable or unparseable pom made Discovery::vendor_entry_live report a JVM entry as live. It now fails closed.

GC, the takeover classifier and vendor --check

  • scan --prune GC: unchanged on purpose. It uses entry_references, which still answers "keep" on a read error. The old text search stays as maven_reactor::references_checked for the GC only, because the .mvn/maven.config tail repository also serves plugin resolution: a tree a <build> plugin or a profile still names must not be reverted out from under the build.
  • scan's overlap takeover classifier: now uses a tri-state: apply::entry_liveness, Discovery::vendor_entry_liveness and LedgerLiveness::vendor_entry_state. "Unknown" is not "dead": an unreadable pom, or a tree still referenced without attestable wiring, gives None. The classifier stays silent then, instead of emitting redirect_supersedes_vendored ("remove ") for an entry the GC keeps.
  • vendor --check: now agrees with vex. An intact, wired entry that isn't attestable stays verified, and the run gets the new warning vendor_jvm_unattested, which names the declarations. The new code is documented in CLI_CONTRACT.md. It's a warning, not a failure, because of the contract's unattested-references rule: liveness gates keep such wiring live, and re-vendoring cannot clear it.

Other behavior changes

  • vendor's kept-older-patch rule: an unreadable kept entry now means the unserved upgrade fails as vendor_prebuilt_required instead of being a benign skip.
  • refuse_unwired: a plan whose only wiring is a rewritten profile declaration, with no root pin, is refused as nothing_wired.
  • A typed executable copy now adds a classifier_declared degraded warning to the plan.

Review findings (/code-review high)

All nine are confirmed and fixed. Each behavior test fails when its fix is neutralized.

  1. test-jar skipped before the classifier check. Fixed with the shared type rule. plugin_dependencies_exclusions_and_other_types_are_untouched pinned the old silent pass-through; it now expects executable types to warn and not attest, while pom and java-source still pass silently.
  2. Takeover regression on undecided claims. Fixed with the tri-state. Test: maven_takeover_stays_silent_on_an_undecided_vendored_claim (CLI), with a superseded control case.
  3. Classifier copy version never override-checked. Fixed. Test: a_classifier_copy_a_reader_moves_back_to_the_base_is_not_attestable.
  4. One pinned root proved an unpinned sibling root live. Fixed with the version-less management check. Test: a_version_less_declaration_under_an_unpinned_root_is_not_attestable.
  5. vendor --check ignored J2. It now runs unattestable_checked and warns vendor_jvm_unattested. Test: vendor_check_reports_an_unattestable_entry.
  6. Interpolation only in the declaring pom. Fixed by also interpolating in inheritors. Test: a_parent_declaration_its_modules_resolve_to_the_pin_is_wired. attestable_checked still treats a parent declaration whose ${...} the parent itself can't resolve as uncertain (property_unresolved); that rule predates this PR and I left it strict.
  7. Duplicate version interpolation. Now uses Doc::coordinate.
  8. Copy-pasted main-jar predicate. Extracted to Doc::is_main_jar / formats::maven::is_main_jar.
  9. Uncalled maven_reactor::wired. Removed.

Tests

  • Results:
    • cargo test -p socket-patch-core --lib -- vendor::jvm:: vex::discover:: formats::maven: 710 passed
    • golden committed_fixture_corpus_matches_golden: passes, output unchanged
    • -- vendor:: patch::redirect:: utils::purl_key: 3756 passed
    • CLI lib -- takeover commands::vendor::: 76 passed
    • --test vendor_jvm_cli: 54 passed
    • --test maven_sidecar_cli: 28 passed
    • --test e2e_vendor_jvm_build: 35 passed, 6 ignored
    • --test e2e_vendor_maven_build: 4 passed, 1 ignored
  • cargo clippy --locked --workspace --all-features -- -D warnings and cargo fmt --all -- --check are clean.

Review findings, round 2 (/code-review high on e0c351f+98716dd, cloud routine)

  1. Hosted mode and discovery ignored <type>-implied executable copies: confirmed and fixed. The vendored reactor treats a type like test-jar (that is, the tests jar), ejb-client, ejb, bundle or an unknown type as an executable copy. The hosted rewriter and VEX discovery only looked at <classifier>. So a test-jar declaration at the base release was pinned by hosted mode and attested by discovery, even though Maven puts the unpatched tests jar on the classpath. All three now share one rule: formats::maven::is_variant / variant_label and is_executable_variant.

    • Discovery's main-jar version grouping now skips such variants.
    • Discovery's management lookup is keyed by type as well as classifier.

    Tests: maven_executable_classifier_variant_blocks_the_redirect (hosted) and executable_classifier_copy_off_the_pin_is_not_attested (discovery) both gain a test-jar case.

    • Behavior change: hosted mode no longer redirects a GA that has an executable type variant at the patched release, and it warns redirect_maven_classifier_unsupported.
  2. The kept-patch rule asked the attestation question: confirmed and fixed. keep_older_vendored_patch used vendor_entry_live. An older JVM vendoring that was still wired but not attestable (for example, an executable classifier copy beside it) therefore gave way, and every re-run failed with vendor_prebuilt_required. The older patch is now kept unless vendor_entry_liveness proves it dead. There is no new CLI test: the path needs a full JVM vendor-and-upgrade fixture. It relies on entry_liveness, which is already covered.

Declined:

  • vex reports vendor_unwired without the reactor's reason for a wired-but-unattestable JVM entry. This affects the note only, and the entry is still withheld. The fix needs a new UnattestedKind path for JVM entries, which is a follow-up.
  • A mixed Maven+Gradle entry's Gradle-half reason. This is a report-only gap.
  • classifier_declared for sources/javadoc is reported as degraded. That's a code/category nit.
  • The <pom> vs <pom>:<line> mix, repeated reactor parses, inheritor walks and a dead match arm. These are report format, performance and style issues.

Tests (cloud run): cargo test -p socket-patch-core --lib -- formats::maven vex::discover:: patch::redirect:: vendor::jvm gives 1581 passed, golden included. The 1 failure is the chmod test, which root ignores. CLI --lib -- vex_sources vendor and --test vendor_jvm_cli pass. clippy and fmt are clean.

Landing note: is_executable_classifier and is_executable_variant overlap with #2005, which also adds is_executable_classifier. When landing #2005 after this PR, keep this PR's shared helpers.


Note

Medium Risk
Changes security-relevant attestability and liveness semantics for Maven vendored patches, with broad impact on scan, vendor upgrade, and check flows; behavior is heavily tested but misclassification could affect prune vs takeover guidance.

Overview
Vendored Maven reactor entries no longer count as fully attested when the build can still pull unpatched bytes, matching hosted redirect and VEX discovery. Wiring now requires a main-jar declaration Maven always reads (not plugin deps, profiles, or plain text matches), with stricter handling for executable classifier/test-jar copies, version-less deps under unpinned roots, and property/profile overrides.

entry_wired fails closed on unreadable POMs, and a new entry_liveness tri-state (Some(true|false) vs None) drives scan's overlap takeover classifier and the kept-older-patch rule so undecided claims are not treated as dead (GC still uses the broader entry_references).

vendor --check keeps such entries verified but emits warning vendor_jvm_unattested with declaration locations. Shared helpers in formats::maven unify main-jar vs executable-variant logic across redirect rewrite, reactor planning, and VEX discovery (including <type>-implied variants like test-jar). CLI_CONTRACT.md documents the new warning.

Reviewed by Cursor Bugbot for commit 11e501d. Configure here.


Generated by Claude Code

Three cases where vendored JVM (Maven reactor) liveness proved an entry
"wired" and let vex attest while the build may still use the unpatched
jar. Hosted mode and VEX discovery already refuse all three.

J2, executable classifier copies: when the project also declares an
executable classifier copy of the patched GA (tests, a native build),
the reactor planner only warned `classifier_declared` and moved on, so
`attestable_checked` passed. The classifier-less pin doesn't retarget the
copy and the vendored tree serves only the main jar, so Maven fetches
the public release and its code runs. A used (non-managed) executable
classifier declaration whose version isn't the suffixed one now marks
the reactor uncertain, so the entry isn't attestable. `sources` and
`javadoc` still only warn. The executable-classifier rule now lives in
`formats::maven::is_executable_classifier`, shared with the hosted
rewriter and discovery.

J8, unscoped "wired" check: `maven_reactor::wired_checked` was a
substring match for the suffixed version over every reactor pom's
comment-masked text. A suffixed version left only in a build plugin's
dependencies, a profile, an unused property, another artifact's or a
classifier copy's declaration kept the entry live. It now requires a
`<dependencies>`/`<dependencyManagement>` declaration outside
`<profiles>` (Doc::declarations already excludes `<build>`/`<reporting>`)
naming the GA's main jar at the suffixed version, literally or through a
property the checkout defines. That covers `vex`/`attestable_checked`,
`vendor --check` (`check_entry`) and the plan's `refuse_unwired`. The
old substring check is kept as `references_checked` and used only by
the `scan --prune` GC (`entry_references`): the maven.config tail also
serves plugin resolution, so a tree a `<build>` plugin or a profile still
names must not be reverted out from under the build.

J15, fail-open on read errors: `entry_wired` was
`entry_wired_checked(..).unwrap_or(true)`, so an unreadable or
unparseable pom made `Discovery::vendor_entry_live` (vendor's kept-patch
rule, scan's takeover classifier and vex's ledger liveness) report a JVM
entry as live. It now fails closed (`false`). The GC doesn't use it: it
asks `entry_references`, which still answers "keep" on a read error, so
nothing is deleted on an unknown answer.

Tests (each fails on origin/main):
- maven_reactor: an_executable_classifier_copy_is_not_attestable
- maven_reactor: a_suffixed_version_outside_the_declarations_is_not_wired
- apply: an_unreadable_pom_is_not_live_but_is_kept (also pins that
  entry_references / vendor_entry_in_use still keep the tree)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Nov 4, 2026
Review findings on the profile / user-property attestation fix:

- A profile <dependencyManagement> entry was flagged even when the live
  pin is a direct literal <version>, which beats management; likewise a
  profile-managed classifier entry no declaration takes. Profile
  management now withholds the pin only when management governs it (no
  live direct version). The old test case managed(Some("1.7.36")) beside
  a direct pin asserted the wrong outcome and now expects an attested
  pin; the governing case (managed live pin) expects the opposite.
- A version-less profile classifier copy was always flagged. One shared
  classifier check now covers live and profile copies and resolves a
  version-less copy from the classifier-keyed management (live, with a
  profile's entry overriding it; none in this pom fails closed). Profile
  classifier copies report MavenClassifierUnpatched, like live ones.
- The same check now runs for the pre-v5 vendored wiring too, where any
  executable classifier copy comes from the next repository (the vendored
  tree holds only the main jar). formats::maven gains
  NON_EXECUTABLE_CLASSIFIERS / is_executable_classifier, worded as in
  #2006 so the two merge cleanly.
- A pom-typed (or java-source / javadoc) profile declaration carries no
  code and no longer counts against the pin. Other types (test-jar, ejb)
  still do: they put a jar on a classpath.
- PomDep now keeps both the version as the pom spells it (`version`, what
  hosted writes and rolls back) and as the build resolves it
  (`build_version`, -D user properties first). Pins are found from the
  former, so a pin .mvn/maven.config moves stays a ref (list / rollback /
  remove and liveness keep the wiring) but is marked unattested with the
  new UnattestedKind::MavenConfigOverride (CLI note
  vex_maven_config_override). A Socket version only maven.config
  supplies is diagnosed (DIAG_REF_UNATTRIBUTABLE naming
  .mvn/maven.config), never a pom.xml ref, so nothing edits pom.xml for
  it.
- The build view leaves project.* / pom.* to the model: -Dproject.version
  no longer resolves a ${project.version} dependency version.
- Cleanup: main_jar_declarations / classifier_declarations take a scope
  predicate and back ga_versions, the classifier check and the profile
  check; group_by_ga is gone (live_gas lists the pin candidates);
  trusted_checksums takes the maven.config text extract already read.

Tests: version_property_reads_maven_config_overrides,
profile_declaration_off_the_pin_is_not_attested and
vendored_ref_bypassed_by_a_profile_or_classifier_is_not_attested
(renamed from vendored_ref_under_a_profile_version_is_not_attested) were
extended with a case per finding; each new case failed on the previous
commit. formats::maven gains model_reference_version_ignores_user_properties.
The golden corpus is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes all nine /code-review high findings on #2006.

1. A typed copy of the GA (`<type>test-jar</type>`, which is the `tests`
   jar, or `ejb`, `ejb-client`, `maven-plugin`, an unknown type) was
   skipped with a bare `continue`, so it still attested. A new shared
   rule, `formats::maven::is_executable_variant(type, classifier)`, maps
   types to the classifier they imply and treats every type except
   `pom`, `java-source` and `javadoc` as executable. The reactor now warns
   `classifier_declared` for such a copy and marks it uncertain unless
   every reader resolves it to the suffixed version.
2. With `entry_wired` failing closed, `scan`'s overlap takeover
   classifier read an unreadable pom, or vendored wiring left only in a
   plugin or profile, as a dead vendored claim. It then emitted
   `redirect_supersedes_vendored` ("remove <purl>") while the GC still
   kept the entry. New tri-state: `apply::entry_liveness`,
   `Discovery::vendor_entry_liveness` and `LedgerLiveness::
   vendor_entry_state`. It returns `None` when the claim is undecided
   (read error, or still referenced without attestable wiring), and
   the classifier stays silent on `None`.
3. A classifier copy's version now goes through the same override check
   as the main jar's (`version_moved`, generalized from
   `version_override`). An inheriting pom or a profile that moves the
   property off the suffixed version makes it uncertain.
4. A version-less declaration now reads its version from the nearest
   local `<dependencyManagement>` on its chain, interpolated as the
   declaring pom reads it. Anything other than the suffixed version,
   including no local management (a local root left unpinned), is
   uncertain. One pinned root no longer proves an unpinned sibling root.
5. `vendor --check` now agrees with `vex`. `check_entry` returns
   `Ok(Some(why))` when the entry is intact and wired but not attestable.
   `vendor --check` keeps the entry `verified` and adds the new run
   warning `vendor_jvm_unattested` naming the declarations (documented
   in CLI_CONTRACT.md). It is a warning rather than a failure because
   the contract's unattested-references rule says liveness gates keep
   such wiring live, and re-vendoring cannot clear it.
6. `Reactor::wires` now interpolates each declaration in the declaring
   pom and in every pom that inherits it, as Maven interpolates after
   inheritance. A parent's managed `${ct.version}` that each module
   defines as the pin now counts as wired again, so `refuse_unwired` no
   longer refuses that plan.
7. The classifier version check uses `Doc::coordinate`.
8. The main-jar predicate is `formats::maven::is_main_jar`, used through
   `Doc::is_main_jar` by `wires`, `rewrite_declarations`,
   `warn_unresolved_coordinates` and `jar_declaration`.
9. Removed the uncalled `maven_reactor::wired`.

Tests (each fails with its fix neutralized):
- plugin_dependencies_exclusions_and_other_types_are_untouched: updated.
  It pinned the silent `test-jar` pass-through; executable types now warn
  and don't attest, while `pom` and `java-source` still pass silently.
- a_classifier_copy_a_reader_moves_back_to_the_base_is_not_attestable
- a_version_less_declaration_under_an_unpinned_root_is_not_attestable
- a_parent_declaration_its_modules_resolve_to_the_pin_is_wired
- vendor_check_reports_an_unattestable_entry
- maven_takeover_stays_silent_on_an_undecided_vendored_claim (CLI)
- an_unreadable_pom_is_not_live_but_is_kept also covers the tri-state

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@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 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Version-less pin ignores profile overrides
    • Added managed_version_can_move() to detect when profiles or inheritors provide different managed versions, preventing unpatched JARs from reaching the classpath undetected.

Create PR

Or push these changes by commenting:

@cursor push 911c1aa479
Preview (911c1aa479)
diff --git a/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs b/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs
--- a/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs
+++ b/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs
@@ -1808,6 +1808,68 @@
             .flatten()
     }
 
+    /// Whether a profile or inheritor can provide a different managed
+    /// version for `g:a` than what `rel` reads from top-level management,
+    /// either through profile property redefinition or profile-specific
+    /// `<dependencyManagement>` entries.
+    fn managed_version_can_move(&self, rel: &str, g: &str, a: &str, base: &str) -> bool {
+        // If the base managed version uses properties, check if profiles
+        // or inheritors redefine them to produce a different version.
+        if base.contains("${") {
+            if self
+                .version_moved(rel, base, &|v: &str| v != base)
+                .is_some()
+            {
+                return true;
+            }
+        }
+
+        // Check if any profile or inheritor has management that provides
+        // a different version (profile management or inheritor management).
+        std::iter::once(rel)
+            .chain(self.inheritors(rel))
+            .any(|reader| {
+                self.chain(reader).any(|at| {
+                    let doc = &self.poms[at].doc;
+
+                    // Check profile management entries
+                    let profile_manages_differently = doc.declarations().into_iter().any(|dep| {
+                        !doc.is_top_level_managed(dep) && doc.is_main_jar(dep) && {
+                            let resolve = |v: &str| self.interpolate(reader, v, 0);
+                            doc.coordinate(dep, "groupId", &resolve).as_deref() == Some(g)
+                                && doc.coordinate(dep, "artifactId", &resolve).as_deref() == Some(a)
+                                && doc.coordinate(dep, "version", &resolve).as_deref() != Some(base)
+                        }
+                    });
+
+                    if profile_manages_differently {
+                        return true;
+                    }
+
+                    // For inheritors, check if their top-level management differs
+                    if reader != rel {
+                        let inheritor_manages_differently =
+                            doc.declarations().into_iter().any(|dep| {
+                                doc.is_top_level_managed(dep) && doc.is_main_jar(dep) && {
+                                    let resolve = |v: &str| self.interpolate(reader, v, 0);
+                                    doc.coordinate(dep, "groupId", &resolve).as_deref() == Some(g)
+                                        && doc.coordinate(dep, "artifactId", &resolve).as_deref()
+                                            == Some(a)
+                                        && doc.coordinate(dep, "version", &resolve).as_deref()
+                                            != Some(base)
+                                }
+                            });
+
+                        if inheritor_manages_differently {
+                            return true;
+                        }
+                    }
+
+                    false
+                })
+            })
+    }
+
     /// Whether every pom that reads `dep` of `rel` resolves its version
     /// to `sv`: `rel` and each pom inheriting it, with or without a
     /// profile on its chain active (see [`Self::version_moved`]).
@@ -2050,13 +2112,20 @@
                 // A version-less declaration reads its management: under a
                 // local root left unpinned (management conflicting or
                 // unresolved outside the checkout) that may be the base.
-                if !managed_section
-                    && self
-                        .managed_version(rel, patch.group_id, patch.artifact_id)
-                        .as_deref()
-                        != Some(sv)
-                {
-                    uncertain.insert(at);
+                if !managed_section {
+                    let managed = self.managed_version(rel, patch.group_id, patch.artifact_id);
+                    if managed.as_deref() != Some(sv)
+                        || managed.as_ref().is_some_and(|mv| {
+                            self.managed_version_can_move(
+                                rel,
+                                patch.group_id,
+                                patch.artifact_id,
+                                mv,
+                            )
+                        })
+                    {
+                        uncertain.insert(at);
+                    }
                 }
                 continue;
             };

You can send follow-ups to the cloud agent here.

A version-less declaration was treated as reading the suffixed version
whenever the nearest local management resolved to it, but that check
interpolated with no profile overlay and ignored profile management. A
profile that redefines the managed property, or manages g:a itself at
the base, puts the unpatched jar back on the classpath while liveness
still attested. Apply the same version_moved check versioned ${...}
declarations get, and reject profile management of g:a at another
version. Addresses the Bugbot High finding on maven_reactor.rs.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@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.

Stale Bugbot comment from a previous run.

@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review November 6, 2026 12:12
@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Nov 6, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review at 98716dd.

  • CI: 30/32 non-skipped checks green. The only failures are the main-wide e2e … pdm:: --ignored leg (module 'hishel' has no attribute 'SyncSqliteStorage', also red on main; Fix pdm e2e red on every PR: pin hishel<2 #2028 fixes it) and the ci-ok aggregate that follows from it.
  • Bugbot: reviewed 98716dd; no unresolved findings.
  • Promoted from draft by the burn-down agent this run. No Slack announcement was sent this run (the Slack send action wasn't available), so there's no slack-announced marker yet.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief (for the human approver)

What it does: this PR tightens liveness and attestation for vendored Maven reactor entries.

  • Wired: "wired" now needs a <dependencies> or <dependencyManagement> declaration of the main jar at the suffixed version, outside profiles and <build>/<reporting>. Before, any text match in a pom counted.
  • Blocks attestation: executable classifier or type copies (tests, test-jar, ejb, ...), and version-less declarations whose root management doesn't always resolve to the pin.
  • Fails closed: entry_wired now fails closed on read errors.
  • Unchanged: the scan --prune GC keeps its old fail-open text search, now named references_checked.

Risk: low-medium. Almost every change makes attestation stricter, so the likely failure is losing an attestation, not a false one. Deletion paths (GC, entry_in_use) keep their old semantics. The planner discards uncertain, so the new checks can't change a plan; they only affect attestation and a new vendor --check warning, vendor_jvm_unattested.

Look here

  • maven_reactor.rs:1763 wires
  • maven_reactor.rs:1817 always_managed_at: version-less declarations
  • maven_reactor.rs:2034-2062:```` executable variants marked uncertain
  • apply.rs:1003-1019 and 1226-1240: fail-closed entry_wired and check_entry
  • scan/mod.rs:1073-1086: the takeover tri-state

Verified

  • I read the full diff (9 files) and checked every caller of entry_wired, vendor_entry_live and entry_liveness.
  • references_checked has the same body as the old wired_checked, and moving NON_EXECUTABLE_CLASSIFIERS to formats::maven doesn't change hosted or discovery semantics.
  • The new tests reach every new branch. CLI_CONTRACT.md documents the new code. CHANGELOG.md is untouched.
  • CI: 30/32 non-skipped checks green. The only failures are the main-wide pdm/hishel leg and the ci-ok aggregate; Fix pdm e2e red on every PR: pin hishel<2 #2028 fixes both.
  • Bugbot: clean on this head.

Changes I made: none.

Open questions (non-blocking)

  • Hard failure on an older ledger shape: vendor --check now hard-fails an older ledger entry whose only wiring is a rewritten profile declaration with no root pin ("no longer wired"), and re-vendoring refuses nothing_wired. The contract says unattested references warn. This is rare, but the message misleads.
  • Kept-patch rule wider than described: the kept-older-patch rule (vendor.rs:179) also stops protecting a kept entry that is wired but not attestable under the new rules, not only the unreadable case the description names.
  • Perf: Reactor::wires recomputes inheritors(rel) for every main-jar declaration before comparing coordinates. A literal group/artifact prefilter would avoid that.

Auto-merge is not armed yet, because CI is red on the main-wide pdm failure. Once #2028 lands and this goes green, the next final-reviewer run arms it, and approving then sends it straight to the merge queue.


Generated by Claude Code

Second review round on vendored Maven liveness.

- The vendored reactor judged a `<type>` that implies an executable copy
  (`test-jar` is the `tests` jar, `ejb-client`, `ejb`, `bundle`, an
  unknown type) like a classifier, but the hosted rewriter and VEX
  discovery looked only at `<classifier>`: a `test-jar` declaration at
  the base release was pinned (hosted) and attested (discovery) while
  Maven put the unpatched tests jar on the classpath, so the two modes
  answered differently for one project. `formats::maven::is_variant` /
  `variant_label` now define the variant both share with the reactor,
  and both use `is_executable_variant`; discovery's main-jar version
  grouping skips such variants too, and keys management by type as well
  as classifier.
- `keep_older_vendored_patch` asked the attestation question
  (`vendor_entry_live`), so an older JVM vendoring that is still wired
  but not attestable (an executable classifier copy beside it) gave way
  and every re-run failed `vendor_prebuilt_required`. It now keeps the
  older patch unless its liveness is provably dead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The e2e_vex_build pdm leg failed on this head because it predated #2028.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Nov 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 7, 2026
# Conflicts:
#	crates/socket-patch-core/src/vendor/jvm/apply.rs
@mikolalysenko Mikola Lysenko (mikolalysenko) removed the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Nov 7, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@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.

✅ 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 11e501d. Configure here.

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Nov 7, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Re-enqueued by the final reviewer (head 11e501d7). The merge queue removed this PR at 01:55 UTC when the burn-down merged main at 7bacb8b. The PR is still approved, ci-ok and clippy are green on the new head, it is mergeable, and it has no unresolved threads. Approvals stay valid across later pushes, so it went back into the queue at 02:49 UTC.


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Nov 7, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Re-labeled Ready for review by the burn-down agent after resolving merge conflicts with main.

  • Head: 11e501d7 (merged main 7bacb8b)
  • CI: 31/31 green (19 skipped)
  • Bugbot: reviewed 11e501d7, no new issues
  • Conflict: vendor/jvm/apply.rs entry_wired. Main (Stop attesting sbt pins a build reassignment or uncommitted wiring bypasses #2030) split entry_wired_checked into entry_wiring_checked(.., attest) and kept entry_wired fail-open with attest=false. I kept this PR's fail-closed entry_wired_checked(..).unwrap_or(false) and entry_liveness. With the attest variant, an sbt/scala-cli attestability Err now reads as not wired rather than wired, which is the fail-closed direction this PR wants. Worth a look from the reviewer.
  • Slack announcement could not be sent from this run.

Generated by Claude Code

Merged via the queue into main with commit 2abbdc7 Nov 7, 2026
50 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the fix/vendored-maven-liveness branch November 7, 2026 02:55
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Nov 7, 2026
Adopt #2006's shared Maven variant rule (is_variant / variant_label /
is_main_jar / is_executable_variant) in VEX discovery: live_gas and
main_jar_declarations exclude every variant (a classifier or a
code-carrying type such as test-jar), classifier_declarations reports
executable variants by their label, and variant management is keyed by
classifier and type. The PR's behavior stays on top: profile
declarations (MavenProfileUnpatched, now gated on is_main_jar),
.mvn/maven.config overrides (MavenConfigOverride) and pre-v5 vendored
classifier coverage. A test-jar typed profile declaration is therefore
reported as a classifier copy, not a profile override. Main's
unpatched_executable_classifier / group_by_ga are superseded by the
PR's helpers; the redirect rewriter takes main's version.

Keep both sides' VEX notes and enum variants (Maven profile / config
overrides and #2007's vex_nuget_lock_other_version), and in
CLI_CONTRACT.md count eight unattested cases besides Gradle's and
NuGet's, with both tables' rows.

Co-Authored-By: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Nov 7, 2026
…property-overrides

The base merged main, which brought in #2006 (shared Maven variant
helpers, classifier-and-type keyed management) and #2008/#2007
(new unattested kinds in CLI_CONTRACT). Kept both sides:

- CLI_CONTRACT: base's eight-case list plus this branch's
  property-interpolation paragraph for profile declarations.
- formats/maven: keep base's is_variant / is_main_jar /
  is_executable_variant helpers; is_model_reference already lives in
  maven/properties.rs on this branch.
- vex/discover/maven: unpatched_classifier_copy keys management by
  classifier and type (base) and still checks every profile-property
  resolution (flagged/resolutions, this branch); profile_override walks
  live and profile main-jar declarations (this branch) through base's
  is_main_jar filter.
- vendor/jvm/maven_reactor: base's new variant checks call this
  branch's two-argument interpolate.

Co-Authored-By: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Nov 7, 2026
…-coordinate-overrides

The base took main's variant refactor (#2006: a <type> such as test-jar
counts like the classifier it implies). Conflicts resolved keeping both:

- redirect/mod.rs: the variant loop labels and weighs copies through
  variant_label / is_executable_variant, and still defers suffixed pins
  to unserved_classifier_copy.
- maven_index.rs: unserved_classifier_copy now weighs every executable
  variant (typed ones too), with management keyed by classifier and
  type, so a test-jar copy off the pin still blocks the redirect.
- maven_reactor.rs: version_moved kept as a wrapper over this PR's
  read_elsewhere (main's callers use it); the property-coordinate filter
  keeps this PR's executable-classifier rule.
- discover/maven.rs: this PR's retargeted helpers with the base's
  variant doc and imports.
- CLI_CONTRACT.md: both sides' additions (invalid_hash checksum, NuGet
  and sbt cases, typed variants, Go workspaces) plus this PR's text.

Co-Authored-By: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Nov 7, 2026
Twelve PRs merged to main after #2015 and ship in 5.1.0: #2003, #2004,
#2006, #2007, #2008, #2011, #2014, #2016, #2017, #2022, #2030 (fixes,
grouped under Go, JVM, NuGet, Composer, PyPI, npm family and Vendoring)
and #2001 (design doc, Maintenance).

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants