Skip to content

[Decision] service-messaging: should fan-out consult per-tenant channel availability (a new MessagingChannel member) so no delivery row is written for a channel with no transport? — split from #17611 #17732

Description

@os-tesla

Split from #17611's ruling (C taken; A deferred to its own pricing). ⛔ A decision card: the question is whether to widen a published interface, which is the maintainer's floor, so it is filed needs-user-decision rather than queued.

一句话问题

租户没有配置某个渠道(例如没有邮件传输)时,通知 fan-out 现在仍然为该渠道写一行投递记录,这一行第一次尝试就死信。要不要让 fan-out 在写之前先问一句「这个租户这个渠道能发吗」,不能就不写这一行,只在通知上记录该渠道被抑制及原因?代价是给已发布的 MessagingChannel 接口新增一个可用性成员。

The proposal (the reporter's option A on #17611)

At fan-out, resolve channel availability per tenant. For a channel whose transport is not configured, write no sys_notification_delivery row; record on sys_notification that the channel was suppressed and why. Hot-table cost for such channels goes to zero; the suppression stays auditable at the notification level.

Why it is its own card, and why it is a decision

Measured by the services seat on origin/main @ 6fa2a8ae1 (5641086533 on #17611): MessagingChannel (packages/services/service-messaging/src/channel.ts) has three members — id, send(), optional classifyError?() — and no availability or capability query; fan-out (messaging-service.ts) only checks that the channel is REGISTERED, and the reported case is a channel that is registered while the tenant has no transport, one layer below. ⇒ A adds a member to a published interface, with tenant context, and every channel implementation (inbox, email today; webhook, push, chat channels later) has to answer it. That is a Clause-②: yes widening and a declare-and-maintain obligation.

What the ruling on #17611 already bought, and what this card is for

#17611 C caps terminal rows at 7d through the existing retention.onlyWhen key (the same shape as sys_automation_run), which by the seat's arithmetic turns a ~28,000-row ceiling into ~2,200. The remaining question is whether the existence of workless rows is acceptable at all, or only their age. If the former, A is the fix; if the latter, C is sufficient and this card closes as not planned.

Options

what cost
A Add the availability member to MessagingChannel, consult it at fan-out, suppress with a recorded reason published-interface widening; every channel implements it; a per-tenant availability answer may cost an I/O round trip at fan-out (unmeasured)
hold Take no contract change; let #17611's 7d retention run and read the table again zero cost; the hot table still carries ~2,200 workless rows at steady state on the reporting tenant

⛔ Confidence gaps carried from #17611

  1. Whether the reporting deployment runs the lifecycle Reaper at all (asked of the reporter on service-messaging: fan-out writes an email delivery for a tenant with no email transport — it dead-letters on its first attempt and is kept 90d, so the hot outbox grows ~1 dead row per notification #17611); if it does not, C yields nothing there and this card is the live one.
  2. Whether anything reads dead email rows (reports, SLA, manual triage).
  3. Whether "can this tenant send on this channel" is cheaply answerable at fan-out time.

Refs

#17611 (ruling C, 2026-09-12) · 5641086533 (the services seat's four-facet price) · #17609 (driver-side missing index) · #17610 (scheduler idle cost) · hotcrm#1892 (12 flows notify on ['inbox','email'])

Filed by the director seat, session_01QsCVSivtpwT6ZXs5Rtvqxe, 2026-09-12T01:0xZ, with Claude Code.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions