scripts/internal/continuous-co-review/review-campaign-orchestrator.ps1

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

# F-198 / T048: synchronous application service over target/harness/runtime/store/clock ports.
# One call performs at most one external invocation; it never schedules background work or retries a
# provider invisibly. Fixture ports are executable foundation proof, not production-support claims.

if (-not (Get-Command -Name 'Invoke-ReviewResultIngress' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'review-result-ingestor.ps1') }
if (-not (Get-Command -Name 'New-GitReviewTargetSnapshot' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'review-target-port.ps1') }
if (-not (Get-Command -Name 'Get-ContinuousCoReviewAuthorityDecision' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'review-authority-cutover.ps1') }
if (-not (Get-Command -Name 'New-ReviewProgressEvent' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'review-progress-projection.ps1') }
if (-not (Get-Command -Name 'Resolve-ContinuousCoReviewDesignContextSelection' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'review-design-context.ps1') }
if (-not (Get-Command -Name 'New-ReviewRunRecoveryFact' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'review-run-reconciler.ps1') }
# HARD dependency: the ONE path-identity primitive. Guarded on a name unique to it, never on a name
# a same-named duplicate could satisfy (DRIFT-198-I009-027).
if (-not (Get-Command -Name 'Get-ContinuousCoReviewPathCaseSensitive' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'path-identity.ps1') }

function New-ReviewSystemClockPort {
    return [pscustomobject]@{
        kind = 'system'
        utc_now = { [DateTimeOffset]::UtcNow.ToString('o') }
        monotonic_ms = { [Environment]::TickCount64 }
    }
}

function New-ReviewFixtureClockPort {
    param([string[]]$UtcValues = @('2026-07-16T00:00:00Z'), [long[]]$MonotonicValues = @(0, 1000))
    $utcQueue = [Collections.Generic.Queue[string]]::new(); foreach ($value in $UtcValues) { $utcQueue.Enqueue($value) }
    $monoQueue = [Collections.Generic.Queue[long]]::new(); foreach ($value in $MonotonicValues) { $monoQueue.Enqueue($value) }
    $utcState = [pscustomobject]@{ queue = $utcQueue; last = $UtcValues[-1] }
    $monoState = [pscustomobject]@{ queue = $monoQueue; last = $MonotonicValues[-1] }
    $utc = { if ($utcState.queue.Count -gt 0) { $utcState.last = $utcState.queue.Dequeue() }; return $utcState.last }.GetNewClosure()
    $mono = { if ($monoState.queue.Count -gt 0) { $monoState.last = $monoState.queue.Dequeue() }; return [long]$monoState.last }.GetNewClosure()
    return [pscustomobject]@{ kind = 'fixture'; utc_now = $utc; monotonic_ms = $mono }
}

function Read-ReviewClockUtc { param([Parameter(Mandatory)]$ClockPort); return [string](& $ClockPort.utc_now) }
function Read-ReviewClockMonotonic { param([Parameter(Mandatory)]$ClockPort); return [long](& $ClockPort.monotonic_ms) }

function ConvertTo-ReviewObservedTimestampString {
    param([AllowNull()]$Value)
    if ($Value -is [datetime]) { return ([DateTimeOffset]$Value).ToUniversalTime().ToString('o') }
    if ($Value -is [datetimeoffset]) { return ([DateTimeOffset]$Value).ToUniversalTime().ToString('o') }
    return [string]$Value
}

function Write-ReviewOrchestrationProgress {
    param(
        [AllowNull()][scriptblock]$Sink,
        [Parameter(Mandatory)]$ClockPort,
        [Parameter(Mandatory)][string]$CampaignId,
        [Parameter(Mandatory)][string]$RunId,
        [Parameter(Mandatory)][string]$Stage,
        [string]$Message,
        [AllowNull()]$ProcessTreeLive,
        [AllowNull()]$OutputActivity,
        [AllowNull()]$ValidatedFindingCount,
        [ValidateRange(0, 86400000)][long]$ElapsedMilliseconds = 0,
        [ValidateRange(1, 7200)][int]$TimeoutSeconds = 900,
        [AllowNull()]$Usage
    )
    if ($null -eq $Sink) { return }
    try {
        $event = New-ReviewProgressEvent -CampaignId $CampaignId -RunId $RunId -Stage $Stage -ObservedAt (Read-ReviewClockUtc -ClockPort $ClockPort) `
            -ElapsedMilliseconds $ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds -Message $Message -ProcessTreeLive $ProcessTreeLive `
            -OutputActivity $OutputActivity -ValidatedFindingCount $ValidatedFindingCount -Usage $Usage
        # Progress is side-channel information. Never let a sink's return values join the runtime adapter's
        # pipeline and change the shape of the authority-bearing result.
        $null = & $Sink $event
    }
    catch {
        # Progress is informational. A renderer/collector failure cannot change review authority,
        # spend, containment, or terminal publication.
        $null = $_
    }
}

function New-ReviewRunStateFact {
    param([string]$CampaignId, [string]$RunId, [string]$TargetDigest, [string]$HarnessId, [string]$State)
    return [pscustomobject][ordered]@{ schema_version = '1.0'; campaign_id = $CampaignId; run_id = $RunId; target_digest = $TargetDigest; harness_id = $HarnessId; state = $State }
}

function New-GitReviewTargetPort {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$OriginRepo,
        [string]$ExternalRoot,
        [AllowEmptyCollection()][string[]]$ExcludedPathPatterns = @()
    )
    # Capture CommandInfo objects as well as values. A PowerShell closure is backed by a dynamic
    # module and cannot otherwise resolve private functions from the module that constructed it.
    $prepareCommand = Get-Command -Name 'New-GitReviewTargetSnapshot' -CommandType Function
    $currentnessCommand = Get-Command -Name 'Test-GitReviewTargetCurrentness' -CommandType Function
    $integrityCommand = Get-Command -Name 'Test-GitReviewTargetSnapshotIntegrity' -CommandType Function
    $protectCommand = Get-Command -Name 'Enable-ReviewTargetReadOnlyProtection' -CommandType Function
    $unprotectCommand = Get-Command -Name 'Disable-ReviewTargetReadOnlyProtection' -CommandType Function
    $disposeCommand = Get-Command -Name 'Remove-GitReviewTargetSnapshot' -CommandType Function
    $prepare = {
        param($runId)
        & $prepareCommand -OriginRepo $OriginRepo -RunId $runId -ExternalRoot $ExternalRoot -ExcludedPathPatterns $ExcludedPathPatterns
    }.GetNewClosure()
    $currentness = { param($snapshot) & $currentnessCommand -Snapshot $snapshot }.GetNewClosure()
    $integrity = { param($snapshot) & $integrityCommand -Snapshot $snapshot }.GetNewClosure()
    $protect = { param($snapshot, $externalWritablePath) & $protectCommand -Snapshot $snapshot -ExternalWritablePath $externalWritablePath }.GetNewClosure()
    $unprotect = { param($snapshot, $lease) & $unprotectCommand -Snapshot $snapshot -Lease $lease }.GetNewClosure()
    $dispose = { param($snapshot) & $disposeCommand -Snapshot $snapshot }.GetNewClosure()
    return [pscustomobject]@{ kind = 'git'; prepare = $prepare; currentness = $currentness; integrity = $integrity; protect = $protect; unprotect = $unprotect; dispose = $dispose }
}

function New-ReviewFixtureTargetPort {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$SnapshotPath,
        [string]$TargetDigest = 'fixture-digest',
        [ValidateSet('current', 'snapshot-moved', 'unknown')][string]$Currentness = 'current',
        [bool]$IntegrityPass = $true,
        [string[]]$IntegrityChangedPaths = @()
    )
    $suppressionEnvironment = Get-ReviewTargetSuppressionEnvironment
    $prepare = { param($runId) [pscustomobject]@{ schema_version = '1.0'; target_kind = 'fixture'; run_id = $runId; target_digest = $TargetDigest; snapshot_path = $SnapshotPath; workspace_root = $SnapshotPath; origin_repo = $null; suppression_environment = $suppressionEnvironment } }.GetNewClosure()
    $checkCurrentness = { param($snapshot) [pscustomobject]@{ classification = $Currentness; exact = ($Currentness -ceq 'current'); reason = 'fixture-currentness' } }.GetNewClosure()
    $checkIntegrity = { param($snapshot) [pscustomobject]@{ intact = $IntegrityPass; classification = $(if ($IntegrityPass) { 'intact' } else { 'snapshot-tampered' }); changed_paths = @($IntegrityChangedPaths) } }.GetNewClosure()
    $protect = { param($snapshot, $externalWritablePath) [pscustomobject]@{ ok = $true; reason = 'fixture-read-only'; lease = $null } }
    $unprotect = { param($snapshot, $lease) [pscustomobject]@{ ok = $true; reason = 'fixture-read-only-removed' } }
    $dispose = { param($snapshot) [pscustomobject]@{ removed = $true; failure_reason = $null } }
    return [pscustomobject]@{ kind = 'fixture'; prepare = $prepare; currentness = $checkCurrentness; integrity = $checkIntegrity; protect = $protect; unprotect = $unprotect; dispose = $dispose }
}

function New-ReviewFixtureHarnessPort {
    [CmdletBinding()]
    param(
        [string]$HarnessId = 'fixture-harness',
        [bool]$PreflightPass = $true,
        [AllowNull()]$Candidate,
        [string]$RawCandidate
    )
    $preflight = { param($invocation) [pscustomobject]@{ ok = $PreflightPass; reason = $(if ($PreflightPass) { 'fixture-ready' } else { 'fixture-harness-unavailable' }) } }.GetNewClosure()
    $invoke = {
        param($invocation, $environment)
        if ($null -ne $Candidate) { $json = $Candidate | ConvertTo-Json -Depth 20 -Compress; [IO.File]::WriteAllText([string]$invocation.candidate_result_path, $json, [Text.UTF8Encoding]::new($false)) }
        elseif (-not [string]::IsNullOrEmpty($RawCandidate)) { [IO.File]::WriteAllText([string]$invocation.candidate_result_path, $RawCandidate, [Text.UTF8Encoding]::new($false)) }
        return [pscustomobject]@{ exit_code = 0; output_activity = $true; suppression_observed = [string]$environment.SPECREW_REFOCUS_DISABLE }
    }.GetNewClosure()
    return [pscustomobject]@{ id = $HarnessId; preflight = $preflight; invoke = $invoke }
}

function New-ReviewFixtureRuntimePort {
    [CmdletBinding()]
    param(
        [bool]$PreflightPass = $true,
        [ValidateSet('completed', 'launch-failed', 'timed-out', 'terminated', 'containment-violated', 'abandoned')][string]$Outcome = 'completed',
        [bool]$TerminationVerified = $true,
        [ValidateSet('verified', 'violated', 'unknown')][string]$Containment = 'verified',
        [string]$FailureReason,
        [AllowNull()]$Usage
    )
    $preflight = { param($invocation) [pscustomobject]@{ ok = $PreflightPass; reason = $(if ($PreflightPass) { 'fixture-runtime-ready' } else { 'fixture-runtime-unavailable' }) } }.GetNewClosure()
    $invoke = {
        param($harness, $invocation, $onStarted, $environment, $progress)
        if ($Outcome -ceq 'launch-failed') { return [pscustomobject]@{ runtime_outcome = 'launch-failed'; termination_verified = $true; containment = 'unknown'; failure_reason = $(if ($FailureReason) { $FailureReason } else { 'fixture launch failed' }); process_tree_live = $false; output_activity = $false } }
        $null = & $onStarted ([pscustomobject][ordered]@{
            schema_version = '1.0'; runtime_id = 'fixture-runtime'; platform = 'fixture'; containment_kind = 'fixture'
            containment_id = 'fixture-contained-process'; process_id = $PID
            process_started_at = (Get-Process -Id $PID).StartTime.ToUniversalTime().ToString('o')
        })
        if ($null -ne $progress) { try { $null = & $progress ([pscustomobject]@{ process_tree_live = $true; output_activity = $false }) } catch { $null = $_ } }
        $harnessResult = & $harness.invoke $invocation $environment
        return [pscustomobject]@{
            runtime_outcome = $Outcome; termination_verified = $TerminationVerified; containment = $Containment
            failure_reason = $FailureReason; process_tree_live = (-not $TerminationVerified); output_activity = [bool]$harnessResult.output_activity
            usage = $Usage
        }
    }.GetNewClosure()
    $recover = {
        param($receipt)
        $valid = $null -ne $receipt -and [string]$receipt.runtime_id -ceq 'fixture-runtime' -and [string]$receipt.containment_kind -ceq 'fixture'
        return [pscustomobject]@{ termination_verified = $valid; containment = $(if ($valid) { 'verified' } else { 'unknown' }); process_tree_live = $false; failure_reason = $(if ($valid) { $null } else { 'fixture-recovery-receipt-mismatch' }) }
    }
    return [pscustomobject]@{ id = 'fixture-runtime'; platform = 'fixture'; containment = 'fixture'; preflight = $preflight; invoke = $invoke; recover = $recover }
}

function New-ReviewFixtureVerificationPort {
    # Explicit test port: orchestration fixtures that are not exercising FR-048 can keep their
    # synthetic target digest without inventing a Git repository or a provider command.
    $execute = {
        param($snapshot)
        return [pscustomobject]@{
            ok = $true; reason = 'fixture-verification-ready'; state = 'fixture'
            review_scope_suffix = ''; command_count = 0; evidence_count = 0
        }
    }
    return [pscustomobject]@{ kind = 'fixture'; execute = $execute }
}

function Get-ReviewCampaignVerificationSupportManifest {
    [CmdletBinding()]
    param([Parameter(Mandatory)]$Snapshot)

    # The reviewer snapshot is intentionally the machinery-stripped canonical digest tree. Project
    # verification is different: release/distribution checks may legitimately need committed Specrew
    # mirrors and governance configuration. Stage only TRACKED machinery from the snapshot's pinned
    # origin commit, never the origin working tree, and remove it again before harness preflight.
    foreach ($name in @('snapshot_path', 'origin_head_before', 'machinery_paths_sha256')) {
        if (-not $Snapshot.PSObject.Properties[$name] -or [string]::IsNullOrWhiteSpace([string]$Snapshot.$name)) {
            return [pscustomobject]@{ commit = ''; paths = @(); files = @() }
        }
    }

    $snapshotPath = [IO.Path]::GetFullPath([string]$Snapshot.snapshot_path)
    $commit = [string]$Snapshot.origin_head_before
    # VOCABULARY CAPTURE (T066 attempt 06 finding f2): the digest resolver already
    # performed the one live marker/host scan that defines reviewable content. The
    # target port freezes that exact normalized vocabulary and currentness-binds its
    # hash. Verification reuses it without a second origin scan; only FILE CONTENT is
    # read from the pinned commit.
    $frozenPaths = Get-ContinuousCoReviewOrdinalUniquePath -Path @($Snapshot.machinery_paths |
            ForEach-Object { ([string]$_ -replace '\\', '/').Trim('/') })
    $pathBytes = [Text.Encoding]::UTF8.GetBytes(($frozenPaths -join "`n"))
    $pathHash = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($pathBytes)).ToLowerInvariant()
    if ($pathHash -cne [string]$Snapshot.machinery_paths_sha256) {
        throw 'verification-support-machinery-vocabulary-binding-mismatch'
    }
    $paths = @($frozenPaths | Where-Object { -not [string]::IsNullOrWhiteSpace($_) -and $_ -cne '.git' })
    if ($paths.Count -eq 0) { return [pscustomobject]@{ commit = $commit; paths = @(); files = @() } }

    $files = [Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
    for ($offset = 0; $offset -lt $paths.Count; $offset += 80) {
        $end = [Math]::Min($offset + 80, $paths.Count) - 1
        # LITERAL pathspecs for the same reason as the restore below: a machinery identity holding
        # glob metacharacters must not select unrelated tracked source into the support manifest.
        $chunk = @($paths[$offset..$end] | ForEach-Object { ConvertTo-ContinuousCoReviewLiteralPathspec -Path $_ })
        $listed = @(Get-GitReviewTargetTreeEntries -WorkingDirectory $snapshotPath -TreeId $commit -Pathspec $chunk)
        foreach ($entry in $listed) {
            # Verification support is controller scaffolding, not product source. Never
            # materialize a symlink from it into the otherwise-contained verification copy.
            if ([string]$entry.mode -ceq '120000') { continue }
            $normalized = ([string]$entry.path -replace '\\', '/').Trim()
            if ([string]::IsNullOrWhiteSpace($normalized)) { continue }
            # The selected plan is captured independently from current origin bytes,
            # hash-bound by the target port, and already materialized in the snapshot.
            # It is controller input, not pinned support to overwrite or remove.
            if ($normalized -ceq '.specrew/verification-plan.json') { continue }
            $full = [IO.Path]::GetFullPath((Join-Path $snapshotPath $normalized))
            if (-not (Test-ReviewTargetPathUnderRoot -Path $full -Root $snapshotPath)) { throw "verification-support-path-unsafe:$normalized" }
            $null = $files.Add($normalized)
        }
    }
    return [pscustomobject]@{ commit = $commit; paths = @($paths); files = @($files | Sort-Object) }
}

function Remove-ReviewCampaignVerificationSupport {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$SnapshotPath, [Parameter(Mandatory)]$Manifest)

    $root = [IO.Path]::GetFullPath($SnapshotPath)
    # Volume-derived, never OS-family: this dedupes parent directories for cleanup, so folding two
    # case-distinct parents would leave one uncleaned. 'distinct' keeps both when undetermined.
    $pathComparer = Get-ContinuousCoReviewPathComparer -Path $root -WhenUndetermined 'distinct'
    $parents = [Collections.Generic.HashSet[string]]::new($pathComparer)
    foreach ($relative in @($Manifest.files)) {
        $full = [IO.Path]::GetFullPath((Join-Path $root ([string]$relative)))
        if (-not (Test-ReviewTargetPathUnderRoot -Path $full -Root $root)) { throw "verification-support-cleanup-path-unsafe:$relative" }
        for ($parent = Split-Path -Parent $full;
            -not [string]::IsNullOrWhiteSpace($parent) -and (Test-ReviewTargetPathUnderRoot -Path $parent -Root $root);
            $parent = Split-Path -Parent $parent) {
            $null = $parents.Add($parent)
        }
        if ([IO.File]::Exists($full)) { [IO.File]::Delete($full) }
        elseif (Test-Path -LiteralPath $full) { Remove-Item -LiteralPath $full -Force }
    }
    foreach ($directory in @($parents | Sort-Object { $_.Length } -Descending)) {
        if ([IO.Directory]::Exists($directory) -and [IO.Directory]::GetFileSystemEntries($directory).Count -eq 0) {
            [IO.Directory]::Delete($directory, $false)
        }
    }
    $left = @($Manifest.files | Where-Object { Test-Path -LiteralPath (Join-Path $root ([string]$_)) })
    if ($left.Count -gt 0) { throw ('verification-support-cleanup-incomplete:' + ($left -join ',')) }
}

function Remove-ReviewCampaignVerificationMachinery {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$SnapshotPath, [Parameter(Mandatory)]$Manifest)

    $root = [IO.Path]::GetFullPath($SnapshotPath)
    foreach ($relative in @($Manifest.paths | Sort-Object { ([string]$_).Length } -Descending)) {
        $full = [IO.Path]::GetFullPath((Join-Path $root ([string]$relative)))
        if (-not (Test-ReviewTargetPathUnderRoot -Path $full -Root $root)) { throw "verification-machinery-purge-path-unsafe:$relative" }
        if (Test-Path -LiteralPath $full) { Remove-Item -LiteralPath $full -Recurse -Force }
    }
    $left = @($Manifest.paths | Where-Object { Test-Path -LiteralPath (Join-Path $root ([string]$_)) })
    if ($left.Count -gt 0) { throw ('verification-machinery-purge-incomplete:' + ($left -join ',')) }
}

function Add-ReviewCampaignVerificationSupport {
    [CmdletBinding()]
    param([Parameter(Mandatory)]$Snapshot)

    $manifest = Get-ReviewCampaignVerificationSupportManifest -Snapshot $Snapshot
    if (@($manifest.files).Count -eq 0) { return $manifest }
    $snapshotPath = [IO.Path]::GetFullPath([string]$Snapshot.snapshot_path)
    foreach ($relative in @($manifest.files)) {
        if (Test-Path -LiteralPath (Join-Path $snapshotPath ([string]$relative))) {
            throw "verification-support-path-collision:$relative"
        }
    }
    try {
        for ($offset = 0; $offset -lt @($manifest.files).Count; $offset += 80) {
            $end = [Math]::Min($offset + 80, @($manifest.files).Count) - 1
            # LITERAL pathspecs, never raw names: these are frozen repository identities, and Git
            # would read a legal name holding `*`, `?`, or `[]` as a glob. That let a machinery
            # identity select a DIFFERENT tracked path, restore it into the verification copy where
            # it could change the command result, then remove it again - leaving the post-run digest
            # matching target_digest and certifying green evidence for a composition that is not the
            # authorized candidate (co-review finding, run run-f198-i009-aab37c3b-codex-2).
            $chunk = @($manifest.files[$offset..$end] | ForEach-Object { ConvertTo-ContinuousCoReviewLiteralPathspec -Path $_ })
            $restored = Invoke-ReviewTargetGit -WorkingDirectory $snapshotPath -Arguments (@('restore', "--source=$($manifest.commit)", '--worktree', '--') + $chunk)
            if ($restored.exit_code -ne 0) { throw ('verification-support-restore-failed:' + $restored.stderr) }
        }
        return $manifest
    }
    catch {
        $restoreFailure = [string]$_.Exception.Message
        $rollbackFailures = [Collections.Generic.List[string]]::new()
        try { Remove-ReviewCampaignVerificationSupport -SnapshotPath $snapshotPath -Manifest $manifest }
        catch { $rollbackFailures.Add('exact-cleanup:' + [string]$_.Exception.Message) }
        # A chunked restore can fail after creating files the exact manifest cleanup cannot
        # remove. Always attempt the complete authoritative machinery purge as the rollback
        # backstop, and preserve every cleanup failure in the controller-visible reason.
        try { Remove-ReviewCampaignVerificationMachinery -SnapshotPath $snapshotPath -Manifest $manifest }
        catch { $rollbackFailures.Add('machinery-purge:' + [string]$_.Exception.Message) }
        if ($rollbackFailures.Count -gt 0) {
            throw ('verification-support-staging-failed:' + $restoreFailure + ';verification-support-rollback-failed:' + ($rollbackFailures -join '|'))
        }
        throw ('verification-support-staging-failed:' + $restoreFailure)
    }
}

function Invoke-ReviewCampaignFrozenVerification {
    [CmdletBinding()]
    param([Parameter(Mandatory)]$Snapshot, [string]$EvidencePath)

    foreach ($name in @('Get-ContinuousCoReviewSelectedVerificationPlan', 'Invoke-ContinuousCoReviewVerificationPlan')) {
        if (-not (Get-Command -Name $name -ErrorAction SilentlyContinue)) {
            . (Join-Path $PSScriptRoot 'verification-plan-runner.ps1')
            break
        }
    }
    if (-not (Get-Command -Name 'Copy-ContinuousCoReviewImplementerEvidence' -ErrorAction SilentlyContinue)) {
        . (Join-Path $PSScriptRoot 'test-evidence-recorder.ps1')
    }

    $snapshotPath = [IO.Path]::GetFullPath([string]$Snapshot.snapshot_path)
    $targetDigest = [string]$Snapshot.target_digest
    $selected = Get-ContinuousCoReviewSelectedVerificationPlan -RepoRoot $snapshotPath
    if (-not [bool]$selected.available) {
        return [pscustomobject]@{
            ok = $false; reason = ('verification-not-configured:' + [string]$selected.reason); state = 'verification-not-configured'
            review_scope_suffix = ''; command_count = 0; evidence_count = 0
        }
    }

    $before = Get-ContinuousCoReviewReviewedStateDigest -RepoRoot $snapshotPath
    if ($null -eq $before -or -not [bool]$before.ok -or [string]$before.tree_id -cne $targetDigest) {
        return [pscustomobject]@{
            ok = $false; reason = 'verification-target-digest-mismatch-before-execution'; state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = 0; evidence_count = 0
        }
    }

    $support = $null
    $execution = $null
    $executionFailure = $null
    $cleanupFailure = $null
    $preparationComplete = $false
    try {
        $support = Add-ReviewCampaignVerificationSupport -Snapshot $Snapshot
        $execution = Invoke-ContinuousCoReviewVerificationPlan -RepoRoot $snapshotPath -Plan $selected.plan
    }
    catch { $executionFailure = [string]$_.Exception.Message }
    finally {
        if ($null -ne $support) {
            try { Remove-ReviewCampaignVerificationSupport -SnapshotPath $snapshotPath -Manifest $support }
            catch { $cleanupFailure = [string]$_.Exception.Message }
        }
    }
    try {
    if (-not [string]::IsNullOrWhiteSpace($cleanupFailure)) {
        return [pscustomobject]@{
            ok = $false; reason = ('verification-support-cleanup-failed:' + $cleanupFailure); state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = 0; evidence_count = 0
        }
    }
    if (-not [string]::IsNullOrWhiteSpace($executionFailure)) {
        return [pscustomobject]@{
            ok = $false; reason = ('verification-runner-failed:' + $executionFailure); state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = 0; evidence_count = 0
        }
    }
    if ([string]$execution.state -cne 'configured') {
        return [pscustomobject]@{
            ok = $false; reason = ('verification-plan-not-runnable:' + [string]$execution.reason); state = [string]$execution.state
            review_scope_suffix = ''; command_count = [int]$execution.command_count; evidence_count = @($execution.evidence).Count
        }
    }

    # A verification command may create ignored caches or its transient result transport, but it may
    # not change the canonical frozen source. A changed digest stops before the provider grant is spent.
    $after = Get-ContinuousCoReviewReviewedStateDigest -RepoRoot $snapshotPath
    if ($null -eq $after -or -not [bool]$after.ok -or [string]$after.tree_id -cne $targetDigest) {
        return [pscustomobject]@{
            ok = $false; reason = 'verification-mutated-frozen-target'; state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = [int]$execution.command_count; evidence_count = @($execution.evidence).Count
        }
    }

    $evidence = @($execution.evidence)
    $joined = @(Test-ContinuousCoReviewPlanEvidenceInjectable -PlanEvidence $evidence -Plan $selected.plan -CurrentDigest $targetDigest)
    # The raw accessor deliberately preserves command arrays with a unary-comma return. A direct
    # assignment unwraps that transport layer; @() here would nest the complete list as one element.
    $planCommands = Get-ContinuousCoReviewVerificationRawProp -Object $selected.plan -Name 'commands'
    $planIds = @($planCommands | ForEach-Object { [string](Get-ContinuousCoReviewContractProp -Object $_ -Name 'command_id') })
    $evidenceIds = @($evidence | ForEach-Object { [string](Get-ContinuousCoReviewContractProp -Object $_ -Name 'command_id') })
    $joinComplete = ($joined.Count -eq $evidence.Count) -and ($evidence.Count -eq $planIds.Count) -and
        (@($joined | Where-Object { -not [bool]$_.injectable }).Count -eq 0)
    foreach ($commandId in $planIds) {
        if (@($evidenceIds | Where-Object { $_ -ceq $commandId }).Count -ne 1) { $joinComplete = $false; break }
    }
    if (-not $joinComplete) {
        return [pscustomobject]@{
            ok = $false; reason = 'verification-evidence-not-exactly-joinable'; state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = $planIds.Count; evidence_count = $evidence.Count
        }
    }

    $failedIds = @($evidence | Where-Object { -not [bool]$_.command_succeeded } | ForEach-Object { [string]$_.command_id })
    if ($failedIds.Count -gt 0) {
        # A red configured command is controller evidence, not work for a paid reviewer. Stop before
        # evidence injection, harness preflight, claim acquisition, or spend; the reservation is
        # released by the caller. Output stays private by default. The stable reason directs the
        # operator to the existing human-authorized, command-scoped bounded-disclosure path.
        return [pscustomobject]@{
            ok = $false
            reason = 'verification-command-failed:' + ($failedIds -join ',') + ':diagnostics-require-command-scoped-disclosure'
            state = 'verification-failed'
            review_scope_suffix = ''
            command_count = $planIds.Count
            evidence_count = $evidence.Count
            failed_command_ids = @($failedIds)
        }
    }

    $injectedPath = if ([string]::IsNullOrWhiteSpace($EvidencePath)) {
        $reviewDirectory = Join-Path $snapshotPath '.review'
        [IO.Directory]::CreateDirectory($reviewDirectory) | Out-Null
        Join-Path $reviewDirectory 'implementer-evidence.json'
    }
    else { [IO.Path]::GetFullPath($EvidencePath) }
    if ((Test-ReviewTargetPathUnderRoot -Path $injectedPath -Root $snapshotPath) -and -not [string]::IsNullOrWhiteSpace($EvidencePath)) {
        return [pscustomobject]@{
            ok = $false; reason = 'verification-evidence-path-inside-frozen-target'; state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = $planIds.Count; evidence_count = $evidence.Count
        }
    }
    $injected = Copy-ContinuousCoReviewImplementerEvidence -RepoRoot $snapshotPath -OutputPath $injectedPath -DigestTreeId $targetDigest -Plan $selected.plan
    if (-not $injected -or -not [IO.File]::Exists($injectedPath)) {
        return [pscustomobject]@{
            ok = $false; reason = 'verification-evidence-injection-failed'; state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = $planIds.Count; evidence_count = $evidence.Count
        }
    }
    try {
        $injectedRecord = [IO.File]::ReadAllText($injectedPath, [Text.UTF8Encoding]::new($false, $true)) | ConvertFrom-Json
        $injectedRuns = if ($injectedRecord.PSObject.Properties['runs']) { @($injectedRecord.runs) } else { @() }
        foreach ($commandId in $planIds) {
            if (@($injectedRuns | Where-Object { [string]$_.command_id -ceq $commandId }).Count -ne 1) {
                throw "injected-command-cardinality:$commandId"
            }
        }
    }
    catch {
        return [pscustomobject]@{
            ok = $false; reason = ('verification-evidence-injection-invalid:' + [string]$_.Exception.Message); state = 'verification-preflight-failed'
            review_scope_suffix = ''; command_count = $planIds.Count; evidence_count = $evidence.Count
        }
    }

    $supportScope = ''
    if ($null -ne $support -and @($support.files).Count -gt 0) {
        $supportScope = @"
Tracked methodology support used by verification came only from pinned commit $([string]$support.commit)
and was removed before reviewer harness preflight; the reviewer-visible tree remains machinery-stripped.
"@

    }
    $scopeSuffix = @"
 
CONTROLLER VERIFICATION EVIDENCE: Read the controller-owned file at $injectedPath. The controller executed the selected
project verification plan against this exact frozen digest before reviewer launch and injected one joined record
for each of $($planIds.Count) declared command(s). Treat failed, timed-out, or missing-required-result records as
approval-blocking evidence; never turn a configured verification failure into a clean result.
$supportScope
"@

    $preparationComplete = $true
    return [pscustomobject]@{
        ok = $true; reason = 'verification-evidence-ready'; state = 'configured'; review_scope_suffix = $scopeSuffix
        command_count = $planIds.Count; evidence_count = $evidence.Count
    }
    }
    finally {
        if ($null -ne $support) { Remove-ReviewCampaignVerificationMachinery -SnapshotPath $snapshotPath -Manifest $support }
        # Verification, evidence injection, and the final machinery purge are controller-owned
        # preparation. Re-baseline only after successful evidence projection and purge; failed
        # verification snapshots retain their original baseline until disposal.
        if ($preparationComplete -and $Snapshot.PSObject.Properties['source_hashes_before'] -and (Get-Command -Name 'Get-ContinuousCoReviewWorktreeSourceHashes' -ErrorAction SilentlyContinue)) {
            $Snapshot.source_hashes_before = Get-ContinuousCoReviewWorktreeSourceHashes -WorktreePath $snapshotPath
        }
    }
}

function New-ReviewProductionVerificationPort {
    $command = Get-Command -Name 'Invoke-ReviewCampaignFrozenVerification' -CommandType Function
    $copyCommand = Get-Command -Name 'New-GitReviewTargetVerificationCopy' -CommandType Function
    $disposeCommand = Get-Command -Name 'Remove-GitReviewTargetSnapshot' -CommandType Function
    if (-not (Get-Command -Name 'Get-ContinuousCoReviewWorktreeSourceHashes' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'worktree-reviewer.ps1') }
    $hashCommand = Get-Command -Name 'Get-ContinuousCoReviewWorktreeSourceHashes' -CommandType Function
    # Captured as a COMMAND OBJECT, like the four above, because $execute below is invoked as a PORT in a
    # foreign scope where ambient function names do not resolve. Guarded on the exact function needed
    # rather than on a sibling name: a guard that probes a DIFFERENT name from the primitive it wants is
    # how DRIFT-198-I009-027's shadow survived, and a stale copy of path-identity.ps1 satisfies
    # `Get-ContinuousCoReviewPathCaseSensitive` while lacking this one.
    if (-not (Get-Command -Name 'Get-ContinuousCoReviewOrdinalUniquePath' -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'path-identity.ps1') }
    $uniquePathCommand = Get-Command -Name 'Get-ContinuousCoReviewOrdinalUniquePath' -CommandType Function
    $execute = {
        param($snapshot, $paths)
        if ($null -eq $paths -or -not $paths.PSObject.Properties['implementer_evidence_path']) {
            return [pscustomobject]@{ ok = $false; reason = 'verification-external-evidence-path-missing'; state = 'verification-preflight-failed'; review_scope_suffix = ''; command_count = 0; evidence_count = 0 }
        }
        $originalBefore = & $hashCommand -WorktreePath ([string]$snapshot.snapshot_path)
        $verificationCopy = $null
        $result = $null
        $disposeFailure = $null
        try {
            $verificationCopy = & $copyCommand -Snapshot $snapshot
            $result = & $command -Snapshot $verificationCopy -EvidencePath ([string]$paths.implementer_evidence_path)
        }
        catch {
            $result = [pscustomobject]@{ ok = $false; reason = ('verification-copy-failed:' + $_.Exception.Message); state = 'verification-preflight-failed'; review_scope_suffix = ''; command_count = 0; evidence_count = 0 }
        }
        finally {
            if ($null -ne $verificationCopy) {
                try {
                    $disposed = & $disposeCommand -Snapshot $verificationCopy
                    if (-not [bool]$disposed.removed) { $disposeFailure = [string]$disposed.failure_reason }
                }
                catch { $disposeFailure = [string]$_.Exception.Message }
            }
        }
        $originalAfter = & $hashCommand -WorktreePath ([string]$snapshot.snapshot_path)
        $changed = [Collections.Generic.List[string]]::new()
        # Ordinal, matching the maps: `-CaseSensitive` leaves Sort-Object CULTURE-aware, so a
        # byte-distinct key the maps kept apart collapsed here and its file was never compared - the
        # integrity check could then report the frozen target intact after a real mutation
        # (DRIFT-198-I009-033).
        foreach ($key in (& $uniquePathCommand -Path @(@($originalBefore.Keys) + @($originalAfter.Keys)))) {
            $beforeValue = if ($originalBefore.ContainsKey($key)) { [string]$originalBefore[$key] } else { '<missing>' }
            $afterValue = if ($originalAfter.ContainsKey($key)) { [string]$originalAfter[$key] } else { '<missing>' }
            if ($beforeValue -cne $afterValue) { $changed.Add([string]$key) | Out-Null }
        }
        if ($changed.Count -gt 0) {
            return [pscustomobject]@{ ok = $false; reason = ('verification-mutated-original-frozen-target:' + (@($changed | Select-Object -First 20) -join ',')); state = 'verification-preflight-failed'; review_scope_suffix = ''; command_count = 0; evidence_count = 0 }
        }
        if (-not [string]::IsNullOrWhiteSpace($disposeFailure)) {
            return [pscustomobject]@{ ok = $false; reason = ('verification-copy-disposal-failed:' + $disposeFailure); state = 'verification-preflight-failed'; review_scope_suffix = ''; command_count = 0; evidence_count = 0 }
        }
        $result | Add-Member -NotePropertyName original_frozen_target_unchanged -NotePropertyValue $true -Force
        $result | Add-Member -NotePropertyName verification_copy_disposed -NotePropertyValue $true -Force
        return $result
    }.GetNewClosure()
    return [pscustomobject]@{ kind = 'production'; execute = $execute }
}

function Complete-ReviewPreInvocationFailure {
    param(
        [string]$StoreRoot, [string]$StagingRoot, [string]$CampaignId, [string]$RunId, [string]$TargetDigest, [string]$HarnessId,
        $Reservation, [object[]]$Spends, [string]$Reason, [string]$ObservedAt, [string]$StartedAt, [long]$DurationMs,
        [ValidateSet('preflight-failed', 'claim-contended', 'launch-failed')][string]$RuntimeOutcome, [ValidateSet('verified', 'unknown')][string]$Containment = 'unknown'
    )
    if ($null -ne $Reservation) {
        $releaseDecision = Resolve-ReviewCampaignReleaseDecision -Reservation $Reservation -Reason $Reason -ObservedAt $ObservedAt -Spends $Spends
        if ($releaseDecision.permitted) { Write-ReviewCampaignReleaseFact -StoreRoot $StoreRoot -Fact $releaseDecision.fact | Out-Null }
    }
    return Invoke-ReviewResultIngress -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId -TargetDigest $TargetDigest -HarnessId $HarnessId -RuntimeOutcome $RuntimeOutcome -Invoked $false -TerminationVerified $true -Containment $Containment -Currentness unknown -StartedAt $StartedAt -EndedAt $ObservedAt -DurationMs $DurationMs -FailureReason $Reason
}

function Get-ReviewCampaignStableToken {
    param([Parameter(Mandatory)][string]$Value, [ValidateRange(8, 32)][int]$Length = 16)
    $bytes = [Text.Encoding]::UTF8.GetBytes($Value)
    return [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant().Substring(0, $Length)
}

function ConvertTo-ReviewCampaignSlug {
    param([Parameter(Mandatory)][string]$Value, [ValidateRange(8, 60)][int]$MaximumLength = 48)
    $slug = ($Value.ToLowerInvariant() -replace '[^a-z0-9]+', '-').Trim('-')
    if ([string]::IsNullOrWhiteSpace($slug)) { $slug = 'review' }
    if ($slug.Length -le $MaximumLength) { return $slug }
    $hash = Get-ReviewCampaignStableToken -Value $Value -Length 12
    return ($slug.Substring(0, $MaximumLength - 13).TrimEnd('-') + '-' + $hash)
}

function Test-ReviewCampaignTargetRootWritable {
    param([Parameter(Mandatory)][string]$Path)
    $probePath = $null
    try {
        if (Test-Path -LiteralPath $Path -PathType Leaf) {
            return [pscustomobject]@{ ok = $false; reason = 'path-is-file' }
        }
        # Candidate roots are intentionally retained after both successful and failed file probes.
        # Deleting an empty-looking shared directory races another process populating it between
        # inspection and deletion; individual rt-* worktrees remain the cleanup unit.
        [IO.Directory]::CreateDirectory($Path) | Out-Null
        $probePath = Join-Path $Path ('.specrew-write-probe-' + [guid]::NewGuid().ToString('N'))
        $stream = [IO.File]::Open($probePath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
        $stream.Dispose(); [IO.File]::Delete($probePath); $probePath = $null
        return [pscustomobject]@{ ok = $true; reason = 'writable' }
    }
    catch {
        if ($probePath -and [IO.File]::Exists($probePath)) { try { [IO.File]::Delete($probePath) } catch { $null = $_ } }
        return [pscustomobject]@{ ok = $false; reason = $_.Exception.GetType().Name }
    }
}

function Get-ReviewCampaignRepositoryToken {
    param([Parameter(Mandatory)][string]$GitRoot)
    $identity = [IO.Path]::GetFullPath($GitRoot)
    if ([OperatingSystem]::IsWindows()) { $identity = $identity.ToUpperInvariant() }
    # Filesystem namespace only: immutable campaign/run identity remains full-length in authority
    # facts, while the workspace leaf is drawn from 96 independent random source bits (about 83
    # effective namespace bits after case-folding). Sixteen hex characters keep the repository
    # namespace bounded without becoming review authority.
    return Get-ReviewCampaignStableToken -Value $identity -Length 16
}

function Resolve-ReviewCampaignTargetExternalRoot {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$RepoRoot,
        [string]$RequestedRoot
    )
    $root = (Resolve-Path -LiteralPath $RepoRoot).Path
    $gitRootResult = Invoke-ReviewTargetGit -WorkingDirectory $root -Arguments @('rev-parse', '--show-toplevel')
    if ($gitRootResult.exit_code -ne 0) { throw ('review-campaign-target-root-repo-invalid:' + $gitRootResult.stderr) }
    $gitRoot = [IO.Path]::GetFullPath($gitRootResult.stdout)

    if (-not [string]::IsNullOrWhiteSpace($RequestedRoot)) {
        $requestedFull = [IO.Path]::GetFullPath($RequestedRoot, $root)
        if (Test-ReviewTargetPathUnderRoot -Path $requestedFull -Root $gitRoot) { throw 'review-campaign-target-root-inside-origin' }
        $probe = Test-ReviewCampaignTargetRootWritable -Path $requestedFull
        if (-not $probe.ok) { throw ('review-campaign-target-root-unusable:' + $probe.reason) }
        return $requestedFull
    }

    $repoToken = Get-ReviewCampaignRepositoryToken -GitRoot $gitRoot
    $candidates = [Collections.Generic.List[string]]::new()
    $parent = Split-Path -Parent $root
    if (-not [string]::IsNullOrWhiteSpace($parent)) { $candidates.Add((Join-Path $parent '.specrew-targets')) | Out-Null }
    if ([OperatingSystem]::IsWindows()) {
        # AppData\Local\Temp reproduced MAX_PATH failure on this repository. Keep the fallback
        # under the writable user home with a deliberately short leaf; --run-root remains the
        # escape hatch for unusually long homes or constrained layouts. The repo-token namespace
        # directory is intentionally retained (at most one per resolved repository identity):
        # deleting a shared empty-looking root races concurrent runs. Individual rt-* worktrees
        # are still removed by the target port.
        $userHome = [Environment]::GetFolderPath([Environment+SpecialFolder]::UserProfile)
        if (-not [string]::IsNullOrWhiteSpace($userHome)) { $candidates.Add((Join-Path $userHome ".sr/$repoToken")) | Out-Null }
    }
    else {
        $candidates.Add((Join-Path ([IO.Path]::GetTempPath()) "specrew-review-targets/$repoToken")) | Out-Null
    }

    $failures = [Collections.Generic.List[string]]::new()
    # Volume-derived, never OS-family: this dedupes candidate external target roots. 'distinct' keeps
    # two case-distinct candidates apart when the volume cannot be determined, so a viable root is
    # never silently discarded as a duplicate of another.
    $comparer = Get-ContinuousCoReviewPathComparer -Path $gitRoot -WhenUndetermined 'distinct'
    $seen = [Collections.Generic.HashSet[string]]::new($comparer)
    foreach ($candidate in @($candidates)) {
        $full = [IO.Path]::GetFullPath($candidate)
        if (-not $seen.Add($full)) { continue }
        if (Test-ReviewTargetPathUnderRoot -Path $full -Root $gitRoot) {
            $failures.Add("inside-origin:$full") | Out-Null
            continue
        }
        $probe = Test-ReviewCampaignTargetRootWritable -Path $full
        if ($probe.ok) { return $full }
        $failures.Add("$($probe.reason):$full") | Out-Null
    }
    throw ('review-campaign-target-root-unavailable:' + ($failures -join ','))
}

function New-ReviewCampaignTargetPort {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$RepoRoot,
        [string]$RequestedRoot,
        [AllowEmptyCollection()][string[]]$ExcludedPathPatterns = @()
    )
    $targetRoot = Resolve-ReviewCampaignTargetExternalRoot -RepoRoot $RepoRoot -RequestedRoot $RequestedRoot
    return New-GitReviewTargetPort -OriginRepo ((Resolve-Path -LiteralPath $RepoRoot).Path) -ExternalRoot $targetRoot -ExcludedPathPatterns $ExcludedPathPatterns
}

