private/RestorePoint.ps1

function New-OctaRestorePoint {
    <#
        Constitution Principle I: attempts a System Restore Point before the first applied
        category each run. Checkpoint-Computer silently no-ops when throttled by Windows'
        24h SystemRestorePointCreationFrequency, so the only reliable way to know whether a
        *new* point actually appeared is to compare Get-ComputerRestorePoint before/after
        (see research.md). Never throws - always returns a status the caller can act on.
    #>

    [CmdletBinding()]
    param(
        [switch]$NoRestorePoint,
        [string]$Description = 'Octa debloat run'
    )

    if ($NoRestorePoint) {
        return [pscustomobject]@{ Status = 'SkippedByUser'; SequenceNumber = $null }
    }

    $before = @(Get-ComputerRestorePoint -ErrorAction SilentlyContinue |
        Select-Object -ExpandProperty SequenceNumber)

    try {
        Checkpoint-Computer -Description $Description -RestorePointType MODIFY_SETTINGS -ErrorAction Stop
    }
    catch {
        # Checkpoint-Computer throws when System Protection is off for the target volume.
        return [pscustomobject]@{ Status = 'SkippedProtectionDisabled'; SequenceNumber = $null }
    }

    $after = @(Get-ComputerRestorePoint -ErrorAction SilentlyContinue)
    $newPoint = $after | Where-Object { $_.SequenceNumber -notin $before } | Select-Object -Last 1

    if ($newPoint) {
        return [pscustomobject]@{ Status = 'Created'; SequenceNumber = $newPoint.SequenceNumber }
    }

    # No error, but no new point appeared -> the 24h creation-frequency throttle suppressed it.
    return [pscustomobject]@{ Status = 'SkippedThrottled'; SequenceNumber = $null }
}

function Invoke-OctaSessionRestorePointGate {
    <#
        010: centralizes the restore-point decision to once per Invoke-Octa session, immediately
        after language resolves and before the main menu first renders - instead of once per
        applied category (Invoke-OctaCategory's own restore-point block, still used by
        non-interactive callers per spec FR-009). Windows' own 24h SystemRestorePointCreationFrequency
        already makes a second same-day attempt a harmless no-op, so re-prompting "proceed without
        a restore point?" for every category in the same session was pure repeated friction with
        no new information - see specs/010-quick-assist-restore-point/research.md.
 
        Returns $true if the interactive session should proceed to the main menu, $false if the
        user declined to continue without a fresh restore point.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]$Strings
    )

    Write-Host $Strings.restorePointSessionIntro
    $restorePoint = New-OctaRestorePoint -Description 'Octa session'
    $warningShown = $false

    if ($restorePoint.Status -eq 'SkippedProtectionDisabled') {
        Write-Host $Strings.restorePointSkippedProtectionDisabled
        $warningShown = $true
        if (Test-OctaInteractiveConsole) {
            $enableResponse = Read-Host 'Enable System Protection and create a restore point now? (y/N)'
            if ($enableResponse -in @('y', 'Y', 's', 'S')) {
                try {
                    Enable-ComputerRestore -Drive "$env:SystemDrive\" -ErrorAction Stop
                    $restorePoint = New-OctaRestorePoint -Description 'Octa session'
                }
                catch {
                    Write-Host "Could not enable System Protection automatically: $($_.Exception.Message)"
                }
            }
        }
    }
    elseif ($restorePoint.Status -eq 'Created') {
        Write-Host $Strings.restorePointCreated
    }

    if ($restorePoint.Status -in @('SkippedThrottled', 'SkippedProtectionDisabled')) {
        if (-not $warningShown) {
            Write-Host $Strings.restorePointSkippedThrottled
        }
        if (-not (Test-OctaInteractiveConsole)) {
            return $true
        }
        $proceedResponse = Read-Host $Strings.confirmPrompt
        return ($proceedResponse -in @('y', 'Y', 's', 'S'))
    }

    return $true
}