Skip to content

coord: raise the mail TTL to 72h, and wire the urgent rewake tier into the installer - #337

Merged
wshallwshall merged 5 commits into
mainfrom
w3-mail-wake-and-ttl
Aug 12, 2026
Merged

coord: raise the mail TTL to 72h, and wire the urgent rewake tier into the installer#337
wshallwshall merged 5 commits into
mainfrom
w3-mail-wake-and-ttl

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Two independent gaps in session-to-session coordination mail, both measured, neither previously visible.

Mail TTL 720 -> 4320 minutes (12h -> 72h). Delivery points are SessionStart and Stop, so a recipient closed or idle overnight receives nothing until someone opens it. At 720 an ordinary overnight gap EXPIRED the message. Expiry is silent in both directions -- the recipient is never told a message existed, the sender is never told it went unread -- which made this the only place in the transport where mail is genuinely LOST rather than late, reachable by doing nothing. Verified end-to-end: a real send now writes expiresUtc at 72.0h. The internal New-Message default moves in step, because two defaults that disagree is how the tested path and the shipped path diverge.

mail-watch.ps1 was built, verified, documented -- and never wired. install-coordination.ps1 had no row for it, so the urgent rewake tier has been inert since it was written. Now armed at Stop, the moment a session goes idle.

Three traps the wiring had to clear, each a previously documented failure mode:

  • A fourth marker. Test-IsOurs is a substring match, and the file names mefor-mail-urgent as the exact mistake -- it would have stripped the drain's own row. mefor-wake is pairwise non-containing with all three existing markers.
  • A separate shim that PROPAGATES THE EXIT CODE. A rewake fires on exit 2 and only on exit 2; the shared shim ends & $s; break and exits 0, so a watcher that found mail would have had its payload discarded silently. The exit sits inside the Test-Path branch, so outside a repo the fall-through exit 0 is reached rather than forwarding a stale code.
  • async AND asyncRewake together. Backgrounding is gated on isInteractive, so asyncRewake alone falls through to the synchronous path in a non-interactive run and would block the session for its full timeout. The pair is emitted only for rows that ask for it, so a PreToolUse gate is never backgrounded -- a gate that returns asynchronously is a gate that does not gate.

Verification: installed with -Only Stop across all five config roots; async, rewake, timeout and the exit suffix present in each; the drain untouched at one row; a second run idempotent at exactly one watch and one drain per root. test_installed_coord_hooks 17 passed; 138 passed across test_session_mail, test_announce_wiring, test_collision_gate. The exit-code claim was re-measured with a NEGATIVE CONTROL after being asserted untested: this shim spelling forwards exit 2, the shared spelling reports 1, independently reproducing the script's own header claim; absent script yields 0 with no stale code forwarded.

Known and NOT claimed. The watcher is one-shot per arming, so each Stop arms one and the session falls back to the drain after a delivery. Whether an exit-2 rewake can wake a session that is IDLE AT A PROMPT, rather than inject mid-turn, is NOT established -- the earlier verification armed at SessionStart and exited after two seconds, which is mid-turn by construction, and the two outcomes produce byte-identical transcripts. This matters because if async is ignored, raising the watcher window would HANG a session rather than wake it. The window therefore stays at 900s, and raising it is gated on answering that question first, which needs an observation only the owner can make.

Authored by the backlog-clearing session; pushed and opened by the coordinator.

…o the installer

Two independent gaps in session-to-session mail, both measured.

TTL 720 -> 4320 (12h -> 72h). The delivery points are SessionStart and Stop,
so a recipient that is closed or idle overnight receives nothing until someone
opens it -- and at 720 an ordinary overnight gap EXPIRED the message. Expiry is
silent in both directions: the recipient is never told a message existed, and
the sender is never told it went unread. That is the only place in this
transport where a message is genuinely lost rather than merely late, and it was
reachable by doing nothing. 72h spans a weekend without abandoning the
staleness argument the original 720 was chosen for. Verified end-to-end: a real
send now writes expiresUtc at 72.0h. The internal New-Message default is moved
in step, because two defaults that disagree is how the tested path and the
shipped path diverge.

mail-watch.ps1 was built, verified against v2.1.221, documented -- and never
wired. install-coordination.ps1 had no row for it, so the urgent tier has been
inert since it was written. Wired at Stop, which is the moment a session goes
idle: the drain already covers arrival-before-you-got-here, and the gap this
closes is mail landing while a session sits at a prompt with nobody typing.

Three things the wiring had to get right, each a documented failure mode:

  - A FOURTH marker. The file names "mefor-mail-urgent" as the exact mistake:
    Test-IsOurs is a substring match, so it would strip the drain's own row.
    "mefor-wake" is pairwise non-containing with all three existing markers.

  - A separate shim that PROPAGATES THE EXIT CODE. A rewake fires on exit 2 and
    only on exit 2. The shared shim ends `& $s; break` and exits 0, so a watcher
    that found mail would have its payload discarded -- silently, the failure
    mode mail-watch.ps1 records as costing a full debug cycle. The exit sits
    inside the Test-Path branch so that outside a repo, where nothing ran, the
    fall-through exit 0 is reached instead of forwarding a stale code.

  - async AND asyncRewake, both. Backgrounding is gated on isInteractive, so
    asyncRewake alone falls through to the synchronous path in a non-interactive
    run and would block the session for its full timeout. The pair is emitted
    only for rows that ask for it, so every existing row keeps its exact shape
    and a PreToolUse gate is never backgrounded -- a gate that returns
    asynchronously is a gate that does not gate.

Timeout 1200 against the script's own 900s wait: a watcher killed at timeout is
indistinguishable from one that found nothing, so the headroom is deliberate.

Installed with -Only Stop across all five config roots. Verified: async, rewake,
timeout and the exit suffix present in each; the drain untouched at one row; a
second run idempotent at exactly one watch and one drain per root. Tests green
-- test_installed_coord_hooks 17 passed, and 138 passed across test_session_mail,
test_announce_wiring and test_collision_gate.

Known and NOT claimed: the watcher is one-shot per arming, so each Stop arms one
and the session falls back to the drain after a delivery. Whether an exit-2
rewake can wake a session that is IDLE AT A PROMPT, rather than inject mid-turn,
is NOT established -- the 2026-08-05 verification armed at SessionStart and
exited after two seconds, which is mid-turn by construction. Raising the 900s
wait to cover hours is one number, and it is gated on answering that first.
@wshallwshall
wshallwshall merged commit 06245f4 into main Aug 12, 2026
34 checks passed
@wshallwshall
wshallwshall deleted the w3-mail-wake-and-ttl branch August 12, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant