Payload/scripts/kaden/utils/record-slice-approved.ps1
|
<# .SYNOPSIS Record an operator slice approval on the harness journal and rebuild the gate projection. .DESCRIPTION Appends a HumanGateDecided event with verdict "approved" and rebuilds .review-loop.json from the journal. The public harness-journal.ps1 instant action does not accept a payload, and the projection only copies verdict and notes from that payload. The feature folder and slice must already exist on the projection. This script refuses to invent a slice. Nothing is written unless -Apply is present. .PARAMETER Feature Feature slug under docs/harness/features. .PARAMETER Slice Existing slice id on that feature's .review-loop.json. .PARAMETER Notes Operator notes stored on human_gate.notes. Default: "Operator accepted the slice." .PARAMETER Summary Journal summary. Default: "Operator accepted <slice>". .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 to point outside docs/harness/features. For a fixture, not a normal approval. .PARAMETER Apply Append HumanGateDecided and rebuild the projection. Required for any write. .PARAMETER Agent Journal actor agent. Default: atdd-developer. .PARAMETER Tool Journal actor tool. Default: chat. .PARAMETER ModelProvider Journal actor model provider. Default: cursor. .PARAMETER ModelSlug Journal actor model slug. Default: unknown (HarnessJournal also reads HARNESS_MODEL_SLUG). .PARAMETER ModelRole Journal actor model role. Default: primary. .EXAMPLE pwsh -NoProfile -File .\scripts\kaden\utils\record-slice-approved.ps1 -Feature second-opinion-workflow -Slice advisory-assessment-routing -Apply .EXAMPLE pwsh -NoProfile -File .\scripts\kaden\utils\record-slice-approved.ps1 -Feature second-opinion-workflow -Slice advisory-assessment-routing -Notes "Accepted after slice review." -ModelSlug grok-4.7-high-fast -Apply .EXAMPLE Get-Help .\scripts\kaden\utils\record-slice-approved.ps1 -Parameter Apply #> #Requires -Version 5.1 [CmdletBinding(DefaultParameterSetName = 'Record')] param( [Parameter(ParameterSetName = 'Record', Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9_-]*$')] [string]$Feature, [Parameter(ParameterSetName = 'Record', Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9_-]*$')] [string]$Slice, [Parameter(ParameterSetName = 'Record')] [string]$Notes = 'Operator accepted the slice.', [Parameter(ParameterSetName = 'Record')] [string]$Summary = '', [Parameter(ParameterSetName = 'Record')] [string]$RepoRoot = '', [Parameter(ParameterSetName = 'Record')] [string]$FeatureRoot = '', [Parameter(ParameterSetName = 'Record')] [switch]$AllowArbitraryFeatureRoot, [Parameter(ParameterSetName = 'Record')] [switch]$Apply, [Parameter(ParameterSetName = 'Record')] [string]$Agent = 'atdd-developer', [Parameter(ParameterSetName = 'Record')] [string]$Tool = 'chat', [Parameter(ParameterSetName = 'Record')] [ValidateSet('cursor', 'copilot', 'other')] [string]$ModelProvider = 'other', [Parameter(ParameterSetName = 'Record')] [string]$ModelSlug = 'unknown', [Parameter(ParameterSetName = 'Record')] [ValidateSet('primary', 'subagent')] [string]$ModelRole = 'primary', [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-RecordSliceApprovedRepoRoot { 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 Get-RecordSliceApprovedProjectionSlice { param( [Parameter(Mandatory = $true)]$Loop, [Parameter(Mandatory = $true)][string]$SliceId ) foreach ($phase in @($Loop.phases)) { if ($null -eq $phase) { continue } $slices = $phase.PSObject.Properties["slices"] if ($null -eq $slices) { continue } foreach ($candidate in @($slices.Value)) { if ($null -eq $candidate) { continue } $idProp = $candidate.PSObject.Properties["id"] if ($null -ne $idProp -and [string]$idProp.Value -eq $SliceId) { return $candidate } } } return $null } try { if ($ShowHelp) { (Get-Help -Name $PSCommandPath -Full | Out-String -Width 100) | Out-Host return } Write-CliSection -Title "Recording slice approval" -Style info $root = Resolve-RecordSliceApprovedRepoRoot -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 $loopPath = Join-Path $featureRootPath ".review-loop.json" if (-not (Test-Path -LiteralPath $loopPath)) { throw "Projection not found: $loopPath" } $loop = Get-Content -LiteralPath $loopPath -Raw -Encoding utf8 | ConvertFrom-Json $projectionSlice = Get-RecordSliceApprovedProjectionSlice -Loop $loop -SliceId $Slice if ($null -eq $projectionSlice) { throw "Slice '$Slice' is not on the projection for feature '$Feature'. Refusing to invent it." } $gateProp = $projectionSlice.PSObject.Properties["human_gate"] if ($null -eq $gateProp -or $null -eq $gateProp.Value) { throw "Slice '$Slice' has no human_gate. The projection rebuild would drop this approval." } if ([string]::IsNullOrWhiteSpace($Summary)) { $Summary = "Operator accepted $Slice" } Write-CliKeyValue -Label "feature" -Value $Feature Write-CliKeyValue -Label "slice" -Value $Slice Write-CliKeyValue -Label "notes" -Value $Notes if (-not $Apply) { Write-CliStatus -State "Info" -Message "Nothing was written. Pass -Apply to record the approval." return } $actor = New-HarnessJournalActor -Agent $Agent -Tool $Tool -Provider $ModelProvider -Slug $ModelSlug -Role $ModelRole $activity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type "other" -Name "human-gate" -Role "instant" $payload = [pscustomobject]@{ verdict = "approved" notes = $Notes } $evt = Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $Slice -Kind "HumanGateDecided" -Activity $activity -Actor $actor -Outcome "PASS" -Summary $Summary -Payload $payload Update-HarnessGateProjectionFromJournal -FeatureRoot $featureRootPath -Feature $Feature -RepoRoot $root -Write | Out-Null Write-CliSection -Title "Slice approved" -Style success Write-CliKeyValue -Label "event" -Value "[info]$($evt.event_id)[-]" } catch { Write-CliSection -Title "Slice approval failed" -Style "error" Write-CliStatus -State "Error" -Message $_.Exception.Message exit 2 } finally { Write-CliEnd } |