specs/mail/mailbox-connection.md §5 requires a bounded clock, and it is not implemented. Deferred deliberately from HT-101 (PR #165) as a starvation risk rather than a correctness one.
What the spec requires
every network operation — IMAP connect, login, SELECT, each FETCH, and every SMTP step — carries its own timeout derived from the remaining invocation budget, and the worker stops starting new work once too little budget remains to finish it.
What exists
src/providers/adapters/imap/client.ts uses independent fixed 30-second connect/greeting/socket timeouts. Ingestion (src/mail/imap-fetch.ts) has no remaining-budget guard and no timeout of its own. Nothing derives a per-operation budget from maxDuration (50s in vercel.json).
Failure scenario
Connect takes 25s, SEARCH/FETCH takes 20s, ingestion starts at second 45 and the platform kills the process mid-ingest. The cursor is never advanced, so nothing is lost — committed messages are safely re-fetched. But the same mailbox can consume every invocation indefinitely without advancing, and the connection may not close on the way out.
This is availability, not silent loss: an intake stall, visible in the sweep's own counters.
Acceptance
- A single invocation budget is threaded from the cron entry point through connect, select, fetch, and ingest.
- The worker declines to start an operation it cannot finish within the remaining budget, rather than starting and being killed.
- Connections close on the budget-exhausted path as reliably as on success and failure.
- A test proves a mailbox that exhausts its budget yields to the next tick instead of re-consuming it.
Found by an adversarial Codex review of PR #165, 2026-07-31.
specs/mail/mailbox-connection.md§5 requires a bounded clock, and it is not implemented. Deferred deliberately from HT-101 (PR #165) as a starvation risk rather than a correctness one.What the spec requires
What exists
src/providers/adapters/imap/client.tsuses independent fixed 30-second connect/greeting/socket timeouts. Ingestion (src/mail/imap-fetch.ts) has no remaining-budget guard and no timeout of its own. Nothing derives a per-operation budget frommaxDuration(50s invercel.json).Failure scenario
Connect takes 25s,
SEARCH/FETCHtakes 20s, ingestion starts at second 45 and the platform kills the process mid-ingest. The cursor is never advanced, so nothing is lost — committed messages are safely re-fetched. But the same mailbox can consume every invocation indefinitely without advancing, and the connection may not close on the way out.This is availability, not silent loss: an intake stall, visible in the sweep's own counters.
Acceptance
Found by an adversarial Codex review of PR #165, 2026-07-31.