fix(gateway): exempt system events from the busy-path auth gate - #35
Conversation
An origin-routed kanban-transition wake carries a user-less SessionSource (the webhook builds no user_id for a shared thread) and is HMAC-authenticated at the webhook route. When the origin session was busy, the busy handler's user-authorization gate (NousResearch#17775) ran first, saw user=None, deemed the wake 'unauthorized', and silently dropped it (return True) BEFORE the wake exemption further down — the final swallow that kept an origin-routed wake from ever becoming a turn while the session was busy. Exempt system-internal events (transition wakes and events flagged internal) from the user-authorization gate: they are not user messages and are already authenticated upstream. They remain exempted from interrupt/steer/fold below, so this only lets them reach the queue-as-distinct-turn path. Ordinary unauthorized user messages are still dropped (the NousResearch#17775 protection is intact). Adds a regression test (witnessed-RED against the pre-fix gate) plus a control proving an unauthorized non-system message is still dropped.
cwest
left a comment
There was a problem hiding this comment.
No changes needed.
The gate placement is the crux and it's right: the exemption is computed as internal-flag OR transition-wake, and it only skips the user-authorization drop. A system event still flows through the two dedicated exemptions below (internal at run.py:5027, wake at run.py:5041), both of which return False so the event falls through to the base adapter's wake-precedence enqueue rather than interrupting the running turn. So the change opens exactly one path — reaching the queue-as-distinct-turn logic — and nothing more.
The exemption isn't forgeable from the wire. internal is a server-set field on MessageEvent, and kanban_transition_wake is stamped in one place only: webhook.py:800, which runs after HMAC validation at webhook.py:487-507 (401 on a bad signature, 403 on a missing secret). An inbound platform message can set neither. The control test confirms an unauthorized non-system message is still dropped, so the NousResearch#17775 protection stays intact.
Verified the regression test is real, not a tautology: reverting just the guard on run.py:4894 fails test_transition_wake_not_dropped_by_auth_gate with the exact live drop (user= (), "Dropping message from unauthorized user"), while the control keeps passing. Full suite is green on this head SHA (all 8 test slices plus e2e).
An origin-routed kanban-transition wake carries a user-less SessionSource (the webhook builds no user_id for a shared thread) and is HMAC-authenticated at the webhook route. When the origin session was busy, the busy handler's user-authorization gate (NousResearch#17775) ran first, saw user=None, deemed the wake 'unauthorized', and silently dropped it (return True) BEFORE the wake exemption further down — the final swallow that kept an origin-routed wake from ever becoming a turn while the session was busy. Exempt system-internal events (transition wakes and events flagged internal) from the user-authorization gate: they are not user messages and are already authenticated upstream. They remain exempted from interrupt/steer/fold below, so this only lets them reach the queue-as-distinct-turn path. Ordinary unauthorized user messages are still dropped (the NousResearch#17775 protection is intact). Adds a regression test (witnessed-RED against the pre-fix gate) plus a control proving an unauthorized non-system message is still dropped. (cherry picked from commit 6f196eb)
The final swallow
With origin routing (#26), busy-precedence (#29), busy-handler exemption (#32),
stale-suppression exemption (#33), and owning-adapter dispatch (#34) all in place
and verified live, an origin-routed wake reaching the busy origin session was
STILL silently dropped. Live log:
The busy handler's user-authorization gate (NousResearch#17775) runs first. A transition
wake carries a user-less
SessionSource(the webhook builds nouser_idfor ashared thread) and is HMAC-authenticated at the webhook route — but the auth gate
sees
user=None, deems it unauthorized, and returnsTrue(dropped) beforethe wake exemption further down ever runs.
The fix
Exempt system-internal events (transition wakes + events flagged
internal) fromthe busy-path user-authorization gate. They are not user messages and are already
authenticated upstream; they remain exempted from interrupt/steer/fold below, so
this only lets them reach the queue-as-distinct-turn path. Ordinary unauthorized
user messages are still dropped — the NousResearch#17775 protection is intact.
Verification
tests/gateway/test_busy_session_auth_bypass.py:test_transition_wake_not_dropped_by_auth_gate— witnessed RED against thepre-fix gate (the wake was dropped: "user= ()"), GREEN with the fix (falls
through, running agent not interrupted).
test_ordinary_userless_message_still_dropped— control: an unauthorizednon-system message is still dropped.
test_busy_session_auth_bypass.py: 6 passed.After merge
Requires a gateway restart (
sys.modules-cachedrun.py) before it goes live.This is expected to be the LAST fix in the autonomous-wake chain — the wake now
survives every gate to the drain.