@@ -249,11 +249,10 @@ async fn read_workspace(
249249 go_mod : Option < & str > ,
250250 go_work : & str ,
251251) -> ( Option < Vec < MainModule > > , Vec < String > ) {
252- let mut mains = Some ( Vec :: new ( ) ) ;
252+ let mut mains = Vec :: new ( ) ;
253253 let mut files = vec ! [ GO_WORK_SUM . to_string( ) ] ;
254254 for dir in go_mod_edit:: parse_use_dirs ( go_work) {
255255 let Some ( dir) = member_dir ( & dir) else {
256- mains = None ;
257256 continue ;
258257 } ;
259258 let text = if dir. is_empty ( ) {
@@ -265,17 +264,15 @@ async fn read_workspace(
265264 None => None ,
266265 }
267266 } ;
268- match ( text, mains. as_mut ( ) ) {
269- ( Some ( text) , Some ( mains) ) => mains. push ( MainModule :: new ( & text) ) ,
270- ( Some ( _) , None ) => { }
271- ( None , _) => mains = None ,
267+ if let Some ( text) = text {
268+ mains. push ( MainModule :: new ( & text) ) ;
272269 }
273270 let sum = member_file ( & dir, GO_SUM ) ;
274271 if !files. contains ( & sum) {
275272 files. push ( sum) ;
276273 }
277274 }
278- ( mains, files)
275+ ( Some ( mains) , files)
279276}
280277
281278/// Normalize a go.mod-grammar file (BOM, simple string literals) and check
@@ -357,7 +354,7 @@ fn extract_file(
357354 SumPin :: Zip ( _) => graph. workspace ,
358355 SumPin :: Unpinned => false ,
359356 } ;
360- if check {
357+ let workspace_higher = if check {
361358 let req = required. get ( & entry. module ) . map ( String :: as_str) ;
362359 // `replaced_purl` resolved one of the two.
363360 let version = entry. version . as_deref ( ) . or ( req) . unwrap_or_default ( ) ;
@@ -375,9 +372,23 @@ fn extract_file(
375372 ) ;
376373 continue ;
377374 }
378- }
375+ // In workspace mode, also check go.work.sum for higher versions.
376+ // Those lines outlive the requirement that raised the module, so
377+ // they don't prove selection: emit the ref but don't attest it.
378+ if graph. workspace {
379+ let workspace_zips = sums. workspace_zip_versions ( & entry. module ) ;
380+ workspace_zips
381+ . iter ( )
382+ . any ( |v| go_semver_cmp ( v, version) == Ordering :: Greater )
383+ } else {
384+ false
385+ }
386+ } else {
387+ false
388+ } ;
379389 let ( integrity, integrity_required) = match pin {
380- SumPin :: Zip ( h1) => ( Some ( LockIntegrity :: GoH1 ( h1) ) , true ) ,
390+ SumPin :: Zip ( h1) if !workspace_higher => ( Some ( LockIntegrity :: GoH1 ( h1) ) , true ) ,
391+ SumPin :: Zip ( _) => ( None , true ) ,
381392 SumPin :: GoModOnly => ( None , graph. workspace ) ,
382393 SumPin :: Unpinned => ( None , true ) ,
383394 } ;
@@ -536,6 +547,9 @@ struct SumPins {
536547 unparseable : bool ,
537548 /// Every module's zip-line versions (the build-list evidence).
538549 zip_versions : HashMap < String , Vec < String > > ,
550+ /// go.work.sum zip-line versions: used to detect inert workspace
551+ /// replaces but not as selection proof.
552+ workspace_zip_versions : HashMap < String , Vec < String > > ,
539553}
540554
541555/// What the sum files pin for one Socket `module@version`.
@@ -553,7 +567,7 @@ impl SumPins {
553567 let mut pins = SumPins :: default ( ) ;
554568 for file in files {
555569 if let Some ( text) = ctx. read_text ( file, out) . await {
556- pins. add ( & text, file != GO_WORK_SUM ) ;
570+ pins. add ( & text, file != GO_WORK_SUM , file == GO_WORK_SUM ) ;
557571 }
558572 }
559573 pins
@@ -562,7 +576,9 @@ impl SumPins {
562576 /// `selects`: whether the file's zip lines show what the build selects.
563577 /// go only ever appends to `go.work.sum`, so its lines outlive the
564578 /// requirement that raised a module.
565- fn add ( & mut self , text : & str , selects : bool ) {
579+ /// `workspace_sum`: whether this is go.work.sum (used to detect inert
580+ /// workspace replaces but not as selection proof).
581+ fn add ( & mut self , text : & str , selects : bool , workspace_sum : bool ) {
566582 // go rejects a whole go.sum with any non-blank line of other than
567583 // three tokens (only `go mod tidy` skips it).
568584 if text
@@ -573,11 +589,19 @@ impl SumPins {
573589 return ;
574590 }
575591 for line in go_sum_lines ( text) {
576- if !line. go_mod && selects {
577- self . zip_versions
578- . entry ( line. module . to_string ( ) )
579- . or_default ( )
580- . push ( line. version . to_string ( ) ) ;
592+ if !line. go_mod {
593+ if selects {
594+ self . zip_versions
595+ . entry ( line. module . to_string ( ) )
596+ . or_default ( )
597+ . push ( line. version . to_string ( ) ) ;
598+ }
599+ if workspace_sum {
600+ self . workspace_zip_versions
601+ . entry ( line. module . to_string ( ) )
602+ . or_default ( )
603+ . push ( line. version . to_string ( ) ) ;
604+ }
581605 }
582606 // go ignores hashes of algorithms it does not know.
583607 if !line. module . starts_with ( HOSTED_GO_MODULE_PREFIX ) || !line. hash . starts_with ( "h1:" ) {
@@ -624,6 +648,13 @@ impl SumPins {
624648 . map ( |v| v. iter ( ) . map ( String :: as_str) . collect ( ) )
625649 . unwrap_or_default ( )
626650 }
651+
652+ fn workspace_zip_versions ( & self , module : & str ) -> Vec < & str > {
653+ self . workspace_zip_versions
654+ . get ( module)
655+ . map ( |v| v. iter ( ) . map ( String :: as_str) . collect ( ) )
656+ . unwrap_or_default ( )
657+ }
627658}
628659
629660/// Root-relative directory of a `go.work` `use` member (`""` for the root
0 commit comments