Skip to content

A provider can claim DLR support and silently never receive one #408

Description

@stephane-segning

Source of Truth

  • backends/crates/sms-provider/src/capabilities.rs — states the guarantee this
    ticket enforces: "routing must not promise a caller a DLR that will never arrive."
  • backends/crates/sms-api/src/route_simulator.rs:86 — records that
    Provider.healthy is never written, because §7.5's probe_providers does not exist.
  • backends/apps/sms-gateway/src/commands/mtn_subscribe_dlr.rs — MTN's DLR
    registration as "an operator action rather than something serve does on startup."
  • Fix Orange's DLR contract against its own docs; wire MTN into worker and gateway #390 — the DLR contract fix (a separate,
    already-closed cause).
  • Live deployment, 2026-09-18: delivery_receipts holds 2 rows, both predating
    the Orange rail going live.

Intent

An operator should be able to tell, without leaving vsms, whether a provider's
delivery receipts are actually working. Today they cannot: a provider that has
never been registered with its upstream looks identical to one that simply has
not been sent anything. Both are silence.

That silence is not hypothetical — it is the current state of the Orange rail,
and MTN is being configured now with the same manual registration step through a
different portal.

Context: what is and is not already uniform

DLR ingestion is uniform and needs no change. /dlr/{providerKey} looks the
provider up, calls SmsProvider::parse_dlr, and hands the result to
sms_api::dlr::ingest, which knows nothing about its origin. A new adapter gets
that for free.

What is not uniform is knowing whether DLR is wired. Two holes:

1. supports_dlr is written and never read. bootstrap.rs:132 and
seed_dispatch.rs:311 both hardcode supportsDlr: true; nothing consults the
column. The guarantee in capabilities.rs is documented and unenforced. It is the
mirror of Provider.healthy — one column nobody writes, one nobody reads, both
looking like working features from the schema alone.

2. Provisioning is external state nothing records. Orange's endpoint is
whitelisted by a manual support ticket; MTN's is registered by running
mtn_subscribe_dlr once, by hand, against an account that may not be the one the
running gateway holds. So a provider can carry supports_dlr: true, ship a correct
parse_dlr, serve a live endpoint, and receive nothing forever because a human
never finished a step in someone else's portal.

Evidence. delivery_receipts holds 2 rows, both delivered, both against
the Orange provider, received 2026-09-13 15:06 and 2026-09-14 01:54. The Orange
rail went live on real credentials at roughly 2026-09-17 20:10. Both predate that:
they came from sms-fake-orange in demo mode. Zero real Orange receipts since
go-live
, and nothing anywhere says so.

Acceptance Criteria

  1. Routing consults supports_dlr. A provider with supports_dlr = false is never
    selected for a message whose caller requires a receipt, and the refusal is
    explained in the routing Decision trail rather than being silent.
  2. Per-provider DLR liveness is queryable — at minimum "last receipt received",
    derivable today from delivery_receipts.provider_id + received_at with no
    schema change.
  3. A provider that claims DLR, has submissions inside the window, and has received
    no receipt inside that window, is reported as a distinct condition. A provider
    with no submissions is not reported — quiet must not read as broken.
  4. That condition is visible to an operator without a database query: the console's
    providers or dashboard screen, plus a log line and metric.
  5. The check runs on a schedule, not only on demand.
  6. A test proves the warning fires for the not-registered case and stays silent for
    the no-traffic case. Both directions — the no-traffic half is what stops this
    becoming noise that gets ignored.

Definition of Ready

  • Source of truth cited and re-verifiable.
  • Reproduced against the live deployment, with data.
  • Home identified: §7.5's probe_providers, already designed and named, and
    already the would-be writer of Provider.healthy — a DLR-wiring probe and a
    provider-health probe are one job asking two questions about the same row.
    Role::Jobs dispatches by kind through a JobHandler registry, with
    expire_stale as the wired example to copy.
  • Window length and reporting thresholds agreed (how long is "no receipts" before
    it is a warning — depends on real traffic volume, which the Orange rail has not
    yet produced).
  • Decided whether dlrRegisteredAt is stored explicitly, or registration is
    inferred purely from receipt arrival.

Definition of Done

  • All acceptance criteria met.
  • probe_providers implemented as a job kind, on the existing registry.
  • The warning is observed firing against the current Orange state, which is a
    genuine instance of the condition rather than a synthetic one.
  • capabilities.rs's guarantee is enforced by code, and its doc comment says
    where.
  • MTN's DLR registration status is visible through the same surface as Orange's,
    with no provider-specific special-casing.

AI Usage Declaration

  • AI (Claude) found both holes, traced them to the code and the live database,
    and drafted this ticket.
  • The condition to warn on was specified by the repository owner ("we even need
    a warning when it's not wired in"); the scoping of quiet-vs-broken is the
    AI's proposal and is open to revision.
  • A human reviews and prioritises this before any implementation.
  • Implemented. Not started.

Why this is worth doing before MTN rather than after

MTN's DLR needs the same manual subscription Orange needed, through a different
portal, and it has never run — the adapter was rewritten against MTN's real
Swagger in #394 and wired into both binaries in #396, both released hours ago in
0.3.3. Without criterion 3, its failure mode is identical and equally silent:
messages send, receipts never arrive, everything reports healthy.

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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions