Follow-ups from #264 (per-thread stream policy). Five small defensive gaps:
policy.py:48 — StreamPolicy.from_dict silently drops unknown keys
Typo'd field from the model (e.g. "delta": true) yields an all-defaults policy plus a success reply — the same silent failure the results validation exists to refuse. Refuse unknown keys.
dispatch.py:149 — resolve() outside _run_turn's try
A poisoned row policy (bad results value via unvalidated store.set_stream_policy — currently test-only — or hand-edited DB) raises ValueError before the try on every turn, propagating out of handle into adapter loops and bricking the thread. Validate in set_stream_policy and/or fall back to the channel default with a log.
web/script_tools.py:78 — Live JS missing result frame branch
No branch for the new inline-mode result frame (invisible on cockpit). Under results=off, addTool(undefined, name) renders a load-result button that fetches /history/tool?call_id=undefined. Hardening: only append the button when callId is truthy.
agent/session_tools.py:73 — Partial-policy semantics
Unspecified fields fill from all-off defaults, not the thread's channel default. Passing {"send_guard": false} on an imessage thread silently turns off deltas/tools/results too. Document unset=off in the tool description or plumb the channel default as base.
agent/manager.py:139 — Micro-TOCTOU
Between ensure_session returning False and set_stream_policy, a concurrent thread delete makes set_stream_policy a silent no-op while the tool still reports 'stream policy updated'. Tiny window, self-race only; close by returning a written-bool from set_stream_policy if ever needed.
Follow-ups from #264 (per-thread stream policy). Five small defensive gaps:
policy.py:48 — StreamPolicy.from_dict silently drops unknown keys
Typo'd field from the model (e.g.
"delta": true) yields an all-defaults policy plus a success reply — the same silent failure the results validation exists to refuse. Refuse unknown keys.dispatch.py:149 — resolve() outside _run_turn's try
A poisoned row policy (bad results value via unvalidated store.set_stream_policy — currently test-only — or hand-edited DB) raises ValueError before the try on every turn, propagating out of handle into adapter loops and bricking the thread. Validate in set_stream_policy and/or fall back to the channel default with a log.
web/script_tools.py:78 — Live JS missing result frame branch
No branch for the new inline-mode result frame (invisible on cockpit). Under results=off, addTool(undefined, name) renders a load-result button that fetches
/history/tool?call_id=undefined. Hardening: only append the button when callId is truthy.agent/session_tools.py:73 — Partial-policy semantics
Unspecified fields fill from all-off defaults, not the thread's channel default. Passing
{"send_guard": false}on an imessage thread silently turns off deltas/tools/results too. Document unset=off in the tool description or plumb the channel default as base.agent/manager.py:139 — Micro-TOCTOU
Between ensure_session returning False and set_stream_policy, a concurrent thread delete makes set_stream_policy a silent no-op while the tool still reports 'stream policy updated'. Tiny window, self-race only; close by returning a written-bool from set_stream_policy if ever needed.