Payload/scripts/kaden/harness/validate-review-loop.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    Gate oracle: validate the review-loop projection against profile requirements.

.DESCRIPTION
    Reads docs/harness/features/<slug>/.review-loop.json (schema 1.2 gate projection) plus
    on-disk artifacts (.reviews, sensors, handoffs). Does NOT treat journal prose as proof
    of completeness - that is intentional (ADR 0003 / docs/kaden/harness-journal.md).

    Checks phases, reviewer cycles, sensor evidence, quality-gates / slice-review ordering,
    and Phase 1-2 verdict shapes (rejects bare APPROVE stubs).

    Exit codes: 0 = PASS; with -Strict, 1 = FAIL; without -Strict, 2 = FAIL (soft).
    With -RequireReviewReady, journal/events.jsonl must exist and be non-empty.
    Invoke-HarnessOwnershipSessionEval (PROCESS_* discretionary codes) is opt-in Pester only -
    RequireReviewReady does not call it (discretionary process-eval only). Honesty is enforced
    by recorder throws and journal validator checks, not the process-eval scorer.
    With -Mode preflight -Next <hop>, runs a subset of checks before expensive gate work.

.PARAMETER Feature
    Feature slug.

.PARAMETER Slice
    Optional slice id to scope validation.

.PARAMETER FeatureRoot
    Override feature directory (test fixtures).

.PARAMETER RequireReviewReady
    Enforce review_ready obligations (reviewer cycles, sensors, handoff, feature phases).

.PARAMETER Mode
    full (default) or preflight.

.PARAMETER Next
    When Mode is preflight: quality-gates | slice-review | review_ready.

.PARAMETER Strict
    Exit 1 on FAIL (agent / stop-hook gates).

.PARAMETER Json
    Emit JSON report to stdout.

.EXAMPLE
    .\scripts\validate-review-loop.ps1 -Feature saved-search-queries -Slice mo-save-list -RequireReviewReady -Strict

.EXAMPLE
    .\scripts\validate-review-loop.ps1 -Feature saved-search-queries -Slice mo-save-list -Mode preflight -Next quality-gates -Strict
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$Feature,

    [string]$Slice = '',

    [string]$FeatureRoot = '',

    [string]$RepoRoot = '',

    [switch]$RequireReviewReady,

    [ValidateSet('full', 'preflight')]
    [string]$Mode = 'full',

    [ValidateSet('quality-gates', 'slice-review', 'review_ready', '')]
    [string]$Next = '',

    [switch]$Strict,

    [switch]$Json,

    [switch]$AllowArbitraryFeatureRoot
)

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

$modulePath = Join-Path $PSScriptRoot 'lib\HarnessContract.psm1'
Import-Module $modulePath -Force
# Slice-review recorder validation reads journal/events.jsonl via Get-HarnessJournalEvents.
# HarnessContract.psm1 does not import HarnessJournal.psm1 - this is a second module, not a duplicate import.
Import-Module (Join-Path $PSScriptRoot 'lib\HarnessJournal.psm1') -Force
Test-HarnessContractModuleReady
# Explicit journal dependency for model-provenance cross-checks (SR-QAP-002 / CS-QAP-06).
if (-not (Get-Command Get-HarnessJournalEvents -ErrorAction SilentlyContinue)) {
    throw 'validate-review-loop requires Get-HarnessJournalEvents after importing HarnessJournal.psm1.'
}

if ([string]::IsNullOrWhiteSpace($RepoRoot)) {
    $RepoRoot = Get-HarnessRepoRoot -StartPath $PSScriptRoot
}

if ($Mode -eq 'preflight' -and [string]::IsNullOrWhiteSpace($Next)) {
    throw 'validate-review-loop: -Next is required when -Mode is preflight (quality-gates | slice-review | review_ready).'
}

$featureRootPath = Get-HarnessFeatureRoot -RepoRoot $RepoRoot -Feature $Feature -FeatureRootOverride $FeatureRoot -AllowArbitraryFeatureRoot:$AllowArbitraryFeatureRoot
Write-Verbose "validate-review-loop featureRoot=$featureRootPath slice=$Slice mode=$Mode next=$Next requireReviewReady=$RequireReviewReady"

$report = Invoke-HarnessReviewLoopValidation -RepoRoot $RepoRoot -FeatureRoot $featureRootPath -SliceId $Slice `
    -RequireReviewReady:$RequireReviewReady -Mode $Mode -PreflightNext $Next

if ($Json) {
    $report | ConvertTo-Json -Depth 10
}
else {
    $modeLabel = [string](Get-HarnessProperty -Object $report -Name 'mode')
    if ([string]::IsNullOrWhiteSpace($modeLabel)) { $modeLabel = $Mode }
    $nextLabel = [string](Get-HarnessProperty -Object $report -Name 'preflight_next')
    if ([string]::IsNullOrWhiteSpace($nextLabel)) { $nextLabel = $Next }
    Write-Output "validate-review-loop: $($report.verdict) (profile=$($report.profile) feature=$($report.feature) slice=$($report.slice) mode=$modeLabel next=$nextLabel)"
    foreach ($finding in $report.findings) {
        $line = " [$($finding.code)] $($finding.message)"
        $nextCmd = [string](Get-HarnessProperty -Object $finding -Name 'next_command')
        if (-not [string]::IsNullOrWhiteSpace($nextCmd)) {
            $line += " -> $nextCmd"
        }
        Write-Output $line
    }
}

if ($Strict -and $report.verdict -ne 'PASS') {
    exit 1
}

if ($report.verdict -ne 'PASS') {
    exit 2
}

exit 0