@@ -330,6 +330,19 @@ function Get-PSModulePublicationReader {
330330 }
331331 $response.Value.object
332332 }.GetNewClosure()
333+ GetVersionReference = {
334+ param ($Name , $Version )
335+ $tag = Get-PSModulePublicationVersionTag - ModuleName $Name - GalleryVersion $Version
336+ $encodedTag = [Uri ]::EscapeDataString($tag )
337+ $response = Invoke-PSModulePublicationApi - Endpoint " repos/$Repository /git/ref/tags/$encodedTag " - AllowMissing
338+ if ($null -eq $response ) {
339+ return
340+ }
341+ if ($response.Value.ref -cne " refs/tags/$tag " -or $null -eq $response.Value.object ) {
342+ throw " Cannot verify the shared module/version ownership coordinate [$tag ]."
343+ }
344+ $response.Value.object
345+ }.GetNewClosure()
333346 GetTagObject = {
334347 param ($Sha )
335348 $response = (Invoke-PSModulePublicationApi - Endpoint " repos/$Repository /git/tags/$Sha " ).Value
@@ -391,7 +404,11 @@ function Get-PSModulePublicationState {
391404 [hashtable ] $Reader
392405 )
393406
394- foreach ($name in @ (' GetSource' , ' GetTagReference' , ' GetTagObject' , ' GetRelease' , ' GetPackage' , ' GetPackageArtifactDigest' )) {
407+ $readerNames = @ (
408+ ' GetSource' , ' GetTagReference' , ' GetVersionReference' , ' GetTagObject' ,
409+ ' GetRelease' , ' GetPackage' , ' GetPackageArtifactDigest'
410+ )
411+ foreach ($name in $readerNames ) {
395412 if ($Reader [$name ] -isnot [scriptblock ]) {
396413 throw " The publication reader [$name ] is missing."
397414 }
@@ -425,25 +442,59 @@ function Get-PSModulePublicationState {
425442 throw " Release tag [$ ( $expected.ReleaseTag ) ] belongs to [$tagCommit ], not source [$ ( $expected.CommitSha ) ]."
426443 }
427444 $claim = Get-PSModulePublicationClaim - Message $tagEvidence.Annotation.message
445+ $versionTag = Get-PSModulePublicationVersionTag - ModuleName $expected.ModuleName - GalleryVersion $expected.GalleryVersion
446+ $versionEvidence = @ { Reference = $null ; Annotation = $null }
447+ $versionParameters = @ {
448+ Tag = $versionTag
449+ AllowMissing = $true
450+ GetTagReference = {
451+ param ($Tag )
452+ if ($Tag -cne $versionTag ) { throw ' Unexpected package ownership coordinate.' }
453+ $versionEvidence.Reference = & $Reader.GetVersionReference $expected.ModuleName $expected.GalleryVersion
454+ $versionEvidence.Reference
455+ }.GetNewClosure()
456+ GetTagObject = {
457+ param ($Sha )
458+ $annotation = & $Reader.GetTagObject $Sha
459+ if ($null -eq $versionEvidence.Annotation ) { $versionEvidence.Annotation = $annotation }
460+ $annotation.object
461+ }.GetNewClosure()
462+ }
463+ $versionCommit = Resolve-PSModuleReleaseTagCommit @versionParameters
464+ if ($null -ne $versionCommit -and $versionCommit -cne $expected.CommitSha ) {
465+ throw " Module/version coordinate [$versionTag ] belongs to another source [$versionCommit ]."
466+ }
467+ $versionClaim = Get-PSModulePublicationClaim - Message $versionEvidence.Annotation.message
428468 $release = & $Reader.GetRelease $expected.ReleaseTag
429469 $package = & $Reader.GetPackage $expected.ModuleName $expected.GalleryVersion
430470 if ($null -eq $tagCommit ) {
431471 if ($null -ne $release -or $null -ne $package ) {
432472 throw " Existing release or Gallery version [$ ( $expected.GalleryVersion ) ] has no verifiable source/artifact ownership proof."
433473 }
434- } else {
435- if ($null -eq $claim -or $tagEvidence.Annotation.tag -cne $expected.ReleaseTag ) {
436- throw " The existing tag [$ ( $expected.ReleaseTag ) ] has no verifiable publication ownership proof."
437- }
438- if ($claim.Repository -ine $expected.Repository -or $claim.PullRequest -ne $expected.PullRequest ) {
474+ } elseif ($null -eq $claim -or $tagEvidence.Annotation.tag -cne $expected.ReleaseTag ) {
475+ throw " The existing tag [$ ( $expected.ReleaseTag ) ] has no verifiable publication ownership proof."
476+ }
477+ if ($null -ne $versionCommit -and
478+ ($null -eq $versionClaim -or $versionEvidence.Annotation.tag -cne $expected.ReleaseTag )) {
479+ throw " The module/version coordinate [$versionTag ] has no verifiable publication proof."
480+ }
481+ if ($null -ne $package -and $null -eq $versionCommit ) {
482+ throw " The existing Gallery package [$ ( $expected.GalleryVersion ) ] has no shared module/version ownership proof."
483+ }
484+ if ($null -ne $tagCommit -and $null -ne $versionCommit -and
485+ $tagEvidence.Reference.sha -cne $versionEvidence.Reference.sha ) {
486+ throw ' The release tag and module/version coordinate reference different immutable claims.'
487+ }
488+ foreach ($proof in @ ($claim , $versionClaim ) | Where-Object { $null -ne $_ }) {
489+ if ($proof.Repository -ine $expected.Repository -or $proof.PullRequest -ne $expected.PullRequest ) {
439490 throw ' The publication claim belongs to a different repository or producing pull request.'
440491 }
441- foreach ($name in @ (' CommitSha' , ' ModuleName' , ' GalleryVersion' , ' ReleaseTag' , ' ReleaseType' , ' AssetName' )) {
442- if ($claim .$name -cne $expected .$name ) {
492+ foreach ($name in @ (' CommitSha' , ' ModuleName' , ' GalleryVersion' , ' VersionPrefix ' , ' ReleaseTag' , ' ReleaseType' , ' AssetName' )) {
493+ if ($proof .$name -cne $expected .$name ) {
443494 throw " The publication claim has conflicting [$name ] ownership."
444495 }
445496 }
446- if ($claim .ArtifactSha256 -cne $expected.ArtifactSha256 -or $claim .AssetSha256 -cne $expected.AssetSha256 ) {
497+ if ($proof .ArtifactSha256 -cne $expected.ArtifactSha256 -or $proof .AssetSha256 -cne $expected.AssetSha256 ) {
447498 throw ' The claimed version belongs to different tested artifact or release asset contents.'
448499 }
449500 }
@@ -487,10 +538,11 @@ function Get-PSModulePublicationState {
487538 }
488539 }
489540 [pscustomobject ]@ {
490- TagSha = $tagEvidence.Reference.sha
491- Claim = $claim
492- Release = $release
493- Package = $package
541+ TagSha = $tagEvidence.Reference.sha
542+ VersionTagSha = $versionEvidence.Reference.sha
543+ Claim = if ($null -ne $versionClaim ) { $versionClaim } else { $claim }
544+ Release = $release
545+ Package = $package
494546 }
495547}
496548
@@ -538,35 +590,61 @@ function New-PSModulePublicationTag {
538590 if ($null -ne $State.TagSha -and $current.TagSha -cne $State.TagSha ) {
539591 throw ' The existing publication tag changed or disappeared after preflight. Refusing Gallery publication.'
540592 }
541- if ($null -ne $current.TagSha ) {
593+ if ($null -ne $State.VersionTagSha -and $current.VersionTagSha -cne $State.VersionTagSha ) {
594+ throw ' The shared module/version claim changed or disappeared after preflight. Refusing publication.'
595+ }
596+ if ($null -ne $current.TagSha -and $null -ne $current.VersionTagSha ) {
542597 return $current
543598 }
544- if (-not $PSCmdlet.ShouldProcess (" $ ( $Claim.Repository ) :$ ( $Claim.ReleaseTag ) " , ' Create source-bound annotated version tag' )) {
599+ if (-not $PSCmdlet.ShouldProcess (" $ ( $Claim.Repository ) :$ ( $Claim.ModuleName ) :$ ( $Claim.GalleryVersion ) " ,
600+ ' Create shared module/version ownership and immutable release tag' )) {
545601 return
546602 }
547- $encoded = [Convert ]::ToBase64String([Text.Encoding ]::UTF8.GetBytes(($Claim | ConvertTo-Json - Compress)))
548- $message = " <!-- PSModule.Publication:v1:$encoded -->"
549- $tagFields = @ {
550- tag = $Claim.ReleaseTag
551- message = $message
552- object = $Claim.CommitSha
553- type = ' commit'
554- }
555- $tag = (Invoke-PSModulePublicationApi - Endpoint " repos/$ ( $Claim.Repository ) /git/tags" `
556- - Method POST - Field $tagFields ).Value
557- if ($tag.sha -notmatch ' ^[0-9a-f]{40}$' -or $tag.tag -cne $Claim.ReleaseTag -or
558- $tag.object.type -cne ' commit' -or $tag.object.sha -cne $Claim.CommitSha -or $tag.message -cne $message ) {
559- throw ' The create-only annotated tag response did not preserve the exact publication claim.'
560- }
561- $reference = Invoke-PSModulePublicationApi - Endpoint " repos/$ ( $Claim.Repository ) /git/refs" `
562- - Method POST - Field @ { ref = " refs/tags/$ ( $Claim.ReleaseTag ) " ; sha = $tag.sha } - AllowConflict
563- if ($reference.StatusCode -ne 201 -and $reference.Value.message -cne ' Reference already exists' ) {
564- throw " Cannot atomically claim release tag [$ ( $Claim.ReleaseTag ) ] (HTTP $ ( $reference.StatusCode ) )."
603+ $claimSha = $current.VersionTagSha
604+ if ($null -eq $claimSha ) {
605+ $claimSha = $current.TagSha
565606 }
566- $current = Get-PSModulePublicationState - ExpectedClaim $Claim - Reader $Reader
567- if ($null -eq $current.TagSha -or
568- ($reference.StatusCode -eq 201 -and $current.TagSha -cne $tag.sha )) {
569- throw ' The publication tag changed during the create-only claim. Refusing Gallery publication.'
607+ if ($null -eq $claimSha ) {
608+ $encoded = [Convert ]::ToBase64String([Text.Encoding ]::UTF8.GetBytes(($Claim | ConvertTo-Json - Compress)))
609+ $message = " <!-- PSModule.Publication:v1:$encoded -->"
610+ $tagFields = @ {
611+ tag = $Claim.ReleaseTag
612+ message = $message
613+ object = $Claim.CommitSha
614+ type = ' commit'
615+ }
616+ $tag = (Invoke-PSModulePublicationApi - Endpoint " repos/$ ( $Claim.Repository ) /git/tags" `
617+ - Method POST - Field $tagFields ).Value
618+ if ($tag.sha -notmatch ' ^[0-9a-f]{40}$' -or $tag.tag -cne $Claim.ReleaseTag -or
619+ $tag.object.type -cne ' commit' -or $tag.object.sha -cne $Claim.CommitSha -or $tag.message -cne $message ) {
620+ throw ' The annotated object did not preserve the exact source/context/artifact claim.'
621+ }
622+ $claimSha = $tag.sha
623+ }
624+ if ($null -eq $current.VersionTagSha ) {
625+ $versionTag = Get-PSModulePublicationVersionTag - ModuleName $Claim.ModuleName - GalleryVersion $Claim.GalleryVersion
626+ $reference = Invoke-PSModulePublicationApi - Endpoint " repos/$ ( $Claim.Repository ) /git/refs" `
627+ - Method POST - Field @ { ref = " refs/tags/$versionTag " ; sha = $claimSha } - AllowConflict
628+ if ($reference.StatusCode -ne 201 -and $reference.Value.message -cne ' Reference already exists' ) {
629+ throw " Cannot atomically reserve module/version [$versionTag ] (HTTP $ ( $reference.StatusCode ) )."
630+ }
631+ $current = Get-PSModulePublicationState - ExpectedClaim $Claim - Reader $Reader
632+ if ($null -eq $current.VersionTagSha -or
633+ ($reference.StatusCode -eq 201 -and $current.VersionTagSha -cne $claimSha )) {
634+ throw ' The module/version coordinate changed during its create-only claim. Refusing publication.'
635+ }
636+ $claimSha = $current.VersionTagSha
637+ }
638+ if ($null -eq $current.TagSha ) {
639+ $reference = Invoke-PSModulePublicationApi - Endpoint " repos/$ ( $Claim.Repository ) /git/refs" `
640+ - Method POST - Field @ { ref = " refs/tags/$ ( $Claim.ReleaseTag ) " ; sha = $claimSha } - AllowConflict
641+ if ($reference.StatusCode -ne 201 -and $reference.Value.message -cne ' Reference already exists' ) {
642+ throw " Cannot atomically create release tag [$ ( $Claim.ReleaseTag ) ] (HTTP $ ( $reference.StatusCode ) )."
643+ }
644+ }
645+ $verified = Get-PSModulePublicationState - ExpectedClaim $Claim - Reader $Reader
646+ if ($verified.TagSha -cne $claimSha -or $verified.VersionTagSha -cne $claimSha ) {
647+ throw ' The release tag or shared module/version coordinate changed before publication.'
570648 }
571- $current
649+ $verified
572650}
0 commit comments