scripts/internal/continuous-co-review/verification-plan-runner.ps1

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

# HARD dependency (DRIFT-198-I009-018): absent, declared-executable containment silently compared
# with a DIFFERENT case rule instead of the volume's own.
if (-not (Get-Command -Name 'Get-ContinuousCoReviewPathCaseSensitive' -ErrorAction SilentlyContinue)) {
    . (Join-Path $PSScriptRoot 'path-identity.ps1')
}

# T019 / FR-048 — the EXECUTION side of the framework-NEUTRAL verification plan (amended 2026-07-13).
# The thin, order-preserving wrapper that drives the universal T018 recorded-run runner
# (Invoke-ContinuousCoReviewRecordedRun) over the ORDERED commands a downstream command-plan SUPPLIER
# declared, and returns per-command, digest+command_id-bound, provenance-tagged evidence.
#
# WHAT THIS DOES (maintainer amendment 2026-07-13):
# - Consults the PURE state contract. A `verification-not-configured` plan (null/empty/all-invalid)
# runs NOTHING and fabricates NO success — { state='verification-not-configured'; command_count=0;
# evidence=@(); all_succeeded=$false }. all_succeeded is NEVER $true when nothing ran.
# - A `configured` plan executes COMMAND-BY-COMMAND IN DECLARED ORDER (never sorted). It RECORDS AN
# EVIDENCE RECORD FOR EVERY ATTEMPTED COMMAND, including failures: a non-zero exit / timeout /
# structurally-un-runnable command / required-result miss is recorded with command_succeeded=$false
# and a distinct reason — NEVER dropped and NEVER promoted to a clean result.
# - Timeout is ALWAYS the ENGINE-BOUNDED value (Resolve-ContinuousCoReviewVerificationTimeout) — never
# the raw 0/unlimited a supplier requested.
# - Each record binds to the reviewed-tree DIGEST and its COMMAND_ID; the recorder PERSISTS command_id +
# provenance (the object) + env_ref NAMES into the digest-keyed store (the T019 join identity).
# - working_directory + result_path are REPOSITORY-relative (the schema semantics) and are anchored
# against RepoRoot before execution - never against the caller process CWD.
# - env_refs have EXECUTION semantics AND are the AUTHORITATIVE allowlist: the child environment is built
# from an EMPTY map + the normative (currently empty) engine baseline + the declared env_ref NAMES
# (values resolved from the ambient environment at spawn) - every unlisted ambient value is structurally
# absent from the child. The executable is resolved to a full path BEFORE the environment is constructed,
# so an inherited PATH is never implicitly required. Env VALUES are NEVER recorded; persisted records
# carry NO output text for plan commands unless a HUMAN-AUTHORIZED, command-scoped diagnostic disclosure
# is supplied (count/hash only by default), so printed output cannot leak into evidence.
#
# WHAT THIS NEVER DOES: it never discovers, infers, selects, or invents a command — it executes EXACTLY
# what the plan declares. Command DISCOVERY is a separate downstream supplier's job; T018 executes and
# T019 injects, neither selects.

# The NORMATIVE, platform-specific ENGINE baseline (maintainer decision 2026-07-14, run 20260714T130410888
# finding f1): the ONLY variable names the engine may pass to a plan child WITHOUT a declared env_ref, and
# ONLY because runtime evidence proves the ENGINE ITSELF (constructed-environment process launch) requires
# them at spawn. CURRENT EVIDENCE (the paired 'engine baseline evidence' tests in
# verification-plan-runner.Tests.ps1, run on Windows AND Linux): a resolved-full-path child launches and
# exits 0 with a COMPLETELY EMPTY constructed environment on BOTH platforms - so the baseline is EMPTY on
# both. HOME / USERPROFILE / APPDATA / LOCALAPPDATA are excluded by maintainer ruling; PSModulePath, locale,
# terminal, and tool-specific variables are supplier-declared env_refs, never implicit baseline. Any future
# addition to this list MUST land together with a paired runtime-evidence test proving the engine fails to
# LAUNCH a child without it on that platform.
function Get-ContinuousCoReviewVerificationEngineBaseline {
    if ($IsWindows) { return @() }
    return @()
}

# The CONSTRUCTED child environment for a supplier-declared verification command (review findings f2 run
# 20260714T123137002 + f1 run 20260714T130410888 - env_refs are the AUTHORITATIVE allowlist): built from an
# EMPTY map, then EXACTLY the normative engine baseline (above; currently empty) + the plan-declared env_ref
# NAMES, each copied from the ambient environment AT SPAWN when present there. Every other ambient value -
# hence every ambient secret AND every implicit infrastructure value (HOME, PATH, TEMP, PSModulePath, ...) -
# is structurally ABSENT from the child process. A command that needs such a variable declares it as an
# env_ref (least privilege + reproducibility; the contract, not the engine, decides what flows). Values pass
# through process creation only; the evidence records env_ref NAMES separately and VALUES never.
function Get-ContinuousCoReviewVerificationChildEnvironment {
    param([string[]]$EnvRefs = @())
    $map = @{}
    $names = @(Get-ContinuousCoReviewVerificationEngineBaseline) + @(@($EnvRefs) | Where-Object { $_ -is [string] -and -not [string]::IsNullOrWhiteSpace($_) })
    foreach ($n in $names) {
        $v = [System.Environment]::GetEnvironmentVariable([string]$n)
        if ($null -ne $v) { $map[[string]$n] = [string]$v }
    }
    return $map
}

# EXECUTABLE RESOLUTION BEFORE SPAWN (maintainer decision 2026-07-14): the declared executable is resolved
# to a full path AGAINST THE AMBIENT PARENT ENVIRONMENT before the child environment is constructed, so the
# child never needs an inherited PATH just to be launched. A rooted path must exist; a relative path with a
# directory separator anchors to RepoRoot and must resolve INSIDE it; a bare name resolves via Get-Command
# (the parent's PATH, at spawn). An unresolvable executable is a RECORDED failure - never a silent skip.
function Resolve-ContinuousCoReviewVerificationExecutable {
    param(
        [Parameter(Mandatory)][string]$Executable,
        [Parameter(Mandatory)][string]$RepoRoot
    )
    if ([System.IO.Path]::IsPathRooted($Executable)) {
        if (Test-Path -LiteralPath $Executable -PathType Leaf) {
            return [pscustomobject]@{ resolved = $true; path = ([System.IO.Path]::GetFullPath($Executable)); method = 'rooted-path'; reason = $null }
        }
        return [pscustomobject]@{ resolved = $false; path = $null; method = 'rooted-path'; reason = "declared executable '$Executable' does not exist" }
    }
    if ($Executable -match '[\\/]') {
        $rootFull = ([System.IO.Path]::GetFullPath($RepoRoot)).TrimEnd([char]'\', [char]'/')
        $full = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($rootFull, $Executable))
        $rootPrefix = $rootFull + [System.IO.Path]::DirectorySeparatorChar
        # VOLUME-APPROPRIATE containment (review finding f1, run 20260714T172315119): on a
        # case-sensitive filesystem '../Repo/tool' is OUTSIDE '/tmp/repo'. 'same' is the refusing
        # direction here - when the volume cannot be determined, treat variants as one path so an
        # aliased executable is refused rather than admitted. (The previous call passed NO arguments.
        # That did not throw as first assumed: it resolved to a same-named zero-arg duplicate in
        # verification-plan-contract.ps1 that keyed off `$IsWindows`. That duplicate is now deleted.)
        $cmp = Get-ContinuousCoReviewPathComparison -Path $rootFull -WhenUndetermined 'same'
        if (-not $full.StartsWith($rootPrefix, $cmp)) {
            return [pscustomobject]@{ resolved = $false; path = $null; method = 'repo-relative'; reason = "declared executable '$Executable' resolves outside the repository root" }
        }
        if (Test-Path -LiteralPath $full -PathType Leaf) {
            return [pscustomobject]@{ resolved = $true; path = $full; method = 'repo-relative'; reason = $null }
        }
        return [pscustomobject]@{ resolved = $false; path = $null; method = 'repo-relative'; reason = "declared executable '$Executable' does not exist under the repository root" }
    }
    $cmd = @(Get-Command -Name $Executable -CommandType Application -ErrorAction SilentlyContinue) | Select-Object -First 1
    if ($null -ne $cmd -and -not [string]::IsNullOrWhiteSpace([string]$cmd.Source)) {
        return [pscustomobject]@{ resolved = $true; path = [string]$cmd.Source; method = 'ambient-path-resolution'; reason = $null }
    }
    return [pscustomobject]@{ resolved = $false; path = $null; method = 'ambient-path-resolution'; reason = "declared executable '$Executable' is not resolvable on the engine's PATH" }
}

