Terminal notice + attempt ledger + W2 wave smoke
This story lands the refused-sender accounting seam for channel ingress
(RFD-AGT-013 T2e-b, D19, CC-22/CC-41), folded INTO the production
zero-amp shield (src/domains/channel/refusal.py, Story-2850)
that the WhatsApp webhook already calls
(src/whatsapp_webhook/handler.py) — no parallel module, no
second wiring. The staff-write grant it rides (0209) merged with
Story-2850; this PR ships zero migrations.
The two refusal kinds — distinct and deliberate
| unregistered (D8) | post_terminal (D19) | |
|---|---|---|
| Who | Subject was NEVER bound — no channel_identities row at all. |
Subject WAS bound; the offboard/unbind sweep revoked every binding (zero firms). |
| Sender sees | Story-2850's zero-amp static refusal, once per rate window; then counted silence. | ONE static terminal notice on the first attempt — names no firm, no matter (F17) — then byte-identical silence: no reply, no run, nothing that confirms the channel is watched. |
| Ledger | Every attempt of every kind lands in business.channel_refusal_ledger — INCLUDING attempts deep into rate-limit backoff (rate_limited rows), so a flood is counted, never dropped. The row lands BEFORE any bytes cross the channel (fail-closed). This is the state that lets "one message then silence" live outside runs (RFD finding 17, A-12). |
|
| Admin visibility | Counted + windowed, never per-attempt (CC-41): the first silent attempt in each fixed 1-hour window emits ONE channel_refusal.alerted audit row carrying attempt counts and the masked subject. Alert transport (in-app + immediate email on the next digest run) is T2f-b. |
|
CC-22 — one terminal message, then silence
offboard sweep lands (Story-2854) │ attempt 1 ──▶ ledger row FIRST (post_terminal), then the terminal notice │ sent once; terminal_notice_sent stamped only after the │ provider accepted it │ attempt 2 ──▶ SILENCE (sender never invoked) │ ledger row + ONE counted admin alert for the window │ attempt 3..N▶ SILENCE, ledger only (rate_limited past the cap) — the │ window's alert is deduped
The terminal notice is a constant
(TERMINAL_NOTICE_TEXT in
src/shared/whatsapp/formatter.py): no template hooks, no
firm/matter naming — its wording is a membership oracle for no one.
A notice the provider never accepted is honestly NOT marked sent, so
the next attempt retries rather than falling silent on a message that
never arrived.
Byte-identical silence (F17)
After each sender's one visible message, a post-terminal attempt and a
never-registered attempt cross EXACTLY the same bytes at this seam:
zero. The pinned proof is
test_silence_is_byte_identical_to_unregistered — the
sender spy sees no invocation and the outcome shape is identical; only
the staff-visible ledger kind tells D8 and D19 apart.
Proofs
tests/integration/test_channel_terminal_silence.py— CC-22 lifecycle, byte-identical silence, unregistered never noticed, failed-notice honesty, CC-41 counted window digest, and the R2 grant check through the realpramaan_approle.tests/integration/test_channel_w2_wave_gate.py— the W2 wave-gate smoke this story owns (TICKET-MAP v2 G5b, UAT M-04), driven through the PRODUCTION webhook-bound path (_handle_delivery_innerwith real HMAC-signed Meta payloads): bound-number ingress → offboard sweep → terminal notice → silence + counted alert, end-to-end against real Postgres. Only the Meta secret, SQS enqueue, and sender Lambda are spies.tests/unit/test_channel_terminal_refusals.py— notice constant (one home, formatter), window math, audit vocabulary.tests/unit/test_channel_zero_amp_shield.py— CC-41 alert decision pins: first-silent-in-window alerts, later attempts don't, the notice-sending attempt never does, backoff attempts stay alert-eligible.- Red-first: the grant check was run at alembic 0208 and failed with
new row violates row-level security policy for table "channel_refusal_ledger"; afteralembic upgrade head(0209, merged with Story-2850) it passes. No green without a red.