# Residual Audit — Messaging Channels and Conversational Surfaces (features.md 5760–5983)

Date: 2026-06-11. Scope: residual after the 57-task 2026-06-10 ground-truth wave
(A-phase telegram hardening, B5 real Sophia grounding, B12 effects port, C8(c)
template rendering, E3 policy enrichment all verified present in current code).
Method: static reading of live source, delegation chains followed end-to-end;
comments/tests/docs not trusted as proof.

What checks out (not findings): real provider transports for
SendGrid/SMTP/Twilio/FCM/APNs/Expo/WebPush/Meta/Slack/Discord
(`libs/oshun/messaging-channels/src/transports.ts`, `smtp-email-transport.ts`,
`web-push-transport.ts` with real RFC 8291 aes128gcm + RFC 8292 VAPID, real
ES256 P1363 APNs JWT, real HTTP/2 transport for APNs); reminder PRODUCERS now
exist and are wired into the worker (streaks/nyx/assignments/sessions/
content-drops over durable event-fed stores — the prior "no domain bulk
enumeration" gap is substantially closed for the BFF's known users,
`server.ts:626–700`); E3 policy enrichment is real (timezone quiet-hours with
overnight wrap, verified-binding gate, live crisis frame,
`reminders-route.ts:224–312`); C8(c) approved lifecycle templates render into
deliveries (`reminders-route.ts:145–216`); B5 Telegram grounding is the real
nisaba retrieval seam with honest abstention
(`telegram/sophia-grounder-bridge.ts`); B12 effects are real durable writes
(`telegram/effects-adapter.ts`, `telegram/user-state-store.ts`); web push
registration is a complete loop (settings UI → `push-registration.ts` →
`POST /v1/device-tokens` → durable token store → `resolveDevicePushBinding` →
per-token-type transport; `sw.js` has push + notificationclick handlers);
channel-binding verify lifecycle (hashing, expiry, attempt lock) and its profile
UI exist; DSAR export includes the reminder inbox
(`data-export/bundle-builder.ts:106`).

---

### 1. The Telegram bot still has no runway — replies are computed and then discarded; the deliverable bot service has zero callers

Severity: P0-STRUCT

Evidence:

- `apps/oshun/bff/src/routes/telegram.ts:207–245` — `POST /telegram/webhook`
  calls `handleBffTelegramWebhook` and returns
  `reply.send({ ok: true, responses: decision.responses, ... })`. That is the
  END of the chain: no `ctx.api.sendMessage`, no POST to
  `https://api.telegram.org/bot<token>/sendMessage`, nothing. The only outbound
  Telegram API call in the entire lib/BFF is `getFile` inside the STT provider
  (`libs/.../telegram/stt-provider.ts:53`).
- Telegram's "answer the webhook directly" mechanism requires the method payload
  at the TOP LEVEL of the response body
  (`{"method":"sendMessage", "chat_id":...,"text":...}`) and supports exactly
  one method per update. The BFF's `{ok, responses:[...]}` envelope is not that
  shape — Telegram discards it. Every user-visible reply (grounded answers,
  crisis copy with hotline buttons, `/save` confirmations, abstentions,
  rate-limit notices) is dropped.
- The component that CAN speak — `apps/oshun/telegram-bot/src/index.ts:203–236`
  `createOshunGrammyBot` with real
  `ctx.api.sendMessage/answerInlineQuery/ answerCallbackQuery` delivery — has
  ZERO callers in the repo (grep: `createOshunGrammyBot` appears only in its own
  file + tests), no entrypoint that calls `bot.start()` or mounts
  `webhookCallback`, no serve target in `project.json` (build/typecheck/test
  only), and no deploy manifest. It is a library that is never run.
- Even if the grammY app were started, it diverges from the BFF webhook: it
  wires NO `effects` port (`index.ts:121–132` — so `/save`, `/quiet`, `/stop`,
  `/voice` reply "not connected" on that path), falls back to
  `createFixtureSophiaGrounder` unless a retriever is passed (`index.ts:89–98`;
  no caller passes one), and has no `/start link-<nonce>` account-link
  completion (that lives only in `routes/telegram.ts:213–223`).

