coord: raise the mail TTL to 72h, and wire the urgent rewake tier into the installer - #337
Merged
Conversation
…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
enabled auto-merge (squash)
August 12, 2026 03:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent gaps in session-to-session coordination mail, both measured, neither previously visible.
Mail TTL 720 -> 4320 minutes (12h -> 72h). Delivery points are
SessionStartandStop, 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 writesexpiresUtcat 72.0h. The internalNew-Messagedefault moves in step, because two defaults that disagree is how the tested path and the shipped path diverge.mail-watch.ps1was built, verified, documented -- and never wired.install-coordination.ps1had no row for it, so the urgent rewake tier has been inert since it was written. Now armed atStop, the moment a session goes idle.Three traps the wiring had to clear, each a previously documented failure mode:
Test-IsOursis a substring match, and the file namesmefor-mail-urgentas the exact mistake -- it would have stripped the drain's own row.mefor-wakeis pairwise non-containing with all three existing markers.& $s; breakand exits 0, so a watcher that found mail would have had its payload discarded silently. The exit sits inside theTest-Pathbranch, so outside a repo the fall-through exit 0 is reached rather than forwarding a stale code.asyncANDasyncRewaketogether. Backgrounding is gated onisInteractive, soasyncRewakealone 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 aPreToolUsegate is never backgrounded -- a gate that returns asynchronously is a gate that does not gate.Verification: installed with
-Only Stopacross 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_hooks17 passed; 138 passed acrosstest_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
Stoparms 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 atSessionStartand exited after two seconds, which is mid-turn by construction, and the two outcomes produce byte-identical transcripts. This matters because ifasyncis 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.