Skip to content

Commit bcd9369

Browse files
committed
docs: record M4 approval Slice B blocker (interceptor misses jit_state/handler)
The enforce cutover cannot land safely: the ApprovalGateInterceptor calls assert_allowed without jit_state/handler, which the inline path passes. assert_allowed uses jit_state to honor session JIT approvals, so enforcing via the interceptor would deny JIT-approved calls the inline path allows — a regression the unit parity test missed (it uses fresh policies). Fix design and state recorded for the next session. Constraint: docs only; halts a known-divergent enforce cutover; controlled-stop, surface not ship Tested: docs validator 0 errors Confidence: high Roadmap-Status: unchanged
1 parent 2da5d6c commit bcd9369

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

docs/generated/docs-inventory.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Generated by `python3 scripts/generate_docs_inventory.py`.
77
Do not edit this file manually — regenerate instead.
88

9-
**Markdown files:** 586
9+
**Markdown files:** 587
1010

1111
| Path | Tier | Bytes | SHA256 (12) |
1212
| --- | --- | ---: | --- |
@@ -589,6 +589,7 @@ Do not edit this file manually — regenerate instead.
589589
| `ux-stability-contract.md` | working | 2238 | `1c5511e10ede` |
590590
| `wasm-skill-ci.md` | working | 974 | `8340d6f1e5c1` |
591591
| `work-log/documentation-optimization-work-items-2026-06-04.md` | archive | 11750 | `9233b40b0bce` |
592+
| `work-log/m4-approval-sliceB-blocked-2026-06-13.md` | archive | 3230 | `f12e188815b7` |
592593
| `work-log/operator-friction-log.md` | working | 2560 | `fe79899db10f` |
593594
| `work-log/p0-p1-governance-implementation-ledger-2026-06-11.md` | archive | 5212 | `0b72cd69de32` |
594595
| `work-log/parallel-phase-0-implementation-report-2026-06-04.md` | archive | 13181 | `098186167459` |
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# M4 Approval Slice B Blocked — Interceptor Misses jit_state/handler
2+
3+
> **Status:** Blocked by a verified correctness gap, 2026-06-13. Approval
4+
> Slice A is committed (`2da5d6c`) and safe (interceptor + unit parity, not
5+
> wired). Slice B (enforce cutover) must NOT land until the gap below is closed.
6+
7+
## The gap
8+
9+
The inline approval check in `AgentRunner._execute_tool_decision`
10+
(`teaagent/runner/_core.py`, the `self.approval_policy.assert_allowed(...)` call)
11+
passes two runtime inputs that the salvaged `ApprovalGateInterceptor`
12+
(`teaagent/runner/_approval_manager.py`) does **not**:
13+
14+
```python
15+
# inline (authoritative today):
16+
self.approval_policy.assert_allowed(
17+
..., jit_state=self.approval_manager.jit_state, handler=tool.handler, ...
18+
)
19+
# interceptor (would replace it in Slice B):
20+
self._approval_policy.assert_allowed(
21+
..., # no jit_state, no handler
22+
)
23+
```
24+
25+
`ApprovalPolicy.assert_allowed` (`teaagent/policy.py`) uses `jit_state` to merge
26+
the session's JIT `approved_call_ids` / `session_approved_tools` into the
27+
decision (verified lines ~23-27 of the method). Without it, a call that the
28+
session has JIT-approved would be **denied** by the interceptor while the inline
29+
path **allows** it — a behavior regression in enforce mode. `handler` is
30+
similarly forwarded and may affect handler-gated decisions.
31+
32+
## Why the unit parity test didn't catch it
33+
34+
`test_approval_gate_interceptor_parity` constructs **fresh** `ApprovalPolicy`
35+
objects per scenario with no JIT/session state, so interceptor and inline agree
36+
trivially. The divergence only appears with live `jit_state` — exactly the
37+
state the interceptor cannot see. The parallel tool's collapsed M4 batch had the
38+
same incomplete interceptor; its acceptance pass did not exercise a
39+
JIT-approved-then-enforced path, so the latent regression went unobserved.
40+
41+
## Fix design (for Slice B, before enforce)
42+
43+
1. Give the interceptor access to JIT state — either hold a reference to the
44+
`RunnerApprovalCoordinator`/`jit_state`, or carry `jit_state` + `handler`
45+
in the `TOOL_CALL_REQUESTED` payload (payload route keeps the interceptor
46+
pure but means putting a callable `handler` in an event payload, which is
47+
ugly and pollutes the audit stream — prefer the reference route).
48+
2. Extend the **parity test** to cover a JIT-approved call and a
49+
session-approved tool, asserting interceptor == inline **with** live
50+
jit_state. This is the assertion that was missing.
51+
3. Only then: register enforce + remove the inline `assert_allowed`
52+
(Slice B), with the JIT parity test green.
53+
54+
## Current state
55+
56+
- Committed and safe: `5b5f007` (PlanGateError), `2da5d6c` (approval Slice A:
57+
interceptor + unit parity, **not wired** — zero behavior change).
58+
- NOT done: approval Slice B (this blocker), budget Slice A, budget Slice B.
59+
- The parallel tool's full collapsed M4 remains in `git stash@{0}`
60+
("parallel-tool-M4-collapsed-batch-salvage-2026-06-13") for salvage —
61+
note it shares this jit_state/handler gap.
62+
- Budget gate has its own trap (see §15 family): the budget interceptor can
63+
emit `budget_warning` audit events, so a shadow budget interceptor must NOT
64+
be given `audit=` while inline budget warnings remain, or warnings
65+
double-write.

0 commit comments

Comments
 (0)