Payload/scripts/kaden/harness/harness-record-task.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Journal background TaskStarted / Heartbeat / TimedOut / Recovered / Abandoned events. .DESCRIPTION Escape hatch for long-running work (mutation:diff, nested Task subagents) so stalled jobs leave an accountable ledger trail instead of silent open turns. Does not kill processes -- agents/operators still decide recover vs abandon. .EXAMPLE pwsh -File scripts/kaden/harness-invoke.ps1 harness-record-task.ps1 -Feature saved-search-queries -Slice mo-save-list -Action start -TaskName mutation:diff -TtlMinutes 30 -ExpectedOutputPath reports/stryker-incremental.json .EXAMPLE pwsh -File scripts/kaden/harness-invoke.ps1 harness-record-task.ps1 -Feature saved-search-queries -Slice mo-save-list -Action timeout -TaskId act_ABC -Summary "mutation:diff exceeded TTL" #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Feature, [Parameter(Mandatory = $true)] [string]$Slice, [Parameter(Mandatory = $true)] [ValidateSet('start', 'heartbeat', 'timeout', 'recover', 'abandon')] [string]$Action, [string]$TaskName = '', [string]$TaskId = '', [int]$TtlMinutes = 0, [string]$ExpectedOutputPath = '', [string]$Summary = '', [ValidateSet('cursor', 'copilot', 'other')] [string]$ModelProvider = 'copilot', [string]$ModelSlug = 'unknown', [string]$Agent = 'atdd-developer', [string]$ParentAgent = '', [string]$FeatureRoot = '', [string]$RepoRoot = '', [switch]$AllowArbitraryFeatureRoot ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' Import-Module (Join-Path $PSScriptRoot 'lib\HarnessContract.psm1') -Force Import-Module (Join-Path $PSScriptRoot 'lib\HarnessJournal.psm1') -Force Import-Module (Join-Path $PSScriptRoot 'lib\HarnessGateProjection.psm1') -Force Test-HarnessContractModuleReady if ([string]::IsNullOrWhiteSpace($RepoRoot)) { $RepoRoot = Get-HarnessRepoRoot -StartPath $PSScriptRoot } $featureRootPath = Get-HarnessFeatureRoot -RepoRoot $RepoRoot -Feature $Feature ` -FeatureRootOverride $FeatureRoot -AllowArbitraryFeatureRoot:$AllowArbitraryFeatureRoot $kindMap = @{ start = 'TaskStarted' heartbeat = 'TaskHeartbeat' timeout = 'TaskTimedOut' recover = 'TaskRecovered' abandon = 'TaskAbandoned' } $kind = $kindMap[$Action] if ($Action -eq 'start') { if ([string]::IsNullOrWhiteSpace($TaskName)) { throw '-TaskName is required for -Action start' } if (Test-HarnessMutationTaskStartBlocked -FeatureRoot $featureRootPath -SliceId $Slice -TaskName $TaskName) { throw "MUTATE is already skipped for slice $Slice; do not start a $TaskName task." } if ($TtlMinutes -le 0) { throw '-TtlMinutes must be > 0 for -Action start (escape hatch requires a deadline)' } if ([string]::IsNullOrWhiteSpace($TaskId)) { $TaskId = New-HarnessJournalActivityId } if ([string]::IsNullOrWhiteSpace($Summary)) { $Summary = "task start $TaskName ttl=${TtlMinutes}m" } } else { if ([string]::IsNullOrWhiteSpace($TaskId)) { throw "-TaskId is required for -Action $Action (use the activity id from TaskStarted)" } if ([string]::IsNullOrWhiteSpace($TaskName)) { $TaskName = 'background-task' } if ([string]::IsNullOrWhiteSpace($Summary)) { $Summary = "task $Action $TaskName" } } $resolvedSlug = Get-HarnessResolvedModelSlug -ModelSlug $ModelSlug $actor = New-HarnessJournalActor -Agent $Agent -ParentAgent $(if ($ParentAgent) { $ParentAgent } else { $null }) ` -Tool 'harness-script' -Provider $ModelProvider -Slug $resolvedSlug -Role 'primary' $activity = New-HarnessJournalActivity -Id $TaskId -Type 'other' -Name $TaskName -Role 'instant' $outcome = switch ($Action) { 'timeout' { 'FAIL' } 'abandon' { 'BLOCKED' } 'recover' { 'PASS' } default { $null } } $deadline = $null if ($Action -eq 'start' -and $TtlMinutes -gt 0) { $deadline = ([DateTime]::UtcNow.AddMinutes($TtlMinutes)).ToString('yyyy-MM-ddTHH:mm:ssZ') } $evt = Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $Slice ` -Kind $kind -Activity $activity -Actor $actor -Outcome $outcome ` -Summary $Summary ` -Payload ([pscustomobject]@{ task_name = $TaskName ttl_minutes = $(if ($TtlMinutes -gt 0) { $TtlMinutes } else { $null }) deadline_utc = $deadline expected_output_path = $(if ($ExpectedOutputPath) { $ExpectedOutputPath } else { $null }) action = $Action }) ` -Refs ([pscustomobject]@{ amends_event_id = $null pair_event_id = $null artifact = $(if ($ExpectedOutputPath) { $ExpectedOutputPath } else { $null }) command = '.\scripts\kaden\harness-record-task.ps1' }) Update-HarnessGateProjectionFromJournal -FeatureRoot $featureRootPath -Feature $Feature -RepoRoot $RepoRoot -Write | Out-Null [pscustomobject]@{ operation = 'record-task' feature = $Feature slice = $Slice action = $Action kind = $kind task_id = $TaskId task_name = $TaskName event_id = $evt.event_id deadline = $deadline } | ConvertTo-Json -Depth 5 |