Payload/scripts/kaden/harness/harness-pretooluse-guard.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    Cursor and Copilot (Windows) preToolUse / beforeShellExecution guard.

.DESCRIPTION
    Denies bare harness scripts without pwsh -File scripts/kaden/harness-invoke.ps1, and denies production
    edits under src/, public/, or tests/ while RED is pending/in_progress without
    a CODE_FIRST_BUILD skip. Windows-only: PowerShell adapter. Copilot cloud/Linux
    is out of scope for this slice.

    Install via scripts/kaden/harness/setup-harness-hooks.ps1.
#>

[CmdletBinding()]
param(
    [string]$InputJson = '',
    [string]$RepoRoot = '',
    [string]$FeatureRoot = '',
    # Fixture/Pester only: skip deny telemetry. Allowed solely when FeatureRoot is under TEMP.
    [switch]$SkipJournal
)

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

$modulePath = Join-Path $PSScriptRoot 'lib\HarnessContract.psm1'
Import-Module $modulePath -Force
Import-Module (Join-Path $PSScriptRoot 'lib\HarnessJournal.psm1') -Force

if ($SkipJournal -and -not (Test-HarnessPretooluseSkipJournalAllowed -FeatureRoot $FeatureRoot)) {
    throw 'SkipJournal is only allowed when -FeatureRoot is an existing path under TEMP (fixture/Pester isolation). Live hooks must not pass -SkipJournal.'
}

$stdin = if ([string]::IsNullOrWhiteSpace($InputJson)) {
    [Console]::In.ReadToEnd()
} else {
    $InputJson
}

if ([string]::IsNullOrWhiteSpace($stdin)) {
    Write-Output '{"permission":"allow"}'
    exit 0
}

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

$payload = $stdin | ConvertFrom-Json
# Cursor tells the hook the model. The subagent cannot see that slug. Persist it
# before the command runs so the same conversation can read the ledger.
try {
    Save-HarnessHostModelObservation -Payload $payload -RepoRoot $RepoRoot
} catch {
    Write-Verbose "host-model capture skipped: $($_.Exception.Message)"
}
$hookEventName = [string](Get-HarnessProperty -Object $payload -Name 'hook_event_name')
if ($hookEventName -eq 'postToolUse' -or $hookEventName -eq 'PostToolUse') {
    $observedModel = [string](Get-HarnessProperty -Object $payload -Name 'model')
    $conversationId = [string](Get-HarnessProperty -Object $payload -Name 'conversation_id')
    $context = ''
    if (-not [string]::IsNullOrWhiteSpace($observedModel)) {
        $context = "Host-observed model slug: $observedModel. Write this exact value as model.slug with model_source host-reported. Ledger: .harness/host-model/$conversationId.json"
    }
    Write-Output (@{ additional_context = $context } | ConvertTo-Json -Compress)
    exit 0
}
$parsed = ConvertFrom-HarnessToolUseEnvelope -Payload $payload
$policy = Test-HarnessToolUsePolicy -RepoRoot $RepoRoot -FeatureRoot $FeatureRoot `
    -ToolFamily $parsed.ToolFamily -Command $parsed.Command -Path $parsed.Path
$decision = [string]$policy.Decision
if ($decision -eq 'deny' -and -not $SkipJournal) {
    try {
        $open = Get-HarnessOpenOwnershipTurn -RepoRoot $RepoRoot -FeatureRoot $FeatureRoot
        if ($open) {
            $actor = New-HarnessJournalActor -Agent 'harness-pretooluse-guard.ps1' -ParentAgent $null `
                -Tool 'harness-script' -Provider 'other' -Slug 'unknown' -Role 'primary'
            $activity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type 'sensor' -Name 'pretooluse-guard' -Role 'instant'
            $denyPath = if ($parsed.Path) { $parsed.Path } else { $parsed.Command }
            Add-HarnessJournalEvent -FeatureRoot $open.FeatureRoot -Feature $open.Feature -Slice $open.Slice `
                -Kind 'SensorRunRecorded' -Activity $activity -Actor $actor -Outcome 'DENY' `
                -Summary "preToolUse denied: $denyPath" `
                -Payload ([pscustomobject]@{
                    name    = 'pretooluse-guard'
                    verdict = 'DENY'
                    path    = $denyPath
                    reason  = [string]$policy.Reason
                }) `
                -Refs ([pscustomobject]@{
                    amends_event_id = $null
                    pair_event_id   = $null
                    artifact        = $null
                    command         = '.\scripts\kaden\harness\harness-pretooluse-guard.ps1'
                }) | Out-Null
        }
    } catch {
        Write-Verbose "pretooluse-guard journal append failed: $($_.Exception.Message)"
    }
}
if ($decision -ne 'deny') {
    $decision = 'allow'
}
Write-Output (ConvertTo-HarnessHookPermissionJson -ResponseFamily $parsed.ResponseFamily -Decision $decision -Reason ([string]$policy.Reason))
exit 0