Spec promise: features.md 5797–5816 — bot delivery of grounded answers with
inline citation buttons, command surface, crisis replies; 5882–5886
(account-link/binding flows reach the user in chat).

What the code actually does: two half-bots. The BFF half has the brains (real
grounding, real effects, link-nonce completion, payments, crisis copy) and no
mouth; the grammY half has the mouth and neither brains-wiring nor an
entrypoint. End-to-end, a Telegram user who messages the bot receives nothing,
ever. This also makes the bot's session "multi-turn" question moot — there is no
turn one. (Per-update handling is otherwise stateless: no conversation state
beyond the rate-limiter and the effects store; crisis "suppression for the
conversation" is not persisted between updates either.)

Fix sketch: pick ONE runway. Either (a) make the BFF webhook deliver: after
`handleBffTelegramWebhook`, POST each `BotResponse` to the Bot API with
`OSHUN_TELEGRAM_BOT_TOKEN` (sendMessage/answerCallbackQuery/answerInlineQuery,
mapping `inlineButtons` → `reply_markup`), keeping the HTTP 200 body inert; or
(b) give `createOshunGrammyBot` a real entrypoint + serve target and wire it
with `createTelegramEffectsPort`, `createBffSophiaRetriever`, and the link-nonce
completion. Delete or fold in the loser.

### 2. `/v1/notifications` fabricates seven canonical notifications for every user — fake billing, safety, and moderation events on the live in-app center

Severity: P0-HONESTY

Evidence: `apps/oshun/bff/src/routes/notifications.ts:336–480`
(`buildCanonicalLaneNotifications`) — hardcoded items injected into every
response of the live `/v1/notifications` route (line 214), filtered only by
domain scope, with synthetic staggered timestamps derived from `generatedAt`
(line 472) so they always look fresh. Among them: "Billing review needs your
confirmation — Support flagged a renewal mismatch on your Veritas upgrade",
"Safety review requires attention — A protected account event needs
confirmation", "Admin moderation follow-up is pending", "Data export package is
ready — Your last export request finished successfully". The web notification
center renders this route verbatim
(`apps/oshun/web/src/components/NotificationsCenterPanel.tsx:964`).

Spec promise: features.md 5770–5772 (channel-aware delivery routed by real user
state); the web/in-app channel is held to the same honesty bar as every other
surface — nothing in the spec authorizes demonstration notifications.

What the code actually does: tells every user that support flagged their
billing, that a safety event needs their confirmation, and that a data export
they never requested is ready. These are result-faking fixtures on a live path —
the exact class the quality standard bans — and the safety/billing ones actively
train users to ignore real alerts.

Fix sketch: delete `buildCanonicalLaneNotifications` and source lane items from
the real stores that exist (reminder inbox, data-export job state, abuse
report/moderation stores, billing events); serve honest emptiness for lanes with
no real events.

### 3. Channel-binding verification codes are never sent — which makes the email/SMS/WhatsApp reminder legs permanently unreachable in production

Severity: P1 (structural consequence chain)

Evidence: `apps/oshun/bff/src/routes/channel-bindings.ts:96–104` — the bind
route mints + hashes a code and then unconditionally reports
`delivery: 'missing-config'` in production ("no transport wired yet" per the
header comment, lines 10–14). It never attempts delivery. But the transports ARE
wired elsewhere in this very BFF: the signup email-verify flow resolves
SendGrid/SMTP from the same env and actually sends
(`auth/verification-email-sender.ts:86–145`), and `deliverWithEnvProviders`
covers Twilio SMS + WhatsApp. Consequence: in prod no member can ever produce a
`status:'verified'` binding → E3's gate (`reminders/reminders-route.ts:272–300`)
sets `userOptIns: []` for every email/sms/whatsapp reminder → the dispatcher
suppresses them all — even on a deployment with full SendGrid/Twilio/WhatsApp
credentials.

Spec promise: features.md 5772–5773 ("channel binding requires verified
ownership" — i.e., verification must be completable), 5917–5926 (email/SMS for
verified-identity flows: login codes etc.).

What the code actually does: honest about the non-send (good), but the "deferred
deploy-bound piece" framing is wrong — the missing piece is ~20 lines of in-repo
wiring to transports that already exist and are already used by the auth flow
next door. As shipped, external-channel reminder parity is structurally zero
regardless of creds.

Fix sketch: in the bind route, dispatch the code through
`deliverWithEnvProviders` (email → SendGrid/SMTP config, sms → Twilio, whatsapp
→ Meta template) and report the real transport outcome; keep `missing-config`
only when the env genuinely lacks the channel's creds.

### 4. Telegram `/quiet` (and the notification-preferences PATCH) write a quiet-hours flag the dispatcher never reads

Severity: P1

Evidence: the bot's `/quiet` effect writes
`notificationPreferencesStore.updateSettings(userId, { quietHours: { enabled: true } })`
(`telegram/effects-adapter.ts:58–65`) and replies "Quiet hours are on for your
account." But the reminder dispatcher discards that store's `enabled` flag:
`reminders-route.ts:286–289` builds
`{ ...preferences.quietHours, enabled: consumerRecord.preferences.notifications.quietHoursEnabled }`
— the enabled bit comes ONLY from `consumerProfileStateStore` (written by
`routes/profile.ts:367`). The same override happens in the preferences API's own
GET/PATCH responses (`routes/notifications-preferences.ts:97–100, 272–275`), so
`PATCH /v1/notifications/preferences { quietHours: { enabled } }` is a dead
write too (the web UI only works because its toggle double-writes the profile
master flag, `NotificationPreferences.tsx:975–983`; the Telegram path has no
such second write).

Spec promise: features.md 5778 (quiet-hours respect per channel); 5800 (`/quiet`
as a real command).

What the code actually does: B12's stated rationale ("the shared
notification-preference store for `/quiet`, so it actually gates delivery") was
true until E3 re-pointed the enabled flag at the consumer-profile store.
Post-E3, a Telegram `/quiet` changes nothing about delivery while the bot
affirms it did — a fabricated outcome by composition of two honest halves.

Fix sketch: make one store authoritative for `quietHours.enabled` (simplest:
have the effects adapter also write `consumerProfileStateStore`
`notifications.quietHoursEnabled`, or have the dispatcher OR the two flags); fix
the PATCH endpoint the same way.

### 5. Notification preference toggles (master push, domain modalities, kinds) are stored, surfaced in the UI, and never enforced on any actual send

Severity: P1

Evidence: the preference model has per-domain `kinds.reminder` and
`modalities: { inApp, push, emailDigest }` plus a profile master `pushEnabled`
(`notifications/preferences-store.ts:14–50`;
`routes/notifications-preferences.ts:92–96` even reports modality "availability"
from the master flags). Enforcement audit: the ONLY consumer is the in-app feed
filter (`routes/notifications.ts:482–514`, inApp modality + kinds, feed items
only). The reminder pipeline never reads any of it —
`enrichReminderWithLivePolicy` (`reminders-route.ts:280–312`) reads quiet hours,
bindings, crisis, nothing else; `resolveDevicePushBinding`
(`routes/device-tokens.ts:185–195`) returns the push target unconditionally;
producers set `channel:'push'` with no preference read
(`reminders/streak-reminder-producer.ts:91–99`); and the planner DEFAULTS to
opted-in when `userOptIns` is absent (`v3-session-reminders.ts:308–313`
`toChannelSet(channels ?? [fallback])`) — the inverse of the spec's "default-off
opt-in".

Spec promise: features.md 5769–5773 ("dispatcher routing by user preference...
per-user channel preferences with default-off opt-in").

What the code actually does: a user who turns off push (master or per-domain) or
turns off the "reminder" kind keeps receiving push reminders on every registered
device; the settings screen is a comprehensive control panel for switches
connected to nothing on the send path.

Fix sketch: in `enrichReminderWithLivePolicy`, drop `userOptIns` to `[]` when
the recipient's master `pushEnabled` is false or the relevant domain
`modalities.push`/`kinds.reminder` is false (map reminder producer → domain);
make the planner's no-opt-in default suppress rather than allow for external
channels.

### 6. WhatsApp leg: the message content is dropped on the floor; template registry, 24-hour session window, and status-webhook ingestion have zero runtime callers and no inbound route

Severity: P1

Evidence:

- `transports.ts:333–370` `sendWhatsAppViaMetaCloud` sends only
  `{ type:'template', template: { name, language } }` — no `components`, so the
  computed `body` (session title, start time, link, disclosure copy, provenance
  footer — assembled at `delivery.ts:185` and passed to every other channel) is
  silently discarded; `delivery.ts:310–324` never passes it.
- One global template for everything: `provider-config-env.ts` reads a single
  `OSHUN_WHATSAPP_TEMPLATE` + `OSHUN_WHATSAPP_LANGUAGE` for all message kinds
  and locales.
- The lib's WhatsApp domain logic — `planWhatsAppTemplateDelivery` (per-locale
  - use-case approval gating, 24-hour customer-initiated session window,
    cost-ledger entry) and `ingestWhatsAppStatusWebhook`
    (`libs/.../whatsapp/index.ts:86–130`) — has zero callers outside its tests,
    and the BFF has no WhatsApp inbound webhook route at all (grep of
    `apps/oshun/bff/src/routes` + `app.ts`: nothing).

Spec promise: features.md 5897–5912 — approved-template messaging WITH content
(receipts, reminders, scheduled-event nudges), template registry with per-locale
approval as a release gate, 24-hour session-window handling, message-status
webhook ingestion, cost ledger.

What the code actually does: a WhatsApp "reminder" delivers whatever static copy
the one Meta-approved template contains — never the session it is reminding
about — and Meta delivery/opt-out statuses are never ingested, so a
`failed`/`opted_out` recipient keeps being messaged.

Fix sketch: extend `sendWhatsAppViaMetaCloud` to accept
`components: [{ type:'body', parameters: [...] }]` and map title/startsAt/link
into them; route sends through `planWhatsAppTemplateDelivery`'s registry/window
checks; add `POST /v1/webhooks/whatsapp` feeding `ingestWhatsAppStatusWebhook`
into the deliverability/suppression store.

### 7. FCM push is configured with a static OAuth access token that expires after ~1 hour

Severity: P1

Evidence: `provider-config-env.ts:106–115` reads `OSHUN_FCM_ACCESS_TOKEN`
straight from env into `config.push.accessToken`; `transports.ts:132–165` sends
it as the Bearer to FCM **HTTP v1**
(`fcm.googleapis.com/v1/projects/<p>/messages:send`). FCM v1 does not have
long-lived server keys — the Bearer must be a Google OAuth2 access token minted
from a service-account key, valid ~3600s. There is no token-mint flow anywhere
in the repo (no JWT-bearer grant, no google-auth dependency on this path;
contrast APNs, which correctly mints its ES256 provider JWT per send at
`transports.ts:183–199`).

Spec promise: features.md 5917–5920 (push adapter covering FCM as a working
production channel).

What the code actually does: any deployment can satisfy the env contract only
for the first hour after manually minting a token; after that every `push-fcm`
send fails 401. The channel is structurally un-operable as configured, not
merely cred-gated.

Fix sketch: accept `OSHUN_FCM_SERVICE_ACCOUNT_JSON` (or key path), mint and
cache the OAuth token via the JWT-bearer grant (sign with `node:crypto`, POST to
`oauth2.googleapis.com/token`, refresh before expiry) — mirroring the APNs JWT
pattern already in the file.

### 8. The always-available in-app reminder inbox has no UI — reminders "deliver" into a store no user can see

Severity: P1

Evidence: `GET /v1/reminders/inbox` (`reminders-route.ts:470–493`) is the read
side of the in-app sink that makes the whole reminder subsystem
"always-available without creds" (`reminder-scheduler.ts:136–154`). Repo-wide
grep for `reminders/inbox` outside the BFF: zero hits — not in `apps/oshun/web`,
not in `apps/oshun/mobile`. The web notification center fetches only
`/v1/notifications` (`NotificationsCenterPanel.tsx:964`), which does not merge
the reminder inbox.

Spec promise: features.md 5769–5771 (delivery dispatcher with per-channel
fall-through — in-app is the terminal fallback channel; a fallback nobody can
read is not delivery).

What the code actually does: on a creds-less deployment (the current default),
every produced reminder — at-risk streaks, upcoming sessions, due assignments,
content drops, Nyx events — lands exclusively in a Map only the DSAR export ever
reads. The "recipient was still reached" accounting at
`reminder-scheduler.ts:174–176` is false in user-experience terms.

Fix sketch: merge `readReminderInbox(userId)` items into the `/v1/notifications`
payload (kind `reminder`, lane by tenant/domain, read state via the existing
read-state store), or add an inbox section to `NotificationsCenterPanel`.

### 9. Stored Telegram controls that nothing enforces: `/stop`'s "scheduled sends cancelled" claim, the voice-replies toggle, and the unlink cascade

Severity: P2

Evidence:

- `/stop`: bot replies "Telegram delivery stopped and scheduled sends cancelled"
  (`bot.ts:254–258`); the adapter sets a `deliveryStopped` flag
  (`effects-adapter.ts:67–70`), but the only reader is the
  `GET /telegram/captures` echo (`routes/telegram.ts:339`) — no delivery or
  scheduling path consults it, and nothing is cancelled.
- `/voice`: `toggleVoiceReplies` persists a flag; `BotResponse.method` includes
  `'sendVoice'` (`bot.ts:49`) but no code path ever emits it,
  `synthesizeTelegramVoice` is an optional interface member with no
  implementation, and `isVoiceRepliesEnabled` is only echoed back
  (`routes/telegram.ts:338`). The spec's "TTS-back reply option" (5806–5807) is
  a stored toggle with no behavior.
- Unlink: spec 5887–5890 requires revocation to cascade (terminate Mini App
  sessions, remove scheduled sends, post revocation notice).
  `unlinkTelegramByTelegramUserId` (`user-state-store.ts:267–292`) only marks
  the link record revoked; minted Mini App session JWTs stay valid to expiry and
  no scheduled-send teardown occurs.

Spec promise: features.md 5800 (`/stop`), 5806–5807 (TTS-back), 5887–5890
(revocation cascade), 5963–5967 (consent-revocation cascade).

What the code actually does: honest writes with overclaiming copy and no
downstream consumers — config stored, never enforced.

Fix sketch: have the reminder cycle and any future Telegram delivery check
`isDeliveryStopped`; soften `/stop` copy to what actually happens until then;
either implement TTS-back (ElevenLabs provider exists in the BFF) or remove the
`/voice` toggle; on unlink, blacklist the Telegram-derived session ids and purge
the user's scheduled reminders for telegram channels.

### 10. Orphaned engines: email bounce/suppression, deliverability metrics, digest scheduler, lib preference/opt-in model, channel audit envelopes — tested, exported, zero runtime callers

Severity: P2

Evidence (repo-wide grep over `apps/`, excluding tests — zero callers each):

- `ingestEmailWebhookEvent` / `buildTransactionalEmail` (list-unsubscribe
  headers, bounce→suppression) — `libs/.../email/index.ts:29–115`; no SendGrid
  event webhook route exists, so bounces/complaints never suppress anything
  (spec 5914–5916).
- `computeChannelDeliverability` / `summarizeDeliverabilityAcrossChannels`
  (`deliverability.ts`) — no delivery result is ever recorded into it; the
  spec's deliverability monitoring (5921–5923) has an engine and no feed.
- `apps/oshun/bff/src/notifications/digest.ts` — a complete digest scheduling
  engine with ZERO importers; the digest preferences users edit in the web UI
  (frequency, delivery time, includeRead) configure a worker that does not exist
  (spec 5914–5917 locale-aware email templates/digest).
- `libs/.../preferences.ts` (`recordBinding`/`activeOptIns`) — superseded by the
  BFF's own binding store; dead parallel model.
- `buildChannelAuditEnvelope` — envelopes are built per Telegram response
  (`bot.ts:114–137`) but only ever serialized into the webhook HTTP response
  body; never written to the audit-events store, so the spec's "channel-aware
  audit envelope on every send/receive" (5968–5971) is constructed and
  discarded. Telegram editorial channel publishing is the same shape:
  `synthesizeTelegramChannelPost`/`takedownTelegramChannelPost` feed the admin
  UI (`apps/oshun/admin/src/app/messaging/telegram-channels/`) but no code posts
  to a Telegram channel or calls edit-message for takedown (5840–5855) —
  subsumed by finding 1's "no outbound Telegram transport".

Spec promise: lines cited inline above.

What the code actually does: the "strong lib (223 tests)" half of the 06-10
headline remains accurate in both directions — strong, and substantially
unconsumed at the edges listed here.

Fix sketch: add the SendGrid/Twilio status webhook routes feeding
`ingestEmailWebhookEvent` + a suppression check in `deliverDispatchedMessage`;
record each `MessageDeliveryResult` into the deliverability store and expose it
on the admin messaging console; either start a digest worker tick (compose from
the read-state store, deliver via the email transport) or remove the digest UI;
persist `BotResponse.auditEvent` to the audit-events store at the webhook.

### 11. UX cohesion — settings promise more than the system delivers

Severity: UX

Evidence: `NotificationPreferences.tsx` exposes per-domain kind/modality
matrices, digest cadence, persona push toggles, and a per-device push
registration flow; `/profile` exposes channel binding + verification with
outcome-specific error copy; `profile/telegram/page.tsx` exposes the deep-link
account-link flow. Each is a polished front for a gap above: domain/kind/push
toggles unenforced (finding 5), digest never generated (finding 10), binding
codes undeliverable in prod (finding 3), and the Telegram link deep-link points
at a bot that cannot reply (finding 1) — the link COMPLETION works (webhook
side-channel, `routes/telegram.ts:213–223`) but the user receives no in-chat
confirmation, so the flow feels dead even when it succeeded.

Fix sketch: fixing findings 1, 3, 5, 10 resolves this; until then the honest UX
move is to mark unenforced toggles and the digest section as not yet active
rather than silently accepting input.

### 12. Deploy-bound (recorded for completeness, not findings)

Severity: DEPLOY

- Channel creds: `OSHUN_SENDGRID_API_KEY`/`OSHUN_SMTP_*`, `OSHUN_TWILIO_*`,
  `OSHUN_FCM_*` (after finding 7 is fixed), `OSHUN_APNS_*`,
  `OSHUN_WEBPUSH_VAPID_*` + `NEXT_PUBLIC_VAPID_PUBLIC_KEY`, `OSHUN_WHATSAPP_*`,
  `OSHUN_TELEGRAM_BOT_TOKEN`/`_WEBHOOK_SECRET`, STT key.
- `OSHUN_REMINDER_WORKER_INTERVAL_MS` to enable the delivery worker (fail-closed
  off by default — correct posture).
- Telegram `setWebhook` registration (no automation in repo; must point at
  whichever runway finding 1 selects).

---

## Severity counts

| Severity   | Count | Findings                                                                                                                                                                                                                                                                           |
| ---------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P0-SEC     | 0     | —                                                                                                                                                                                                                                                                                  |
| P0-HONESTY | 1     | #2 fabricated notification-center events                                                                                                                                                                                                                                           |
| P0-STRUCT  | 1     | #1 Telegram bot has no runway (replies never delivered; deliverable service never run)                                                                                                                                                                                             |
| P1         | 6     | #3 binding codes never sent → email/SMS/WhatsApp legs dead; #4 `/quiet` writes a flag the dispatcher ignores; #5 preference toggles unenforced on sends; #6 WhatsApp content dropped + no status ingestion; #7 static FCM v1 token cannot work; #8 in-app reminder inbox has no UI |
| P2         | 2     | #9 `/stop`/`/voice`/unlink stored-not-enforced; #10 orphaned engines (email webhooks, deliverability, digest, audit envelopes, channel publishing)                                                                                                                                 |
| UX         | 1     | #11 settings/binding/link surfaces front unenforced or unreachable behavior                                                                                                                                                                                                        |
| DEPLOY     | 1     | #12 creds + worker interval + setWebhook                                                                                                                                                                                                                                           |
