diff --git a/crates/socket-patch-cli/tests/vendor_jvm_cli.rs b/crates/socket-patch-cli/tests/vendor_jvm_cli.rs
index bd75dd126..18b749a51 100644
--- a/crates/socket-patch-cli/tests/vendor_jvm_cli.rs
+++ b/crates/socket-patch-cli/tests/vendor_jvm_cli.rs
@@ -1153,6 +1153,90 @@ fn gradle_vendor_533_unpatched_classifier_copy_withholds_vex() {
assert!(!vex.exists(), "{env}");
}
+/// #513 / #459: a reactor `${prop}` version that can't be pinned with
+/// certainty (undefined in the checkout, or raised by a profile) is left
+/// as is with a warning, and `vex` does not attest the package the build
+/// still takes unpatched.
+#[test]
+fn maven_reactor_uncertain_property_version_withholds_vex() {
+ let cases = [
+ ("", "property_unresolved"),
+ (
+ "1.0\
+ new1.1\
+ ",
+ "conflicting_literal_version",
+ ),
+ ];
+ // With a sibling module at the literal base, that module is rewritten
+ // and the patch is wired, yet `a` still builds the base.
+ for ((extra, reason), sibling) in cases
+ .into_iter()
+ .flat_map(|case| [(case, false), (case, true)])
+ {
+ let tmp = tempfile::tempdir().unwrap();
+ let root = tmp.path();
+ fixture(root, Shape::Reactor, &[("foo", FOO_UUID)]);
+ let pristine = std::fs::read_to_string(root.join("proj/a/pom.xml")).unwrap();
+ let a = pristine
+ .replace(
+ "1.0",
+ "${foo.version}",
+ )
+ .replace(
+ "a",
+ &format!("a{extra}"),
+ );
+ write(root, "proj/a/pom.xml", a.as_bytes());
+ if sibling {
+ let agg = std::fs::read_to_string(root.join("proj/pom.xml")).unwrap();
+ let agg = agg.replace(
+ "a",
+ "a\n b",
+ );
+ write(root, "proj/pom.xml", agg.as_bytes());
+ std::fs::create_dir_all(root.join("proj/b")).unwrap();
+ let b = pristine.replace("a", "b");
+ write(root, "proj/b/pom.xml", b.as_bytes());
+ }
+ let env = ok(root, &["vendor"]);
+ assert!(
+ env.to_string().contains(&format!("reason: {reason}")),
+ "{env}"
+ );
+ assert_eq!(
+ std::fs::read_to_string(root.join("proj/a/pom.xml")).unwrap(),
+ a
+ );
+ assert_eq!(
+ wiring_text(root).contains("-socket."),
+ sibling,
+ "{reason}: wired"
+ );
+ let vex = root.join("vex.json");
+ let (_, env) = socket(
+ root,
+ &[
+ "vex",
+ "-O",
+ vex.to_str().unwrap(),
+ "--product",
+ "pkg:generic/x@1",
+ ],
+ );
+ assert_eq!(
+ events(&env),
+ [(
+ purl("foo"),
+ "skipped".to_string(),
+ "vendor_unwired".to_string()
+ )],
+ "{reason} (sibling: {sibling}): {env}"
+ );
+ assert!(!vex.exists(), "{env}");
+ }
+}
+
/// #429 repair: a deleted owned `.gitattributes` (and nothing else) is
/// rewritten by `repair` (no download), so the next autocrlf clone keeps the
/// script and index LF.
diff --git a/crates/socket-patch-core/src/vendor/jvm/apply.rs b/crates/socket-patch-core/src/vendor/jvm/apply.rs
index e9d17f762..212a39a50 100644
--- a/crates/socket-patch-core/src/vendor/jvm/apply.rs
+++ b/crates/socket-patch-core/src/vendor/jvm/apply.rs
@@ -830,10 +830,11 @@ pub fn entry_references(root: &Path, entry: &VendorEntry) -> bool {
referenced || reader.read_error.borrow().is_some()
}
-/// The liveness proof `vex` needs: every half of the entry is wired, and
-/// a Gradle half's script is intact and re-plans with no refusal and no
+/// The liveness proof `vex` needs: every half of the entry is wired, a
+/// Gradle half's script is intact and re-plans with no refusal and no
/// degraded warning (a conflicting rule or unchecked build logic added
-/// since vendoring withholds attestation).
+/// since vendoring withholds attestation), and no Maven declaration is
+/// left at a version that may still read the base.
pub fn entry_wired_checked(root: &Path, entry: &VendorEntry) -> Result {
let (g, a, v) = entry_gav(entry)?;
let c = Coords {
@@ -856,7 +857,7 @@ pub fn entry_wired_checked(root: &Path, entry: &VendorEntry) -> Result, c: &Coords<'_>) -> Result, c: &Coords<'_>) -> Result {
+ let sv = c.suffixed_version();
+ let reactor = Reactor::discover(read)?;
+ if !reactor
+ .scope
+ .iter()
+ .any(|rel| reactor.poms[rel].doc.masked.contains(&sv))
+ {
+ return Ok(false);
+ }
+ let patch = JvmPatch {
+ group_id: c.group_id,
+ artifact_id: c.artifact_id,
+ version: c.version,
+ uuid: c.uuid,
+ jar: &[],
+ upstream_pom: &[],
+ upstream_module: None,
+ extra_artifacts: &[],
+ patched_members: &[],
+ };
+ let mut uncertain = BTreeSet::new();
+ for rel in &reactor.scope {
+ reactor.rewrite_declarations(
+ rel,
+ &patch,
+ &sv,
+ &mut BTreeMap::new(),
+ &mut Vec::new(),
+ &mut BTreeSet::new(),
+ &mut BTreeSet::new(),
+ &mut uncertain,
+ );
+ }
+ Ok(uncertain.is_empty())
+}
+
/// `