fix(compaction): review round 9 — drain-exit ownership, missed-edge settle, pre-turn hook guard

Three point-guards from the ceiling round (no primitive took a hit;
correctness yield halved at identical review sensitivity):

- The drain's clean-exit wake moved OUT of the function-level try: it
  runs after the drain has already retired its slot, so a raise out of
  the wake (the dispatcher re-raises Thread.start failures) could reach
  the last-resort handler and clear a slot this thread no longer owned —
  nulling a successor drain's live registration and letting two drains
  service one list. The wake now runs post-try under its own guard
  (mirroring _retry_pending_wake), only on the clean-exit path, and the
  last-resort slot-clear is identity-guarded like every sibling exit
  seam. The except arm needed a function-local threading import: the
  module-top import is TYPE_CHECKING-only, so the guard would have
  NameErrored inside the handler with strict mypy fully green.
- The shared settle helper promotes a non-deferred chip that binds onto
  an already-idle pane: its only sweep fired mid-POST (unbound then) and
  no message_dispatched ever comes for non-deferred sends, so the chip
  stayed a permanently retractable "queued" bubble for a delivered
  message. Keyed on post-bind chip state (also catching a raced folded
  settle bind just reconciled) and skipping dismiss-in-flight chips —
  the sweep's own aria-busy discipline. Pinned behaviorally: the helper
  now executes under node (a 4-row missed-edge matrix), possible since
  the consumer-less window bridge is gone.
- _claim_generation's on_generation_claimed emission is call-guarded:
  it sits on send()'s pre-turn path, before the user turn is appended
  and before the fatal handler's coverage, so a raising override
  degrades to a lost latch-break instead of silently dropping every
  user message on that session.

Cleanups: /command's transport catch and status-less non-2xx bodies are
loud now (threading {ok, status} through the parse — deliberately no
throw-on-!ok pre-gate, since the busy and error arms ride 409/503);
PENDING_SENDS_MAX lives in workstream.py and ChatSession._QUEUE_MAX
aliases it (one backpressure bound, structurally incapable of
diverging); the send handler's not-ok arm uses _queue_full_response();
the dead window.createQueueController bridge is deleted and the file
header's consumer map corrected.
This commit is contained in:
Patrick Buckley
2026-07-17 03:54:55 -07:00
parent 1224b02d03
commit d280db514e
9 changed files with 412 additions and 66 deletions
+28
View File
@@ -2170,6 +2170,34 @@ class TestPreHookUICompat:
)
class TestClaimGenerationHookGuard:
"""_claim_generation's on_generation_claimed emission sits on send()'s
PRE-turn path — before the user turn is appended, before
_record_fatal_error's coverage — so a raising override must degrade
to a lost latch-break, never to a silently dropped user message."""
def test_raising_claim_hook_does_not_abort_the_claim(self, session):
calls: list[int] = []
def _boom(gen: int) -> None:
calls.append(gen)
raise RuntimeError("broadcast backend down")
session.ui = SimpleNamespace(
on_thinking_start=lambda: None,
on_thinking_stop=lambda: None,
on_error=lambda _m: None,
on_generation_claimed=_boom,
)
before = session._generation
claimed = session._claim_generation()
assert claimed == before + 1 # claim-state writes stayed infallible
assert session._generation == claimed
assert calls == [claimed] # the hook WAS attempted, then contained
# And again — every claim survives, not just the first.
assert session._claim_generation() == claimed + 1
class TestCompactionNoticeStamp:
"""_compaction_event is the single display-policy site: failed ends
carry ``notice`` — renderers show the message iff it is true, instead