function Resolve-ReviewCampaignPublicIdentity {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$RepoRoot,
        [string]$FeatureId,
        [string]$IterationNumber,
        [string]$RunId
    )
    $root = (Resolve-Path -LiteralPath $RepoRoot).Path
    $feature = $FeatureId
    if ([string]::IsNullOrWhiteSpace($feature)) {
        $featureRoot = $null
        if (Get-Command -Name 'Get-ContinuousCoReviewNavigatorFeatureRoot' -ErrorAction SilentlyContinue) {
            try { $featureRoot = Get-ContinuousCoReviewNavigatorFeatureRoot -RepoRoot $root } catch { $featureRoot = $null }
        }
        if (-not [string]::IsNullOrWhiteSpace([string]$featureRoot)) { $feature = Split-Path -Leaf $featureRoot }
    }
    if ([string]::IsNullOrWhiteSpace($feature)) {
        $branch = Invoke-ReviewTargetGit -WorkingDirectory $root -Arguments @('branch', '--show-current')
        if ($branch.exit_code -eq 0 -and -not [string]::IsNullOrWhiteSpace($branch.stdout) -and (Test-Path -LiteralPath (Join-Path $root "specs/$($branch.stdout)") -PathType Container)) {
            $feature = $branch.stdout
        }
    }
    if (-not (Test-ReviewCampaignFeatureIdentity -Value $feature)) { throw 'review-campaign-active-feature-unresolved' }
    $featureDirectory = Join-Path $root "specs/$feature"
    if (-not (Test-Path -LiteralPath $featureDirectory -PathType Container)) { throw "review-campaign-feature-missing:$feature" }

    $iteration = $IterationNumber
    if ([string]::IsNullOrWhiteSpace($iteration)) {
        $iterationsRoot = Join-Path $featureDirectory 'iterations'
        if (Test-Path -LiteralPath $iterationsRoot -PathType Container) {
            $iteration = @(Get-ChildItem -LiteralPath $iterationsRoot -Directory | Where-Object { $_.Name -match '^\d{3,}$' } | Sort-Object Name -Descending | Select-Object -First 1 -ExpandProperty Name)
        }
    }
    if ($iteration -is [array]) { $iteration = if ($iteration.Count -gt 0) { [string]$iteration[0] } else { '' } }
    if (-not (Test-ReviewCampaignIterationIdentity -Value $iteration)) { throw 'review-campaign-active-iteration-unresolved' }

    $featureSlug = ConvertTo-ReviewCampaignSlug -Value $feature -MaximumLength 44
    $campaignId = "cmp-$featureSlug-i$iteration"
    $lineageId = "lin-$featureSlug"
    if ([string]::IsNullOrWhiteSpace($RunId)) {
        $stamp = [DateTimeOffset]::UtcNow.ToString('yyyyMMddTHHmmssfff')
        $RunId = "run-$stamp-" + [guid]::NewGuid().ToString('N').Substring(0, 8)
    }
    if (-not (Test-ReviewAuthorityIdentifier -Value $RunId -Kind run)) { throw "review-campaign-invalid-run-id:$RunId" }
    $reservationId = 'res-' + (Get-ReviewCampaignStableToken -Value "$campaignId/$RunId/reservation" -Length 20)
    return [pscustomobject][ordered]@{
        campaign_id = $campaignId; run_id = $RunId; reservation_id = $reservationId
        target_lineage = $lineageId; feature_id = $feature; iteration_number = [string]$iteration
    }
}

function New-ReviewUnavailableHarnessPort {
    param([string]$HarnessId = 'unavailable-harness', [string]$Reason = 'production-harness-unavailable')
    $preflight = { param($invocation) [pscustomobject]@{ ok = $false; reason = $Reason } }.GetNewClosure()
    $invoke = { param($invocation, $environment) throw $Reason }.GetNewClosure()
    return [pscustomobject]@{ id = $HarnessId; preflight = $preflight; invoke = $invoke }
}

function New-ReviewUnavailableRuntimePort {
    param([string]$Reason = 'production-runtime-unavailable')
    $preflight = { param($invocation) [pscustomobject]@{ ok = $false; reason = $Reason } }.GetNewClosure()
    $invoke = { param($harness, $invocation, $onStarted, $environment) throw $Reason }.GetNewClosure()
    $recover = { param($receipt) [pscustomobject]@{ termination_verified = $false; containment = 'unknown'; process_tree_live = $null; failure_reason = $Reason } }.GetNewClosure()
    return [pscustomobject]@{ id = 'unavailable-runtime'; platform = 'unknown'; containment = 'unknown'; preflight = $preflight; invoke = $invoke; recover = $recover }
}

function New-ReviewCampaignProductionPorts {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$RepoRoot,
        [string]$ReviewerHost,
        [string]$Model,
        [string]$TargetRoot,
        [AllowEmptyCollection()][string[]]$ExcludedPathPatterns = @(),
        [ValidateRange(1, 7200)][int]$TimeoutSeconds = 900
    )
    # The one target-root policy is reused by live execution and reconciliation. It prefers the
    # short sibling root proven by T060, falls back to a writable platform root when the parent is
    # unavailable, supports an explicit external override, and fails with a domain-named reason.
    $target = New-ReviewCampaignTargetPort -RepoRoot $RepoRoot -RequestedRoot $TargetRoot -ExcludedPathPatterns $ExcludedPathPatterns
    $harness = if (Get-Command -Name 'New-ReviewProductionHarnessPort' -ErrorAction SilentlyContinue) {
        New-ReviewProductionHarnessPort -HostName $ReviewerHost -Model $Model -TimeoutSeconds $TimeoutSeconds
    }
    else { New-ReviewUnavailableHarnessPort -HarnessId $(if ($ReviewerHost) { $ReviewerHost } else { 'unselected-harness' }) -Reason 'production-harness-catalog-not-installed' }
    $runtime = if (Get-Command -Name 'New-ReviewProductionRuntimePort' -ErrorAction SilentlyContinue) {
        New-ReviewProductionRuntimePort -TimeoutSeconds $TimeoutSeconds
    }
    else { New-ReviewUnavailableRuntimePort -Reason 'production-os-runtime-not-installed' }
    return [pscustomobject]@{
        target = $target; harness = $harness; runtime = $runtime; verification = New-ReviewProductionVerificationPort; clock = New-ReviewSystemClockPort
        prompt_path = (Join-Path $PSScriptRoot 'reviewer-candidate-prompt.md')
    }
}

