#!/usr/bin/env pwsh # CASAN H5 Compliance Report Generator # Usage: # generate-compliance-report.ps1 -FeatureId -OutputPath param( [Parameter(Mandatory=$true)][string]$FeatureId, [string]$OutputPath = "" ) $ErrorActionPreference = "Stop" $scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent $projectRoot = (Resolve-Path (Join-Path $scriptDir "../../..")).Path $auditLog = Join-Path $projectRoot ".specify/logs/audit/audit.jsonl" $secAudit = Join-Path $projectRoot ".specify/logs/audit/security.jsonl" $toolAudit = Join-Path $projectRoot ".specify/logs/audit/tool-calls.jsonl" if (!$OutputPath) { $outDir = Join-Path $projectRoot "docs/output/output_logs/$FeatureId" if (!(Test-Path $outDir)) { New-Item -ItemType Directory -Force -Path $outDir | Out-Null } $OutputPath = Join-Path $outDir "compliance-report.md" } $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") # ── Read audit records ───────────────────────────────────────────────────── function Read-Jsonl ([string]$path) { if (!(Test-Path $path)) { return @() } Get-Content $path | ForEach-Object { try { $_ | ConvertFrom-Json } catch { $null } } | Where-Object { $_ } } $govRecords = Read-Jsonl $auditLog $secRecords = Read-Jsonl $secAudit $toolRecords = Read-Jsonl $toolAudit # ── Statistics ───────────────────────────────────────────────────────────── $totalActions = $govRecords.Count $highRisk = $govRecords | Where-Object { $_.risk_level -eq "high" } $denied = $govRecords | Where-Object { $_.decision -eq "denied" } $humanApproved = $govRecords | Where-Object { $_.approval_status -eq "human_approved" } $secBlocked = $secRecords | Where-Object { $_.status -eq "blocked" } $piiMasked = $secRecords | Where-Object { $_.action -eq "allow" -and $_.matched_rules } $toolCalls = $toolRecords.Count $toolDenied = $toolRecords | Where-Object { $_.decision -eq "denied" } # ── Audit chain validation ───────────────────────────────────────────────── $chainValid = $true $prevHash = "" foreach ($rec in $govRecords) { if ($prevHash -and $rec.previous_record_hash -ne $prevHash) { $chainValid = $false; break } $prevHash = $rec.record_hash } $chainStatus = if ($chainValid) { "VALID ✅" } else { "BROKEN ⚠️" } # ── Report content ───────────────────────────────────────────────────────── $highRiskTable = if ($highRisk) { $highRisk | ForEach-Object { "| $($_.timestamp) | $($_.action) | $($_.decision) | $($_.approver) | $($_.approval_status) |" } } else { "| — | — | — | — | — |" } $deniedTable = if ($denied) { $denied | ForEach-Object { "| $($_.timestamp) | $($_.action) | $($_.risk_level) | $($_.approval_status) |" } } else { "| — | — | — | — |" } $report = @" # Compliance Report — $FeatureId **Generated:** $timestamp **Pipeline:** $FeatureId **Audit chain status:** $chainStatus --- ## Action Summary | Metric | Count | |--------|------:| | Total governance actions | $totalActions | | High-risk actions | $($highRisk.Count) | | Denied actions | $($denied.Count) | | Human-approved (identity verified) | $($humanApproved.Count) | | Security blocks (H4) | $($secBlocked.Count) | | PII-masked inputs | $($piiMasked.Count) | | Tool registry calls | $toolCalls | | Tool registry denials | $($toolDenied.Count) | --- ## High-Risk Actions | Timestamp | Action | Decision | Approver | Approval Status | |-----------|--------|----------|----------|-----------------| $($highRiskTable -join "`n") --- ## Denied Actions | Timestamp | Action | Risk Level | Approval Status | |-----------|--------|:----------:|-----------------| $($deniedTable -join "`n") --- ## Security Events (H4) - Prompt injections blocked: **$($secRecords | Where-Object { $_.status -eq "blocked" -and $_.mode -eq "input" } | Measure-Object | Select-Object -ExpandProperty Count)** - Output redactions: **$($secRecords | Where-Object { $_.action -eq "redact" } | Measure-Object | Select-Object -ExpandProperty Count)** - Hallucination risk flags: **$($secRecords | Where-Object { $_.action -eq "flag" } | Measure-Object | Select-Object -ExpandProperty Count)** --- ## Audit Chain Status - Records checked: **$totalActions** - Chain integrity: **$chainStatus** - Storage: hash-chain JSONL at `.specify/logs/audit/audit.jsonl` --- ## Tool Registry Compliance (H2) - Total tool calls logged: **$toolCalls** - Denied (missing idempotency key): **$($toolDenied.Count)** - Side-effecting actions: **$($toolRecords | Where-Object { $_.decision -eq "approved" } | Measure-Object | Select-Object -ExpandProperty Count)** --- *Report auto-generated by CASAN H5 Governance Harness* "@ Set-Content -Path $OutputPath -Value $report -Encoding UTF8 Write-Output "COMPLIANCE_REPORT_GENERATED: $OutputPath"