fix: harden Codex hook finalization
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"description": "CASAN Plan-20 lifecycle hooks. Review with /hooks; the project bootstrap resolves and verifies the pinned global harness.",
|
||||
"description": "CASAN lifecycle hooks. Review with /hooks; the project bootstrap resolves and verifies the pinned Core runtime.",
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event UserPromptSubmit",
|
||||
"commandWindows": "py -3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event UserPromptSubmit",
|
||||
"command": "python3 \".casan/casan-hook.py\" --client codex --event UserPromptSubmit",
|
||||
"commandWindows": "py -3 \".casan\\casan-hook.py\" --client codex --event UserPromptSubmit",
|
||||
"timeout": 15,
|
||||
"statusMessage": "CASAN admission"
|
||||
}
|
||||
@@ -20,8 +20,8 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event PreToolUse",
|
||||
"commandWindows": "py -3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event PreToolUse",
|
||||
"command": "python3 \".casan/casan-hook.py\" --client codex --event PreToolUse",
|
||||
"commandWindows": "py -3 \".casan\\casan-hook.py\" --client codex --event PreToolUse",
|
||||
"timeout": 15,
|
||||
"statusMessage": "CASAN policy gate"
|
||||
}
|
||||
@@ -34,8 +34,8 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event PostToolUse",
|
||||
"commandWindows": "py -3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event PostToolUse",
|
||||
"command": "python3 \".casan/casan-hook.py\" --client codex --event PostToolUse",
|
||||
"commandWindows": "py -3 \".casan\\casan-hook.py\" --client codex --event PostToolUse",
|
||||
"timeout": 15,
|
||||
"statusMessage": "CASAN evidence"
|
||||
}
|
||||
@@ -47,8 +47,8 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event Stop",
|
||||
"commandWindows": "py -3 \"$(git rev-parse --show-toplevel)/.casan/casan-hook.py\" --client codex --event Stop",
|
||||
"command": "python3 \".casan/casan-hook.py\" --client codex --event Stop",
|
||||
"commandWindows": "py -3 \".casan\\casan-hook.py\" --client codex --event Stop",
|
||||
"timeout": 15,
|
||||
"statusMessage": "CASAN finalize"
|
||||
}
|
||||
|
||||
@@ -857,14 +857,39 @@ def op_finalize(req):
|
||||
# summary as the closing verification control.
|
||||
reasons = []
|
||||
if req.get("assistant_summary"):
|
||||
ok, why = h4_scan(req.get("assistant_summary"), "output")
|
||||
add_evidence(rec, "H3/H7", "finalize-verify", "allow" if ok else "flag", why)
|
||||
if not ok:
|
||||
h4 = h4_scan(req.get("assistant_summary"), "output")
|
||||
if h4 == "ok":
|
||||
add_evidence(
|
||||
rec, "H3/H7", "finalize-verify", "allow", "h4_ok")
|
||||
elif h4 == "unavailable":
|
||||
# An output that could not be scanned must never be certified.
|
||||
# Match begin/pre-tool graceful degradation: keep the developer
|
||||
# workflow available, but make the missing control explicit.
|
||||
rec["integration_mode"] = "observed_only"
|
||||
rec["certification_strength"] = "observed_only"
|
||||
add_evidence(
|
||||
rec, "H3/H7", "finalize-verify", "degraded",
|
||||
"gate_unavailable_no_bash")
|
||||
reasons.append("output_gate_unavailable")
|
||||
else:
|
||||
# A policy block or timeout is fail-closed for certification.
|
||||
# Treat unknown statuses the same way so a future contract change
|
||||
# cannot silently certify an unverified output.
|
||||
why = {
|
||||
"blocked": "h4_blocked",
|
||||
"timeout": "h4_internal_timeout",
|
||||
}.get(h4, "h4_unknown_status")
|
||||
add_evidence(
|
||||
rec, "H3/H7", "finalize-verify", "flag", why)
|
||||
reasons.append("output_flagged:%s" % why)
|
||||
|
||||
# Certification decision.
|
||||
strength = rec.get("certification_strength", "observed_only")
|
||||
certified = True
|
||||
if reasons:
|
||||
# Any closing output-verification failure above is certification-fatal,
|
||||
# even when every earlier admission/tool control succeeded.
|
||||
certified = False
|
||||
if enforcement_mode() != "enforce":
|
||||
certified = False
|
||||
reasons.append("observe_mode")
|
||||
|
||||
@@ -116,6 +116,27 @@ METRICS=$(grep -c '"harness":"H6-agentic"' "$CASAN_STATE_ROOT/logs/cost/metrics.
|
||||
[[ "$(printf '%s' "$F2" | field reason)" == "already_finalized" && "$METRICS" == "1" ]] \
|
||||
&& pass "second finalize is idempotent (one metric only)" || fail "finalize not idempotent (reason=$(printf '%s' "$F2" | field reason) metrics=$METRICS)"
|
||||
|
||||
# ── C8b: output scan status contract is handled without finalize crashes ────
|
||||
echo "===== C8b: finalize handles blocked and unavailable output scans ====="
|
||||
newstate
|
||||
B=$(bridge '{"op":"begin","client":"codex","project":"'"$PROJ"'","session":"c8b-block","prompt":"summarize the work","integration_mode":"project_hook"}')
|
||||
AID=$(printf '%s' "$B" | field admission_id)
|
||||
F=$(bridge '{"op":"finalize","admission_id":"'"$AID"'","stop_reason":"completed","assistant_summary":"API_KEY=supersecret"}')
|
||||
[[ "$(printf '%s' "$F" | field decision)" == "non_certified" ]] \
|
||||
&& printf '%s' "$F" | field reason | grep -q "output_flagged:h4_blocked" \
|
||||
&& pass "blocked assistant output finalizes as non-certified without crashing" \
|
||||
|| fail "blocked assistant output finalize contract failed ($F)"
|
||||
|
||||
newstate
|
||||
B=$(bridge '{"op":"begin","client":"codex","project":"'"$PROJ"'","session":"c8b-unavailable","prompt":"summarize the work","integration_mode":"project_hook"}')
|
||||
AID=$(printf '%s' "$B" | field admission_id)
|
||||
F=$(CASAN_AGENTIC_BASH=/nonexistent/bash-xyz bridge '{"op":"finalize","admission_id":"'"$AID"'","stop_reason":"completed","assistant_summary":"completed safely"}')
|
||||
[[ "$(printf '%s' "$F" | field decision)" == "non_certified" ]] \
|
||||
&& [[ "$(printf '%s' "$F" | field certification_strength)" == "observed_only" ]] \
|
||||
&& printf '%s' "$F" | field reason | grep -q "output_gate_unavailable" \
|
||||
&& pass "unavailable output gate degrades finalize without crashing" \
|
||||
|| fail "unavailable output gate finalize contract failed ($F)"
|
||||
|
||||
# ── C10: token/cost unavailable -> null + warning, never 0 ───────────────────
|
||||
echo "===== C10: missing token/cost = null + partial warning, not zero ====="
|
||||
newstate
|
||||
|
||||
Reference in New Issue
Block a user