fix: steer-delivery starvation — continuation coalescing (claimSteers)

The bug (seen live): a teacher that ends every turn with a new pending quiz
keeps the wake pipeline's idle gates permanently closed — the steer sits in
'delivering result' forever while the learner answers check after check.

- claimSteers (server/jobs.ts): the chat-handler claims every queued,
  undelivered steer the instant a client turn wins the turn registry, and
  appends it after the learner's answer — conversion-safe, the answered
  quiz carries its output. Every answer flushes pending steers immediately.
- The pump keeps its post-idle duplicate-guard (re-check steerDelivered
  before firing), so a claim racing the pump converges to exactly one
  delivery either way.
- The stale-job sweep now skips chats with a turn in flight (a healthy but
  slow wake no longer gets falsely re-queued as 'recovering').
- Out-of-lane surgical touch (flagged for master review): the 3-line
  handleChatRequest seam (claim after beginTurn, persist+stream 'outgoing').
- Verified live end-to-end on the stuck session: answering the pending quiz
  fired the steer + wake turn within a second; job flipped done/delivered.
This commit is contained in:
amosblomqvist
2026-09-09 03:04:21 +03:00
parent 67fbc0575f
commit e33b83ca2d
4 changed files with 165 additions and 4 deletions
+11
View File
@@ -73,6 +73,17 @@ export interface JobRecord {
* a polling client that sees the flip re-syncs once and gets the
* finished record.
*
* CONTINUATION COALESCING: the idle window above can starve — a
* quiz-chaining teacher ends every turn with a new pending check, so
* the gates never stay open long enough for a wake to fire. The fix:
* when a client turn wins the turn registry, the handler CLAIMS every
* queued, undelivered steer (jobs.ts claimSteers) and appends it after
* the learner's answer in that very turn — conversion-safe, because the
* answered quiz carries its output. Every answer flushes pending
* steers immediately; the wake pump remains for turns that end with no
* pending question. The pump re-checks steerDelivered right before
* firing, so a claim racing the pump converges to exactly one delivery.
*
* 5. No pending job parts ever exist, so the composer never gates on jobs
* (the learner can interject mid-job; the steer queues behind their
* turn) and job tool outputs never trigger the client-side