ADR-0052 — Handyman quiet hours as a Temporal hold-and-release

Status: Accepted (2026-06-09) Supersedes: the v1 inline "drop-with-timeline-note" quiet-hours gate (PR #2077)

Amendment (2026-06-10 — ADR-0053 Phase 5, window source + default flip): the MAINTENANCE_HANDYMAN_QUIET_HOURS env window-read described below is retired. The window source is now the per-handyman VendorMembership.quietHours (three-state: unset = the default 21:00–07:00 window — the gate is ON by default — / a custom window / 'none' = opt-out), resolved at the start edge via resolveHandymanMembershipQuietHours + resolveHandymanQuietWindow. There is no surviving env at all: the fail-open fleet kill-switch MAINTENANCE_HANDYMAN_QUIET_HOURS_DISABLED was deleted pre-merge (owner decision 2026-06-11, "no flags"). Deploys armed — unset memberships get the default hold at dispatch immediately; the per-handyman membership 'none' is the only opt-out. Everything else in this ADR — the hold-and-release shape, determinism split, fire-time re-read, fail-open, USE_EXISTING dedup — is unchanged.

Context

A new work order pages the property's maintenance handyman by SMS (dispatchToMaintenanceTech, the single chokepoint for all handyman pages — Clara-created WOs, AppFolio-sync-imported WOs, the PATCH enrichment landing). Operators want a quiet-hours policy: don't page the handyman late at night for a non-emergency; an emergency always pages.

The v1 implementation (ADR-free, PR #2077) gated inside dispatchToMaintenanceTech and, during the window, dropped the page — writing a passive timeline note and never paging. Its documented v1 limitation: no auto-re-page when the window closes, because sync-imported WOs don't ride a Temporal workflow and the page was sent inline from the poller Lambda.

That's the wrong shape. The right behavior is hold-and-release: a page queued at 11pm should fire at 7am, not be lost. The user framed it precisely: "instead of sending the message, we would just have it like waiting on a queue that's paused" — i.e. a durable hold. Temporal's sleep(until window-close) is the textbook primitive.

Decision

Move all non-emergency handyman pages onto a Temporal HOLD-AND-RELEASE workflow, uniformly across both WO origins, replacing the v1 inline drop.

Consequences

Alternatives considered