feat(messaging): ADR-0030 P3b-1 — quiet-hours (deferred dispatch)#1453
Merged
Conversation
A notification landing inside a recipient's quiet-hours window is deferred to
the window's end instead of disturbing them, then delivers normally.
- Implemented as deferred dispatch on the P1 outbox (no parallel scheduler):
EnqueueDeliveryInput.notBefore → the delivery row's initial nextAttemptAt;
the dispatcher already skips pending rows whose nextAttemptAt is in the future.
- PreferenceResolver reads quiet_hours ({tz,start,end}, P2's field) off a
channel-wildcard preference row (quiet hours are per-person, channel-agnostic),
computes the deferral via quietHoursDeferral() (HH:MM in tz, default UTC,
overnight windows supported), and stamps notBefore on the target; emit() passes
it to the outbox.
- critical severity bypasses quiet hours (delivers immediately). Honored on the
durable outbox path; inline fan-out ignores it.
Tests: service-messaging 92 passing (quietHoursDeferral unit cases + resolver
notBefore/critical-bypass/JSON-string cases).
Follow-up: P3b-2 digest (window collapse) builds on this deferral foundation.
https://claude.ai/code/session_015pRGvrm3zrk5m8YvZhkAmF
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Summary
Implements ADR-0030 P3b-1 — quiet-hours, continuing from merged P0–P3a. A notification that lands inside a recipient's quiet-hours window is deferred to the window's end instead of disturbing them; it then delivers normally.
P3b is split: P3b-1 (this PR) = quiet-hours (deferral); P3b-2 (next) = digest (window collapse), which builds on the same deferral foundation.
What changed (the elegant bit: reuse the P1 outbox)
No new scheduler, no parallel table — quiet-hours is a deferred dispatch on the existing outbox:
EnqueueDeliveryInput.notBefore→ the delivery row's initialnextAttemptAt. The P1 dispatcher already skips pending rows whosenextAttemptAtis in the future (memory-outbox/sql-outboxclaim both check this), so the row simply isn't claimed until the window ends. One delivery spine, reusing claim / retry / dead-letter / observability.PreferenceResolverreadsquiet_hours({ tz, start, end }— P2's field) off a channel-wildcard preference row (quiet hours are a per-person, channel-agnostic setting), computes the deferral withquietHoursDeferral()(HH:MM in the row'stz, default UTC; overnight windows that wrap midnight supported; usesIntlfor the tz wall-clock), and stampsnotBeforeon the target.emit()passes it through toenqueue.criticalseverity bypasses quiet hours (delivers immediately), mirroring how mandatory topics bypass muting. Honored on the durable outbox path; inline best-effort fan-out ignores it (quiet-hours needs the outbox).Acceptance
next_attempt_at); outside the window it sends immediately; critical bypasses.Testing
service-messaging: 92 passing — adds
quietHoursDeferralunit cases (same-day / overnight-wrap / outside / degenerate / bad input) and resolver cases (notBeforestamped on target, critical bypass, JSON-stringquiet_hours).Follow-ups
(user, channel, window)rows into one materialization at window time (needs adigest_keyon the delivery row + a digest assembler + a digest render template). Consumes P2'sdigestfield. Completes the ADR-0030 build spec.sys_usertimezone field (currentlyquiet_hours.tz→ UTC).🤖 Generated with Claude Code
Generated by Claude Code