Skip to content

Commit 461505b

Browse files
committed
docs: M4 approval — reference approach hit a third coupling gap; escalate architecture decision
Followed the owner's "reference approach": extended the interceptor design to recover jit_state (provider) and handler (registry), closing gaps 1-2. Wiring Slice B then surfaced gap 3: auto-mode swaps self.approval_policy at runtime after the emit, so a construction-time interceptor reference diverges. The approval gate is deeply runtime-coupled (live JIT state, handler, swappable policy) — unlike the stateless plan gate. Recorded two options: (A) heavy provider-shim interceptor, or (B, recommended) leave approval enforcement inline and use the spine only for approval observability. Owner decision needed before any enforce cutover. Reverted the in-progress interceptor edit; committed state (PlanGateError + approval Slice A, not wired) is clean and behavior-unchanged. Constraint: docs only; no enforce cutover; reverted in-progress edit to keep tree clean; escalates an architecture decision rather than shipping coupling Tested: lifecycle 36 (Slice A intact); docs validator 0 errors Confidence: high Roadmap-Status: unchanged
1 parent bcd9369 commit 461505b

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

docs/generated/docs-inventory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +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` |
592+
| `work-log/m4-approval-sliceB-blocked-2026-06-13.md` | archive | 6038 | `a69b3abac821` |
593593
| `work-log/operator-friction-log.md` | working | 2560 | `fe79899db10f` |
594594
| `work-log/p0-p1-governance-implementation-ledger-2026-06-11.md` | archive | 5212 | `0b72cd69de32` |
595595
| `work-log/parallel-phase-0-implementation-report-2026-06-04.md` | archive | 13181 | `098186167459` |

docs/work-log/m4-approval-sliceB-blocked-2026-06-13.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,56 @@ JIT-approved-then-enforced path, so the latent regression went unobserved.
6363
emit `budget_warning` audit events, so a shadow budget interceptor must NOT
6464
be given `audit=` while inline budget warnings remain, or warnings
6565
double-write.
66+
67+
---
68+
69+
## Update — reference approach attempted, deeper coupling found (2026-06-13)
70+
71+
Per the owner's "use the reference approach" decision, I extended the
72+
interceptor to recover the runtime inputs (jit_state via a provider callable,
73+
handler via a registry reference). That closes gaps 1-2. But wiring Slice B
74+
surfaced a **third** runtime-coupling gap:
75+
76+
3. **`self.approval_policy` is swapped at runtime by auto-mode.** In
77+
`_execute_tool_decision`, after the `TOOL_CALL_REQUESTED` emit,
78+
`get_auto_approve_policy()` may reassign `self.approval_policy`
79+
(`_core.py` ~line 664-665). The inline `assert_allowed` then runs against
80+
the *new* policy. An interceptor that captured `approval_policy` at
81+
construction holds the *stale* reference → divergence under auto-mode. Fix
82+
would require a policy **provider** too (and re-ordering the emit after the
83+
swap).
84+
85+
## Architectural finding and recommendation (owner decision needed)
86+
87+
The plan gate moved to an interceptor cleanly because it is nearly stateless
88+
(`evaluate_write_gate` over context). The **approval gate is deeply
89+
runtime-coupled**: its decision depends on (1) live JIT/session state,
90+
(2) the tool handler, and (3) a permission policy that auto-mode can swap
91+
mid-call — none of which live in the event payload. Making the interceptor
92+
faithful means turning it into a thin shim that reaches back into runner state
93+
for *all three* via providers. That is doable but couples the "pure interceptor
94+
on an event" model tightly to runner internals, and each provider is a
95+
regression surface (gaps 1-3 were each invisible to the unit parity test).
96+
97+
**Two honest options for the owner:**
98+
99+
- **(A) Heavy shim:** give the interceptor policy/jit_state/handler providers,
100+
re-order the emit after the auto-mode swap, and extend the parity test to
101+
cover JIT-approved, handler-gated, and auto-mode-swapped cases. Completes the
102+
spine vision (approval is an interceptor) at the cost of coupling.
103+
- **(B) Leave approval inline (recommended):** keep the approval gate as a
104+
runner concern (it is legitimately stateful), and have the spine carry an
105+
approval *observability* event (e.g. emit the decision as a consumer-only
106+
event for evidence/receipts) rather than moving enforcement into an
107+
interceptor. The spine/interceptor model fits stateless gates (plan); forcing
108+
a stateful gate into it is the source of every gap here.
109+
110+
Budget gate (still pending) is closer to plan (cost thresholds) than to
111+
approval, so it is likely interceptor-suitable — but should be assessed after
112+
this decision.
113+
114+
## Current committed state (unchanged, clean)
115+
116+
`bcd9369` is HEAD-ish for this thread: PlanGateError (`5b5f007`) + approval
117+
Slice A (`2da5d6c`, interceptor + unit parity, NOT wired) + this report. No
118+
enforce cutover landed. Working tree clean.

0 commit comments

Comments
 (0)