Show a gap notice when a stalled connection drops watch notices

Merged
AF
3h ago
+1845-59

Stacked on #1095 (needs its migrations 22-23; this adds migration 24). Retarget to main after #1095 merges.

When the 256-entry reliable-push retry buffer evicts a watch-match notice on a stalled connection, AFT records a durable gap. The next notice for that watch says, in the approved wording:

Some watch notices were lost: a stalled connection dropped 2 of them, and they cannot be recovered. At least 1 match is missing. Read the task output with bash_status({ task_id: "bash-gap", stderr_offset: 16 }).

("1 of them" for one notice; "Some matches may be missing." when matches can't be counted; OpenCode uses taskId/stderrOffset.) The gap is shown once, survives restart, follows the watch's exact-version acknowledgement, and never counts as a match for a one-shot watch. Health reports transport evictions separately from outstanding watch gaps and stays degraded until gaps are acknowledged. Recoverable snapshots and task completions are not counted as missing.


Summary by cubic

Shows a gap notice when a stalled connection drops watch-match notices, so the user learns that matches were lost and where to read the task output, and health stays degraded until the gap is acknowledged.

Migration

  • Adds schema migration 24 with bash_watch_delivery_acks and bash_watch_delivery_gaps tables.

Behavior

  • When the 256-entry reliable-push retry buffer evicts a watch-match notice, AFT records a durable gap; the next notice for that watch says how many notices and at least how many matches are missing, with a bash_status(...) pointer to the task output, or "Some matches may be missing" when matches cannot be counted.
  • The gap shows once, survives restart, follows the watch's exact-version acknowledgement, and never counts as a match for a one-shot watch.
  • Health reports transport evictions separately from outstanding watch gaps and stays degraded until gaps are acknowledged.
  • Recoverable snapshots and task completions are not counted as missing.