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:
co-authored by
Claude Opus 4.8
parent
0cc43d94d3
commit
4bb184b935
@@ -0,0 +1,192 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://casan.local/schemas/agentic-lifecycle.schema.json",
|
||||
"title": "CASAN Agentic Lifecycle Contract",
|
||||
"description": "Plan-20 client-agnostic lifecycle contract for the CASAN agentic bridge. Each request is a single JSON object read on stdin; each response is a single JSON object written on stdout. Client adapters (Claude Code, Codex, VS Code) translate their native hook payloads into these shapes and render bridge responses back into client-native JSON. The bridge NEVER calls a model — it only admits, gates, records evidence, and finalizes traces (single-model invariant, Plan-20 §3.1).",
|
||||
"type": "object",
|
||||
"required": ["op"],
|
||||
"properties": {
|
||||
"op": {
|
||||
"type": "string",
|
||||
"enum": ["begin", "pre-tool", "post-tool", "telemetry", "finalize", "abort"],
|
||||
"description": "Lifecycle operation to perform."
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "op": { "const": "begin" } } },
|
||||
"then": { "$ref": "#/definitions/beginRequest" }
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "op": { "const": "pre-tool" } } },
|
||||
"then": { "$ref": "#/definitions/preToolRequest" }
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "op": { "const": "post-tool" } } },
|
||||
"then": { "$ref": "#/definitions/postToolRequest" }
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "op": { "const": "telemetry" } } },
|
||||
"then": { "$ref": "#/definitions/telemetryRequest" }
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "op": { "const": "finalize" } } },
|
||||
"then": { "$ref": "#/definitions/finalizeRequest" }
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "op": { "const": "abort" } } },
|
||||
"then": { "$ref": "#/definitions/abortRequest" }
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"clientContext": {
|
||||
"type": "object",
|
||||
"required": ["client"],
|
||||
"properties": {
|
||||
"client": {
|
||||
"type": "string",
|
||||
"enum": ["claude-code", "codex", "vscode", "unknown"],
|
||||
"description": "Agentic client family."
|
||||
},
|
||||
"client_version": { "type": ["string", "null"] },
|
||||
"adapter_version": { "type": ["string", "null"] },
|
||||
"project": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Project root as the client sees it; canonicalized by the bridge."
|
||||
},
|
||||
"session": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Client-native session id. Hashed by the bridge, never stored raw."
|
||||
},
|
||||
"integration_mode": {
|
||||
"type": ["string", "null"],
|
||||
"enum": ["casan_owned", "managed_hook", "project_hook", "observed_only", null],
|
||||
"description": "Declared integration mode; the bridge may DOWNGRADE (never upgrade) it based on enforcement mode and coverage."
|
||||
}
|
||||
}
|
||||
},
|
||||
"beginRequest": {
|
||||
"allOf": [{ "$ref": "#/definitions/clientContext" }],
|
||||
"required": ["op", "client", "prompt"],
|
||||
"properties": {
|
||||
"op": { "const": "begin" },
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "Raw user prompt. Scanned by H4 then discarded — only a salted hash is persisted."
|
||||
},
|
||||
"turn": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional client-native turn correlation id; hashed, not stored raw."
|
||||
}
|
||||
}
|
||||
},
|
||||
"preToolRequest": {
|
||||
"required": ["op", "admission_id", "tool"],
|
||||
"properties": {
|
||||
"op": { "const": "pre-tool" },
|
||||
"admission_id": { "type": "string" },
|
||||
"tool": {
|
||||
"type": "string",
|
||||
"description": "Client-native tool name, e.g. Bash, Edit, Write, WebFetch."
|
||||
},
|
||||
"tool_input": {
|
||||
"description": "Tool input payload. Scanned/redacted; only a hash + redacted summary are persisted."
|
||||
},
|
||||
"project": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"postToolRequest": {
|
||||
"required": ["op", "admission_id", "tool"],
|
||||
"properties": {
|
||||
"op": { "const": "post-tool" },
|
||||
"admission_id": { "type": "string" },
|
||||
"tool": { "type": "string" },
|
||||
"status": {
|
||||
"type": ["string", "null"],
|
||||
"enum": ["success", "error", "denied", "timeout", null]
|
||||
},
|
||||
"duration_ms": { "type": ["integer", "null"], "minimum": 0 },
|
||||
"result": { "description": "Tool result. Never stored raw — hashed + redacted." }
|
||||
}
|
||||
},
|
||||
"telemetryRequest": {
|
||||
"required": ["op", "admission_id"],
|
||||
"properties": {
|
||||
"op": { "const": "telemetry" },
|
||||
"admission_id": { "type": "string" },
|
||||
"model": { "type": ["string", "null"] },
|
||||
"runtime_ms": { "type": ["integer", "null"], "minimum": 0 },
|
||||
"input_tokens": { "type": ["integer", "null"], "minimum": 0 },
|
||||
"output_tokens": { "type": ["integer", "null"], "minimum": 0 },
|
||||
"cache_tokens": { "type": ["integer", "null"], "minimum": 0 },
|
||||
"cost_amount": { "type": ["number", "null"], "minimum": 0 },
|
||||
"cost_currency": { "type": ["string", "null"] },
|
||||
"cost_source": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Provenance of cost/token numbers, e.g. provider_reported, statusline_estimate, session_delta, unavailable. Numbers WITHOUT an accurate source MUST be null with a warning (Plan-20 §5).",
|
||||
"enum": [
|
||||
"provider_reported",
|
||||
"sdk_result_message",
|
||||
"statusline_estimate",
|
||||
"session_delta",
|
||||
"unavailable",
|
||||
null
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalizeRequest": {
|
||||
"required": ["op", "admission_id"],
|
||||
"properties": {
|
||||
"op": { "const": "finalize" },
|
||||
"admission_id": { "type": "string" },
|
||||
"stop_reason": {
|
||||
"type": ["string", "null"],
|
||||
"enum": ["completed", "user_interrupt", "error", "max_turns", "timeout", null]
|
||||
},
|
||||
"assistant_summary": { "type": ["string", "null"] },
|
||||
"changed_files": {
|
||||
"type": ["array", "null"],
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"abortRequest": {
|
||||
"required": ["op"],
|
||||
"properties": {
|
||||
"op": { "const": "abort" },
|
||||
"admission_id": { "type": ["string", "null"] },
|
||||
"reason": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"bridgeResponse": {
|
||||
"type": "object",
|
||||
"required": ["op", "decision", "schema_version"],
|
||||
"properties": {
|
||||
"op": { "type": "string" },
|
||||
"schema_version": { "type": "string" },
|
||||
"decision": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "block", "deny", "recorded", "certified", "non_certified", "error"]
|
||||
},
|
||||
"admission_id": { "type": ["string", "null"] },
|
||||
"trace_id": { "type": ["string", "null"] },
|
||||
"integration_mode": { "type": ["string", "null"] },
|
||||
"certification_strength": {
|
||||
"type": ["string", "null"],
|
||||
"enum": ["casan_owned", "managed_hook", "project_hook", "observed_only", null]
|
||||
},
|
||||
"telemetry_quality": {
|
||||
"type": ["string", "null"],
|
||||
"enum": ["complete", "partial", "insufficient", null]
|
||||
},
|
||||
"reason": { "type": ["string", "null"] },
|
||||
"warnings": { "type": "array", "items": { "type": "string" } },
|
||||
"context": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional additional context the adapter may inject into the turn (e.g. certification banner)."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user