Payload/scripts/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/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/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
$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\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