@@ -49,6 +49,10 @@ Describe 'Gallery-only version selection requires original ownership proof' {
4949 ref = ' refs/tags/v1.2.4'
5050 object = [pscustomobject ]@ { type = ' tag' ; sha = ' 3333333333333333333333333333333333333333' }
5151 }
52+ VersionReference = [pscustomobject ]@ {
53+ ref = ' refs/tags/PSModule-Publication/testmodule/1.2.4'
54+ object = [pscustomobject ]@ { type = ' tag' ; sha = ' 3333333333333333333333333333333333333333' }
55+ }
5256 Annotation = [pscustomobject ]@ {
5357 sha = ' 3333333333333333333333333333333333333333'
5458 tag = ' v1.2.4'
@@ -61,11 +65,13 @@ Describe 'Gallery-only version selection requires original ownership proof' {
6165 }
6266 $state = @ {
6367 Record = $record
68+ Records = [Collections.Generic.List [object ]]::new()
6469 Claims = @ ($record.Reference )
6570 Calls = [Collections.Generic.List [string ]]::new()
6671 ModulePath = $modulePath
6772 Claim = $claim
6873 }
74+ $state.Records.Add ($record )
6975 $script :state = $state
7076 $reader = @ {
7177 GetSource = {
@@ -80,36 +86,53 @@ Describe 'Gallery-only version selection requires original ownership proof' {
8086 GetTagReference = {
8187 param ($Tag )
8288 $state.Calls.Add (" tag-read:$Tag " )
83- if ($Tag -ceq $state.Record.Annotation.tag ) { $state.Record.Reference.object }
89+ foreach ($item in $state.Records ) {
90+ if ($Tag -ceq $item.Annotation.tag ) { return $item.Reference.object }
91+ }
8492 }.GetNewClosure()
8593 GetVersionReference = {
8694 param ($Name , $Version )
87- if ($Name -cne ' TestModule' -or $Version -cne ' 1.2.4' ) {
88- throw ' Unexpected module/version coordinate lookup.'
89- }
95+ if ($Name -cne ' TestModule' ) { throw ' Unexpected module/version coordinate lookup.' }
9096 $state.Calls.Add (' module-version-coordinate-read' )
91- $state.Record.Reference.object
97+ foreach ($item in $state.Records ) {
98+ if ($item.VersionReference.ref -ceq " refs/tags/PSModule-Publication/testmodule/$Version " ) {
99+ return $item.VersionReference.object
100+ }
101+ }
92102 }.GetNewClosure()
93103 GetTagObject = {
94104 param ($Sha )
95- if ($Sha -cne $state.Record.Annotation.sha ) { throw ' Unexpected annotated tag lookup.' }
96- $state.Record.Annotation
105+ foreach ($item in $state.Records ) {
106+ if ($Sha -ceq $item.Annotation.sha ) { return $item.Annotation }
107+ }
108+ throw ' Unexpected annotated tag lookup.'
97109 }.GetNewClosure()
98110 GetRelease = {
99111 param ($Tag )
100112 $state.Calls.Add (" release-read:$Tag " )
101- if ($Tag -ceq $state.Record.Annotation.tag ) { $state.Record.Release }
113+ foreach ($item in $state.Records ) {
114+ if ($Tag -ceq $item.Annotation.tag ) { return $item.Release }
115+ }
102116 }.GetNewClosure()
103117 GetPackage = {
104118 param ($Name , $Version )
105119 $state.Calls.Add (" package-read:$Version " )
106120 if ($Name -cne ' TestModule' ) { throw ' Unexpected module lookup.' }
107- if ($Version -ceq ' 1.2.4' ) { $state.Record.Package }
121+ foreach ($item in $state.Records ) {
122+ if ($item.VersionReference.ref -ceq " refs/tags/PSModule-Publication/testmodule/$Version " ) {
123+ return $item.Package
124+ }
125+ }
108126 }.GetNewClosure()
109127 GetPackageArtifactDigest = {
110128 param ($Name , $Version )
111129 $state.Calls.Add (" raw-package-read:$Version " )
112- Get-PSModulePackageArtifactDigest - Path $state.Record.PackagePath - ModuleName $Name - GalleryVersion $Version
130+ foreach ($item in $state.Records ) {
131+ if ($item.VersionReference.ref -ceq " refs/tags/PSModule-Publication/testmodule/$Version " ) {
132+ return Get-PSModulePackageArtifactDigest - Path $item.PackagePath - ModuleName $Name - GalleryVersion $Version
133+ }
134+ }
135+ throw ' Unexpected raw package lookup.'
113136 }.GetNewClosure()
114137 }
115138 $script :parameters = @ {
@@ -154,6 +177,54 @@ Describe 'Gallery-only version selection requires original ownership proof' {
154177 (Get-ResolvedModuleVersion @script :parameters ).ToString() | Should - BeExactly ' v1.2.4'
155178 }
156179
180+ It ' deduplicates shared and release refs for one exact original publication' {
181+ $script :state.Claims = @ ($script :state.Record.Reference , $script :state.Record.VersionReference )
182+
183+ (Get-ResolvedModuleVersion @script :parameters ).ToString() | Should - BeExactly ' v1.2.4'
184+ }
185+
186+ It ' rejects an unpublished shared-only claim before a later completed baseline can select a new version' {
187+ $original = $script :state.Record
188+ $original.Reference = $null
189+ $original.Package = $null
190+ $later = [pscustomobject ]@ {
191+ Reference = [pscustomobject ]@ {
192+ ref = ' refs/tags/v1.2.5'
193+ object = [pscustomobject ]@ { type = ' tag' ; sha = ' 4444444444444444444444444444444444444444' }
194+ }
195+ VersionReference = [pscustomobject ]@ {
196+ ref = ' refs/tags/PSModule-Publication/testmodule/1.2.5'
197+ object = [pscustomobject ]@ { type = ' tag' ; sha = ' 4444444444444444444444444444444444444444' }
198+ }
199+ Annotation = [pscustomobject ]@ {
200+ sha = ' 4444444444444444444444444444444444444444' ; tag = ' v1.2.5' ; message = ' Completed later legacy baseline.'
201+ object = [pscustomobject ]@ { type = ' commit' ; sha = ' 2222222222222222222222222222222222222222' }
202+ }
203+ Release = [pscustomobject ]@ {
204+ tag_name = ' v1.2.5' ; prerelease = $false ; draft = $false
205+ assets = @ ([pscustomobject ]@ { name = ' TestModule-1.2.5.zip' ; state = ' uploaded' })
206+ }
207+ }
208+ $script :state.Records.Add ($later )
209+ $script :state.Claims = @ ($original.VersionReference , $later.Reference )
210+ $script :parameters.GitHubVersion = New-PSSemVer - Version ' 1.2.5'
211+ $script :parameters.PSGalleryVersion = New-PSSemVer - Version ' 1.2.5'
212+ Mock - CommandName Get-NextModuleVersion - ModuleName Resolve-PSModuleVersion .Helpers - MockWith {
213+ New-PSSemVer - Version ' v1.2.6'
214+ }
215+
216+ { Get-ResolvedModuleVersion @script :parameters } | Should - Throw ' *original source claim*no published raw package proof*'
217+
218+ Should - Invoke - CommandName Get-NextModuleVersion - ModuleName Resolve-PSModuleVersion .Helpers - Times 0 - Exactly
219+ }
220+
221+ It ' rejects a shared-only published package whose observed release tag is missing' {
222+ $script :state.Record.Reference = $null
223+ $script :state.Claims = @ ($script :state.Record.VersionReference )
224+
225+ { Get-ResolvedModuleVersion @script :parameters } | Should - Throw ' *no verifiable source/artifact ownership proof*'
226+ }
227+
157228 It ' rejects a Gallery-only version with unavailable legacy proof instead of inventing a fallback' {
158229 $script :state.Record.Annotation.message = ' Legacy tag without artifact proof.'
159230
@@ -186,6 +257,48 @@ Describe 'Gallery-only version selection requires original ownership proof' {
186257 (Get-ResolvedModuleVersion @script :parameters ).ToString() | Should - BeExactly ' v1.2.5'
187258 }
188259
260+ It ' uses the actual completed historical tag when the new source changes its prefix' {
261+ $script :state.Record.Annotation.object.sha = ' 2222222222222222222222222222222222222222'
262+ $script :state.Record.Annotation.message = ' Completed legacy tag.'
263+ $script :state.Record.Release = [pscustomobject ]@ {
264+ tag_name = ' v1.2.4' ; prerelease = $false ; draft = $false
265+ assets = @ ([pscustomobject ]@ { name = ' TestModule-1.2.4.zip' ; state = ' uploaded' })
266+ }
267+ $script :parameters.Configuration.VersionPrefix = ' release-'
268+
269+ (Get-ResolvedModuleVersion @script :parameters ).ToString() | Should - BeExactly ' release-1.2.5'
270+
271+ $script :state.Calls | Should - Contain ' release-read:v1.2.4'
272+ $script :state.Calls | Should -Not - Contain ' release-read:release-1.2.4'
273+ }
274+
275+ It ' rejects ambiguous completed historical baseline tags instead of choosing a prefix' {
276+ $script :state.Record.Annotation.object.sha = ' 2222222222222222222222222222222222222222'
277+ $script :state.Record.Annotation.message = ' Completed legacy tag.'
278+ $script :state.Record.Release = [pscustomobject ]@ {
279+ tag_name = ' v1.2.4' ; prerelease = $false ; draft = $false
280+ assets = @ ([pscustomobject ]@ { name = ' TestModule-1.2.4.zip' ; state = ' uploaded' })
281+ }
282+ $other = [pscustomobject ]@ {
283+ Reference = [pscustomobject ]@ {
284+ ref = ' refs/tags/release-1.2.4'
285+ object = [pscustomobject ]@ { type = ' tag' ; sha = ' 4444444444444444444444444444444444444444' }
286+ }
287+ Annotation = [pscustomobject ]@ {
288+ sha = ' 4444444444444444444444444444444444444444' ; tag = ' release-1.2.4' ; message = ' Another completed legacy baseline.'
289+ object = [pscustomobject ]@ { type = ' commit' ; sha = ' 2222222222222222222222222222222222222222' }
290+ }
291+ Release = [pscustomobject ]@ {
292+ tag_name = ' release-1.2.4' ; prerelease = $false ; draft = $false
293+ assets = @ ([pscustomobject ]@ { name = ' TestModule-1.2.4.zip' ; state = ' uploaded' })
294+ }
295+ }
296+ $script :state.Records.Add ($other )
297+ $script :state.Claims = @ ($script :state.Record.Reference , $other.Reference )
298+
299+ { Get-ResolvedModuleVersion @script :parameters } | Should - Throw ' *ambiguous*baseline*'
300+ }
301+
189302 It ' rejects changed package bytes despite matching source, version, and PR' {
190303 [IO.File ]::WriteAllText(
191304 (Join-Path - Path $script :state.ModulePath - ChildPath ' TestModule.psm1' ), ' # Different package payload.'
0 commit comments