@@ -320,7 +320,8 @@ func (adapter *profileAdapter) PlanInstall(
320320 return AdapterPlan {Harness : adapter .ID ()}, findings
321321 }
322322 for _ , file := range inspection .Files {
323- if file .State != "missing" {
323+ expected := adapterFileMap (candidate .Files )[file .Path ]
324+ if file .State != "missing" && (file .State != "regular" || file .Digest != expected .Digest || ! ownedByOtherAdapter (targetRoot , adapter .ID (), file .Path , file .Digest )) {
324325 return AdapterPlan {Harness : adapter .ID ()}, []domain.Finding {harnessFinding (
325326 "GDS_HARNESS_INSTALL_COLLISION" ,
326327 "Install refuses to replace an existing managed-path candidate; use update or resolve the collision." ,
@@ -386,6 +387,17 @@ func (adapter *profileAdapter) PlanRemove(
386387 if len (findings ) != 0 {
387388 return AdapterPlan {Harness : adapter .ID ()}, findings
388389 }
390+ keptFiles := make ([]AdapterFile , 0 , len (candidate .Files ))
391+ keptContents := map [string ][]byte {}
392+ for _ , file := range candidate .Files {
393+ if ownedByOtherAdapter (targetRoot , adapter .ID (), file .Path , file .Digest ) {
394+ continue
395+ }
396+ keptFiles = append (keptFiles , file )
397+ keptContents [file .Path ] = candidate .contents [file .Path ]
398+ }
399+ candidate .Files = keptFiles
400+ candidate .contents = keptContents
389401 return adapter .buildPlan ("remove" , targetRoot , "" , candidate , AdapterCandidate {}, inspection .Fingerprint )
390402}
391403
@@ -409,6 +421,15 @@ func (adapter *profileAdapter) planTransition(
409421 }
410422 previousPaths := adapterFileMap (previous .Files )
411423 desiredPaths := adapterFileMap (desired .Files )
424+ for _ , file := range desired .Files {
425+ if otherDigest , owned := otherAdapterDigest (targetRoot , adapter .ID (), file .Path ); owned && otherDigest != file .Digest {
426+ return AdapterPlan {Harness : adapter .ID ()}, []domain.Finding {harnessFinding (
427+ "GDS_HARNESS_UPDATE_COLLISION" ,
428+ "Transition would change a path still owned by another installed adapter." ,
429+ map [string ]any {"harness" : adapter .ID (), "path" : file .Path },
430+ )}
431+ }
432+ }
412433 for _ , file := range inspection .Files {
413434 if _ , wasManaged := previousPaths [file .Path ]; wasManaged {
414435 continue
@@ -497,6 +518,37 @@ func adapterFileMap(files []AdapterFile) map[string]AdapterFile {
497518 return result
498519}
499520
521+ func ownedByOtherAdapter (targetRoot , currentHarness , targetPath , digest string ) bool {
522+ otherDigest , found := otherAdapterDigest (targetRoot , currentHarness , targetPath )
523+ return found && otherDigest == digest
524+ }
525+
526+ func otherAdapterDigest (targetRoot , currentHarness , targetPath string ) (string , bool ) {
527+ entries , err := os .ReadDir (filepath .Join (targetRoot , ".gds" , "harness" ))
528+ if err != nil {
529+ return "" , false
530+ }
531+ for _ , entry := range entries {
532+ if entry .IsDir () || ! strings .HasSuffix (entry .Name (), ".lock.json" ) {
533+ continue
534+ }
535+ raw , err := os .ReadFile (filepath .Join (targetRoot , ".gds" , "harness" , entry .Name ()))
536+ if err != nil || len (raw ) > maxAdapterSourceBytes {
537+ continue
538+ }
539+ var lock adapterLock
540+ if json .Unmarshal (raw , & lock ) != nil || lock .Harness == currentHarness {
541+ continue
542+ }
543+ for _ , file := range lock .Files {
544+ if file .Path == targetPath {
545+ return file .Digest , true
546+ }
547+ }
548+ }
549+ return "" , false
550+ }
551+
500552func (adapter * profileAdapter ) inspectCandidate (
501553 targetRoot string ,
502554 candidate AdapterCandidate ,
0 commit comments