Payload/scripts/kaden/harness/harness-record-review.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Record a reviewer verdict artifact as journal truth and rebuild projection. .DESCRIPTION Validates a reviewer payload before it is written, writes the review artifact, appends ReviewerCycleRecorded to the journal, and rebuilds the gate projection. YAML is the default transport for flat reviewer payloads; JSON is allowed when the payload is genuinely nested. Recordable verdicts: APPROVED, NEEDS_REVISION, REJECTED. review_ready validation still requires a later APPROVED cycle for the reviewer. Default MinBytes is 800; persist the subagent YAML (timestamp restamped UTC; undersize digests throw). G7 FAIL requires escape_hatch in YAML or -AllowStub to record APPROVED. Feature-level specification review: pass -FeaturePhase spec-review instead of -Slice. The verdict must come from spec-writer-reviewer (role separation); an APPROVED verdict completes the feature-phase spec-review row, a NEEDS_REVISION / REJECTED verdict leaves it pending or blocked. Producer-authored stand-ins are refused. Slice APPROVED is refused when the resolved reviewer slug matches the open turn's primary slug, parent_agent is empty, and tool is chat (H-15). A parent agent, or tool cursor-task or copilot-agent, is distinct-actor evidence and stays valid on the same slug. .EXAMPLE pwsh -File scripts/kaden/harness-invoke.ps1 harness-record-review.ps1 -Feature saved-search-queries -Slice mo-save-list -Reviewer atdd-developer-reviewer -PayloadPath .reviews\payload.yaml -VerdictArtifact .reviews\atdd-developer-reviewer-mo-save-list-iter1.yaml -ModelProvider cursor -ModelSlug composer-2 .EXAMPLE Feature-level specification review (spec-review outer-loop phase): pwsh -File scripts/kaden/harness-invoke.ps1 harness-record-review.ps1 -Feature my-feature -FeaturePhase spec-review -Reviewer spec-writer-reviewer -PayloadPath .reviews\spec-writer-reviewer-spec-review-iter1.yaml -VerdictArtifact .reviews\spec-writer-reviewer-spec-review-iter1.yaml -ModelProvider cursor -ModelSlug <slug> .EXAMPLE # Register specialist-written verdict (Content alone without on-disk file throws): pwsh -File scripts/kaden/harness-invoke.ps1 harness-record-review.ps1 -Feature saved-search-queries -Slice mo-save-list -Reviewer atdd-developer-reviewer -VerdictContent "<verbatim yaml matching .reviews file>" -VerdictArtifact .reviews\atdd-developer-reviewer-mo-save-list-iter1.yaml -ModelProvider cursor -ModelSlug composer-2 #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Feature, # Provide -Slice for slice review cycles, or -FeaturePhase spec-review for the # feature-level specification review. Exactly one is required. [string]$Slice = '', [ValidateSet('', 'spec-review')] [string]$FeaturePhase = '', [Parameter(Mandatory = $true)] [string]$Reviewer, # Provide EITHER -PayloadPath (path to an existing file) OR -VerdictContent (inline YAML/JSON text). # -VerdictContent registers an already-written allowlist file (must match on disk unless -AllowStub). [string]$PayloadPath = '', [string]$VerdictContent = '', [Parameter(Mandatory = $true)] [string]$VerdictArtifact, [ValidateSet('auto', 'yaml', 'json')] [string]$PayloadFormat = 'auto', [string]$FeatureRoot = '', [string]$RepoRoot = '', [ValidateSet('cursor', 'copilot', 'claude', 'other')] [string]$ModelProvider = 'copilot', [string]$ModelSlug = 'unknown', [ValidateSet('primary', 'subagent')] [string]$ModelRole = 'primary', [string]$Agent = 'atdd-developer', [string]$ParentAgent = '', [ValidateSet('cursor-task', 'copilot-agent', 'chat', 'harness-script')] [string]$Tool = 'harness-script', # Immutable host dispatch id when the host exposes one (Cursor Task). Copilot does not expose it. [string]$SourceInvocationId = '', [string]$ChildConversationId = '', [string]$EvidenceRoot = '', [string]$EnvelopeSlug = '', # Copilot subagents must explicitly attest their on-disk canonical artifact when no host id exists. [ValidateSet('host-id', 'artifact-attested')] [string]$SourceProvenance = 'host-id', [int]$MinBytes = 800, [switch]$AllowStub, [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 Import-Module (Join-Path $PSScriptRoot 'lib\RepoRuntime.psm1') -Force Test-HarnessContractModuleReady if ([string]::IsNullOrWhiteSpace($RepoRoot)) { $RepoRoot = Get-HarnessRepoRoot -StartPath $PSScriptRoot } $script:HarnessIgnoreHostLedger = $false $script:HarnessChildCorrelation = $null if (-not [string]::IsNullOrWhiteSpace($ChildConversationId)) { $script:HarnessChildCorrelation = Resolve-HarnessCorrelatedChildRegistration -EvidenceRoot $EvidenceRoot -RepoRoot $RepoRoot -TaskId $SourceInvocationId -ChildConversationId $ChildConversationId -RegisteredSlug $ModelSlug -EnvelopeSlug $(if ([string]::IsNullOrWhiteSpace($EnvelopeSlug)) { $ModelSlug } else { $EnvelopeSlug }) $childCorrelation = $script:HarnessChildCorrelation if ($childCorrelation.model_source -eq 'unknown-runtime') { $ModelSlug = 'unknown' $script:HarnessIgnoreHostLedger = $true } elseif ($childCorrelation.model_source -eq 'host-reported') { $ModelSlug = [string]$childCorrelation.model_slug } } Initialize-RepoShellEnvironment -LogPrefix 'harness-record-review' | Out-Null $featureRootPath = Get-HarnessFeatureRoot -RepoRoot $RepoRoot -Feature $Feature -FeatureRootOverride $FeatureRoot -AllowArbitraryFeatureRoot:$AllowArbitraryFeatureRoot # Feature-level spec-review mode: role-separated verdict evidence with no # implementation slice. Exactly one of -Slice / -FeaturePhase selects the mode. $isFeatureReview = $false if (-not [string]::IsNullOrWhiteSpace($FeaturePhase)) { if (-not [string]::IsNullOrWhiteSpace($Slice)) { throw 'Specify either -Slice <id> or -FeaturePhase spec-review, not both.' } if ($Reviewer -ne 'spec-writer-reviewer') { throw 'spec-review recording requires -Reviewer spec-writer-reviewer; the specification producer cannot author or stand in for the reviewer verdict (role separation).' } $isFeatureReview = $true } elseif ([string]::IsNullOrWhiteSpace($Slice)) { throw 'Specify -Slice <id> for slice review cycles, or -FeaturePhase spec-review for feature-level specification review.' } # Containment before content (SEC-OLJR3-03): -PayloadPath must be the on-disk allowlist # VerdictArtifact (same path, under the feature root), checked before any bytes are read. # A relative -PayloadPath resolves against the feature root like -VerdictArtifact, so the # documented relative-path command registers without a path-form workaround (PROV-01). # One resolution path: candidate -> full path -> under-root containment -> allowlist # equality. Containment applies whenever a payload path is given; -AllowStub governs # stub acceptance, never path containment. $resolvedPayloadPath = $null if (-not [string]::IsNullOrWhiteSpace($PayloadPath)) { $verdictFull = [System.IO.Path]::GetFullPath((Resolve-HarnessPathUnderFeatureRoot -FeatureRoot $featureRootPath -RelativePath $VerdictArtifact -ThrowOnEscape)) $payloadCandidate = if ([System.IO.Path]::IsPathRooted($PayloadPath)) { $PayloadPath } else { (Join-Path $featureRootPath $PayloadPath) } $pathFull = [System.IO.Path]::GetFullPath($payloadCandidate) $rootFull = [System.IO.Path]::GetFullPath($featureRootPath) if (-not $pathFull.StartsWith($rootFull, [StringComparison]::OrdinalIgnoreCase)) { throw ("PayloadPath must be the on-disk allowlist VerdictArtifact ({0}); parent staging/transcription paths are not allowed" -f $VerdictArtifact) } $resolvedPayloadPath = $pathFull if (-not [string]::Equals($pathFull, $verdictFull, [StringComparison]::OrdinalIgnoreCase)) { throw ("PayloadPath must be the on-disk allowlist VerdictArtifact ({0}); parent staging/transcription paths are not allowed" -f $VerdictArtifact) } } # Resolve payload text: from inline -VerdictContent or from -PayloadPath file. if (-not [string]::IsNullOrWhiteSpace($VerdictContent)) { $payloadText = $VerdictContent.Trim() if ([string]::IsNullOrWhiteSpace($payloadText)) { throw '-VerdictContent was provided but is empty.' } } elseif (-not [string]::IsNullOrWhiteSpace($PayloadPath)) { $payloadReadPath = if ($resolvedPayloadPath) { $resolvedPayloadPath } else { $PayloadPath } if (-not (Test-Path -LiteralPath $payloadReadPath)) { throw "Payload not found: $PayloadPath" } $payloadText = Get-Content -LiteralPath $payloadReadPath -Raw if ([string]::IsNullOrWhiteSpace($payloadText)) { throw 'Reviewer payload file is empty.' } } else { throw 'Specify either -PayloadPath <file> or -VerdictContent <yaml-text>.' } Assert-HarnessReviewerVerdictFileOnDisk -FeatureRoot $featureRootPath -VerdictArtifact $VerdictArtifact ` -VerdictContent $(if (-not [string]::IsNullOrWhiteSpace($VerdictContent)) { $payloadText } else { '' }) ` -AllowStub:$AllowStub $payloadBytes = [System.Text.Encoding]::UTF8.GetByteCount($payloadText) if (-not $AllowStub -and $payloadBytes -lt $MinBytes) { throw "Reviewer payload is only $payloadBytes bytes (min $MinBytes). Persist the subagent YAML verbatim, not a digest." } $normalizedFormat = $PayloadFormat if ($normalizedFormat -eq 'auto') { if (-not [string]::IsNullOrWhiteSpace($PayloadPath)) { $normalizedFormat = if ([System.IO.Path]::GetExtension($PayloadPath) -eq '.json') { 'json' } else { 'yaml' } } elseif ($payloadText -match '^\s*\{') { $normalizedFormat = 'json' } else { $normalizedFormat = 'yaml' } } $loop = Get-ReviewLoopDocument -FeatureRoot $featureRootPath $producer = 'spec-writer' if (-not $isFeatureReview) { $sliceEntry = Get-ImplementationSliceEntry -LoopData $loop.Data -SliceId $Slice if (-not $sliceEntry) { throw "Slice '$Slice' not found in review loop." } $producer = if ($sliceEntry.owner) { [string]$sliceEntry.owner } else { 'atdd-developer' } } $resolvedSlug = Get-HarnessResolvedModelSlug -ModelSlug $ModelSlug -IgnoreHostLedger:$script:HarnessIgnoreHostLedger # Recording accepts honest non-APPROVED cycles. review_ready validation still requires # an APPROVED cycle (min_verdict) and re-validates that artifact with APPROVED-only rules. $recordableVerdicts = @('APPROVED', 'NEEDS_REVISION', 'REJECTED') if ($normalizedFormat -eq 'json') { $payloadDoc = ConvertFrom-HarnessJson -Json $payloadText if ((Get-HarnessProperty -Object $payloadDoc -Name 'agent') -ne $Reviewer) { throw "Reviewer payload agent must be '$Reviewer'." } if ((Get-HarnessProperty -Object $payloadDoc -Name 'role') -ne 'reviewer') { throw 'Reviewer payload role must be reviewer.' } $jsonVerdict = [string](Get-HarnessProperty -Object $payloadDoc -Name 'verdict') if ($recordableVerdicts -notcontains $jsonVerdict) { throw "Reviewer payload verdict must be one of: $($recordableVerdicts -join ', ') (found '$jsonVerdict')." } if ((Get-HarnessProperty -Object $payloadDoc -Name 'slice') -and (Get-HarnessProperty -Object $payloadDoc -Name 'slice') -ne $Slice) { throw "Reviewer payload slice must be '$Slice'." } if (-not (Get-HarnessProperty -Object $payloadDoc -Name 'summary')) { throw 'Reviewer payload summary is required.' } if (-not (Get-HarnessProperty -Object $payloadDoc -Name 'model')) { throw 'Reviewer payload model block is required.' } $payloadTimestamp = Get-HarnessProperty -Object $payloadDoc -Name 'timestamp' if (-not (Test-HarnessUtcTimestamp -Value $payloadTimestamp)) { throw "Reviewer payload timestamp must be RFC 3339 UTC Z (found '$payloadTimestamp')." } } else { if (-not $isFeatureReview -and (Test-HarnessReviewerG2OnCodeFirstSkip -Content $payloadText)) { if (Test-HarnessSliceRedSkippedCodeFirst -FeatureRoot $featureRootPath -SliceId $Slice) { throw 'G2_valid_red_failure must be N/A when RED was skipped CODE_FIRST_BUILD (not PASS).' } } $tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('harness-review-validate-' + [guid]::NewGuid().ToString('N')) $tempArtifact = Resolve-HarnessPathUnderFeatureRoot -FeatureRoot $tempRoot -RelativePath $VerdictArtifact -ThrowOnEscape New-Item -ItemType Directory -Path (Split-Path $tempArtifact -Parent) -Force | Out-Null [System.IO.File]::WriteAllText($tempArtifact, $payloadText, [System.Text.UTF8Encoding]::new($false)) try { $findings = @(Invoke-HarnessReviewerVerdictArtifactValidation -FeatureRoot $tempRoot -VerdictArtifact $VerdictArtifact ` -ExpectedReviewer $Reviewer -SliceId $Slice -AllowedVerdicts $recordableVerdicts) if ($findings.Count -gt 0) { $messages = @($findings | ForEach-Object { $_.message }) throw "Reviewer payload failed validation: $($messages -join '; ')" } } finally { Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue } } $previewVerdict = if ($normalizedFormat -eq 'json') { [string](Get-HarnessProperty -Object $payloadDoc -Name 'verdict') } else { [string](Get-HarnessYamlScalarMatch -Content $payloadText -Key 'verdict') } if ($previewVerdict -eq 'APPROVED' -and -not $AllowStub) { if ($normalizedFormat -eq 'yaml') { if (-not (Test-HarnessYamlBlockPresent -Content $payloadText -Key 'gates') -and -not (Test-HarnessYamlBlockPresent -Content $payloadText -Key 'quality_gates')) { throw 'APPROVED reviewer YAML must include gates: or quality_gates: block; persist verbatim subagent output.' } if (Test-HarnessReviewerYamlHasBlockingGateFail -Content $payloadText) { throw 'APPROVED cannot be recorded when reviewer YAML contains a blocking FAIL gate (digest guard).' } } # Feature-level spec-review has no slice-test rework gate: revision then approval is # replayable directly from the reviewer verdict sequence. if (-not $isFeatureReview -and -not (Test-HarnessReviewerReworkEvidenceSatisfied -FeatureRoot $featureRootPath -SliceId $Slice -Reviewer $Reviewer)) { throw 'APPROVED requires harness-run-slice-tests PASS recorded after the latest NEEDS_REVISION cycle for this reviewer (rework evidence).' } } $priorVerified = @() $escapeHatch = $null $yamlIterationRaw = '' if ($normalizedFormat -eq 'json') { $priorVerified = @((Get-HarnessProperty -Object $payloadDoc -Name 'prior_defects_verified' -Default @())) $escapeHatch = Get-HarnessProperty -Object $payloadDoc -Name 'escape_hatch' $yamlIterationRaw = [string](Get-HarnessProperty -Object $payloadDoc -Name 'iteration' -Default '') } else { $priorVerified = @(Get-HarnessYamlStringList -Content $payloadText -Key 'prior_defects_verified') $escapeHatch = Get-HarnessYamlScalarMatch -Content $payloadText -Key 'escape_hatch' $yamlIterationRaw = [string](Get-HarnessYamlScalarMatch -Content $payloadText -Key 'iteration') } $stampUtc = Get-HarnessIso8601UtcNow if ($normalizedFormat -eq 'json') { $payloadDoc.timestamp = $stampUtc $payloadText = ($payloadDoc | ConvertTo-Json -Depth 20) } else { $payloadText = Format-HarnessYamlTimestampField -Content $payloadText -Timestamp $stampUtc } $fullArtifactPath = Resolve-HarnessPathUnderFeatureRoot -FeatureRoot $featureRootPath -RelativePath $VerdictArtifact -ThrowOnEscape New-Item -ItemType Directory -Path (Split-Path $fullArtifactPath -Parent) -Force | Out-Null [System.IO.File]::WriteAllText($fullArtifactPath, $payloadText, [System.Text.UTF8Encoding]::new($false)) $existingCycles = @() if (-not $isFeatureReview) { $existingCycles = @($sliceEntry.cycles | Where-Object { (Get-HarnessProperty -Object $_ -Name 'reviewer') -eq $Reviewer }) } if (-not [string]::IsNullOrWhiteSpace($yamlIterationRaw) -and $yamlIterationRaw -match '^\d+$' -and [int]$yamlIterationRaw -ge 1) { $iteration = [int]$yamlIterationRaw } else { $iteration = $existingCycles.Count + 1 } $reviewVerdict = if ($normalizedFormat -eq 'json') { [string](Get-HarnessProperty -Object $payloadDoc -Name 'verdict') } else { [string](Get-HarnessYamlScalarMatch -Content $payloadText -Key 'verdict') } $reviewTimestamp = $stampUtc $defectIds = @() if ($normalizedFormat -eq 'json') { $defects = Get-HarnessProperty -Object $payloadDoc -Name 'defects' if ($defects) { foreach ($d in @($defects)) { $id = [string](Get-HarnessProperty -Object $d -Name 'id') if (-not [string]::IsNullOrWhiteSpace($id)) { $defectIds += $id } } } } else { $defectIds = @(Get-HarnessYamlDefectId -Content $payloadText) } if ($reviewVerdict -eq 'APPROVED') { if ($SourceProvenance -eq 'host-id' -and [string]::IsNullOrWhiteSpace($SourceInvocationId)) { throw 'APPROVED with SourceProvenance host-id requires -SourceInvocationId.' } if ($SourceProvenance -eq 'artifact-attested' -and ($ModelProvider -notin @('copilot', 'claude') -or -not [string]::IsNullOrWhiteSpace($SourceInvocationId))) { throw 'SourceProvenance artifact-attested is only valid for Copilot or Claude Code subagents without a host invocation id.' } } $journalOutcome = if ($reviewVerdict -eq 'APPROVED') { 'PASS' } else { 'FAIL' } $envelopeDoc = $null if ($normalizedFormat -eq 'json') { $envelopeDoc = $payloadDoc } $artifactEnvelope = Get-HarnessArtifactProvenanceEnvelope -Content $payloadText -Document $envelopeDoc -Format $normalizedFormat $resolvedProvenance = Resolve-HarnessArtifactModelProvenance ` -Envelope $artifactEnvelope ` -RegisteredAgent $Agent ` -RegisteredProvider $ModelProvider ` -RegisteredSlug $resolvedSlug ` -RegisteredRole $ModelRole ` -RegisteredParentAgent $ParentAgent ` -RegisteredTool $Tool ` -UnspecifiedAgent 'atdd-developer' ` -UnspecifiedRole 'primary' ` -UnspecifiedTool 'harness-script' ` -ErrorCode 'MODEL_PROVENANCE_MISMATCH' $resolvedAgent = [string]$resolvedProvenance.agent $resolvedProvider = [string]$resolvedProvenance.provider $resolvedSlug = [string]$resolvedProvenance.slug $resolvedRole = [string]$resolvedProvenance.role $resolvedParent = [string]$resolvedProvenance.parent_agent $resolvedTool = [string]$resolvedProvenance.tool $resolvedModelSource = [string]$resolvedProvenance.model_source # Provenance integrity (PROV-01): a reviewer envelope slug is a token, and an unknown # model identity is labelled unknown-runtime, never host-reported - same rules the # phase recorder enforces, so review-cycle provenance cannot carry values the phase # rows may not. if ($resolvedSlug -match '\s' -or $resolvedSlug.Length -gt 64) { throw ("Reviewer envelope model slug is not a token (no whitespace, at most 64 characters): '{0}'." -f $resolvedSlug) } if ($resolvedSlug -eq 'unknown' -and $resolvedModelSource -ne 'unknown-runtime') { $resolvedModelSource = 'unknown-runtime' } # H-15: the chat that owns the open turn cannot approve its own slice. A same-slug # subagent stays valid when a parent is set or the tool is a real dispatch. if ($reviewVerdict -eq 'APPROVED' -and -not $isFeatureReview) { $openTurnModel = Get-HarnessOpenTurnModel -FeatureRoot $featureRootPath -SliceId $Slice $openTurnSlug = if ($openTurnModel) { [string]$openTurnModel.slug } else { '' } $sameChatAsOpenTurn = (Test-HarnessConcreteModelSlug -Slug $openTurnSlug) -and (Test-HarnessConcreteModelSlug -Slug $resolvedSlug) -and [string]::Equals($resolvedSlug, $openTurnSlug, [StringComparison]::OrdinalIgnoreCase) -and -not (Test-HarnessConcreteProvenanceToken -Value $resolvedParent) -and [string]::Equals($resolvedTool, 'chat', [StringComparison]::OrdinalIgnoreCase) if ($sameChatAsOpenTurn) { throw ("APPROVED refused: reviewer model slug '{0}' matches the open turn, parent_agent is empty, and tool is chat. Record the verdict from a distinct reviewer (set parent_agent, or use tool cursor-task or copilot-agent)." -f $resolvedSlug) } } $invocation = Format-HarnessModelInvocation -Agent $resolvedAgent -Provider $resolvedProvider -Slug $resolvedSlug ` -Role $resolvedRole -ParentAgent $(if ($resolvedParent) { $resolvedParent } else { $null }) ` -Tool $resolvedTool -ModelSource $resolvedModelSource $actor = New-HarnessJournalActor -Agent $resolvedAgent -ParentAgent $(if ($resolvedParent) { $resolvedParent } else { $null }) ` -Tool $resolvedTool -Provider $resolvedProvider -Slug $resolvedSlug -Role $resolvedRole $activity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type 'review.cycle' -Name $Reviewer -Role 'instant' $cyclePayload = [pscustomobject]@{ producer = $producer reviewer = $Reviewer iteration = $iteration verdict = $reviewVerdict defect_ids = @($defectIds) prior_defects_verified = @($priorVerified) escape_hatch = $(if ($escapeHatch) { $escapeHatch } else { $null }) verdict_artifact = $VerdictArtifact timestamp = $reviewTimestamp invocation = $invocation source_provenance = $SourceProvenance } if (-not [string]::IsNullOrWhiteSpace($SourceInvocationId)) { $cyclePayload | Add-Member -NotePropertyName 'source_invocation_id' -NotePropertyValue $SourceInvocationId } if ($isFeatureReview) { $cyclePayload | Add-Member -NotePropertyName 'feature_phase' -NotePropertyValue $FeaturePhase } $cyclePayload = Add-HarnessHostCorrelationRegistration -Payload $cyclePayload -Correlation $script:HarnessChildCorrelation $journalEvent = Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $(if ($isFeatureReview) { $null } else { $Slice }) ` -Kind 'ReviewerCycleRecorded' -Activity $activity -Actor $actor -Outcome $journalOutcome ` -Summary "review cycle recorded for $Reviewer verdict=$reviewVerdict" ` -Payload ([pscustomobject]@{ cycle = $cyclePayload }) ` -Refs ([pscustomobject]@{ amends_event_id = $null pair_event_id = $null artifact = $VerdictArtifact command = '.\scripts\kaden\harness-record-review.ps1' supersedes_event_id = $null }) Complete-HarnessRegisteredArtifactCustodyStamp -FeatureRoot $featureRootPath -RelativePath $VerdictArtifact ` -Source 'harness-record-review.ps1' -Slice $(if ($isFeatureReview) { '' } else { $Slice }) # An approved spec-review verdict completes the feature-level spec-review phase. The # duplicate-suppression decision runs UNDER the journal append lock (SEC-OLJR3-02): # the check against fresh in-lock text and the append are atomic with respect to # concurrent writers. The projection folds this completion only when the backing # APPROVED cycle is folded from this sanctioned recorder. if ($isFeatureReview -and $reviewVerdict -eq 'APPROVED') { $phaseActivity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type 'pipeline.phase' -Name $FeaturePhase -Role 'instant' $completionSkipCheck = { param($journalText) -not (Test-HarnessJournalTextHasFeaturePhaseStatus -JournalText $journalText -FeaturePhase $FeaturePhase -Status 'complete') } $completionEvent = Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $null ` -Kind 'PhaseStatusChanged' -Activity $phaseActivity -Actor $actor -Outcome $null ` -Summary "feature phase $FeaturePhase status=complete (approved reviewer verdict)" ` -Payload ([pscustomobject][ordered]@{ feature_phase = $FeaturePhase status = 'complete' artifacts = @($VerdictArtifact) verdict_artifact = $VerdictArtifact reviewer = $Reviewer source_provenance = $SourceProvenance source_invocation_id = $(if ($SourceInvocationId) { $SourceInvocationId } else { $null }) model_source = $resolvedModelSource }) ` -Refs ([pscustomobject]@{ amends_event_id = $null pair_event_id = $null artifact = $VerdictArtifact command = '.\scripts\kaden\harness-record-review.ps1' }) ` -UnderLockCheck $completionSkipCheck if ($null -eq $completionEvent) { Write-Verbose "spec-review already status=complete for $Feature - duplicate completion suppressed under the append lock" } } Update-HarnessGateProjectionFromJournal -FeatureRoot $featureRootPath -Feature $Feature -RepoRoot $RepoRoot -Write | Out-Null if (-not [string]::IsNullOrWhiteSpace($Slice)) { Sync-HarnessOwnSliceNextArtifact -FeatureRoot $featureRootPath -Feature $Feature -Slice $Slice -RepoRoot $RepoRoot | Out-Null } Remove-HarnessStagedPayloadIfEligible -CandidatePath $PayloadPath -RepoRoot $RepoRoot -FeatureRoot $featureRootPath [pscustomobject]@{ operation = 'record-review' feature = $Feature slice = $Slice reviewer = $Reviewer artifact = $VerdictArtifact canonical_path = $fullArtifactPath journal_event_ids = @($journalEvent.event_id) status = 'PASS' } | ConvertTo-Json -Depth 10 |