# One digest+command_id-bound FAILURE record for an attempted-but-unsuccessful command. Shaped close to
# a real recorded-run record so the T019 evidence-join validator and downstream readers treat it
# uniformly; command_succeeded is ALWAYS $false and the reason/classification name why.
function New-ContinuousCoReviewVerificationFailureRecord {
    param(
        [string]$Executable,
        [string[]]$Arguments = @(),
        [string]$CommandId,
        $Provenance,
        [string[]]$EnvRefs = @(),
        [AllowNull()][string]$DigestTreeId,
        [string]$Classification,
        [string]$Reason,
        [datetime]$Now = [datetime]::UtcNow
    )
    $stamp = $Now.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
    return [pscustomobject]@{
        command                 = [pscustomobject]@{ executable = [string]$Executable; arguments = @($Arguments); working_directory = $null }
        command_id              = [string]$CommandId
        provenance              = $Provenance
        env_refs                = @($EnvRefs)
        reviewed_digest_tree_id = $DigestTreeId
        started_at              = $stamp
        ended_at                = $stamp
        duration_seconds        = 0
        exit_code               = $null
        timed_out               = $false
        command_succeeded       = $false
        counts_available        = $false
        test_result             = $null
        attempted               = $true
        classification          = $Classification
        failure_reason          = $Reason
        recorded_at             = $stamp
    }
}

# THE SELECTED-PLAN RESOLVER (maintainer wiring directive 2026-07-15): the ONE production seam that loads
# the project's SELECTED VerificationPlan for the evidence-injection boundary and any other consumer. The
# FR-049 supplier chain (config-authoritative -> detected -> profile -> provider-gated) TARGETS this seam by
# writing its selected plan to the canonical config-authoritative location `.specrew/verification-plan.json`.
# Returns { available; plan; source; reason } and NEVER throws:
# - file absent -> available=$false (the explicit no-supplier state; consumers fail closed)
# - unparseable JSON -> available=$false + a LOUD warn (a broken supplier output is never silently 'none')
# - structurally invalid -> available=$false + a LOUD warn naming the schema violation
# - valid -> available=$true with the parsed plan
# It NEVER derives a plan from evidence, defaults, or discovery - selection is the supplier's job (FR-048/FR-049).
function Get-ContinuousCoReviewSelectedVerificationPlan {
    param([Parameter(Mandatory)][string]$RepoRoot)
    $result = [pscustomobject]@{ available = $false; plan = $null; source = $null; reason = $null }
    try {
        $path = Join-Path $RepoRoot '.specrew/verification-plan.json'
        if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
            $result.reason = 'no supplier output at .specrew/verification-plan.json (FR-049 supplier not configured)'
            return $result
        }
        $plan = $null
        try { $plan = Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json }
        catch {
            $result.reason = "selected plan at '$path' is unparseable: $($_.Exception.Message)"
            [Console]::Error.WriteLine("[co-review] WARN SELECTED_PLAN_UNREADABLE $($result.reason)")
            return $result
        }
        if (-not (Get-Command -Name 'Test-ContinuousCoReviewVerificationPlan' -ErrorAction SilentlyContinue)) {
            $cp = Join-Path $PSScriptRoot 'verification-plan-contract.ps1'; if (Test-Path -LiteralPath $cp -PathType Leaf) { . $cp }
        }
        $structural = Test-ContinuousCoReviewVerificationPlan -Plan $plan -RepoRoot $RepoRoot
        if (-not [bool]$structural.valid) {
            $result.reason = "selected plan at '$path' is schema-invalid: $($structural.reason)"
            [Console]::Error.WriteLine("[co-review] WARN SELECTED_PLAN_INVALID $($result.reason)")
            return $result
        }
        $result.available = $true
        $result.plan = $plan
        $result.source = $path
        return $result
    }
    catch {
        $result.reason = "selected-plan resolution failed: $($_.Exception.Message)"
        return $result
    }
}

