Payload/scripts/kaden/utils/repair-inherited-recorder-source.ps1

<#
.SYNOPSIS
    Append script-runner corrections for harness-script rows stamped parent-inherited.

.DESCRIPTION
    Finds PhaseStatusChanged, SensorRunRecorded, and GateCheckRun rows whose recorder
    tool is harness-script and whose model_source is parent-inherited. Those rows are
    the role mis-stamp: the parent copied ModelRole subagent onto harness-record-phase.
    The original lines stay in the journal. This script appends a new row with
    refs.amends_event_id set to the original event id and model_source script-runner.
    The inherited-source gate reads that chain tip. An ActivityAdjusted row does not.

    Rows whose linked artifact already attests a child are left alone. A real
    cursor-task or copilot-agent delegation is left alone. A feature-phase correction
    does not move current_phase.

    Nothing is written unless -Apply is present.

.PARAMETER Feature
    Feature slug under docs/harness/features. Default: host-surface-consistency.

.PARAMETER RepoRoot
    Repository root. Default: walk up from this script to scripts/kaden/harness-invoke.ps1.

.PARAMETER FeatureRoot
    Existing feature folder. Default: docs/harness/features/<Feature>.

.PARAMETER AllowArbitraryFeatureRoot
    Allow -FeatureRoot outside docs/harness/features. For a fixture.

.PARAMETER Apply
    Append the correction rows and rebuild .review-loop.json.

.EXAMPLE
    pwsh -NoProfile -File .\scripts\kaden\utils\repair-inherited-recorder-source.ps1

.EXAMPLE
    pwsh -NoProfile -File .\scripts\kaden\utils\repair-inherited-recorder-source.ps1 -Feature host-surface-consistency -Apply

.EXAMPLE
    Get-Help .\scripts\kaden\utils\repair-inherited-recorder-source.ps1 -Parameter Apply
#>


#Requires -Version 5.1
[CmdletBinding(DefaultParameterSetName = 'Repair')]
param(
    [Parameter(ParameterSetName = 'Repair')]
    [ValidateNotNullOrEmpty()]
    [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9_-]*$')]
    [string]$Feature = 'host-surface-consistency',

    [Parameter(ParameterSetName = 'Repair')]
    [string]$RepoRoot = '',

    [Parameter(ParameterSetName = 'Repair')]
    [string]$FeatureRoot = '',

    [Parameter(ParameterSetName = 'Repair')]
    [switch]$AllowArbitraryFeatureRoot,

    [Parameter(ParameterSetName = 'Repair')]
    [switch]$Apply,

    [Parameter(ParameterSetName = 'Help')]
    [Alias('h', 'help')]
    [switch]$ShowHelp
)

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

$cliKitManifest = Join-Path $PSScriptRoot 'PsCliUiKit\PsCliUiKit.psd1'
if (-not (Test-Path -LiteralPath $cliKitManifest)) {
    throw "PsCliUiKit manifest not found: $cliKitManifest"
}
Import-Module -Name $cliKitManifest -Force

function Resolve-RepairInheritedRepoRoot {
    param([string]$RequestedRoot)

    if (-not [string]::IsNullOrWhiteSpace($RequestedRoot)) {
        $resolved = (Resolve-Path -LiteralPath $RequestedRoot).Path
        $marker = Join-Path $resolved 'scripts\kaden\harness-invoke.ps1'
        if (-not (Test-Path -LiteralPath $marker)) {
            throw "RepoRoot is not a Kaden checkout (missing scripts\kaden\harness-invoke.ps1): $resolved"
        }
        return $resolved
    }

    $current = $PSScriptRoot
    while ($true) {
        $marker = Join-Path $current 'scripts\kaden\harness-invoke.ps1'
        if (Test-Path -LiteralPath $marker) {
            return $current
        }
        $parent = Split-Path $current -Parent
        if ([string]::IsNullOrWhiteSpace($parent) -or $parent -eq $current) {
            throw 'Could not locate repository root (scripts\kaden\harness-invoke.ps1) from this script.'
        }
        $current = $parent
    }
}

function Copy-RepairInheritedPayload {
    param($Payload)

    $copy = [ordered]@{}
    if ($null -ne $Payload) {
        foreach ($property in @($Payload.PSObject.Properties)) {
            $copy[$property.Name] = $property.Value
        }
    }
    $copy['model_source'] = 'script-runner'
    return [pscustomobject]$copy
}

try {
    if ($ShowHelp) {
        (Get-Help -Name $PSCommandPath -Full | Out-String -Width 100) | Out-Host
        return
    }

    Write-CliSection -Title 'Repair inherited recorder source' -Style info

    $root = Resolve-RepairInheritedRepoRoot -RequestedRoot $RepoRoot
    Import-Module (Join-Path $root 'scripts\kaden\harness\lib\HarnessContract.psm1') -Force
    Import-Module (Join-Path $root 'scripts\kaden\harness\lib\HarnessJournal.psm1') -Force
    Import-Module (Join-Path $root 'scripts\kaden\harness\lib\HarnessGateProjection.psm1') -Force

    $featureRootPath = Get-HarnessFeatureRoot -RepoRoot $root -Feature $Feature -FeatureRootOverride $FeatureRoot -AllowArbitraryFeatureRoot:$AllowArbitraryFeatureRoot
    $journalPath = Join-Path $featureRootPath 'journal\events.jsonl'
    if (-not (Test-Path -LiteralPath $journalPath)) {
        throw "Journal not found: $journalPath"
    }

    $watchedKinds = @('PhaseStatusChanged', 'SensorRunRecorded', 'GateCheckRun')
    $watched = @(
        Get-HarnessJournalEvents -FeatureRoot $featureRootPath |
            Where-Object { $watchedKinds -contains [string]$_.kind }
    )
    $tips = @(Select-HarnessGreenAmendmentChainTip -Events $watched)
    $targets = [System.Collections.Generic.List[object]]::new()
    foreach ($evt in $tips) {
        $source = (Get-HarnessEventModelSource -JournalEvent $evt).ToLowerInvariant()
        if ($source -ne 'parent-inherited') {
            continue
        }
        if ((Get-HarnessEventActorTool -JournalEvent $evt) -ne 'harness-script') {
            continue
        }
        $linked = Get-HarnessPhaseSensorLinkedArtifactRel -JournalEvent $evt
        if (Test-HarnessLinkedArtifactAttestsChildModel -FeatureRoot $featureRootPath -LinkedArtifact $linked) {
            continue
        }
        $targets.Add($evt)
    }

    Write-CliKeyValue -Label 'feature' -Value $Feature
    Write-CliKeyValue -Label 'rows' -Value ([string]$targets.Count)
    foreach ($evt in $targets) {
        $seq = [string](Get-HarnessProperty -Object $evt -Name 'seq' -Default '')
        $slice = [string](Get-HarnessProperty -Object $evt -Name 'slice' -Default '')
        $eventId = [string](Get-HarnessProperty -Object $evt -Name 'event_id' -Default '')
        Write-CliKeyValue -Label "seq $seq" -Value "$slice $eventId"
    }

    if ($targets.Count -eq 0) {
        Write-CliStatus -State 'Info' -Message 'No harness-script parent-inherited rows need a correction.'
        return
    }

    if (-not $Apply) {
        Write-CliStatus -State 'Info' -Message 'Nothing was written. Pass -Apply to append the script-runner corrections.'
        return
    }

    $written = [System.Collections.Generic.List[string]]::new()
    foreach ($evt in $targets) {
        $eventId = [string](Get-HarnessProperty -Object $evt -Name 'event_id' -Default '')
        $slice = [string](Get-HarnessProperty -Object $evt -Name 'slice' -Default '')
        $payload = Get-HarnessProperty -Object $evt -Name 'payload' -Default $null
        $activity = Get-HarnessProperty -Object $evt -Name 'activity' -Default $null
        $actor = Get-HarnessProperty -Object $evt -Name 'actor' -Default $null
        $model = Get-HarnessProperty -Object $actor -Name 'model' -Default $null
        $provider = [string](Get-HarnessProperty -Object $model -Name 'provider' -Default 'other')
        $slug = [string](Get-HarnessProperty -Object $model -Name 'slug' -Default 'unknown')
        $agent = [string](Get-HarnessProperty -Object $actor -Name 'agent' -Default 'operator')
        $activityType = [string](Get-HarnessProperty -Object $activity -Name 'type' -Default 'pipeline.phase')
        $activityName = [string](Get-HarnessProperty -Object $activity -Name 'name' -Default 'quality-gates')
        $refsIn = Get-HarnessProperty -Object $evt -Name 'refs' -Default $null
        $artifact = [string](Get-HarnessProperty -Object $refsIn -Name 'artifact' -Default '')
        $turnId = Get-HarnessProperty -Object $refsIn -Name 'turn_id' -Default $null
        $fixActor = New-HarnessJournalActor -Agent $agent -ParentAgent $null -Tool 'harness-script' `
            -Provider $provider -Slug $slug -Role 'primary' -ModelSource 'script-runner'
        $fixActivity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type $activityType -Name $activityName -Role 'instant'
        $fixRefs = [pscustomobject]@{
            amends_event_id = $eventId
            pair_event_id   = $null
            artifact        = $(if ([string]::IsNullOrWhiteSpace($artifact)) { $null } else { $artifact })
            command         = '.\scripts\kaden\utils\repair-inherited-recorder-source.ps1'
            turn_id         = $turnId
        }
        $added = Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $slice `
            -Kind ([string]$evt.kind) -Activity $fixActivity -Actor $fixActor -Outcome $null `
            -Summary "Recorder source correction amends $eventId. Honest source is script-runner." `
            -Payload (Copy-RepairInheritedPayload -Payload $payload) -Refs $fixRefs
        $written.Add([string]$added.event_id)
    }

    Update-HarnessGateProjectionFromJournal -FeatureRoot $featureRootPath -Feature $Feature -RepoRoot $root -Write | Out-Null
    Write-CliSection -Title 'Recorder source corrected' -Style success
    foreach ($id in $written) {
        Write-CliKeyValue -Label 'event' -Value $id
    }
} catch {
    Write-CliSection -Title 'Recorder source repair failed' -Style 'error'
    Write-CliStatus -State 'Error' -Message $_.Exception.Message
    exit 2
} finally {
    Write-CliEnd
}