lib/rules/releases/release_should_be_immutable/release_should_be_immutable.ps1

#############################################################################
# Rule: release_should_be_immutable
# Category: releases
# Priority: 12
#############################################################################

# Load shared release helpers
. "$PSScriptRoot/../ReleaseRulesHelper.ps1"

$Rule_ReleaseShouldBeImmutable = [ValidationRule]@{
    Name = "release_should_be_immutable"
    Description = "Published releases for patch versions should be immutable (via repository settings)"
    Priority = 12
    Category = "releases"
    
    Condition = { param([RepositoryState]$State, [hashtable]$Config)
        # Only apply when check-release-immutability is enabled
        $checkImmutability = $Config.'check-release-immutability'
        if ($checkImmutability -ne 'error' -and $checkImmutability -ne 'warning') {
            return @()
        }
        
        # Find all published (non-draft) releases for patch versions
        $publishedReleases = $State.Releases | Where-Object {
            -not $_.IsDraft -and -not $_.IsIgnored
        }
        
        # Filter to only patch versions
        $patchPublishedReleases = $publishedReleases | Where-Object {
            # Parse version to check if it's a patch (vX.Y.Z)
            $tagName = $_.TagName
            if ($tagName -match '^v(\d+)\.(\d+)\.(\d+)') {
                return $true
            }
            return $false
        }
        
        # Exclude releases that are duplicates and will be deleted by duplicate_release rule
        # (rare case: multiple published releases for same tag)
        $duplicateReleaseIds = @()
        $patchReleases = $State.Releases | Where-Object {
            -not $_.IsIgnored -and $_.TagName -match '^v\d+\.\d+\.\d+$'
        }
        $releasesByTag = $patchReleases | Group-Object -Property TagName
        foreach ($group in $releasesByTag) {
            if ($group.Count -gt 1) {
                $releases = $group.Group
                # Sort to find which release to keep (same logic as duplicate_release rule)
                $sortedReleases = $releases | Sort-Object -Property @(
                    @{ Expression = { -not $_.IsDraft }; Descending = $true }
                    @{ Expression = { $_.IsImmutable }; Descending = $true }
                    @{ Expression = { $_.Id }; Ascending = $true }
                )
                # Mark all but the first as duplicates (only drafts can be deleted, but exclude all duplicates from validation)
                $duplicates = $sortedReleases | Select-Object -Skip 1
                $duplicateReleaseIds += $duplicates.Id
            }
        }
        
        # Filter out duplicates
        $patchPublishedReleases = $patchPublishedReleases | Where-Object {
            $_.Id -notin $duplicateReleaseIds
        }
        
        return $patchPublishedReleases
    }
    
    Check = { param([ReleaseInfo]$ReleaseInfo, [RepositoryState]$State, [hashtable]$Config)
        return $ReleaseInfo.IsImmutable
    }
    
    CreateIssue = { param([ReleaseInfo]$ReleaseInfo, [RepositoryState]$State, [hashtable]$Config)
        $version = $ReleaseInfo.TagName

        $severity = if ($Config.'check-release-immutability' -eq 'warning') { 'warning' } else { 'error' }
        $issue = [ValidationIssue]::new(
            "non_immutable_release",
            $severity,
            "Release $version is published but not immutable (repository 'Release immutability' setting may not be enabled)"
        )
        $issue.Version = $version
        
        # RepublishReleaseAction constructor: tagName
        $action = [RepublishReleaseAction]::new($version)

        # Determine if this release should become "latest" when republished
        # If the release is currently marked as latest, preserve that
        # If it's the highest non-prerelease version, it should become latest
        $shouldBeLatest = $ReleaseInfo.IsLatest -or (Test-ShouldBeLatestRelease -State $State -Version $version -ReleaseInfo $ReleaseInfo)
        $action.MakeLatest = $shouldBeLatest

        $issue.RemediationAction = $action
        
        return $issue
    }
}

# Export the rule
$Rule_ReleaseShouldBeImmutable