function Invoke-ContinuousCoReviewVerificationPlan {
    <#
        Execute a framework-neutral VerificationPlan in DECLARED order. Returns
        { state; command_count; evidence[]; all_succeeded }.
 
        - verification-not-configured -> { state='verification-not-configured'; command_count=0;
          evidence=@(); all_succeeded=$false } WITHOUT executing anything and WITHOUT fabricating success.
        - configured -> each command executed IN ORDER via the universal recorded-run runner with an
          ENGINE-BOUNDED timeout; EVERY attempt is recorded (successes AND failures) in order, each
          tagged with its command_id, provenance object, and env_ref NAMES; all_succeeded is $true only
          when at least one command ran and EVERY record has command_succeeded=$true.
    #>

    param(
        [Parameter(Mandatory)][string]$RepoRoot,
        [Parameter(Mandatory)][AllowNull()]$Plan,
        # OPTIONAL human-authorized, command-scoped diagnostic disclosure (maintainer decision 2026-07-14):
        # { authorized_by; reason; command_id; max_tail_bytes? }. NEVER automatic - absent means every plan
        # command persists NO output text. Validated FAIL-FAST here (a structurally invalid authorization is
        # a caller error and runs ZERO commands); scoping to the named command_id is enforced by the recorder.
        [AllowNull()]$DiagnosticDisclosure = $null
    )

    # Load guard (same pattern as test-evidence-recorder.ps1): make the universal recorded-run runner +
    # the reviewed-state digest available when this file is used standalone.
    if (-not (Get-Command -Name 'Invoke-ContinuousCoReviewRecordedRun' -ErrorAction SilentlyContinue) -or
        -not (Get-Command -Name 'Get-ContinuousCoReviewReviewedStateDigest' -ErrorAction SilentlyContinue)) {
        $lp = Join-Path $PSScriptRoot '_load.ps1'; if (Test-Path -LiteralPath $lp -PathType Leaf) { . $lp }
    }
    # The PURE plan contract + the StrictMode-safe property reader.
    if (-not (Get-Command -Name 'Resolve-ContinuousCoReviewVerificationPlanState' -ErrorAction SilentlyContinue)) {
        $cp = Join-Path $PSScriptRoot 'verification-plan-contract.ps1'; if (Test-Path -LiteralPath $cp -PathType Leaf) { . $cp }
    }

    $resolvedRoot = (Resolve-Path -LiteralPath $RepoRoot).Path

    # NULL plan = the explicit NO-SUPPLIER state (verification-not-configured): nothing was declared, so there
    # is nothing to validate. Everything else validates FIRST.
    if ($null -eq $Plan) {
        return [pscustomobject]@{ state = 'verification-not-configured'; command_count = 0; evidence = @(); all_succeeded = $false; reason = 'plan is null' }
    }

    # STRUCTURAL GATE BEFORE THE STATE GATE (maintainer decision 2026-07-13; reordered per review finding f2,
    # run 20260714T190233598): a SUPPLIED plan is validated against the FULL closed schema (schema_version const,
    # closed key sets, typed fields, identity graph) BEFORE any state classification - a schema-invalid plan
    # (e.g. {commands:[]} with no schema_version, or an all-invalid commands list) must fail LOUDLY as
    # verification-plan-invalid with ZERO execution, never downgrade to the benign not-configured state.
    $structural = Test-ContinuousCoReviewVerificationPlan -Plan $Plan -RepoRoot $resolvedRoot
    if (-not $structural.valid) {
        return [pscustomobject]@{ state = 'verification-plan-invalid'; command_count = $structural.command_count; evidence = @(); all_succeeded = $false; reason = $structural.reason }
    }

    # STATE GATE: a SCHEMA-VALID plan with zero commands is the explicit not-configured state - it runs NOTHING
    # and fabricates NO success.
    $state = Resolve-ContinuousCoReviewVerificationPlanState -Plan $Plan -RepoRoot $resolvedRoot
    if ($state.state -eq 'verification-not-configured') {
        return [pscustomobject]@{ state = 'verification-not-configured'; command_count = 0; evidence = @(); all_succeeded = $false; reason = $state.reason }
    }

    # DIAGNOSTIC-DISCLOSURE GATE (fail-fast, zero side effects): a structurally invalid authorization object
    # is a CALLER error - refuse it BEFORE any command executes, exactly like the malformed-identity gate.
    if ($null -ne $DiagnosticDisclosure) {
        $dv = Test-ContinuousCoReviewDiagnosticDisclosure -Disclosure $DiagnosticDisclosure
        if (-not [bool]$dv.valid) {
            throw "verification-plan: the supplied DiagnosticDisclosure is INVALID ($($dv.reason)) - refusing to run ANY command under a malformed disclosure authorization (never automatic, never degraded)."
        }
    }

    # Best-effort current digest, used to STAMP synthetic failure records so every record (real +
    # synthetic) binds to the same reviewed-tree identity. Real records carry their own (identical) id.
    $digestTreeId = $null
    try {
        $dg = Get-ContinuousCoReviewReviewedStateDigest -RepoRoot $resolvedRoot
        if (($null -ne $dg) -and [bool]$dg.ok) { $digestTreeId = [string]$dg.tree_id }
    }
    catch { $digestTreeId = $null }

    # PERSIST every synthetic failure into the digest-keyed store (review finding f4, run 20260714T182921446):
    # FR-048's record-every-attempt means the DURABLE store the reviewer injection reads, not only this
    # invocation's return value - an unpersisted attempted-failure would be missing reviewer evidence. Write
    # failures throw (the same fail-loud posture as the real recorder's store write). Digest-unavailable ->
    # the store has no key; WARN and keep the record in-memory only (the fail-loud digest gate already stops
    # REAL runs in that state).
    $persistFailure = {
        param($rec)
        if (-not [string]::IsNullOrWhiteSpace([string]$digestTreeId)) {
            Save-ContinuousCoReviewRunRecord -RepoRoot $resolvedRoot -TreeId $digestTreeId -Entry $rec -CommandId ([string]$rec.command_id) -Executable ([string]$rec.command.executable) -Arguments @($rec.command.arguments) -WorkingDirectory ([string]$rec.command.working_directory) -RecordedAt ([string]$rec.recorded_at)
        }
        else {
            [Console]::Error.WriteLine('[co-review] WARN SYNTHETIC_FAILURE_NOT_PERSISTED reviewed-state digest unavailable; the attempt record exists only in this invocation''s return value.')
        }
        return $rec
    }

    # CONFIGURED: execute each declared command IN ORDER (never sorted); RECORD EVERY ATTEMPT.
    # Array-preserving read (NO @() wrapper — the accessor's `, $val` return would otherwise nest as a
    # single wrapper element; the bare assignment captures the real command array, 1 or N elements).
    $commands = Get-ContinuousCoReviewVerificationRawProp -Object $Plan -Name 'commands'
    $evidence = @()
    foreach ($cmd in $commands) {
        # StrictMode-safe field reads: every field except command_id/executable/provenance is optional.
        $commandId = [string](Get-ContinuousCoReviewContractProp -Object $cmd -Name 'command_id')
        $executable = [string](Get-ContinuousCoReviewContractProp -Object $cmd -Name 'executable')
        $argsRaw = Get-ContinuousCoReviewContractProp -Object $cmd -Name 'arguments'
        $arguments = if ($null -eq $argsRaw) { @() } else { @($argsRaw) }
        $workingDir = [string](Get-ContinuousCoReviewContractProp -Object $cmd -Name 'working_directory')
        # WIDE-typed passthrough (review finding f2, run 20260714T193411985): no [int] narrowing here - a
        # contract-valid Int64/BigInteger timeout must reach the policy CLAMP, never throw an overflow that
        # aborts the plan without a durable attempt record.
        $timeoutRaw = Get-ContinuousCoReviewContractProp -Object $cmd -Name 'timeout_seconds'
        $requestedTimeout = if ($null -eq $timeoutRaw) { 0 } else { $timeoutRaw }
        $resultPath = [string](Get-ContinuousCoReviewContractProp -Object $cmd -Name 'result_path')
        $requireResult = [bool](Get-ContinuousCoReviewContractProp -Object $cmd -Name 'require_result')
        $provenance = Get-ContinuousCoReviewContractProp -Object $cmd -Name 'provenance'
        $envRefsRaw = Get-ContinuousCoReviewContractProp -Object $cmd -Name 'env_refs'
        # env_ref NAMES only — the VALUES are never read or recorded (redaction by construction).
        $envRefs = @(@($envRefsRaw) | Where-Object { $_ -is [string] -and -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { [string]$_ })

        # STRUCTURALLY UN-RUNNABLE: an invalid command (bad id/executable/args/provenance/env/path) is
        # recorded as a failed attempt and skipped — never executed, never dropped, never made clean.
        $cmdCheck = Test-ContinuousCoReviewVerificationCommand -Command $cmd -RepoRoot $resolvedRoot
        if (-not $cmdCheck.valid) {
            $evidence += (& $persistFailure (New-ContinuousCoReviewVerificationFailureRecord -Executable $executable -Arguments $arguments -CommandId $commandId -Provenance $provenance -EnvRefs $envRefs -DigestTreeId $digestTreeId -Classification 'structurally-un-runnable' -Reason $cmdCheck.reason -Now ([datetime]::UtcNow)))
            continue
        }

        # require_result with nowhere to write the result can NEVER be satisfied -> verification failure.
        if ($requireResult -and [string]::IsNullOrWhiteSpace($resultPath)) {
            $evidence += (& $persistFailure (New-ContinuousCoReviewVerificationFailureRecord -Executable $executable -Arguments $arguments -CommandId $commandId -Provenance $provenance -EnvRefs $envRefs -DigestTreeId $digestTreeId -Classification 'required-result-missing-or-invalid' -Reason 'require_result=true but no result_path declared to satisfy it' -Now ([datetime]::UtcNow)))
            continue
        }

        # ENGINE-BOUNDED timeout — never the raw 0/unlimited a supplier requested.
        $boundedTimeout = (Resolve-ContinuousCoReviewVerificationTimeout -Requested $requestedTimeout).effective_seconds

        # RESOLVE THE EXECUTABLE BEFORE SPAWN (maintainer decision 2026-07-14): full-path resolution against
        # the AMBIENT parent environment happens here, so the constructed child environment never needs an
        # inherited PATH for launch. Unresolvable -> a RECORDED failure; the plan continues (never dropped).
        $exeResolution = Resolve-ContinuousCoReviewVerificationExecutable -Executable $executable -RepoRoot $resolvedRoot
        if (-not [bool]$exeResolution.resolved) {
            $evidence += (& $persistFailure (New-ContinuousCoReviewVerificationFailureRecord -Executable $executable -Arguments $arguments -CommandId $commandId -Provenance $provenance -EnvRefs $envRefs -DigestTreeId $digestTreeId -Classification 'executable-not-resolvable' -Reason $exeResolution.reason -Now ([datetime]::UtcNow)))
            continue
        }

        $runParams = @{
            RepoRoot           = $resolvedRoot
            Executable         = $exeResolution.path
            DeclaredExecutable = $executable
            Arguments          = $arguments
            TimeoutSeconds   = $boundedTimeout
            RequireResult    = $requireResult
            # IDENTITY INTO THE DURABLE RECORD (review finding f5, run 20260714T123137002): the recorder
            # persists command_id + provenance + env_ref NAMES into the digest-keyed store BEFORE
            # serialization, so the persisted run joins on the authoritative command_id + reviewed digest.
            CommandId        = $commandId
            Provenance       = $provenance
            EnvRefs          = @($envRefs)
            # NO PERSISTED OUTPUT TEXT for supplier-declared commands (review finding f3 + maintainer
            # decision 2026-07-14): arbitrary plan output may carry secrets no pattern can recognize, so
            # tails are SUPPRESSED (count/hash only); the recorded facts (exit code, duration, digest,
            # optional SpecrewTestResult) carry the verdict. The ONLY door is the human-authorized,
            # command_id-scoped DiagnosticDisclosure the recorder enforces - never automatic.
            OutputTailBytes  = 0
            # ENV ALLOWLIST EXECUTION SEMANTICS (review findings f2 + f1 run 20260714T130410888): the child
            # receives an EMPTY-map-constructed environment - the normative engine baseline + the declared
            # env_refs only, resolved from the ambient environment at spawn - every unlisted ambient value
            # is structurally absent. Values are never recorded.
            ChildEnvironment = (Get-ContinuousCoReviewVerificationChildEnvironment -EnvRefs $envRefs)
        }
        if ($null -ne $DiagnosticDisclosure) { $runParams.DiagnosticDisclosure = $DiagnosticDisclosure }
        # REPO-ROOT ANCHORING (review finding f4): the schema defines working_directory AND result_path as
        # REPOSITORY-relative. Anchor BOTH against RepoRoot here so execution is independent of the caller
        # process CWD and result_path never silently re-anchors to the working directory. (Path safety was
        # validated above; the anchored absolute paths are inside RepoRoot by construction.)
        if (-not [string]::IsNullOrWhiteSpace($workingDir)) { $runParams.WorkingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($resolvedRoot, $workingDir)) }
        if (-not [string]::IsNullOrWhiteSpace($resultPath)) { $runParams.ResultPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($resolvedRoot, $resultPath)) }

        try {
            # Execute EXACTLY what the command declares (never inferred/invented) via the universal runner.
            $ev = Invoke-ContinuousCoReviewRecordedRun @runParams
            # command_id/provenance/env_refs are persisted INTO the durable record by the recorder (finding
            # f5); only the runner-level annotations are tagged here (in-memory, per-attempt).
            $ev | Add-Member -NotePropertyName 'attempted' -NotePropertyValue $true -Force
            $ev | Add-Member -NotePropertyName 'classification' -NotePropertyValue 'executed' -Force
            $evidence += $ev
        }
        catch {
            # The universal runner FAILS LOUD (a REQUIRED-but-missing/invalid result, a start failure, or
            # a digest-unavailable bind). Amendment 8/9: that failure is RECORDED as a failed command,
            # never thrown away and never promoted to a clean result.
            $msg = [string]$_.Exception.Message
            $cls = if ($requireResult -and (($msg -match 'REQUIRED') -or ($msg -match 'INVALID'))) { 'required-result-missing-or-invalid' } else { 'recorded-run-failed' }
            # OBSERVED-FACTS PRESERVATION (review finding f1, run 20260714T215545754): a required-result miss
            # now PERSISTS the full real process record (timestamps, exit code, output metadata) before its
            # fail-loud throw - REUSE that record (the latest attempt for this command_id at the digest)
            # instead of substituting a zero-detail synthetic one. Anything else (start failure,
            # digest-unavailable, recording failure) still synthesizes.
            $reused = $null
            if ($cls -eq 'required-result-missing-or-invalid' -and -not [string]::IsNullOrWhiteSpace([string]$digestTreeId)) {
                try {
                    $storeRec = Get-Content -LiteralPath (Join-Path (Get-ContinuousCoReviewTestEvidenceDirectory -RepoRoot $resolvedRoot) ($digestTreeId + '.json')) -Raw | ConvertFrom-Json
                    $mine = @(@($storeRec.runs) | Where-Object { $null -ne $_ -and (@($_.PSObject.Properties.Name) -contains 'command_id') -and ([string]$_.command_id -ceq $commandId) })
                    if (@($mine).Count -gt 0) {
                        $reused = @($mine | Sort-Object { if (@($_.PSObject.Properties.Name) -contains 'attempt') { [int]$_.attempt } else { 1 } })[-1]
                    }
                }
                catch { $reused = $null }
            }
            if ($null -ne $reused) {
                $reused | Add-Member -NotePropertyName 'attempted' -NotePropertyValue $true -Force
                $reused | Add-Member -NotePropertyName 'classification' -NotePropertyValue $cls -Force
                $evidence += $reused
            }
            else {
                $evidence += (& $persistFailure (New-ContinuousCoReviewVerificationFailureRecord -Executable $executable -Arguments $arguments -CommandId $commandId -Provenance $provenance -EnvRefs $envRefs -DigestTreeId $digestTreeId -Classification $cls -Reason $msg -Now ([datetime]::UtcNow)))
            }
        }
    }

    $failedCount = @($evidence | Where-Object { -not [bool]$_.command_succeeded }).Count
    $allSucceeded = (@($evidence).Count -gt 0) -and ($failedCount -eq 0)
    return [pscustomobject]@{ state = 'configured'; command_count = @($commands).Count; evidence = @($evidence); all_succeeded = $allSucceeded; reason = $null }
}