feat(harness): implement Plan-20 transparent agentic client bridge

Wave 0 + Wave 1 core of the transparent agentic-client integration: a
developer types prompts normally in Claude Code / Codex while every
certified turn still carries a full H1->H7 trace and an H6 record.

- agentic_bridge.py: stdlib-only lifecycle state machine (begin/pre-tool/
  post-tool/telemetry/finalize/abort + report/doctor). Single-model
  invariant (never calls a model), fail-closed at the side-effect point,
  admission TTL + canonical-project/session binding, atomic state under
  .specify/state/agentic-sessions/, secret redaction, null-not-zero H6.
- agentic-lifecycle.schema.json: client-agnostic JSON contract.
- adapters/claude-code + adapters/codex: thin hook renderers + config
  templates that call the core bridge.
- phase-agentic-bridge-tests.sh: C1-C12 acceptance + threat suite (30/30).
- devkit templates/{claude,codex} + windows/install-agentic.ps1
  (install/doctor/uninstall with manifest, path-safe).
- docs/casan Windows + security/bypass guides; plan status -> IMPLEMENTED.
- harden generate-agentops-dashboard.py aggregation against null H6 costs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
thanhnv
2026-07-23 20:44:07 +07:00
co-authored by Claude Opus 4.8
parent 0cc43d94d3
commit 4bb184b935
18 changed files with 2647 additions and 6 deletions
@@ -0,0 +1,204 @@
#requires -Version 5.1
<#
.SYNOPSIS
CASAN Plan-20 agentic-client installer for Windows PowerShell.
.DESCRIPTION
Installs, verifies (doctor) or removes the CASAN agentic-client integration
(Claude Code and/or Codex project hooks) in a target repository — from a
clean clone, with NO manual file copying. Every created file is recorded in a
manifest so uninstall never touches a user's own config.
The hooks call the CASAN agentic bridge, which never runs a model (Claude/
Codex remains the sole model executor). See:
docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md
docs/casan/CASAN_AGENTIC_CLIENT_SECURITY.md
.PARAMETER Action
install | doctor | uninstall (default: install)
.PARAMETER Client
claude | codex | all (default: all)
.PARAMETER Target
Target repository root. Default: the repo this script lives in.
.PARAMETER Mode
observe | enforce (default: observe) — sets the bridge
enforcement mode written into the target's .casan/agentic.env.
.EXAMPLE
pwsh packages/casan-devkit/windows/install-agentic.ps1 -Client claude -Mode enforce
.EXAMPLE
pwsh packages/casan-devkit/windows/install-agentic.ps1 -Action doctor -Client all
#>
[CmdletBinding()]
param(
[ValidateSet('install', 'doctor', 'uninstall')] [string]$Action = 'install',
[ValidateSet('claude', 'codex', 'all')] [string]$Client = 'all',
[string]$Target,
[ValidateSet('observe', 'enforce')] [string]$Mode = 'observe'
)
$ErrorActionPreference = 'Stop'
# ── Resolve roots ────────────────────────────────────────────────────────────
# This script lives at <repo>/packages/casan-devkit/windows/install-agentic.ps1
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = (Resolve-Path (Join-Path $ScriptDir '..\..\..')).Path
if (-not $Target) { $Target = $RepoRoot }
$Target = (Resolve-Path $Target).Path
$HarnessRel = 'packages\casan-harness'
$Bridge = Join-Path $Target "$HarnessRel\scripts\python\agentic_bridge.py"
$ClaudeTpl = Join-Path $Target "$HarnessRel\adapters\claude-code\settings.template.json"
$CodexHooks = Join-Path $Target "$HarnessRel\adapters\codex\hooks.template.json"
$CodexConf = Join-Path $Target "$HarnessRel\adapters\codex\config.template.toml"
$ManifestDir = Join-Path $Target '.casan'
$Manifest = Join-Path $ManifestDir 'agentic-install-manifest.json'
function Resolve-Python {
foreach ($cand in @('python3', 'python', 'py')) {
$cmd = Get-Command $cand -ErrorAction SilentlyContinue
if ($cmd) {
try { & $cmd.Source --version *> $null; if ($LASTEXITCODE -eq 0) { return $cmd.Source } } catch {}
}
}
throw 'No working Python 3 interpreter found on PATH (need python3/python/py).'
}
function Write-Info($m) { Write-Host "[casan] $m" }
function Write-Ok($m) { Write-Host "[casan] OK $m" -ForegroundColor Green }
function Write-Warn2($m) { Write-Host "[casan] !! $m" -ForegroundColor Yellow }
function Write-Err($m) { Write-Host "[casan] ERR $m" -ForegroundColor Red }
function Load-Manifest {
if (Test-Path $Manifest) {
try { return Get-Content -Raw -Path $Manifest | ConvertFrom-Json } catch {}
}
return [PSCustomObject]@{ created = @(); clients = @() }
}
function Save-Manifest($m) {
if (-not (Test-Path $ManifestDir)) { New-Item -ItemType Directory -Force -Path $ManifestDir | Out-Null }
$m | ConvertTo-Json -Depth 6 | Set-Content -Path $Manifest -Encoding UTF8
}
# Copy a template into place. Never overwrite a user's existing config silently:
# back it up first and record both in the manifest so uninstall can restore it.
function Install-File($src, $dst, $manifest) {
$dstDir = Split-Path -Parent $dst
if (-not (Test-Path $dstDir)) { New-Item -ItemType Directory -Force -Path $dstDir | Out-Null }
if (Test-Path $dst) {
$backup = "$dst.casan-bak"
if (-not (Test-Path $backup)) {
Copy-Item -Path $dst -Destination $backup -Force
Write-Warn2 "existing $([System.IO.Path]::GetFileName($dst)) backed up to $([System.IO.Path]::GetFileName($backup))"
}
}
Copy-Item -Path $src -Destination $dst -Force
$entry = @{ path = $dst; from = $src }
$manifest.created = @($manifest.created + $dst | Select-Object -Unique)
Write-Ok "installed $dst"
}
function Do-Install {
if (-not (Test-Path $Bridge)) { throw "Bridge not found at $Bridge — is this a CASAN repo?" }
$py = Resolve-Python
$manifest = Load-Manifest
$clients = @()
if ($Client -in @('claude', 'all')) {
Install-File $ClaudeTpl (Join-Path $Target '.claude\settings.json') $manifest
$clients += 'claude'
}
if ($Client -in @('codex', 'all')) {
Install-File $CodexHooks (Join-Path $Target '.codex\hooks.json') $manifest
Install-File $CodexConf (Join-Path $Target '.codex\config.toml') $manifest
$clients += 'codex'
Write-Warn2 'Codex loads project hooks only AFTER you accept its trust prompt — open the repo in Codex once to complete onboarding.'
}
# Bridge feature flags for the target (sourced by the user's shell/session).
$envFile = Join-Path $Target '.casan\agentic.env'
@(
"# CASAN Plan-20 agentic bridge flags (Windows). Source before starting the client.",
"CASAN_AGENTIC_BRIDGE_ENABLED=1",
"CASAN_AGENTIC_ENFORCEMENT_MODE=$Mode",
"CASAN_AGENTIC_INTEGRATION_MODE=project_hook"
) | Set-Content -Path $envFile -Encoding UTF8
$manifest.created = @($manifest.created + $envFile | Select-Object -Unique)
Write-Ok "wrote flags -> $envFile (mode=$Mode)"
$manifest.clients = @($clients | Select-Object -Unique)
Save-Manifest $manifest
Write-Info 'Running doctor to verify...'
Do-Doctor
Write-Info "Install complete. Set CASAN_AGENTIC_ENFORCEMENT_MODE=enforce when ready to certify turns."
}
function Do-Doctor {
$py = Resolve-Python
Write-Info "python : $py"
Write-Info "target : $Target"
Write-Info "bridge : $Bridge"
if (-not (Test-Path $Bridge)) { Write-Err 'bridge missing'; exit 1 }
# Core bridge self-diagnostics.
& $py $Bridge doctor
if ($LASTEXITCODE -ne 0) { Write-Err 'bridge doctor reported a problem'; }
$ok = $true
if ($Client -in @('claude', 'all')) {
if (Test-Path (Join-Path $Target '.claude\settings.json')) { Write-Ok '.claude\settings.json present' }
else { Write-Warn2 '.claude\settings.json missing (run install)'; $ok = $false }
}
if ($Client -in @('codex', 'all')) {
if (Test-Path (Join-Path $Target '.codex\hooks.json')) { Write-Ok '.codex\hooks.json present (Codex trust review still required)' }
else { Write-Warn2 '.codex\hooks.json missing (run install)'; $ok = $false }
}
# A real begin->finalize smoke turn against an isolated state dir.
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("casan-agentic-" + [guid]::NewGuid().ToString('N'))
$env:CASAN_STATE_ROOT = (Join-Path $tmp '.specify')
try {
$begin = '{"op":"begin","client":"claude-code","project":"' + ($Target -replace '\\','/') + '","session":"doctor","prompt":"doctor smoke","integration_mode":"project_hook"}'
$resp = $begin | & $py $Bridge run | ConvertFrom-Json
if ($resp.decision -eq 'allow') { Write-Ok "smoke begin admitted (strength=$($resp.certification_strength))" }
else { Write-Warn2 "smoke begin decision=$($resp.decision)"; }
} finally {
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
Remove-Item Env:\CASAN_STATE_ROOT -ErrorAction SilentlyContinue
}
if ($ok) { Write-Ok 'doctor passed' } else { Write-Warn2 'doctor found missing config' }
}
function Do-Uninstall {
$manifest = Load-Manifest
if (-not $manifest.created -or $manifest.created.Count -eq 0) {
Write-Warn2 'no install manifest found — nothing to remove'
return
}
foreach ($f in $manifest.created) {
if (Test-Path $f) {
Remove-Item -Force $f
Write-Ok "removed $f"
}
$backup = "$f.casan-bak"
if (Test-Path $backup) {
Move-Item -Force $backup $f
Write-Ok "restored user's original $f from backup"
}
}
Remove-Item -Force $Manifest -ErrorAction SilentlyContinue
Write-Info 'Uninstall complete — your own (non-CASAN) config was preserved.'
}
switch ($Action) {
'install' { Do-Install }
'doctor' { Do-Doctor }
'uninstall' { Do-Uninstall }
}