@@ -345,31 +345,40 @@ fn extract_file(
345345 let Some ( purl) = replaced_purl ( entry, required, & versioned, file, out) else {
346346 continue ;
347347 } ;
348- let ( integrity, integrity_required) = match sums. pin ( rhs, rhs_version) {
349- SumPin :: Zip ( h1) => ( Some ( LockIntegrity :: GoH1 ( h1) ) , true ) ,
350- SumPin :: GoModOnly => {
351- // MVS reads (and go.sum keeps the `/go.mod` line of)
352- // versions it does not select; only a zip line shows
353- // the replaced version is the one built.
354- let req = required. get ( & entry. module ) . map ( String :: as_str) ;
355- // `replaced_purl` resolved one of the two.
356- let version = entry. version . as_deref ( ) . or ( req) . unwrap_or_default ( ) ;
357- let zips = sums. zip_versions ( & entry. module ) ;
358- let selected = graph. select ( & entry. module , version, & zips) ;
359- if let Some ( other) = selected. other_version ( ) {
360- out. diag (
361- DIAG_REF_INVALID ,
362- file,
363- format ! (
364- "{file}: replace {} {version} is inert: the build \
365- selects {other}, so it uses the unpatched module",
366- entry. module
367- ) ,
368- ) ;
369- continue ;
370- }
371- ( None , graph. workspace )
348+ let pin = sums. pin ( rhs, rhs_version) ;
349+ // MVS reads (and go.sum keeps the `/go.mod` line of) versions
350+ // it does not select, so a `/go.mod`-only pin needs proof the
351+ // replaced version is the one built. In a workspace a zip
352+ // line is no proof either: a sum file is only appended to, so
353+ // a member that later raises the module leaves the socket zip
354+ // line in place while go builds the higher version.
355+ let check = match pin {
356+ SumPin :: GoModOnly => true ,
357+ SumPin :: Zip ( _) => graph. workspace ,
358+ SumPin :: Unpinned => false ,
359+ } ;
360+ if check {
361+ let req = required. get ( & entry. module ) . map ( String :: as_str) ;
362+ // `replaced_purl` resolved one of the two.
363+ let version = entry. version . as_deref ( ) . or ( req) . unwrap_or_default ( ) ;
364+ let zips = sums. zip_versions ( & entry. module ) ;
365+ let selected = graph. select ( & entry. module , version, & zips) ;
366+ if let Some ( other) = selected. other_version ( ) {
367+ out. diag (
368+ DIAG_REF_INVALID ,
369+ file,
370+ format ! (
371+ "{file}: replace {} {version} is inert: the build \
372+ selects {other}, so it uses the unpatched module",
373+ entry. module
374+ ) ,
375+ ) ;
376+ continue ;
372377 }
378+ }
379+ let ( integrity, integrity_required) = match pin {
380+ SumPin :: Zip ( h1) => ( Some ( LockIntegrity :: GoH1 ( h1) ) , true ) ,
381+ SumPin :: GoModOnly => ( None , graph. workspace ) ,
373382 SumPin :: Unpinned => ( None , true ) ,
374383 } ;
375384 let url = format ! ( "{rhs}@{rhs_version}" ) ;
@@ -1168,6 +1177,48 @@ mod tests {
11681177 }
11691178 }
11701179
1180+ /// A workspace's sum files are only appended to, so the socket zip line
1181+ /// can outlive the build that wrote it: a member that requires a higher
1182+ /// version makes even a zip-pinned replace inert.
1183+ #[ tokio:: test]
1184+ async fn workspace_zip_pin_is_inert_under_a_higher_member_require ( ) {
1185+ let m = gopatch ( UUID_A ) ;
1186+ let sver = "v1.4.2-socketpatch.1" ;
1187+ for ( member_version, inert) in [ ( VERSION , false ) , ( "v1.4.1" , false ) , ( "v1.5.0" , true ) ] {
1188+ let p = Project :: new ( ) ;
1189+ p. write ( "go.mod" , BASE_GO_MOD )
1190+ . write (
1191+ "go.work" ,
1192+ format ! (
1193+ "go 1.21\n \n use (\n \t .\n \t ./svc\n )\n \n replace {MODULE} {VERSION} => {m} {sver}\n "
1194+ ) ,
1195+ )
1196+ . write (
1197+ "go.work.sum" ,
1198+ format ! ( "{m} {sver} {ZIP_H1}\n {m} {sver}/go.mod {MOD_H1}\n " ) ,
1199+ )
1200+ . write (
1201+ "svc/go.mod" ,
1202+ format ! ( "module example.com/svc\n \n go 1.21\n \n require {MODULE} {member_version}\n " ) ,
1203+ ) ;
1204+ let out = run ( & p) . await ;
1205+ if inert {
1206+ assert_refs ( & out, & [ ] ) ;
1207+ assert_eq ! ( out. diagnostics. len( ) , 1 , "{member_version}" ) ;
1208+ assert_eq ! ( out. diagnostics[ 0 ] . code, DIAG_REF_INVALID ) ;
1209+ assert ! ( out. diagnostics[ 0 ] . detail. contains( "selects v1.5.0" ) ) ;
1210+ } else {
1211+ assert_refs ( & out, & [ ( PURL , UUID_A , WiringMode :: Hosted ) ] ) ;
1212+ assert_eq ! (
1213+ out. refs[ 0 ] . locked_integrity,
1214+ Some ( LockIntegrity :: GoH1 ( ZIP_H1 . to_string( ) ) ) ,
1215+ "{member_version}"
1216+ ) ;
1217+ assert ! ( out. diagnostics. is_empty( ) , "{:?}" , out. diagnostics) ;
1218+ }
1219+ }
1220+ }
1221+
11711222 /// go verifies against the sum files of the mode it builds in: in
11721223 /// workspace mode `go.work.sum` and the members' go.sum (the root's only
11731224 /// when `.` is a member); under `GOWORK=off` the root go.sum alone, with
0 commit comments