Disciplines · Audits

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

Gap audits and as-built reviews.

1section16 minread

On this page

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.tsPOST /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–245POST /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 inlineButtonsreply_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.

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