function Invoke-ReviewCampaignCommand {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$RepoRoot,
        [string]$FeatureId,
        [string]$IterationNumber,
        [string]$RunId,
        [string]$ReviewerHost,
        [switch]$ReviewerHostExplicit,
        [string]$Model,
        [string]$GrantAuthorizationRef,
        [AllowEmptyCollection()][string[]]$DesignContextRefs = @(),
        [AllowEmptyCollection()][string[]]$ExcludedPathPatterns = @(),
        [string]$ReviewScope = 'Review the complete frozen target and return the versioned candidate JSON contract.',
        [ValidateRange(1, 7200)][int]$TimeoutSeconds = 900,
        [string]$AuthorityConfigPath,
        [string]$StoreRoot,
        [string]$StagingRoot,
        [string]$TargetRoot,
        [AllowNull()]$Ports,
        [scriptblock]$ProgressSink
    )
    $progressCollector = New-ReviewProgressCollector -ExternalSink $ProgressSink
    $authority = Get-ContinuousCoReviewAuthorityDecision -ConfigPath $AuthorityConfigPath
    if (-not $authority.campaign_authority_enabled) {
        return [pscustomobject]@{
            status = 'suppressed'; reason = ('campaign-authority-disabled:' + $authority.reason); invoked = $false; result = $null
            authority_mode = $authority.mode; diagnostics = Get-ReviewProgressDiagnostics -Events @($progressCollector.events)
        }
    }
    $root = (Resolve-Path -LiteralPath $RepoRoot).Path
    $identity = Resolve-ReviewCampaignPublicIdentity -RepoRoot $root -FeatureId $FeatureId -IterationNumber $IterationNumber -RunId $RunId
    # T034b: reject every invalid explicit ref before grant persistence, harness selection,
    # reservation, snapshot creation, or provider spend. Omitted refs use the same auto resolver
    # as legacy review and carry an explicit bounded partial-evidence degrade when none resolve.
    $designContext = Resolve-ContinuousCoReviewDesignContextSelection -RepoRoot $root -DesignContextFiles $DesignContextRefs -FeatureId $FeatureId
    if (-not $designContext.valid) {
        return [pscustomobject][ordered]@{
            status = 'not-started'; reason = [string]$designContext.reason; invoked = $false; result = $null
            campaign_id = $identity.campaign_id; run_id = $identity.run_id; target_lineage = $identity.target_lineage
            authority_mode = 'campaign'; design_context = 'unresolved'; resolved_design_context = @()
            unresolved_design_context = @($designContext.unresolved_refs)
            diagnostics = Get-ReviewProgressDiagnostics -Events @($progressCollector.events)
        }
    }
    try { $ReviewScope = Add-ContinuousCoReviewDesignContextToScope -ReviewScope $ReviewScope -Selection $designContext }
    catch {
        return [pscustomobject][ordered]@{
            status = 'not-started'; reason = [string]$_.Exception.Message; invoked = $false; result = $null
            campaign_id = $identity.campaign_id; run_id = $identity.run_id; target_lineage = $identity.target_lineage
            authority_mode = 'campaign'; design_context = [string]$designContext.classification
            resolved_design_context = @($designContext.resolved_refs); unresolved_design_context = @()
            diagnostics = Get-ReviewProgressDiagnostics -Events @($progressCollector.events)
        }
    }
    if ($ReviewerHostExplicit) {
        $hostDefinition = Get-ContinuousCoReviewProductionHarnessDefinition -HostName $ReviewerHost
        $hostCommandAvailable = $null -ne $hostDefinition -and
            -not [string]::IsNullOrWhiteSpace([string]$hostDefinition.command) -and
            $null -ne (Get-Command -Name ([string]$hostDefinition.command) -ErrorAction SilentlyContinue)
        $hostAuthorized = -not [string]::IsNullOrWhiteSpace($GrantAuthorizationRef)
        if ($null -eq $hostDefinition -or -not $hostCommandAvailable -or -not $hostAuthorized) {
            return [pscustomobject][ordered]@{
                status = 'not-started'
                reason = "requested-host-not-available: '$ReviewerHost' is not installed+authorized+cataloged (an explicit --host is honoured or surfaced, never silently substituted)"
                invoked = $false; result = $null; campaign_id = $identity.campaign_id; run_id = $identity.run_id
                target_lineage = $identity.target_lineage; authority_mode = 'campaign'
                design_context = [string]$designContext.classification
                resolved_design_context = @($designContext.resolved_refs); unresolved_design_context = @()
                resolved_timeout_seconds = $TimeoutSeconds
                diagnostics = Get-ReviewProgressDiagnostics -Events @($progressCollector.events)
            }
        }
    }
    if ([string]::IsNullOrWhiteSpace($StoreRoot)) { $StoreRoot = Join-Path $root '.specrew/review/authority' }
    if ([string]::IsNullOrWhiteSpace($StagingRoot)) {
        $repoToken = Get-ReviewCampaignStableToken -Value $root -Length 20
        $StagingRoot = Join-Path ([IO.Path]::GetTempPath()) "specrew-review-staging/$repoToken"
    }
    $StagingRoot = [IO.Path]::GetFullPath($StagingRoot)
    if (Test-ReviewTargetPathUnderRoot -Path $StagingRoot -Root $root) {
        throw "review-campaign-staging-root-inside-origin:$StagingRoot"
    }
    if (-not [string]::IsNullOrWhiteSpace($GrantAuthorizationRef)) {
        # One human authorization reference creates at most one campaign slot. A new run that reuses
        # the same reference sees the already-spent grant; it does not mint another allowance slot.
        $grantId = 'grant-' + (Get-ReviewCampaignStableToken -Value "$($identity.campaign_id)/$GrantAuthorizationRef" -Length 20)
        $grant = [pscustomobject][ordered]@{
            schema_version = '1.0'; fact_type = 'grant'; campaign_id = $identity.campaign_id; grant_id = $grantId
            slots = 1; authority_kind = 'human'; authorization_ref = $GrantAuthorizationRef
            observed_at = [DateTimeOffset]::UtcNow.ToString('o')
        }
        $existingGrant = @(Get-ReviewAuthorityCampaignFacts -StoreRoot $StoreRoot -CampaignId $identity.campaign_id -Kind grants | Where-Object { [string]$_.grant_id -ceq $grantId })
        if ($existingGrant.Count -eq 0) {
            try { Add-ReviewCampaignGrantFact -StoreRoot $StoreRoot -Fact $grant | Out-Null }
            catch {
                if ($_.Exception.Message -notlike 'review-store-corruption:conflicting-immutable-fact:*') { throw }
                $existingGrant = @(Get-ReviewAuthorityCampaignFacts -StoreRoot $StoreRoot -CampaignId $identity.campaign_id -Kind grants | Where-Object { [string]$_.grant_id -ceq $grantId })
                if ($existingGrant.Count -ne 1) { throw }
            }
        }
        if ($existingGrant.Count -gt 0 -and ([string]$existingGrant[0].authorization_ref -cne $GrantAuthorizationRef -or [int]$existingGrant[0].slots -ne 1)) {
            throw "review-store-corruption:grant-identity-mismatch:$grantId"
        }
    }
    if ($null -eq $Ports) {
        $Ports = New-ReviewCampaignProductionPorts -RepoRoot $root -ReviewerHost $ReviewerHost -Model $Model -TargetRoot $TargetRoot `
            -ExcludedPathPatterns $ExcludedPathPatterns -TimeoutSeconds $TimeoutSeconds
    }
    $run = Invoke-ReviewCampaignRun -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $identity.campaign_id -RunId $identity.run_id `
        -ReservationId $identity.reservation_id -TargetLineage $identity.target_lineage -TargetPort $Ports.target -HarnessPort $Ports.harness `
        -RuntimePort $Ports.runtime -VerificationPort $Ports.verification -ClockPort $Ports.clock -PromptPath ([string]$Ports.prompt_path) -TimeoutSeconds $TimeoutSeconds `
        -ReviewScope $ReviewScope -DesignContextEmpty:([bool]$designContext.design_context_empty) `
        -ProgressSink $progressCollector.sink -AuthorityConfigPath $AuthorityConfigPath
    return [pscustomobject][ordered]@{
        status = $run.status; reason = $run.reason; invoked = $run.invoked; result = $run.result
        result_path = $(if ($run.PSObject.Properties['result_path']) { $run.result_path } else { $null })
        report_path = $(if ($run.PSObject.Properties['report_path']) { $run.report_path } else { $null })
        campaign_id = $identity.campaign_id; run_id = $identity.run_id; target_lineage = $identity.target_lineage
        store_root = [IO.Path]::GetFullPath($StoreRoot); authority_mode = 'campaign'
        design_context = [string]$designContext.classification; resolved_design_context = @($designContext.resolved_refs)
        unresolved_design_context = @()
        diagnostics = Get-ReviewProgressDiagnostics -Events @($progressCollector.events)
    }
}

function Add-ReviewCampaignHumanDisposition {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$StoreRoot,
        [Parameter(Mandatory)][string]$CampaignId,
        [Parameter(Mandatory)][string]$RunId,
        [Parameter(Mandatory)][ValidateSet('accept-current', 'require-correction')][string]$Decision,
        [Parameter(Mandatory)][string]$AuthorizedBy,
        [Parameter(Mandatory)][string]$AuthorizationRef,
        [Parameter(Mandatory)][string]$Rationale
    )
    $result = Get-ReviewRunAuthorityFact -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -Stage result
    if ($null -eq $result) { throw 'review-human-disposition-result-missing' }
    if ([string]$result.completion -cne 'complete' -or [string]$result.validation -cne 'valid' -or [string]$result.currentness -cne 'current') {
        throw 'review-human-disposition-requires-complete-current-valid-result'
    }
    if ($Decision -ceq 'accept-current' -and [string]$result.verdict -cne 'findings') { throw 'review-human-disposition-accept-requires-findings-result' }
    if ([string]::IsNullOrWhiteSpace($AuthorizedBy) -or [string]::IsNullOrWhiteSpace($AuthorizationRef) -or [string]::IsNullOrWhiteSpace($Rationale)) {
        throw 'review-human-disposition-requires-explicit-human-evidence'
    }
    $token = Get-ReviewCampaignStableToken -Value "$CampaignId/$RunId/$($result.target_digest)/$Decision/$AuthorizationRef" -Length 20
    $fact = [pscustomobject][ordered]@{
        schema_version = '1.0'; fact_type = 'human-disposition'; disposition_id = "disposition-$token"
        campaign_id = $CampaignId; run_id = $RunId; target_digest = [string]$result.target_digest; decision = $Decision
        authority_kind = 'human'; authorized_by = $AuthorizedBy; authorization_ref = $AuthorizationRef; rationale = $Rationale
        observed_at = [DateTimeOffset]::UtcNow.ToString('o')
    }
    $existing = @(Get-ReviewCampaignHumanDispositionFacts -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId | Where-Object { [string]$_.disposition_id -ceq $fact.disposition_id })
    if ($existing.Count -gt 0) {
        return [pscustomobject]@{ fact = $existing[0]; created = $false; idempotent = $true; path = $null }
    }
    try { $write = Write-ReviewCampaignHumanDispositionFact -StoreRoot $StoreRoot -Fact $fact }
    catch {
        if ($_.Exception.Message -notlike 'review-store-corruption:conflicting-immutable-fact:*') { throw }
        $existing = @(Get-ReviewCampaignHumanDispositionFacts -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId | Where-Object { [string]$_.disposition_id -ceq $fact.disposition_id })
        if ($existing.Count -ne 1) { throw }
        return [pscustomobject]@{ fact = $existing[0]; created = $false; idempotent = $true; path = $null }
    }
    return [pscustomobject]@{ fact = $fact; created = $write.created; idempotent = $write.idempotent; path = $write.path }
}

function Invoke-ReviewCampaignRun {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$StoreRoot,
        [Parameter(Mandatory)][string]$StagingRoot,
        [Parameter(Mandatory)][string]$CampaignId,
        [Parameter(Mandatory)][string]$RunId,
        [Parameter(Mandatory)][string]$ReservationId,
        [Parameter(Mandatory)][string]$TargetLineage,
        [Parameter(Mandatory)]$TargetPort,
        [Parameter(Mandatory)]$HarnessPort,
        [Parameter(Mandatory)]$RuntimePort,
        [Parameter(Mandatory)]$VerificationPort,
        [Parameter(Mandatory)]$ClockPort,
        [Parameter(Mandatory)][string]$PromptPath,
        [string]$ReviewScope = 'Review the complete frozen target and return the versioned candidate JSON contract.',
        [switch]$DesignContextEmpty,
        [ValidateScript({
            $limits = Get-ReviewAuthorityTimingLimits
            if ($_ -lt 1 -or $_ -gt [int]$limits.max_invocation_timeout_seconds) {
                throw "TimeoutSeconds must be between 1 and $($limits.max_invocation_timeout_seconds)."
            }
            return $true
        })][int]$TimeoutSeconds = 900,
        [scriptblock]$ProgressSink,
        [string]$AuthorityConfigPath
    )
    $authority = Get-ContinuousCoReviewAuthorityDecision -ConfigPath $AuthorityConfigPath
    if (-not $authority.campaign_authority_enabled) { return [pscustomobject]@{ status = 'suppressed'; reason = ('campaign-authority-disabled:' + $authority.reason); invoked = $false; result = $null } }
    $attemptStartedAt = Read-ReviewClockUtc -ClockPort $ClockPort
    $attemptMono = Read-ReviewClockMonotonic -ClockPort $ClockPort
    $progressWatch = [Diagnostics.Stopwatch]::StartNew()
    Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage 'requested' -Message 'run requested' -ElapsedMilliseconds 0 -TimeoutSeconds $TimeoutSeconds

    $placeholderDigest = 'pending-target'
    Write-ReviewRunAuthorityFact -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -Stage requested -Fact (New-ReviewRunStateFact -CampaignId $CampaignId -RunId $RunId -TargetDigest $placeholderDigest -HarnessId ([string]$HarnessPort.id) -State requested) | Out-Null
    $reservationResult = Request-ReviewCampaignReservationFact -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -ReservationId $ReservationId -ObservedAt (Read-ReviewClockUtc -ClockPort $ClockPort)
    if (-not $reservationResult.acquired) {
        Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage failed -Message ([string]$reservationResult.reason) -ProcessTreeLive $false -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
        return [pscustomobject]@{ status = 'not-started'; reason = $reservationResult.reason; invoked = $false; result = $null }
    }
    $reservation = $reservationResult.fact
    Write-ReviewRunAuthorityFact -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -Stage reserved -Fact (New-ReviewRunStateFact -CampaignId $CampaignId -RunId $RunId -TargetDigest $placeholderDigest -HarnessId ([string]$HarnessPort.id) -State reserved) | Out-Null

    $snapshot = $null; $disposeSnapshot = $true; $targetProtection = $null
    try {
        try {
            $snapshot = & $TargetPort.prepare $RunId
            $targetDigest = [string]$snapshot.target_digest
            $paths = Initialize-ReviewRunStaging -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId
            $verification = & $VerificationPort.execute $snapshot $paths
            if (-not [bool]$verification.ok) {
                $reason = [string]$verification.reason
                $endedAt = Read-ReviewClockUtc -ClockPort $ClockPort; $duration = [Math]::Max(0, (Read-ReviewClockMonotonic -ClockPort $ClockPort) - $attemptMono)
                $failed = Complete-ReviewPreInvocationFailure -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -Reservation $reservation -Spends @() -Reason $reason -ObservedAt $endedAt -StartedAt $attemptStartedAt -DurationMs $duration -RuntimeOutcome preflight-failed
                Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage failed -Message $reason -ProcessTreeLive $false -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
                return [pscustomobject]@{ status = 'failed'; reason = $reason; invoked = $false; result = $failed.result; result_path = $failed.result_path; report_path = $failed.report_path }
            }
            $effectiveReviewScope = $ReviewScope + [string]$verification.review_scope_suffix
            $deadline = ([DateTimeOffset]::Parse((Read-ReviewClockUtc -ClockPort $ClockPort))).AddSeconds($TimeoutSeconds).ToString('o')
            $invocation = [pscustomobject][ordered]@{
                schema_version = '1.0'; campaign_id = $CampaignId; run_id = $RunId; target_digest = $targetDigest
                snapshot_path = [string]$snapshot.snapshot_path; review_scope = $effectiveReviewScope; prompt_path = [IO.Path]::GetFullPath($PromptPath)
                candidate_result_path = $paths.candidate_result_path; candidate_report_path = $paths.candidate_report_path; deadline = $deadline
            }
            $contract = Test-ReviewAuthorityContractObject -ContractName ReviewInvocation -InputObject $invocation -ExpectedCampaignId $CampaignId -ExpectedRunId $RunId -ExpectedTargetDigest $targetDigest
            $targetReady = -not [string]::IsNullOrWhiteSpace($targetDigest) -and [IO.Directory]::Exists([string]$snapshot.snapshot_path) -and [IO.File]::Exists([string]$invocation.prompt_path)
            $protection = if ($TargetPort.PSObject.Properties['protect']) {
                & $TargetPort.protect $snapshot ([string]$invocation.candidate_result_path)
            }
            elseif ([string]$TargetPort.kind -ceq 'fixture') { [pscustomobject]@{ ok = $true; reason = 'fixture-read-only'; lease = $null } }
            else { [pscustomobject]@{ ok = $false; reason = 'review-target-protection-port-missing'; lease = $null } }
            if ([bool]$protection.ok) { $targetProtection = $protection.lease }
            # Protection is a prerequisite, not merely another collected boolean. Do not let a
            # host adapter inspect or bootstrap inside an unprotected governed target.
            $harnessReady = if ([bool]$protection.ok) { & $HarnessPort.preflight $invocation } else { [pscustomobject]@{ ok = $false; reason = 'blocked-by-target-protection' } }
            $runtimeReady = if ([bool]$protection.ok) { & $RuntimePort.preflight $invocation } else { [pscustomobject]@{ ok = $false; reason = 'blocked-by-target-protection' } }
            $preflight = @{ target = $targetReady; target_protection = [bool]$protection.ok; store = $true; contract = [bool]$contract.valid; containment = $targetReady; verification = [bool]$verification.ok; harness = [bool]$harnessReady.ok; runtime = [bool]$runtimeReady.ok }
            if (@($preflight.Values | Where-Object { -not [bool]$_ }).Count -gt 0) {
                $reason = 'preflight-failed:' + (@($preflight.Keys | Where-Object { -not [bool]$preflight[$_] } | Sort-Object) -join ',')
                if (-not [bool]$protection.ok) { $reason += ':' + [string]$protection.reason }
                $endedAt = Read-ReviewClockUtc -ClockPort $ClockPort; $duration = [Math]::Max(0, (Read-ReviewClockMonotonic -ClockPort $ClockPort) - $attemptMono)
                $failed = Complete-ReviewPreInvocationFailure -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -Reservation $reservation -Spends @() -Reason $reason -ObservedAt $endedAt -StartedAt $attemptStartedAt -DurationMs $duration -RuntimeOutcome preflight-failed
                Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage failed -Message $reason -ProcessTreeLive $false -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
                return [pscustomobject]@{ status = 'failed'; reason = $reason; invoked = $false; result = $failed.result; result_path = $failed.result_path; report_path = $failed.report_path }
            }
        }
        catch {
            $reason = 'preflight-failed:' + $_.Exception.Message
            $endedAt = Read-ReviewClockUtc -ClockPort $ClockPort; $duration = [Math]::Max(0, (Read-ReviewClockMonotonic -ClockPort $ClockPort) - $attemptMono)
            $failed = Complete-ReviewPreInvocationFailure -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId -TargetDigest $placeholderDigest -HarnessId ([string]$HarnessPort.id) -Reservation $reservation -Spends @() -Reason $reason -ObservedAt $endedAt -StartedAt $attemptStartedAt -DurationMs $duration -RuntimeOutcome preflight-failed
            Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage failed -Message $reason -ProcessTreeLive $false -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
            return [pscustomobject]@{ status = 'failed'; reason = $reason; invoked = $false; result = $failed.result; result_path = $failed.result_path; report_path = $failed.report_path }
        }

        Write-ReviewRunAuthorityFact -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -Stage preflighted -Fact (New-ReviewRunStateFact -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -State preflighted) | Out-Null
        try {
            $contractVersion = if ($HarnessPort.PSObject.Properties['contract_version']) { [string]$HarnessPort.contract_version } else { '1.0' }
            $priorResults = @(Get-ReviewAuthorityCampaignRunResults -StoreRoot $StoreRoot -CampaignId $CampaignId)
            $duplicate = Test-ReviewCampaignDuplicateCombination -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -ContractVersion $contractVersion -Runs $priorResults
            if ($duplicate.duplicate) {
                $priorIds = (@($duplicate.prior_run_ids | Select-Object -First 10) -join ',')
                Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage duplicate-warning -Message ("same target/harness/contract previously reviewed by: $priorIds") -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
            }
        }
        catch {
            # Duplicate detection is advisory and cannot block or authorize a run.
            $null = $_
        }
        $claim = Request-ReviewAuthorityClaim -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -TargetLineage $TargetLineage -ObservedAt (Read-ReviewClockUtc -ClockPort $ClockPort)
        if (-not $claim.acquired) {
            $endedAt = Read-ReviewClockUtc -ClockPort $ClockPort; $duration = [Math]::Max(0, (Read-ReviewClockMonotonic -ClockPort $ClockPort) - $attemptMono)
            $failed = Complete-ReviewPreInvocationFailure -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -Reservation $reservation -Spends @() -Reason ('claim-not-acquired:' + $claim.reason) -ObservedAt $endedAt -StartedAt $attemptStartedAt -DurationMs $duration -RuntimeOutcome claim-contended -Containment unknown
            Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage failed -Message ('claim-not-acquired:' + $claim.reason) -ProcessTreeLive $false -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
            return [pscustomobject]@{ status = 'not-started'; reason = ('claim-not-acquired:' + $claim.reason); invoked = $false; result = $failed.result; result_path = $failed.result_path; report_path = $failed.report_path }
        }
        Write-ReviewRunAuthorityFact -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -Stage claimed -Fact (New-ReviewRunStateFact -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -State claimed) | Out-Null
        Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage 'preflighted' -Message 'target, verification, store, contract, containment, harness, and runtime preflight passed' -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds

        $readClockCommand = Get-Command -Name 'Read-ReviewClockUtc' -CommandType Function
        $getFactsCommand = Get-Command -Name 'Get-ReviewAuthorityCampaignFacts' -CommandType Function
        $resolveSpendCommand = Get-Command -Name 'Resolve-ReviewCampaignSpendDecision' -CommandType Function
        $writeSpendCommand = Get-Command -Name 'Write-ReviewCampaignSpendFact' -CommandType Function
        $writeRunCommand = Get-Command -Name 'Write-ReviewRunAuthorityFact' -CommandType Function
        $newRunFactCommand = Get-Command -Name 'New-ReviewRunStateFact' -CommandType Function
        $newRecoveryFactCommand = Get-Command -Name 'New-ReviewRunRecoveryFact' -CommandType Function
        $writeRecoveryFactCommand = Get-Command -Name 'Write-ReviewRunRecoveryFact' -CommandType Function
        $writeProgressCommand = Get-Command -Name 'Write-ReviewOrchestrationProgress' -CommandType Function
        $onStarted = {
            param($runtimeReceipt)
            $startedAt = & $readClockCommand -ClockPort $ClockPort
            $recoveryFact = & $newRecoveryFactCommand -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) `
                -TargetLineage $TargetLineage -RuntimeReceipt $runtimeReceipt -Snapshot $snapshot -StagingRoot $StagingRoot `
                -InvocationStartedAt $startedAt -InvocationStartedMonotonicMs $attemptMono
            & $writeRecoveryFactCommand -StoreRoot $StoreRoot -Fact $recoveryFact | Out-Null
            $existingSpends = @(& $getFactsCommand -StoreRoot $StoreRoot -CampaignId $CampaignId -Kind spend)
            $existingReleases = @(& $getFactsCommand -StoreRoot $StoreRoot -CampaignId $CampaignId -Kind releases)
            $spendDecision = & $resolveSpendCommand -Reservation $reservation -InvocationStartedAt $startedAt -Preflight $preflight -Spends $existingSpends -Releases $existingReleases
            if (-not $spendDecision.permitted) { throw ('review-invocation-spend-refused:' + $spendDecision.reason) }
            & $writeSpendCommand -StoreRoot $StoreRoot -Fact $spendDecision.fact | Out-Null
            & $writeRunCommand -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -Stage invoked -Fact (& $newRunFactCommand -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -State invoked) | Out-Null
            & $writeProgressCommand -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage running -Message 'reviewer invoked under verified containment' -ProcessTreeLive $true -OutputActivity $false -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
        }.GetNewClosure()

        $runtimeProgress = {
            param($sample)
            $treeLive = if ($null -ne $sample -and $sample.PSObject.Properties['process_tree_live']) { $sample.process_tree_live } else { $true }
            $activity = if ($null -ne $sample -and $sample.PSObject.Properties['output_activity']) { $sample.output_activity } else { $null }
            & $writeProgressCommand -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage running -Message 'reviewer heartbeat; activity is not semantic progress' -ProcessTreeLive $treeLive -OutputActivity $activity -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
        }.GetNewClosure()

        try { $runtimeResult = & $RuntimePort.invoke $HarnessPort $invocation $onStarted $snapshot.suppression_environment $runtimeProgress }
        catch { $runtimeResult = [pscustomobject]@{ runtime_outcome = 'abandoned'; termination_verified = $false; containment = 'unknown'; failure_reason = ('runtime-adapter-failed:' + $_.Exception.Message); process_tree_live = $null; output_activity = $null } }
        $spends = @(Get-ReviewAuthorityCampaignFacts -StoreRoot $StoreRoot -CampaignId $CampaignId -Kind spend | Where-Object { [string]$_.run_id -ceq $RunId })
        $invoked = $spends.Count -gt 0
        if (-not $invoked) {
            $endedAt = Read-ReviewClockUtc -ClockPort $ClockPort; $duration = [Math]::Max(0, (Read-ReviewClockMonotonic -ClockPort $ClockPort) - $attemptMono)
            $reason = if ($runtimeResult.failure_reason) { [string]$runtimeResult.failure_reason } else { 'launch failed before invocation' }
            $failed = Complete-ReviewPreInvocationFailure -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -Reservation $reservation -Spends $spends -Reason $reason -ObservedAt $endedAt -StartedAt $attemptStartedAt -DurationMs $duration -RuntimeOutcome launch-failed -Containment unknown
            Complete-ReviewAuthorityClaim -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -TargetLineage $TargetLineage -Disposition abandoned -ObservedAt $endedAt | Out-Null
            Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage failed -Message $reason -ProcessTreeLive $false -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds
            return [pscustomobject]@{ status = 'failed'; reason = $reason; invoked = $false; result = $failed.result; result_path = $failed.result_path; report_path = $failed.report_path }
        }

        $observedUsage = if ($runtimeResult.PSObject.Properties['usage']) { $runtimeResult.usage } else { $null }
        Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage 'terminalizing' -Message 'runtime returned; validating target and candidate' -ProcessTreeLive $runtimeResult.process_tree_live -OutputActivity $runtimeResult.output_activity -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds -Usage $observedUsage
        $containment = [string]$runtimeResult.containment; $runtimeOutcome = [string]$runtimeResult.runtime_outcome
        $failureReason = [string]$runtimeResult.failure_reason
        try { $integrity = & $TargetPort.integrity $snapshot } catch { $integrity = [pscustomobject]@{ intact = $false; classification = 'integrity-check-failed' } }
        if (-not $integrity.intact) {
            $containment = 'violated'; $runtimeOutcome = 'containment-violated'
            $changedPaths = @()
            if ($integrity.PSObject.Properties['changed_paths']) {
                $changedPaths = @($integrity.changed_paths | ForEach-Object { [string]$_ } | Select-Object -First 20)
            }
            $integrityReason = 'target-integrity-failed:' + [string]$integrity.classification
            if ($changedPaths.Count -gt 0) { $integrityReason += ':' + ($changedPaths -join ',') }
            $failureReason = if ([string]::IsNullOrWhiteSpace($failureReason)) { $integrityReason } else { $failureReason + ';' + $integrityReason }
            $failureReason = ConvertTo-ReviewAuthorityBoundedText -Value $failureReason -MaximumLength 1900
        }
        try { $currentness = & $TargetPort.currentness $snapshot } catch { $currentness = [pscustomobject]@{ classification = 'unknown'; exact = $false; reason = 'currentness-check-failed' } }
        $endedAt = Read-ReviewClockUtc -ClockPort $ClockPort
        $duration = [Math]::Max(0, (Read-ReviewClockMonotonic -ClockPort $ClockPort) - $attemptMono)
        $startedAt = ConvertTo-ReviewObservedTimestampString -Value $spends[0].invocation_started_at
        $degradeReason = if ($DesignContextEmpty) { 'DESIGN_CONTEXT_EMPTY: no spec, design analysis, or formal contract resolved; this run is partial evidence and cannot approve the current target.' } else { $null }
        $ingress = Invoke-ReviewResultIngress -StoreRoot $StoreRoot -StagingRoot $StagingRoot -CampaignId $CampaignId -RunId $RunId -TargetDigest $targetDigest -HarnessId ([string]$HarnessPort.id) -RuntimeOutcome $runtimeOutcome -Invoked $true -TerminationVerified ([bool]$runtimeResult.termination_verified) -Containment $containment -Currentness ([string]$currentness.classification) -StartedAt $startedAt -EndedAt $endedAt -DurationMs $duration -FailureReason $failureReason -ControllerDegradeReason $degradeReason
        if ($ingress.published) {
            Complete-ReviewAuthorityClaim -StoreRoot $StoreRoot -CampaignId $CampaignId -RunId $RunId -TargetLineage $TargetLineage -Disposition released -ObservedAt (Read-ReviewClockUtc -ClockPort $ClockPort) | Out-Null
            $findingCount = if ($ingress.candidate_category -ceq 'valid' -and [string]$ingress.result.completion -ceq 'complete' -and [string]$ingress.result.validation -ceq 'valid') { @($ingress.result.findings).Count } else { $null }
            Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage 'terminal' -Message $ingress.reason -ProcessTreeLive $false -OutputActivity $runtimeResult.output_activity -ValidatedFindingCount $findingCount -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds -Usage $observedUsage
            return [pscustomobject]@{ status = 'terminal'; reason = $ingress.reason; invoked = $true; result = $ingress.result; result_path = $ingress.result_path; report_path = $ingress.report_path }
        }
        # A reviewer tree may still be using the frozen target. Recovery owns disposal after it proves
        # termination; removing the worktree here could race a live process or strand an OS-specific
        # cleanup failure.
        $disposeSnapshot = $false
        Write-ReviewOrchestrationProgress -Sink $ProgressSink -ClockPort $ClockPort -CampaignId $CampaignId -RunId $RunId -Stage failed -Message $ingress.reason -ProcessTreeLive $runtimeResult.process_tree_live -OutputActivity $runtimeResult.output_activity -ElapsedMilliseconds $progressWatch.ElapsedMilliseconds -TimeoutSeconds $TimeoutSeconds -Usage $observedUsage
        return [pscustomobject]@{ status = 'awaiting-termination-verification'; reason = $ingress.reason; invoked = $true; result = $null; result_path = $null }
    }
    finally {
        if ($null -ne $snapshot -and $disposeSnapshot) {
            if ($TargetPort.PSObject.Properties['unprotect']) { try { $null = & $TargetPort.unprotect $snapshot $targetProtection } catch { $null = $_ } }
            try { $null = & $TargetPort.dispose $snapshot } catch { $null = $_ }
        }
    }
}