fix(e2e): rm_gate block contract is exit 2, not exit 0 (FPLAN-0289 CI)

beb048d made the Claude bridge propagate a hook's exit code so presence_gate's
UserPromptSubmit block can cancel a prompt. Every security gate
(rm/git/edit/subagent/presence) already returned exit_code 2 for a block, but
the old bridge swallowed it — so test_t2a_rm_gate_blocks pinned exit 0. Update
the e2e contract to expect exit 2 + the stdout decision JSON, which is the real,
live-proven block signal.

Sole CI red on the P1-activation commits: 1 failed, 10116 passed. Fixes both
e2e-wheel (all 3 OSes) and Windows Test (full suite includes tests/e2e).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmDj4eu8aFFVP2rapovqkg
This commit is contained in:
AIOSAI
2026-06-30 04:36:01 -07:00
co-authored by Claude Opus 4.8
parent dc5c1d23fc
commit 91cb59154d
+9 -4
View File
@@ -241,15 +241,20 @@ def hook_workspace(tmp_path_factory: pytest.TempPathFactory) -> Path:
def test_t2a_rm_gate_blocks(clean_venv: CleanVenv, hook_workspace: Path) -> None:
"""rm -rf is blocked via {"decision":"block"} on STDOUT with exit code 0.
"""rm -rf is blocked via {"decision":"block"} on STDOUT with exit code 2.
Corrected contract (NOT exit 2): the bridge writes the engine's block JSON
to stdout and exits 0.
Block contract: every security gate (rm/git/edit/subagent/presence) returns
exit_code 2 plus a block-JSON decision on stdout, and the bridge propagates
that exit code. The non-zero exit is the cross-event block signal — it is
what lets a UserPromptSubmit gate (presence_gate) actually cancel a prompt,
not just PreToolUse. Claude Code surfaces the stdout decision reason either
way. (Pre-FPLAN-0289 the bridge swallowed the exit code, so this test once
pinned exit 0; beb048d corrected the bridge to propagate it.)
"""
sentinel = f"e2e-block-{uuid.uuid4()}"
proc = _fire_hook(clean_venv, hook_workspace, "rm -rf /tmp/x", sentinel)
assert proc.returncode == 0, f"expected exit 0, got {proc.returncode}. stderr:\n{proc.stderr}"
assert proc.returncode == 2, f"expected exit 2 (block), got {proc.returncode}. stderr:\n{proc.stderr}"
decision = json.loads(proc.stdout)
assert decision.get("decision") == "block", f"stdout was: {proc.stdout!r}"