0. How to read this document
This SRS is written backwards from working code, not forwards from an idea. Every requirement is tagged with the reality found in the repositories, so the document does double duty as both a specification of what Whisperr should be and an honest map of what it currently is.
Status tags on each functional requirement:
| Tag | Meaning |
|---|---|
| ✅ BUILT | Implemented and wired end‑to‑end in the current code. |
| 🟡 PARTIAL | Implemented but incomplete, single‑channel, mocked in places, or not fully wired across repos. |
| 🔩 STUB | Interface/schema/scaffold exists; behaviour is a placeholder, hardcoded, or dead. |
| ❌ MISSING | Required by the product vision but absent from code. |
| 🕮 LEGACY | Exists only in the ancestral user-whisperer monorepo; a pattern to reclaim, not live production. |
| 📌 NOTE | A documented invariant or design fact, not a build‑status. |
Where the code contradicts the project’s own docs, this SRS follows the code and flags the drift. The single most important such correction: the mid‑2026 context doc claims “nothing is delivered”; the current runtime does generate content and deliver via email and push. See §7.
1. Introduction
1.1 Purpose
Whisperr is an AI‑driven customer‑engagement and churn‑prevention platform. A business (Whisperr’s customer) streams its own end‑users’ product events into Whisperr. For each event, Whisperr recomputes that user’s standing against a set of retention interventions, decides in real time whether an intervention should fire, arbitrates to a single winner, selects a delivery channel, generates the message content, delivers it, and then measures whether the intended outcome occurred — feeding that result back into channel/timing learning. The promise to the customer is retention automation without a lifecycle‑marketing team writing rules.
This document specifies the complete system: its actors, its end‑to‑end lifecycle, its functional requirements per subsystem, its data and interface contracts, its non‑functional requirements, and — critically — a target architecture and sequenced roadmap for assembling the seven existing parts into the best possible version of the product.
1.2 Product vision and mission
Turn a single product event into a single, well‑timed, on‑brand retention action — and prove it worked.
The unit of value is the closed loop: Event → Signal → Decision → Content → Delivery → Outcome → Learning. Whisperr’s differentiation is that this loop is (a) derived from a conversational onboarding rather than hand‑configured, (b) installed into the customer’s codebase by an AI agent rather than by an engineer reading docs, and (c) governed by deterministic, replayable decisioning rather than an opaque model in the hot path.
1.3 Scope
In scope (this SRS specifies):
- Demand capture and the funnel (marketing site, waitlist, qualification).
- Customer onboarding: conversational intake → event selection → weight/policy derivation.
- Runtime import, event‑universe generation, and policy generation.
- Integration/activation: the agentic
wizardCLI, client SDKs, and in‑app activation. - The runtime engine: ingestion, stateless decisioning, content generation, delivery, outcomes, learning.
- The customer dashboard (
app) and the operator console (backoffice). - Identity, authentication, multi‑tenancy, security, observability, and deployment.
Out of scope (explicitly not owned by Whisperr):
- The customer’s own product analytics warehouse. Whisperr consumes events; it is not a general BI/analytics product.
- The end‑user’s account system. Whisperr identifies users by a customer‑supplied
external_idand channel addresses only. - Content authoring tools for humans — content is LLM‑generated with human approval, not a CMS.
1.4 Definitions and glossary
| Term | Meaning |
|---|---|
| Customer / Partner | The business that uses Whisperr to retain its users. |
| End‑user | The customer’s user, whose events flow in and to whom interventions are (eventually) delivered. |
| Operator | A Whisperr staff member using the backoffice. |
| Organization → App | Tenancy hierarchy. An App is a single integration (one product surface / API‑key domain). |
| Event | An immutable, append‑only record of an end‑user action, keyed idempotently by (app_id, message_id). |
| Canonical event | An event type known to the app’s active configuration; raw events are mapped to canonical codes (exact → alias‑exact → alias‑ci). Derived codes cannot be ingested directly. |
| Derived event | A synthetic, deterministic event (e.g. “absence for N days”, “offset after X”) materialised by the engine into the same event log. |
| Intervention | A retention play (one of ~24 codes under 8 parent strategies) that can fire for a user. |
| Variant | A specific version of an intervention (active / paused / blocked / declined). |
| Business context | Versioned, validated product/ICP/brand facts used to ground LLM prompts (not a decisioning gate). |
| ICP | Ideal Customer Profile; a classification guided by (not hard‑constrained to) the runtime’s registry — onboarding may mint a new ICP when none fits (BFF migrations 0012/0013 opened the vocabulary), fetching the live registry via /internal/onboarding/icp-names. |
| Universe | The generated event‑taxonomy + intervention set for an app, produced from onboarding. |
| Policy version | Immutable, versioned decisioning config: per‑event weights/decay/repeat/gate rules and per‑intervention thresholds/priority/refire/quiet‑hours‑waiver. Exactly one is active per app. |
Evaluation (runtime_evaluations) |
One decisioning tick for a user; carries a full scoring_snapshot; idempotent on job_execution_key. |
Decision outcome (runtime_decision_outcomes) |
One fired candidate’s fate: would_dispatch / held_* / lost_arbitration / no_eligible_channel / manual_* / dispatched / failed, plus channel shortlist, chosen channel, generated message object, approval, delivery result, engagement, intent‑success. |
Episode (runtime_episodes) |
A latch that prevents an intervention from re‑firing while “open”; ≤1 open per (app,user,intervention). |
| A5 / A6 / A7 | The pacing‑gate ladder, arbitration, and channel‑shortlist stages of the decision pipeline. |
| Manifest | The onboarding‑derived integration packet the wizard consumes: ingest key, base URL, identify() guidance, wireable events, driving interventions, universe summary. |
| Intent success | Evidence that the intended post‑intervention outcome event occurred within a success window. |
| Fatigue | A time‑decayed accrual of recent sends; a decision is held when accrual ≥ ceiling (default 6). Not a 0–1 score. |
1.5 Repository ↔ component map
The system is delivered as eight repositories (six active, one ancestral, plus a published client‑SDK family). Names in quotes are the internal/package names.
| # | Repository | Component | Production host | Stack | ~LOC |
|---|---|---|---|---|---|
| 1 | whisperr-landing-main |
Landing — marketing, waitlist, calculators, blog, pitch decks | whisperr.net (Cloudflare Pages) | React 19 + Vite 6 | 66k |
| 2 | whisperr-app-main |
App / “whisperr‑watch” — customer product UI: onboarding + dashboard | app.whisperr.net (Cloudflare Pages) | React 18 + Vite 5 + shadcn | 83k |
| 3 | whisperr-onboarding-chat-main |
BFF — onboarding backend, Supabase state, LLM stage machine, manifest, import handoff | api.whisperr.net/supa | Next.js 16 | 24k |
| 4 | whisperr-go-main |
Runtime / “whisperr‑go” — the core engine | api.whisperr.net | Go 1.25, Postgres, Redis | 122k |
| 5 | whisperr-backoffice-main |
Backoffice / “whisp‑admin” — operator console | backoffice.whisperr.net (Cloudflare Pages) | React 18 + Vite 5 | 23k |
| 6 | whisperr-wizard-main |
Wizard — npx @whisperr/wizard agentic code integrator |
npm | TypeScript + Claude Agent SDK | 7.5k |
| 7 | Whisperr-main |
user‑whisperer (LEGACY) — original microservices monorepo + Python ML | local/demo | TS/Express + Python/FastAPI + Kong | 51k |
| 8 | whisperr-web (external, on npm) |
Client SDKs — @whisperr/web, @whisperr/next, @whisperr/react (v0.1.7), from github.com/WhisperrAI/whisperr-web |
npm | TypeScript | — |
1.6 References
Primary source of truth: the seven repositories above. Notable in‑repo documents (treated as hints, verified against code): whisperr-landing-main/WHISPERR_CONTEXT.md (state‑of‑the‑union, mid‑2026 — partly stale), whisperr-go-main/docs/{derivation-engine,engine-tuning,dashboard-api-reliability}.md and docs/rfcs/000{1,2}, whisperr-wizard-main/ARCHITECTURE.md (stale vs. richer real pipeline), whisperr-go-main/scripts/audit_engine.sh (CI‑enforced architectural invariants), Whisperr-main/user-whisperer/PHASE_* docs (aspirational — do not cite as capability evidence).
2. Overall description
2.1 System context — the closed loop
┌──────────────┐ waitlist / qualify / book ┌───────────────────────────┐
│ LANDING │──────────────────────────────────▶ │ Prospect → signup link │
│ whisperr.net │ (churn calcs, voice qualifier, │ (currently a COLD handoff)│
└──────────────┘ pitch decks, PostHog funnel) └────────────┬──────────────┘
▼
┌──────────────────────────────┐ REST + SSE ┌─────────────────────────────────┐
│ APP (whisperr-watch) │◀───────────────▶ │ BFF (onboarding-chat, Next.js) │
│ • Onboarding UI (Trilogy) │ │ • LLM stage machine: │
│ • Dashboard (KPIs, events, │ │ intake → event_selection → │
│ interventions, users, │ │ weight_derivation → completed│
│ API keys, in-app AI) │ │ • Supabase state (RLS) │
└──────────────┬───────────────┘ │ • Generates MANIFEST + weights │
│ Supabase-JWT dashboard bridge └───────────────┬─────────────────┘
│ POST /internal/onboarding/import (shared secret)
▼ ▼
┌───────────────────────────────────────────────────────────────────────────────────┐
│ RUNTIME (whisperr-go) — single binary, modes api / worker / all │
│ │
│ import → runtimeuniverse (LLM taxonomy) → policygen (LLM weights) → sim-gated │
│ activation │
│ │
│ SDK: /v1/identify · /v1/events/track · /v1/events/batch (≤500) │
│ └─ ingest (idempotent, canonical mapping) → user_state → enqueue evaluate_user│
│ │
│ DECISIONING (evaluate_user job, NO LLM, on-read stateless): │
│ score (recomputed from immutable events) → trigger/episode → │
│ A5 9-rung pacing gate → A6 arbitration (one winner) → A7 channel shortlist │
│ → would_dispatch | held_* | manual_pending │
│ │
│ generate_message (Anthropic) → approval gate (manual/autonomous) → │
│ dispatch → send_message → Postmark(email) | FCM/OneSignal(push) [SMS/in-app/voice ✗]│
│ │
│ engagement webhooks → runtime_decision_outcomes.engagement → intent_success → │
│ channel-affinity learning (email-only today) │
│ │
│ Serves: /dashboard/* (for app) · /backoffice/* (~138 routes) · /wizard/* (device) │
└──────────────┬───────────────────────────────────────────────┬──────────────────────┘
│ Supabase-JWT │ device-auth + manifest
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────────────────┐
│ BACKOFFICE (whisp-admin) │ │ WIZARD (npx @whisperr/wizard) │
│ operator control plane: │ │ detect → device-auth → fetch manifest │
│ onboarding, imports, │ │ → git checkpoint → Claude Agent wires │
│ policy gen + sim, │ │ identify()/track() → verify first event│
│ delivery controls, │ │ (planner=Opus / editor=Sonnet split) │
│ approvals, jobs, audit │ └───────────────────────────────────────┘
└──────────────────────────┘
2.2 Actors and personas
| Actor | Description | Primary surfaces |
|---|---|---|
| Prospect | Evaluating Whisperr. | Landing (calculators, voice qualifier, waitlist, Cal.com). |
| Customer Admin | Owns the integration; runs onboarding, watches the dashboard, approves messages in manual mode. | App onboarding + dashboard; in‑app AI assistant. |
| Developer Integrator | Wires Whisperr into the customer’s codebase. | Wizard CLI; client SDKs; API keys page. |
| End‑user | The customer’s user. Emits events; receives interventions. | None (subject of the system). |
| Whisperr Operator | Whisperr staff running/observing the platform. | Backoffice (full lifecycle, tuning, approvals, audit). |
| AI agents (system actors) | The onboarding LLM stage machine (BFF), the wizard coding agent, the runtime content/policy/universe LLMs, and the in‑app assistant. | Internal; governed by prompts in llm_prompt_versions and playbooks. |
2.3 The canonical end‑to‑end lifecycle (sequential)
This is the backbone the whole system exists to execute. Each numbered stage is elaborated as functional requirements in §3.
- Attract & qualify (Landing) → prospect signs up / joins waitlist / books a call.
- Sign up & authenticate (App ↔ BFF/Supabase) → Organization + App created.
- Onboard (App UI ↔ BFF stage machine): conversational intake → event_selection → weight_derivation → completed. Produces a manifest + weight manifest.
- Import & derive (BFF → Runtime): completed session imported; runtime generates the event universe (LLM) and a policy (LLM), then requires a clean simulation before the policy can be activated.
- Integrate & activate (Wizard / SDKs / in‑app): agent wires
identify()/track()into the customer’s code using the manifest; first event lights up the dashboard. - Ingest (Runtime SDK API): events arrive (single/batch), are canonicalised and idempotently stored;
user_stateupdated;evaluate_userenqueued transactionally. - Decide (Runtime engine, no LLM): recompute scores from the immutable log → trigger/episode → A5 pacing gate → A6 arbitration → A7 channel shortlist → outcome recorded.
- Generate content (Runtime, LLM) for a fired candidate → approval gate (manual: operator/customer approves; autonomous: auto‑approve).
- Deliver (Runtime → providers): email (Postmark) / push (FCM, OneSignal). (SMS, in‑app, voice not yet.)
- Measure & learn: provider webhooks normalise engagement →
intent_successchecked live → channel‑affinity updated (email‑only today). - Observe & steer: Customer watches the dashboard; Operator tunes policy/delivery‑controls, approves, and audits via backoffice.
2.4 Component responsibilities (crisp)
- Landing — top‑of‑funnel only: educate, quantify pain (churn calculators), qualify (Gemini‑Live voice “Alexa” BANT qualifier), capture (waitlist double opt‑in, referral), and hand off to app signup. Owns no product data.
- App — the customer’s two‑phase product surface: the onboarding “Trilogy” and the operational dashboard, plus an in‑app Claude assistant with read tools and confirm‑gated mutations. Talks to BFF (onboarding/auth) and runtime (dashboard) through a service layer with defensive parsers and a mock/live fallback.
- BFF — the onboarding brain: an LLM stage machine over Supabase, deterministic fallbacks everywhere, manifest generation/validation, and the single import handoff to the runtime. Also fronts auth, a VAPI Recall demo, and (scaffold‑only) a GitHub PR agent.
- Runtime — the core: import, universe/policy derivation, ingestion, stateless decisioning, content generation, delivery, outcomes/learning, and the dashboard/backoffice/wizard HTTP surfaces. Single Go binary, three run modes, Postgres + Redis, Postgres‑backed durable job queue.
- Backoffice — the operator control plane: inspect onboarding/imports, generate/simulate/activate policy, tune delivery controls and engine settings (both revisioned with rollback), approve manual‑mode messages, replay jobs, and read the audit trail. Pure UI over
/backoffice/*. - Wizard — the self‑serve activation path: an
npxagentic integrator (planner=Opus / editor=Sonnet) that maps a repo, plans event placements, wires the SDK, and verifies the first event — never shipping an Anthropic key (routes through a metered gateway). - user‑whisperer (LEGACY) — the ancestral full vision: a runnable microservice stack whose patterns are the reclaim list for finishing the loop — content fact‑provenance, the channel webhook→outcome plumbing and provider‑abstraction shape (the actual SendGrid/Twilio/Firebase send code is mock scaffolding that throws under the production contract — reclaim the plumbing, not the integrations),
EventCorrelatoroutcome attribution, and theShadowModeRunner/IMLProviderML pattern.
2.5 Design constraints and principles (to preserve)
These emerged from the code and are load‑bearing architectural commitments the target design must keep:
- P1 — Immutable event log. Events are append‑only and idempotent on
(app_id, message_id). All scoring is derived, never destructively mutated. - P2 — On‑read, stateless decisioning. Scores are recomputed from raw events on every evaluation (migration 0051 deliberately dropped the old persisted weighted‑state tables). A decision is replayable by re‑running
Computeat a historicalasOf. No persisted weighted‑state table may be reintroduced. - P3 — No LLM in the decision hot path.
evaluate_useris pure Postgres + arithmetic. LLMs are confined to offline universe/policy/content generation. This is CI‑enforced by an AST invariants test (whisperr-go-main/internal/engine/invariants_test.go, run ingo testin CI) that forbidsruntimederivationimports andtime.Nowin the scoring/trigger/gate/arbitrate/channel/simulate packages;scripts/audit_engine.shis a local mirror (not wired to a workflow). - P4 — Deterministic, ordered gates. A5 is a fixed first‑failure‑wins ladder over one shared history snapshot; A6 yields exactly one winner; A7 shortlists channels. Compliance floors (SMS 21:00–08:00) are non‑waivable.
- P5 — Sim‑gated activation. Learning‑loop/manual policies cannot go live without a completed, clean simulation; raw
policy.Activatemust never be HTTP‑reachable. - P6 — Transactional outbox. Event ingest and
evaluate_userenqueue commit in the same transaction; jobs are durable with retry/dead‑letter. - P7 — Multi‑tenant by construction. Every runtime read/write is app‑scoped; Supabase RLS enforces onboarding‑side isolation.
- P8 — Prompt/versioned‑config as data. Every LLM prompt (
llm_prompt_versions) and every policy/engine/delivery‑control revision is versioned in the database with rollback. - P9 — Provenance & PII discipline. Event properties are sanitised before any LLM sees them; content generation must not fabricate metrics/social proof (a reclaimable legacy guarantee).
2.6 Assumptions and dependencies
- Data stores: Postgres (runtime, ≥80 tables, goose migrations; separate onboarding/Supabase DB; optional backoffice/Supabase pool), Redis/Valkey (API‑key auth cache + rate limit only, today).
- Identity: Supabase Auth (onboarding, dashboard, backoffice JWTs).
- LLM providers: Anthropic (content generation, profile refinement, wizard gateway), OpenAI (universe/derivation, onboarding chat/decision), model‑configurable. Gemini Live (landing voice qualifier).
- Delivery providers: Postmark (email, managed), FCM + OneSignal (push, BYOK). (SMS/Twilio, BYOK email — not present.)
- Voice: VAPI (Recall demo scaffolding).
- Hosting: Cloudflare Pages (landing, app, backoffice), Azure Container Apps and a systemd/Caddy droplet (runtime — two coexisting pipelines), Vercel adapters (vestigial).
- Analytics: PostHog (landing/app funnel), Cal.com (booking).
3. Functional requirements
Requirements are grouped by subsystem and numbered FR-<area>-<n>. Priority: M (must, core loop), S (should), C (could). Status tags per §0.
3.1 Marketing & demand capture — Landing
- FR-MKT-1 (M) ✅ Serve a public marketing site (home, platform pages, pricing) with SEO/OG pipeline (per‑post OG image generation, static blog HTML head injection, SEO verification in the build).
- FR-MKT-2 (M) ✅ Waitlist capture with attribution (UTM/referrer/device), double opt‑in (
/confirm?token=), and a referral system with share/resolve. - FR-MKT-3 (S) ✅ Churn calculators (SaaS/DTC/mobile) as lead magnets, sharing single‑sourced churn math with structured JSON‑LD.
- FR-MKT-4 (S) ✅ A 7‑question business‑profile survey that assigns an ICP and produces an async, polled retention PDF report.
- FR-MKT-5 (C) 🟡 A real‑time Gemini‑Live voice qualifier (“Alexa”) running a BANT sales‑qualification call via a Supabase Edge Function (anon‑auth JWT, AudioWorklet capture, tool‑call scoring). Production‑hardened, but the homepage hero widget is currently commented out (replaced by a Cal.com CTA); the voice qualifier is live only inside the
/surveyflow. Naming collides conceptually with the product’s “Recall” retention voice — must be disambiguated. - FR-MKT-6 (S) ✅ PostHog funnel analytics (67 distinct capture events repo‑wide; 11 in the Product Hunt campaign client union, bilingual EN/KA) and Cal.com booking.
- FR-MKT-7 (M) ❌ (cross‑repo gap) Warm handoff of prospect context (UTM, ICP, survey answers, calculator inputs) into onboarding. Today the funnel ends at a bare external link to
app.whisperr.net/signup; none of the rich context collected is passed forward. Target: signed handoff token or structured query params consumed by onboarding to pre‑seed business context. - FR-MKT-8 (C) 🔩 Consolidate the 12 near‑duplicate pitch‑deck routes (multiple 3,000‑line forks + a 4,500‑line
primitivesfile) into one parameterised deck; retire legacy variants. Engineering hygiene, not product behaviour. - FR-MKT-9 (M) 🔩 (funnel‑reliability risk) The Product Hunt waitlist runs on a separate Lovable Cloud backend (
project--…lovable.app) via the same‑origin proxy, and the main‑site/api/waitlist+/api/churn-reports/*rewrites exist only invercel.jsonwhile production is served by Cloudflare Pages (public/_redirectshas only the SPA catch‑all) — so the main‑site waitlist/report path may 404 in production. Target: verify and (if dead) restore these routes on the Cloudflare host; document the Lovable dependency (§5.7). (A cross‑page prospect cache exists —lib/prospect.ts+ Supabase anonymous visitor auth — but stops at the origin boundary; see FR‑MKT‑7.)
3.2 Identity, authentication & tenancy
- FR-AUTH-1 (M) ✅ Multi‑tenant hierarchy Organization → membership → App, created via a single RPC (
create_personal_org) on the onboarding side; every App owns a publishable ingest key and scoped API keys. - FR-AUTH-2 (M) ✅ Onboarding/dashboard/backoffice authenticate via Supabase JWT, verified locally (RS256/ES256 + cached JWKS) with an HS256/remote fallback.
- FR-AUTH-3 (M) 🟡 App (customer dashboard) production auth. Currently
app’sauth.tsis an explicit localStorage mock with a live‑fallback to the BFF;getDashboardAccessToken()checks only presence/expiry, not JWT validity — trust is pushed entirely to the runtime. Target: complete the Supabase swap with real token validation. - FR-AUTH-4 (M) 🟡 Scoped API keys. Keys are
wrk_‑prefixed, SHA‑256‑hashed, prefix‑visible, show‑once, revocable, Redis‑rate‑limited (~600/min), fail‑open on Redis error. Gap: there is no scope tier — a browser‑shipped publishable key authenticates identically to a server secret across ingest, identify, preview, and user‑state reads. Target: introduce key tiers (publishable/ingest‑only vs. server/secret) enforced at the auth layer. - FR-AUTH-5 (M) 🔩 Remove the insecure dashboard‑auth bypass.
X-Whisp-User-IDcan stand in for a verified identity whenAllowInsecureUserHeader=true, guarded only by a warning log. Target: forbid in production by build‑time assertion. - FR-AUTH-6 (S) 🔩 Backoffice RBAC. Backoffice auth checks an
admin_operatorsallow‑list but then grants flat full read+write to every tenant (only 2 of ~138 routes add cross‑tenant checks). Target: at minimum a read‑only vs. write‑capable operator tier, and two‑person control for operator grant/revoke. - FR-AUTH-7 (M) ✅ Internal service‑to‑service calls (import + derivation control) are gated by a shared secret, constant‑time compared, hard‑failing 503 when unset. Gap (S): one secret gates five capabilities including LLM‑spend‑triggering ones; target per‑capability secrets or service identity.
- FR-AUTH-8 (M) ✅ Onboarding tenant isolation enforced by Supabase RLS (
is_org_member); Gap: zero automated RLS test exists — the sole isolation mechanism is unverified. Target: a cross‑tenant RLS test suite.
3.3 Onboarding — BFF stage machine
- FR-ONB-1 (M) ✅ Canonical onboarding record
partner_intake_sessionswith a CHECK‑enforced stage enum:intake → event_selection → weight_derivation → completed → escalated. - FR-ONB-2 (M) ✅ Intake stage: a conversational LLM interview whose completion is gated by a second, independent “decision‑review” LLM call (proposer/approver split), with a deterministic fallback so onboarding always completes without keys.
- FR-ONB-3 (M) ✅ Event‑selection stage: deterministic manifest builder (
buildDeterministicEventManifest, schema2.4-deterministic) reusing already‑scored intervention recommendations — no live LLM negotiation. - FR-ONB-4 (M) ✅ Weight‑derivation stage: LLM‑generated weight manifest, pair‑validated exactly against the event manifest, with one auto‑repair round‑trip; failure routes to
escalated. - FR-ONB-5 (M) ✅ Dual‑provider LLM routing (OpenAI + Anthropic,
LLM_PROVIDERenv), with profile‑refinement and Recall‑scenario calls forcing Anthropic when a key is present; retry/backoff + response‑format auto‑downgrade. - FR-ONB-6 (M) ✅ Event‑source connectors (GA/Mixpanel/Amplitude/Segment) with OAuth/API‑key, optimistic‑concurrency (409) and env‑gated preview/mock.
- FR-ONB-7 (M) ✅ Deployment status tracked both as legacy JSONB and normalised tables (
generating → validating → deploying → live/failed), decoupled so dashboard unlock does not depend on runtime‑import success (idempotent self‑healing retry on later reads). - FR-ONB-8 (M) ✅ Runtime import handoff:
POST {runtime}/internal/onboarding/importwithX-Whisp-Onboarding-Import-Secret, body{session_id, force_new_version}; silentlyskipped(not error) when unconfigured. - FR-ONB-9 (C) 🔩 GitHub PR agent. Webhook ingestion (HMAC‑verified) and persistence exist; the actual scan→plan→patch→PR logic does not exist (schema‑only). Target: build or remove; stop ingesting partner repo data until a consumer exists (data‑minimisation).
- FR-ONB-10 (M) 🔩 Retire the hardcoded
runtimeTelemetryAvailable:falsestub in the deployment‑status route and wire real telemetry. - FR-ONB-11 (S) 🔩 Resolve prompt‑lineage drift: live prompts (v2.1) vs. staged‑not‑applied (v2.2, one version un‑bumped despite changes) vs. an unused “internal‑prompts” family that already reflects where the code has moved. Adopt formal prompt schema versioning.
- FR-ONB-12 (M) 🔩 BFF “background” LLM jobs are synchronous, client‑poll‑driven pseudo‑async. Weight‑manifest generation (
queueOnboardingWeightManifestBackground) and intervention‑recommendation generation run inline in the HTTP request; stalein_progressrecovery only fires when a client happens to poll; there is no queue or cron in the BFF. This liveness‑depends‑on‑polling model is a reliability hazard. Target: move to a real durable queue/cron (generalises NFR‑REL‑2 across repos). - FR-ONB-13 (S) 🔩 Company‑enrichment scraping. The BFF crawls the partner’s live site (
/,/pricing,/features,/about) and feeds an LLM (with deterministic fallbacks) via two routes — a third LLM/data‑ingestion surface with an SSRF‑shaped outbound‑fetch risk (see NFR‑SEC‑9). Target: allow‑list/timeout the fetcher and constrain targets.
3.4 Runtime import, universe & policy derivation
- FR-DRV-1 (M) ✅ Import a completed onboarding session across a separate Postgres pool into runtime tables (org/app/business‑context/event‑catalog/policy‑draft); returns
{import_id, app_id, config_version_id, generation_job_id, status}. - FR-DRV-2 (M) ✅ Universe generation (
runtimeuniverse): a single OpenAI Responses‑API call (with one‑shot repair, ICP grounding, SHA‑256 input‑fingerprint idempotency) turns onboarding into the event taxonomy + interventions. - FR-DRV-3 (M) ✅ Policy generation (
policygen): a 3‑stage LLM pipeline (intervention‑batch → app‑wide → review) producing weights/thresholds/refire rules, with a deterministic 7‑family lint and one repair round. - FR-DRV-4 (M) ✅ Sim‑gated activation (
learn.ActivateWithGate): learning‑loop/manual policies require a completed, clean simulation before activation; rawActivateis not HTTP‑reachable (P5). - FR-DRV-5 (M) ✅ Payload‑contracts derivation (
derive_payload_contracts) is wired. Gap (S) 🔩: the other declared derivation units/stages (intervention‑weights, explanation, final‑assembly) are vestigial, unwired scaffolding;derive_intervention_weightsjob type is declared but never registered (dead). Target: finish or delete. - FR-DRV-6 (M) ✅ Every LLM call is DB‑logged (
llm_model_calls, cost/tokens/thinking log) and every prompt loaded fromllm_prompt_versions(single chokepoint).
3.5 Integration & activation — Wizard, SDKs, in‑app
- FR-ACT-1 (M) ✅
npx @whisperr/wizardruns in a customer repo: detect stack → device‑auth to the onboarded account → fetch manifest → git checkpoint → agentic wiring → verify first event. - FR-ACT-2 (M) ✅ Planner/editor split: a read‑only repo‑map + event‑placement plan (planner, default Opus) and mechanical edits (editor, default Sonnet); interactive TTY plan review; deterministic post‑wire diff scan that never trusts the agent’s self‑report; one auto‑repair pass.
- FR-ACT-3 (M) ✅ Manifest contract (
GET /wizard/manifest):appId/appName, stableingestionApiKey(minted on first fetch),ingestionBaseUrl,identifyguidance, wireableevents[](derived filtered out, per‑surface visibility),universeSummary, fullinterventions[],supportedTargets. - FR-ACT-4 (M) ✅ Device‑authorization flow (
/wizard/device/authorize→ app approval →/wizard/device/token, sliding TTL) and a gateway model so the CLI never ships an Anthropic key (ANTHROPIC_BASE_URL+ session token; server injects the real key and meters per app). - FR-ACT-5 (M) 🟡 End‑to‑end self‑serve activation is substantially built (this corrects an earlier assumption): the app’s
/wizard/activateapproval page exists (457‑linewhisperr-app-main/src/pages/wizard/Activate.tsx, routed, callingPOST /dashboard/wizard/approve), and the client SDKs@whisperr/web,@whisperr/next,@whisperr/react(v0.1.7) are published on npm from the eighth repo (github.com/WhisperrAI/whisperr-web); all 8 wizard playbooks reportavailable. Remaining work: production E2E verification of the whole detect→approve→wire→first‑event flow; publishing SDKs for the remainingsupportedTargets(RN/Swift/Node/Python/PHP); and verify commands per FR‑ACT‑7. - FR-ACT-6 (S) ✅ “Universe opportunities” from a wizard run are staged for dashboard approval (
/wizard/universe/suggestions) rather than merged directly (legacy/universe/additionsdeprecated) — a good trust boundary to keep. - FR-ACT-7 (S) 🔩 Add cheap verify commands to the 7 command‑less playbooks (only Flutter has one); resolve symlinks before the repo‑containment check; confirm gateway‑side budget metering is authoritative (client
WHISPERR_WIZARD_BUDGET_USD, default 25, is env‑overridable).
3.6 Event ingestion — SDK API
- FR-ING-1 (M) ✅
POST /v1/identify— upsert user, merge traits, register channels (email/phone/push shortcuts expand to channels). - FR-ING-2 (M) ✅
POST /v1/events/track— ingest one event →202 {event, user, job:{type:evaluate_user}};POST /v1/events/batch— ≤500 events in one transaction with partial accept ({accepted, rejected}). - FR-ING-3 (M) ✅ Canonical mapping (exact → alias‑exact → alias‑ci over the active config); derived codes rejected from direct ingestion; unknown top‑level fields rejected (must nest under
properties/traits). - FR-ING-4 (M) ✅ Idempotency on
(app_id, message_id); on a genuine (non‑replay) event, recomputelifecycle_stage / first_seen_at / last_active_at / event_count_7d/30d / session_count_30dfrom a live aggregate query and enqueueevaluate_userin the same transaction (P6). - FR-ING-5 (M) ✅ Auto‑suppression: a user with zero opted‑in channels is marked
suppressed. - FR-ING-6 (M) 🔩 Fix
/v1/users/{id}/state. Itsengagement_score / churn_risk_v0 / intervention_scorecolumns are written nowhere (dead/legacy), yet exposed on a public endpoint returning plausible zeros. Target: repopulate from the current model or retire and document in the OpenAPI spec.
3.7 Decisioning engine (the heart) — evaluate_user
- FR-DEC-1 (M) ✅ Three enqueue sources produce
evaluate_userwith distincttrigger_reason: raw event (event_received), predicate crossing via sweep (scheduled), derived‑event materialisation (derived_event_materialized). - FR-DEC-2 (M) ✅ Compute is pure and read‑only with a single
asOfcaptured once (P2/P3): load active policy → pin live delivery‑controls + engine‑settings revisions → suppression check → score → trigger‑decide → build fullscoring_snapshot→ A5 → A6 → A7 → mode routing → episode linkage → flag content generation.Persistcommits everything in one transaction (idempotent onjob_execution_key). - FR-DEC-3 (M) ✅ Scoring (
engine/scoring, one SQL query):lookback = min(90d, 5×longest half‑life); per interventionΣ weight × 0.5^(age/half_life) × repeat_factorover surviving occurrences (repeat rulesevery / diminishing / once_per_window / first_only); gate links re‑lock independently;Score = clamp(raw_sum, 0, threshold)— scores never exceed threshold. - FR-DEC-4 (M) ✅ Trigger/episode (
engine/trigger): fire iffscore ≥ threshold ∧ gate_open; refire modesfire_once_then_a5(never latches),re_cross(fires if no open episode; closes belowthreshold×hysteresis),periodic(refire afterRefirePeriodSeconds); retired interventions force‑close. - FR-DEC-5 (M) ✅ A5 pacing gate — the exact 9‑rung ladder (first‑failure‑wins, one shared snapshot, per fired candidate):
1.
quiet_hours(app‑level, timezone‑resolved, per‑intervention waivable) 2.cooldown_global(default 3600 s / 1 h, since last any send) 3.cooldown_intervention(default 86400 s / 24 h, since last this intervention) 4.cap_per_user_day(default 2) 5.cap_per_user_week(default 5) 6.cap_per_channel_day(default 1; fails only if every enabled channel saturated) 7.cap_per_channel_week(default 4) 8.cap_per_intervention_week(default 2) 9.fatigue— time‑decayed accrualΣ accrual × 0.5^(age/half_life); held if≥ ceiling(defaults accrual 1, half‑life 604800 s / 7 d, ceiling 6). This is an unbounded accrual sum, not a normalised 0–1 “0.85” score — a persistent doc myth to correct. All caps/cooldowns are tenure‑scaled (stricter for new users). - FR-DEC-6 (M) ✅ A6 arbitration: sort by
Normalized()=score/thresholddesc →Prioritydesc → open‑episode age asc → code asc; rank 1 wins, losers recordedlost_arbitration. - FR-DEC-7 (M) ✅ A7 channel shortlist (winner only, order
email < in_app < push < sms, 6 checks): controls enabled → addressable channel row → opted‑in ∧ not opted‑out → per‑channel day/week cap → per‑channel quiet hours (SMS 21:00–08:00 is a non‑waivable statutory floor) → SMS priority threshold; provider readiness + email verification are deliberately deferred to dispatch. Survivors ranked by learned affinity → cost → canonical order. - FR-DEC-8 (M) ✅ Every fired candidate produces a
runtime_decision_outcomesrow regardless of fate; the fullscoring_snapshotis persisted on every evaluation for replay/debug (/v1/decisions/previewprovides a read‑only would‑fire view that writes nothing). - FR-DEC-9 (S) 📌 Documented invariant: passing A5 does not guarantee dispatch — a candidate can clear A5 yet fail A7 with no eligible channel. Consumers must not conflate “gate allowed” with “will send.”
- FR-DEC-10 (M) ✅ Sweep (
engine/sweep) is “the engine’s clock”: it holds no decision logic, only finds users crossing policy‑declared time predicates (anchor columns Go‑side whitelisted against predicate injection) and enqueues ordinaryevaluate_userjobs. - FR-DEC-11 (M) ✅ Derived events (
engine/derive): deterministic recipes (absence/offset) materialise synthetic events into the same immutable log (distinct fromsyntheticusage, which is LLM‑driven load simulation).
3.8 Content generation
- FR-CNT-1 (M) ✅
generate_message(async job, Anthropic) fills thea8_objecton an already‑recorded outcome row — content is strictly off the decision path (P3); it never gates or changes a decision. - FR-CNT-2 (M) ✅ A PII/secret sanitiser strips 30+ sensitive‑key patterns from event properties (size/depth budgets) before any LLM sees them (P9).
- FR-CNT-3 (M) ✅ Anti‑repetition context is built from a digest of real sent messages (
learnhistory digest). - FR-CNT-4 (S) ❌ (reclaim from legacy) A fact‑provenance / anti‑hallucination guarantee — prompts that forbid inventing metrics/social proof/testimonials — exists in the legacy content service but is not confirmed in the runtime generator. Target: port the provenance guardrail (P9). Caveat: the legacy
ContentValidatorparadoxically scores content higher for social‑proof/trust words — the port must exclude or reconcile that scorer, not copy it wholesale.
3.9 Delivery / dispatch
- FR-DLV-1 (M) ✅ Approval gate: in manual mode a message stays
manual_pendinguntil an operator/customerApprove/Reject(14‑day expiry sweep); in autonomous mode it auto‑approves and dispatches. Both paths real, per‑app toggle. - FR-DLV-2 (M) ✅ Dispatch chain (
dispatch.Producer.Handoff→send_messagejob →message_dispatcher→ providerSend()): re‑checks suppression, provider readiness, and address opt‑in/verification in‑transaction; atomic claim/lease on the outbound message prevents double‑send; outcome status written back (dispatched/failedon theruntime_decision_outcomesrow; thesentstate lives ondelivery_outbound_messages). - FR-DLV-3 (M) ✅ Email via Postmark (managed): domain/DKIM provisioning, webhook registration (all 6 event types), inbound normalisation into
delivery_message_events, templated and raw sends. - FR-DLV-4 (M) 🟡 Push via FCM (hand‑rolled JWT OAuth2 + FCM v1) and OneSignal (REST), both real and BYOK‑connectable. Gap: neither feeds engagement back — no push receipt/webhook normaliser; the loop is open for push.
- FR-DLV-5 (M) ❌ SMS — no adapter exists (interface only);
send_messagehard‑rejects any channel but email/push. Target: build an SMS provider (the A7 statutory quiet‑hours floor already anticipates it). - FR-DLV-6 (S) ❌ In‑app delivery — a channel in the taxonomy (A7 order includes
in_app) with no delivery implementation. Target: an in‑app inbox/SDK surface. - FR-DLV-7 (C) 🔩 Voice (“Recall”) — VAPI scaffolding and a Recall demo exist (landing + app + BFF), but voice is not a wired runtime delivery channel. Target: promote to a real channel or keep clearly demo‑scoped.
- FR-DLV-8 (S) 🟡 BYOK marketplace — registry + encrypted credential intake + verify/rotate/disconnect are real, but only push is BYOK‑connectable today (Postmark managed‑only; BYOK email SendGrid/Resend absent). Target: complete per RFC 0001 phases.
3.10 Outcome attribution & learning loop
- FR-OUT-1 (M) ✅ Provider engagement webhooks (Postmark today) normalise into
runtime_decision_outcomes.engagementvia a continuous worker‑mode sync. - FR-OUT-2 (M) ✅ Intent success is checked live inside
evaluate_user(pure SQL, no LLM): did the intended success event occur withinSuccessWindowSeconds? - FR-OUT-3 (M) 🟡 Channel‑affinity learning (
runtime_user_channel_affinity) is computed and actually consulted by the A7 selector. Gaps: recompute runs only on manual backoffice‑triggered recalibration (no scheduler), and the loop is effectively email‑only because push has no engagement feed (FR‑DLV‑4). Target: auto‑schedule affinity recompute; close push engagement. - FR-OUT-4 (M) ❌ (reclaim from legacy) Conversion / ROI attribution — the legacy
EventCorrelator/outcome‑tracker (72 h window, confidence‑scored, honestly returnsinsufficient_datarather than fabricating) is the model for “recovered revenue,” the core sales claim. The runtime records per‑outcome engagement/intent but has no aggregated attribution or ROI. Target: build an attribution layer. - FR-OUT-5 (S) ✅ (governance) Simulation isolation (
SimStore, determinism tripwire): sim runs share no SQL with production and are strict‑diff self‑checked — keep as the safe way to validate policy changes before activation.
3.11 Customer dashboard — App
- FR-DSH-1 (M) ✅ Session bootstrap resolves the active app (
/dashboard/session) and scopes every surface; graceful empty‑states when unconfigured. - FR-DSH-2 (M) ✅ Onboarding “Trilogy” UI: Start → Building demo → Stage 1 intake (AI chat) → Recap → Recall demo → Stage 2 catalog (pick ≥3 interventions from 8 strategies) → Stage 3 events mapping → Calibrating → Complete, with resumable localStorage progress (allow‑listed routes, milestone fallback).
- FR-DSH-3 (M) ✅ Dashboard surfaces (live‑wired to runtime): Overview KPIs + lifecycle funnel, Interventions (live event stream, 3 tabs), Users, Events pipeline (+SSE stream), Integrations/instrumentation coverage, Developer/API‑keys, Settings, Approvals (wizard‑suggestion review), plus lazy Analytics/Experiments/Outcomes/Communications/Message‑delivery/Auto‑tuner/System‑health.
- FR-DSH-4 (M) ✅ In‑app AI assistant (Claude
claude-sonnet-5via a server proxy that validates the dashboard Bearer before any paid call; rate‑limited): 7 read tools + 6 confirm‑gated mutation tools executed client‑side against the same service layer as the UI. - FR-DSH-5 (M) 🔩 Fix live/mock leaks:
Calibratingis a pure 6.4 s client timer that always “succeeds” (never polls the real backend);interventionsService.getDetail()returns synthetic data even in live mode; the GitHub PR install path is asetTimeoutfake. Target: wire to real runtime status; quarantine/delete dead code (ScaffoldPlaceholder, duplicateIntegrationsManagement, dormant Recall‑setup wizard, paywall experiment,orb-preview.html). - FR-DSH-6 (S) 🔩 Unify the parent‑strategy taxonomy (defined twice with drift:
src/lib/constants.tsvssrc/services/intervention-contract.ts). - FR-DSH-7 (S) ❌ Aggregated analytics surfaces (Analytics/Outcomes/Experiments) need a real read‑model backend — see §3.13.
3.12 Operator backoffice
- FR-BO-1 (M) ✅ ~138 routes over
/backoffice/*(operator‑gated), zero TODO/mock markers — the most complete UI in the system. Surfaces: Command Center (attention/tenants/pipeline/LLM‑cost snapshot), Orgs/Apps/Members, Onboarding sessions (5‑tab inspector) + Pipeline + Deployments + Imports, Universe/Config‑version browser, ICP contexts, Universe‑test sandbox, LLM observability (calls/prompts/usage/cost), Decision Explorer (5‑tab outcome trace), Evaluations/Would‑be‑dispatch/User‑timeline, Policy‑generation settings + versions + diff, Simulations + Synthetic usage, Approval queue, Engine settings + Delivery controls (both revisioned + rollback + cutover‑drift), Jobs/Dead‑letter (replay), Runtime logs (journalctl), Events, Runtime users (suppress), Waitlist, Operators (grant/revoke), Audit log (before/after JSON). - FR-BO-2 (M) ✅ Consistent action lifecycle (confirm → toast → audit‑aware → reload) across ~15 mutating flows; every operator write is server‑gated and audited by the runtime.
- FR-BO-3 (S) 🔩
useCan()is a no‑op (all operators can do everything). Target: wire the already‑designedActionenum to real server‑returned scopes (see FR‑AUTH‑6). - FR-BO-4 (S) 🔩 Move the config‑assistant’s async LLM runs onto the durable job queue — today they run in a bare goroutine with weaker crash recovery than the rest of the platform.
- FR-BO-5 (S) 🔩 Redact tenant PII/secrets in the LLM‑calls explorer; add a CI test/lint gate before the
promote.ymlproduction fast‑forward; make the audit log exportable; source LLM pricing from the runtime in the universe‑test sandbox estimator (still hardcoded), noting the LLM‑calls explorer already uses server‑supplied cost.
3.13 Analytics & reporting (read models)
- FR-ANL-1 (M) ❌ Aggregated read‑model layer. The dashboard currently reads runtime data via a direct CQRS bridge (
runtimequery); there is no aggregation (trends, funnels, cohorts, per‑intervention effectiveness) overruntime_decision_outcomes+delivery_message_events. Target: a rollup/materialised‑view layer + rollup jobs feeding the lazy dashboard analytics surfaces and ROI reporting. - FR-ANL-2 (S) ❌ Recovered‑revenue / ROI report — the marketed headline metric — depends on FR‑ANL‑1 + FR‑OUT‑4.
3.14 In‑app AI assistant & config assistant
- FR-ASI-1 (M) ✅ App assistant (customer‑facing) — see FR‑DSH‑4.
- FR-ASI-2 (S) ✅ Backoffice config assistant — an LLM copilot that proposes/applies universe/config edits over a config version (threads/messages/proposals/runs schema). Keep; move execution to the durable queue (FR‑BO‑4).
3.15 Voice channel (“Recall”)
- FR-VOX-1 (C) 🔩 Recall is demo/marketing‑stage across landing (Gemini‑Live qualifier — a different system), app (Recall demo console), and BFF (VAPI webhook capture). It is not a runtime delivery channel. Target: either productise as a real channel (delivery adapter + engagement feed + A7 integration) or scope it explicitly as a demo and disambiguate the “Alexa” (sales) vs “Recall” (retention) naming across repos.
3.16 Billing, plans & entitlements
- FR-BIL-1 (M) ❌ No billing/subscription/entitlement subsystem exists in any repository. The marketing site sells Starter/Growth/Enterprise tiers (MAU/event caps, channel gating, SSO/HIPAA), but nothing in code enforces a plan, meters MAU/events for billing, gates features by tier, or integrates a payment processor (the only metering present is the wizard LLM gateway’s per‑app usage). Target: a plan/entitlement service (plan catalogue, per‑app entitlements, usage metering against caps, tier‑gated feature flags) and a payment integration.
- FR-BIL-2 (S) ❌ Enforce plan limits at the ingestion and channel layers (MAU/event caps → soft‑throttle/upgrade prompts; channel availability by tier), surfaced in the dashboard and backoffice.
4. Data requirements
The runtime owns a single Postgres database of ~80 tables (goose migrations 0001–0074, embedded in the binary). Onboarding owns a separate Supabase Postgres (26 tables, 13 migrations, RLS‑enforced). This section specifies the canonical data model by domain, not column‑by‑column.
4.1 Data principles
- D1 — Immutable event log.
eventsis append‑only, idempotent on(app_id, message_id), carryingcanonical_event_type,is_derived, and thepolicy_version_idin force. It is the single source of truth from which all scores are derived (P1/P2). - D2 — Versioned config, never in place. Policy, engine settings, delivery controls, business context, and LLM prompts are each stored as immutable revisions with exactly‑one‑active semantics and rollback (P8).
- D3 — Outcome as the durable decision record.
runtime_decision_outcomesis the join point of decision, content, delivery, and measurement — the row that a decision writes and that delivery/engagement later enrich. - D4 — Isolation by construction for simulation. Sim tables (
runtime_sim_*) share no SQL path with production; CI forbids cross‑references (P5).
4.2 Runtime data domains (Postgres)
| Domain | Representative tables | Purpose |
|---|---|---|
| Tenancy & identity | organizations, organization_members, apps, api_keys, app_channel_settings |
Multi‑tenant hierarchy; hashed keys; per‑app channel enablement. |
| Users & channels | users, user_state, user_channels |
Per‑(app_id, external_id) identity; event‑derived lifecycle/last‑active; addressable channels + opt‑in. |
| Events | events |
The immutable log (D1). |
| Business context | app_business_context_versions (+ tones / forbidden_phrases / customer_signals), app_delivery_policies |
Versioned brand/ICP facts grounding LLM prompts (not a gate). |
| Config import / universe / derivation | app_runtime_config_imports, …_versions, …_derivations, app_runtime_event_definitions (+ aliases), app_runtime_interventions (+ variants), app_runtime_event_weights, runtime_universe_icp_contexts, universe_test_runs |
Materialised onboarding → event taxonomy + interventions + derivation state. |
| Policy | runtime_policy_versions (+ event_weights / interventions / app_config), policy_generation_settings (+ parts / runs / run_steps), runtime_app_engine_settings, runtime_delivery_control_revisions, app_delivery_controls, runtime_engine_settings_revisions |
Immutable decisioning config + generation lineage + revisioned live controls (D2). |
| Decisioning & outcomes | runtime_episodes, runtime_evaluations, runtime_decision_outcomes, runtime_sweep_state, runtime_user_channel_affinity, runtime_engagement_cursor |
Latches, per‑tick evaluations with snapshots, the outcome record (D3), learning state. |
| Simulation | runtime_sim_runs, …_evaluations, …_decision_outcomes |
Isolated replay (D4). |
| Content & delivery | delivery_provider_accounts (managed/BYOK, encrypted creds), email_domains, email_templates (+ versions), delivery_outbound_messages, delivery_message_events, delivery_inbound_events_raw |
Provider config; outbound queue with claim/lease; normalised engagement. |
| Jobs | jobs, dead_letter_jobs |
Durable Postgres queue (SKIP LOCKED; partial‑unique dedupe). |
| LLM infra | llm_prompt_versions, llm_model_calls |
Every prompt + every LLM HTTP call (cost/tokens/thinking log). |
| Config assistant / wizard / synthetic / audit / waitlist | ai_assistant_*, wizard_device_authorizations, integration_runs, event_coverage, suggestions, synthetic_usage_*, backoffice audit, waitlist |
Copilot state, device‑auth + integration telemetry, load simulation, audit, public signup. |
Schema‑churn note (important for maintainers): migrations 0001/0002 built a persisted weighted‑state model (app_runtime_intervention_state, decision_runs, app_runtime_intervention_executions) whose execution enum was ready → held → content_pending → content_ready → dispatch_pending → dispatched → delivered → resolved. Migration 0051 deliberately dropped all four tables (including app_runtime_intervention_execution_events) for the leaner on‑read model (runtime_episodes + runtime_evaluations + runtime_decision_outcomes). scripts/audit_engine.sh CI‑enforces that the legacy tables are never referenced again. Any documentation describing “event ingest updates weighted app_runtime_intervention_state” is describing retired v0, not the live system.
4.3 Onboarding data (Supabase)
partner_intake_sessions (canonical, stage‑enum), partner_intake_profiles (durable ICP/context), organizations + organization_memberships + apps, event_sources (+ snapshots), event‑selection/alias tables, onboarding_deployment_statuses (+ events), system_prompts, plus GitHub‑app and Recall‑demo tables. RLS tiers: service‑role‑only (admin/GitHub), member‑read, member‑CRUD via is_org_member, and CRUD‑with‑cross‑row‑checks. The sole isolation mechanism is RLS, and it has no automated test (FR‑AUTH‑8). Session resumability/confidence is modelled by a per‑field extraction table (partner_intake_field_values, migration 0007): each captured field carries a confidence score and a status (captured / inferred / defaulted / needs_confirmation), with fields_below_threshold gating stage progression — this is the real mechanism behind “the AI already knows enough to continue.”
5. External interface requirements
All runtime HTTP is stdlib net/http (Go 1.22 method+pattern routing), wrapped in RequestID → Logging → Recovery, with per‑mount middleware.
5.1 Public SDK API — /v1/* (auth: API key)
| Method & path | Purpose |
|---|---|
POST /v1/identify |
Upsert user + traits + channels + baseline. |
POST /v1/events/track |
Ingest one event → 202 {event, user, job}. |
POST /v1/events/batch |
≤500 events, one tx, {accepted, rejected}. |
POST /v1/decisions/preview |
Read‑only would‑fire view (writes nothing). |
GET /v1/users/{external_id} |
User lookup. |
GET /v1/users/{external_id}/state |
Lifecycle/score read model (dead score columns — FR‑ING‑6). |
Interface requirements: snake_case event types; unknown top‑level fields rejected; permissive CORS on ingest/identify only; Redis fixed‑window rate limit (~600/min) fail‑open. Requirement: publish and version a machine‑readable OpenAPI contract (the swagger endpoint exists) and generate client/consumer types from it (see FR‑X‑1).
5.2 Internal contract — /internal/* (auth: shared secret, constant‑time)
POST /internal/onboarding/import, GET /internal/onboarding/icp-names, GET /internal/runtime-configs/{cv}/derive, POST …/derive/payloads, POST /internal/runtime-configs/{cv}/generate-policy. Requirement: the import payload contract (session/org/app/profile/field‑values/event‑sources/event‑selections/aliases) must be a shared, versioned schema with one canonical env‑var name per side, failing loudly on misconfiguration rather than silently skipped (FR‑AUTH‑7).
5.3 Dashboard bridge — /dashboard/* (auth: Supabase JWT)
Session; interventions (list/generation‑status/retry/set‑status/recent); events (list/KPIs/catalog/SSE); integrations + coverage; business‑context get+patch; users + stats; api‑keys (list/create/delete); delivery/email/provider connect/rotate/disconnect; wizard bridge (POST /dashboard/wizard/approve, suggestions, policy drafts). Envelope {error:{code,message,request_id}}.
5.4 Backoffice API — /backoffice/* (auth: Supabase JWT + operator allow‑list)
~138 routes (see §3.12), core always‑on + conditional blocks mounted only when the matching dependency is wired (universe‑tests, synthetic‑usage, onboarding‑source browsing, runtime intelligence, config assistant, LLM observability, decision‑trace, engine control‑plane, ops explorers, actions, governance). Requirement: add authorization tiers (FR‑AUTH‑6).
5.5 Wizard API — /wizard/* (self‑authenticating device flow)
POST /wizard/device/authorize → app approval → POST /wizard/device/token (sliding TTL 30 min idle / 90 min hard) → GET /wizard/manifest, POST /wizard/report, GET /wizard/first-event, POST/GET /wizard/universe/suggestions (+ /{id}/integrated), /wizard/llm/* gateway passthrough. Requirement: the manifest schema is the client contract — version it and keep types.ts (wizard) and the runtime handler in lock‑step.
5.6 Client SDK contract (identify / track / batch / flush / reset)
Durable ordered offline queue, batch coalescing (/v1/events/batch), exponential backoff + jitter, distinct auth (401/403 pause) vs transient (retry) vs permanent (drop) handling, lifecycle flush; email/phone/pushToken shortcuts expand to channels. Requirement: publish official SDKs for every supportedTarget. Flutter, @whisperr/web, @whisperr/next, @whisperr/react exist; the remaining targets (React Native, Swift, Node, Python, PHP) must still be published (FR‑ACT‑5).
5.7 Third‑party integrations
Supabase (auth + onboarding DB), Postmark (email + webhooks), FCM + OneSignal (push), Anthropic + OpenAI (content/derivation/onboarding LLMs), Gemini Live (landing voice), VAPI (Recall demo), PostHog, Cal.com, GA/Mixpanel/Amplitude/Segment (event‑source connectors), the Anthropic‑compatible wizard gateway (server injects key, meters per app), and a Lovable Cloud backend hosting the Product Hunt waitlist (proxied same‑origin from landing — a live, under‑documented third‑party dependency; see FR‑MKT‑9).
6. Non‑functional requirements
6.1 Security
- NFR-SEC-1 (M) Tiered API keys (publishable/ingest vs. server/secret) enforced at auth (FR‑AUTH‑4).
- NFR-SEC-2 (M) No production identity bypass;
X-Whisp-User-IDforbidden by build‑time assertion (FR‑AUTH‑5). - NFR-SEC-3 (S) Backoffice RBAC + two‑person control on operator grant/revoke; redact tenant PII in LLM explorer (FR‑AUTH‑6, FR‑BO‑5).
- NFR-SEC-4 (M) Per‑capability internal secrets or service identity/mTLS; rotation mechanism (FR‑AUTH‑7).
- NFR-SEC-5 (M) Cross‑tenant RLS test suite; app‑layer isolation is 100% RLS today and untested (FR‑AUTH‑8).
- NFR-SEC-6 (M) PII/secret sanitisation before any LLM call (already present — keep and extend to all call sites) (P9).
- NFR-SEC-7 (M) Compliance floors are non‑waivable in code: SMS 21:00–08:00 quiet hours; opt‑out honoured at dispatch; suppression re‑checked in‑transaction (already present).
- NFR-SEC-8 (S) Secrets are never shipped to clients: wizard gateway model, landing ingest‑key server proxy, encrypted BYOK creds (AES‑256‑GCM) — keep; wizard should resolve symlinks before repo containment checks.
- NFR-SEC-9 (S) SSRF‑harden the BFF company‑enrichment fetcher (FR‑ONB‑13): it fetches arbitrary partner‑supplied URLs server‑side. Require an allow‑list/scheme check, private‑IP/localhost blocking, timeouts, and response‑size limits.
6.2 Reliability & correctness
- NFR-REL-1 (M) Transactional outbox for ingest→evaluate (present, P6).
- NFR-REL-2 (M) — cross‑repo Durable jobs: exponential backoff,
max_attempts → dead_letter_jobs, stale‑lock reclamation, replay CLI (present in runtime). Requirement: no production async work may run outside a durable queue anywhere in the platform — fix the runtime config‑assistant goroutine (FR‑BO‑4) and the BFF’s inline, client‑poll‑driven pseudo‑async LLM generation (FR‑ONB‑12), which is the larger violation. - NFR-REL-3 (M) Idempotency: events on
(app_id, message_id); evaluations onjob_execution_key; jobs on(job_type, dedupe_key); outbound message claim/lease prevents double‑send (present). - NFR-REL-4 (M) Sim‑gated activation + determinism tripwire (present, P5).
- NFR-REL-5 (S) Replayability: a decision is reproducible by re‑running
Computeat a historicalasOf— preserve; never reintroduce persisted weighted state (P2).
6.3 Performance & scalability
- NFR-PERF-1 (M) On‑read scoring is bounded by
lookback = min(90d, 5×half‑life)and executed as one SQL query per evaluation — keep the query indexed on(app_id, external_id, occurred_at). - NFR-PERF-2 (S) Batch ingestion ≤500 events/request in one transaction; rate limit ~600/min/key.
- NFR-PERF-3 (S) Worker horizontal scaling via
--mode=workerreplicas over SKIP‑LOCKED; API scaling via--mode=api. - NFR-PERF-4 (C) Introduce read‑model materialisation to keep dashboard/analytics off the hot transactional tables (FR‑ANL‑1).
6.4 Observability
- NFR-OBS-1 (M) Prometheus
/metrics, zerolog structured logs,/health(503 on DB/Redis failure). - NFR-OBS-2 (M) Full LLM audit:
llm_model_calls(cost/tokens/thinking), prompt registry, backoffice explorer (redact per NFR‑SEC‑3). - NFR-OBS-3 (M) Operator audit log with before/after JSON on every write; make exportable (FR‑BO‑5).
- NFR-OBS-4 (S) Full decision trace per outcome (scoring snapshot, gate ladder, channel shortlist/exclusions) — present in the Decision Explorer; surface a customer‑safe subset in the dashboard.
6.5 Privacy & data governance
- NFR-PRIV-1 (M) Data minimisation: do not ingest/persist data without a consumer (stop GitHub‑repo ingestion until the PR agent exists — FR‑ONB‑9).
- NFR-PRIV-2 (S) Retention policy for raw events, LLM logs, and delivery bodies (define; not found in code).
- NFR-PRIV-3 (M) Encrypt provider credentials at rest (present, AES‑256‑GCM).
- NFR-PRIV-4 (M) ❌ Right‑to‑erasure / end‑user data deletion. No deletion path exists anywhere (no
/v1delete, nothing in runtime or BFF) despite the platform processing end‑user PII (emails, phones, traits, event bodies). Target: an authenticated per‑external_iderasure endpoint that removes/anonymises the user, channels, events, outcomes, and message bodies across the runtime and delivery stores, with an audit record — a GDPR/CCPA baseline requirement.
6.6 Maintainability
- NFR-MNT-1 (M) One shared, generated API contract across app ↔ bff ↔ runtime (OpenAPI + codegen) to replace the hand‑rolled defensive parsers whose silent field‑dropping can mask contract drift (FR‑X‑1).
- NFR-MNT-2 (S) Single source of truth for shared taxonomies (parent strategies defined 2–3× across app/bff — unify).
- NFR-MNT-3 (S) Test coverage on the highest‑risk, currently‑untested code: BFF’s 11k‑line orchestration, RLS isolation, backoffice destructive ops, wizard live‑agent runs.
- NFR-MNT-4 (S) Remove dead code across repos (dead job type, unwired derivation stages, dead score columns, duplicate
messaging_readySQL ×4, dormant UI experiments).
6.7 Deployability
- NFR-DEP-1 (M) Single binary, three modes; embedded goose migrations with a
MIGRATE_RESET_CONFIRMguard; migration‑gated deploys. - NFR-DEP-2 (M) Reconcile the two coexisting runtime pipelines (Azure Container Apps vs. systemd/Caddy droplet) into one authoritative pipeline; likewise the BFF’s two pipelines (droplet vs. Azure) and the landing’s Vercel‑vs‑Cloudflare doc drift.
- NFR-DEP-3 (S) Add a CI test/lint gate before every production promotion (backoffice/app/landing fast‑forward workflows currently have none).
7. Current‑state assessment (as‑is maturity matrix)
The single honest picture of what exists today. Legend: ✅ built · 🟡 partial · 🔩 stub/dead · ❌ missing · 🕮 legacy‑only.
| Subsystem | State | One‑line reality |
|---|---|---|
| Marketing funnel | ✅ | Rich and live; but hands off cold to signup (❌ warm handoff). |
| Signup / tenancy | ✅ | Org→App via Supabase RPC. |
| App production auth | 🟡 | Still a localStorage mock; Supabase swap pending. |
| Onboarding stage machine | ✅ | LLM intake→events→weights, deterministic fallbacks, escalation. |
| Manifest generation | ✅ | Deterministic event manifest + validated weight manifest. |
| Runtime import | ✅ | Wired, idempotent, decoupled from dashboard unlock. |
| Universe generation | ✅ | LLM taxonomy, ICP‑grounded, fingerprint‑idempotent. |
| Policy generation | ✅ | 3‑stage LLM + lint + repair. |
| Sim‑gated activation | ✅ | Clean sim required before go‑live; isolation‑by‑construction. |
| Event ingestion | ✅ | identify/track/batch, canonical mapping, idempotent, outbox. |
| Decisioning engine | ✅ | On‑read stateless scoring; A5/A6/A7; no LLM in hot path (CI‑enforced). |
| Content generation | ✅ | Real Anthropic call; PII‑sanitised; anti‑repetition. Missing fact‑provenance (❌). |
| Approval gate | ✅ | Manual/autonomous, 14‑day expiry sweep. |
| Email delivery | ✅ | Postmark, DKIM, webhooks, engagement feed. |
| Push delivery | 🟡 | FCM + OneSignal real; no engagement feedback (open loop). |
| SMS delivery | ❌ | Interface only; hard‑rejected. |
| In‑app delivery | ❌ | In taxonomy; no implementation. |
| Voice (Recall) | 🔩 | VAPI demo scaffolding; not a runtime channel. |
| Outcome / intent success | 🟡 | Per‑outcome engagement + intent; email‑only; no auto‑scheduled affinity. |
| ROI / conversion attribution | ❌ | The headline sales metric is unbuilt (🕮 pattern in legacy EventCorrelator). |
| Aggregated analytics | ❌ | Direct query bridge only; no read models/rollups. |
| Wizard CLI | ✅ | Planner/editor, device‑auth, manifest, verify. |
| Self‑serve activation E2E | 🟡 | Web/Next/React SDKs published + /wizard/activate page built; needs production E2E verification + remaining SDK targets. |
| Customer dashboard | 🟡 | Mostly live; a few live/mock leaks (Calibrating, getDetail, PR‑install fake). |
| Operator backoffice | ✅ | ~138 routes, complete; no RBAC tiers, no tests. |
| ML scoring / auto‑tuner | 🕮/🔩 | Legacy ML service (LSTM/Thompson/Prophet) orphaned; runtime auto‑tuner surface is a shell. |
| Deploy pipeline | 🟡 | Two coexisting per backend; doc drift on hosts. |
| Shared API contract | ❌ | Hand‑rolled parsers; no codegen; taxonomy duplicated. |
| Test coverage | 🟡 | Dense in runtime engine/wizard; ~zero in bff/backoffice/app‑auth/RLS. |
Headline verdict: the loop is closed for email, in both manual and autonomous modes, with real content generation and engagement feedback — materially further than the “nothing is delivered” snapshot. The remaining product‑defining gaps are (a) additional channels (SMS/in‑app/voice) and push‑engagement, (b) ROI/attribution + aggregated analytics, and (c) the self‑serve activation seams (web SDK + activation page) and production auth. Everything else is hygiene, security‑hardening, and reclaiming proven legacy patterns.
8. Gap analysis (vision − reality)
Grouped by severity relative to the product thesis and go‑to‑market.
G1 — Loop‑completing (block the core promise for non‑email channels & proof): - ROI/conversion attribution + aggregated analytics (FR‑OUT‑4, FR‑ANL‑1/2) — without these the product cannot show “recovered revenue,” its central claim. - Push‑engagement feedback (FR‑DLV‑4/FR‑OUT‑3) — an entire channel learns nothing. - SMS + in‑app delivery (FR‑DLV‑5/6).
G2 — Activation & trust (block self‑serve growth and safe scale): - Production E2E verification of the (already‑built) wizard→activate→first‑event flow + the remaining client SDK targets, RN/Swift/Node/Python/PHP (web/next/react + the activate page already exist) (FR‑ACT‑5). - App production auth / Supabase swap with real token validation (FR‑AUTH‑3). - Scoped API keys; remove dev auth bypass; backoffice RBAC; per‑capability internal secrets; RLS tests (FR‑AUTH‑4/5/6/7/8).
G3 — Integrity & correctness (mislead users/operators if unfixed):
- App live/mock leaks: Calibrating fake, getDetail() synthetic in live, PR‑install fake (FR‑DSH‑5).
- Dead public /v1/users/{id}/state score columns (FR‑ING‑6).
- Warm funnel handoff missing (FR‑MKT‑7).
- Landing waitlist likely 404s on the Cloudflare prod host; hidden Lovable backend dependency (FR‑MKT‑9).
G4 — Platform hygiene (cost & velocity tax): - Shared generated API contract; unify taxonomies; remove dead code (NFR‑MNT‑1/2/4). - One deploy pipeline per backend; CI gates on promotion (NFR‑DEP‑2/3). - Move config‑assistant to the durable queue; auto‑schedule affinity recompute (FR‑BO‑4, FR‑OUT‑3). - Prompt‑version lineage cleanup; finish/delete unwired derivation stages (FR‑ONB‑11, FR‑DRV‑5). - Move the BFF’s inline pseudo‑async LLM generation onto a durable queue/cron (FR‑ONB‑12, NFR‑REL‑2).
G5 — Reclaim from legacy (accelerators, not rebuilds):
- Content fact‑provenance/anti‑hallucination guardrail (FR‑CNT‑4).
- EventCorrelator outcome‑attribution model (FR‑OUT‑4).
- ShadowModeRunner / IMLProvider pattern to introduce real churn/timing ML safely (see §9).
- The AutonomousPipelineRunner five‑stage shape as the reference for the closed loop.
- The production‑no‑mock contract test as a CI governance pattern (it exists in legacy but is not wired into CI even there — adopt and wire it).
- The legacy JS + Python client SDKs (accelerate the remaining SDK matrix in M4) and the built‑but‑disabled auto‑tuner service (informs M5) — but not the mock provider‑send code (see §2.4/C2) nor the trust‑word content scorer (FR‑CNT‑4 caveat).
G6 — Commercial & compliance essentials (absent entirely, block go‑to‑market/legal): - No billing/entitlement/plan subsystem anywhere — cannot enforce the marketed tiers or meter usage (FR‑BIL‑1/2). - No end‑user data‑erasure path — a GDPR/CCPA baseline gap (NFR‑PRIV‑4). - No defined data‑retention windows for events / LLM logs / message bodies (NFR‑PRIV‑2).
9. Target architecture — the best version
The good news from the reverse‑engineering pass: the hard, easy‑to‑get‑wrong parts are already right. The on‑read stateless engine (P2), the no‑LLM‑in‑hot‑path discipline (P3), the deterministic ordered gates with statutory floors (P4), sim‑gated activation (P5), the transactional outbox (P6), and versioned config (P8) are a genuinely strong spine. The “best version” is therefore not a rewrite — it is (i) closing the act→measure loop across all channels, (ii) hardening the trust/activation seams, and (iii) unifying the seven parts behind shared contracts, while reclaiming the proven legacy patterns.
9.1 Principles to preserve (do not regress)
P1–P9 from §2.5 are the architectural commitments. The most important negative constraint: never reintroduce a persisted weighted‑state table (0051 killed it once for good reason) and never put an LLM in evaluate_user.
9.2 Cross‑cutting target changes
- One contract to bind them (NFR‑MNT‑1). Promote the runtime OpenAPI to the source of truth; codegen TypeScript clients for app/backoffice and validate the internal import/manifest contracts as shared schemas. This retires the hand‑rolled parsers and the silent‑drop failure mode, and makes the app↔bff↔runtime seams type‑safe.
- Identity & keys (NFR‑SEC‑1..5). Introduce API‑key scopes; complete the app Supabase auth with real JWT validation; add a read‑only/write operator tier + two‑person control; per‑capability internal secrets; ship the cross‑tenant RLS test.
- Close the loop for every channel. Push‑engagement normaliser →
delivery_message_events; SMS provider; in‑app inbox; auto‑scheduled affinity recompute — so learning is channel‑complete, not email‑only. - Measurement layer. An attribution service (reclaim
EventCorrelator: windowed, confidence‑scored,insufficient_data‑honest) + a read‑model/rollup layer feeding dashboard analytics and the recovered‑revenue report. - Warm activation path. Landing → signed context handoff → onboarding pre‑seed; publish the remaining client SDKs (web/next/react and the
/wizard/activatepage already exist — finish RN/Swift/Node/Python/PHP) and E2E‑verify the wizard flow so activation is fully self‑serve. - Safe ML reintroduction. Add churn/timing scoring behind the
ShadowModeRunner/IMLProviderpattern: run models in shadow, log divergence against the deterministic baseline, and only promote a model to influence decisions once it clears an offline gate — mirroring sim‑gated policy activation. ML augments scoring inputs; it never enters the deterministic gate ladder. - One deploy pipeline per backend + CI gates. Pick Azure or droplet per service; add test/lint gates before every promotion.
9.3 Target component diagram (unchanged topology, completed edges)
Landing ──signed context──▶ App (onboarding+dashboard, real Supabase auth)
│ shared OpenAPI-typed clients
BFF (onboarding) ──import(shared schema)──▶ Runtime (Go core)
├─ ingest → on-read decisioning (unchanged)
├─ content (LLM + fact-provenance)
├─ delivery: email✅ push✅+engagement · SMS+ · in-app+ · voice+
├─ ML shadow scoring (new, gated) ─┐
├─ attribution + read-models (new) ─┴─▶ dashboard analytics / ROI
└─ /wizard/* ──▶ Wizard CLI + @whisperr/* SDKs (new web/RN/…)
Backoffice (RBAC tiers) ──/backoffice/*──▶ Runtime
10. Roadmap — sequenced path to the best version
Milestones are dependency‑ordered; each has a goal, the requirements it closes, and an exit criterion. Sequencing rationale: fix integrity/contract foundations first (cheap, unblock everything), then complete the provable loop (attribution) because it unlocks the sales narrative, then broaden channels and self‑serve, then differentiate with ML/voice.
M0 — Foundations & integrity (weeks; low risk, high leverage)
- Goal: stop lying to users/operators; make the seams type‑safe.
- Closes: FR‑DSH‑5, FR‑ING‑6, FR‑MKT‑7, NFR‑MNT‑1/2/4, NFR‑DEP‑2/3, FR‑ONB‑11, FR‑DRV‑5, FR‑BO‑4.
- Exit: no live/mock leaks in app; shared OpenAPI + generated clients in app & backoffice; one deploy pipeline per backend with CI gates; dead code removed; warm handoff live.
M1 — Trust & tenancy hardening (security‑critical, parallelisable with M0)
- Goal: make multi‑tenant isolation and auth production‑grade.
- Closes: FR‑AUTH‑3/4/5/6/7/8, NFR‑SEC‑1..5, NFR‑PRIV‑1.
- Exit: app real Supabase auth; scoped keys enforced; no prod identity bypass (asserted); backoffice read/write tiers + two‑person operator control; cross‑tenant RLS test green; GitHub‑repo ingestion gated behind a consumer.
M2 — Prove it: attribution + analytics (unlocks the core sales claim)
- Goal: show recovered revenue / intervention effectiveness.
- Closes: FR‑OUT‑3/4, FR‑ANL‑1/2, FR‑DLV‑4 (push engagement), NFR‑OBS‑4.
- Depends on: M0 (contracts) for dashboard surfaces.
- Exit: push‑engagement loop closed; auto‑scheduled affinity; attribution service (windowed, confidence‑scored) + read‑model rollups feeding a working ROI report and the dashboard analytics tabs.
M3 — Complete the channels (breadth of the promise)
- Goal: deliver on every marketed channel.
- Closes: FR‑DLV‑5 (SMS), FR‑DLV‑6 (in‑app), FR‑DLV‑8 (BYOK email/SMS), FR‑CNT‑4 (fact‑provenance).
- Exit: SMS + in‑app delivery live with engagement feeding attribution; BYOK email/SMS per RFC 0001; content generator carries the provenance guardrail.
M4 — Self‑serve activation (growth)
- Goal: a customer can go from signup to first event with zero human help — mostly verifying and completing an already‑built path, not building it from scratch (the activate page + web/next/react SDKs already exist — C1).
- Closes: FR‑ACT‑5/7, FR‑DSH‑2 polish; publish the remaining SDK targets (RN/Swift/Node/Python/PHP; the legacy repo’s JS + Python client SDKs are accelerators — O6); add verify commands to the 7 command‑less playbooks.
- Depends on: M1 — the activate page already functions; M1 is needed for production‑credible auth on device approval, not for the page to exist.
- Exit:
npx @whisperr/wizardcompletes end‑to‑end on a Next.js/web repo against production, lighting up the dashboard, with the flow covered by an E2E test.
M5 — Intelligence & differentiation (moat)
- Goal: better decisions and a real voice channel.
- Closes: ML shadow scoring (reclaim
ShadowModeRunner), auto‑tuner (replace the shell), FR‑VOX‑1 (productise Recall). - Depends on: M2 (measurement to judge models), M3 (voice as a channel).
- Exit: at least one churn/timing model influencing scoring inputs only after passing an offline gate; Recall a real, engagement‑measured channel; “Alexa” vs “Recall” naming disambiguated.
M6 — Scale & compliance hardening (continuous)
- Goal: run it safely at volume.
- Closes: NFR‑PERF‑1..4, NFR‑PRIV‑2, NFR‑REL‑2, remaining NFR‑OBS/DEP items.
- Exit: read‑model isolation keeps analytics off hot tables; retention policies enforced; all async work on the durable queue; single observability + audit story.
11. Traceability & acceptance
11.1 Requirement → component ownership
| Area | Owning repo(s) |
|---|---|
| MKT | landing (+ app signup seam) |
| AUTH | bff (Supabase), runtime (keys/JWT/operator), app/backoffice (clients) |
| ONB | bff |
| DRV, ING, DEC, CNT, DLV, OUT, ANL | runtime |
| ACT | wizard, runtime (/wizard/*), app (/wizard/activate), new SDKs |
| DSH, ASI | app (+ runtime /dashboard/*) |
| BO | backoffice (+ runtime /backoffice/*) |
| VOX | runtime (channel) + app/bff (demo) |
11.2 Definition of “the best version” (acceptance)
The system is at its target when all of the following hold:
- A customer completes onboarding and, via
npx @whisperr/wizardalone, sends a first event that appears on a real‑auth dashboard. (M0/M1/M4) - An end‑user receives an intervention on any enabled channel (email, push, SMS, in‑app), content is on‑brand and provenance‑safe, and manual/autonomous modes both work. (M3)
- Engagement and intent‑success are captured for every channel and feed channel‑affinity learning automatically. (M2/M3)
- The dashboard shows recovered‑revenue / effectiveness backed by a confidence‑scored attribution service that returns
insufficient_datarather than fabricating. (M2) - Multi‑tenant isolation is enforced and tested; API keys are scoped; there is no production identity bypass; operators have tiered privileges with an exportable audit trail. (M1)
- All app↔bff↔runtime seams are governed by a shared, versioned, generated contract; there is one deploy pipeline per backend behind CI gates; no live surface shows mock data. (M0)
- The decisioning invariants (immutable log, on‑read stateless scoring, no‑LLM‑in‑hot‑path, ordered gates with statutory floors, sim‑gated activation) remain intact and CI‑enforced. (all milestones)
Appendix A — Component reference (one‑pagers)
- Landing (React 19/Vite 6, Cloudflare Pages): funnel, waitlist (double opt‑in + referral), churn calculators, ICP survey → PDF, Gemini‑Live voice qualifier, blog + OG pipeline, 12 pitch decks. Owns no product data; hands off cold to app signup. Hotspots:
App.tsxmonolith, pitch‑deck duplication, two waitlist systems, host doc drift. - App / whisperr‑watch (React 18/Vite 5/shadcn, Cloudflare Pages): onboarding Trilogy + dashboard + in‑app Claude assistant. Two API bases (bff, runtime). Auth is a localStorage mock (pending Supabase). Live/mock leaks: Calibrating,
getDetail(), PR‑install fake. - BFF / onboarding‑chat (Next.js 16, Supabase): 41 route handlers, LLM stage machine (intake→events→weights), deterministic fallbacks, manifest gen/validation, import handoff. 11k‑line orchestration file, zero tests, two deploy pipelines, prompt‑version drift, GitHub PR agent schema‑only.
- Runtime / whisperr‑go (Go 1.25, Postgres 80 tables, Redis): import → universe/policy derivation (LLM, sim‑gated) → ingestion → on‑read decisioning (A5/A6/A7, no LLM) → content (Anthropic) → delivery (email✅/push🟡/sms❌) → outcomes/learning (email‑only). 3 auth models, 11 job types,
/v1+/internal+/dashboard+/backoffice(~138) +/wizard. CI‑enforced architectural invariants. The strong spine of the system. - Backoffice / whisp‑admin (React 18/Vite 5, Cloudflare Pages): 63 frontend routes over exactly 138
/backoffice/*runtime API routes, revisioned engine/delivery controls with rollback, approvals, sims, jobs/dead‑letter, audit, config assistant, plus System/health and a global search. Complete but flat‑authz and untested. - Wizard (TS, Claude Agent SDK, npm): detect → device‑auth → manifest → git checkpoint → planner(Opus)/editor(Sonnet) wiring → deterministic verify. 8 playbooks all
available(only Flutter has a build‑verify command); the@whisperr/web/@whisperr/next/@whisperr/reactSDKs and the app/wizard/activatepage exist (remaining SDK targets: RN/Swift/Node/Python/PHP); gateway model (no shipped key). - user‑whisperer (LEGACY) (TS microservices + Python ML + Kong): the full intended loop (AutonomousPipelineRunner), content fact‑provenance, channel webhook→outcome plumbing (provider send code is mock scaffolding, not real integrations),
EventCorrelatorattribution, shadow‑mode ML, plus JS + Python client SDKs, a Vue admin‑portal, anops-console, and a built‑but‑disabled auto‑tuner service. Docs overclaim; code is fail‑honest. Reclaim patterns + the SDKs, not the orphaned/disabled services.
Appendix B — Corrections to prior/marketing documentation
- Delivery is built (email + push), not absent. The “nothing is delivered” snapshot predates the
delivery/messagegenwork. - Decisioning is on‑read & stateless. The persisted weighted‑state model was dropped (migration 0051); do not describe
app_runtime_intervention_stateas live. - Fatigue ceiling is an accrual sum (default 6), not a 0–1 “0.85” threshold.
- Hosting: landing/app/backoffice are on Cloudflare Pages (not Vercel); runtime has two coexisting pipelines.
- “Alexa” (landing sales‑qualifier voice) ≠ “Recall” (product retention voice). Different systems sharing Gemini/VAPI tech.
- The wizard flow is richer than
ARCHITECTURE.md(planner/editor split, plan review, deterministic diff scan, opportunities staging). - Self‑serve activation is more complete than assumed: the
/wizard/activatepage and the@whisperr/web/@whisperr/next/@whisperr/reactSDKs already exist (an 8th repo) — do not describe them as unbuilt. - The sold pricing tiers have no enforcing code: there is no billing/entitlement subsystem in any repo — the plans are marketing‑only today (FR‑BIL‑1).
Appendix C — Open decisions
- OD‑1: Autonomous‑by‑default vs. manual‑approval‑by‑default for new apps (safety vs. time‑to‑value).
- OD‑2: Which deploy target is authoritative for runtime and bff (Azure vs. droplet).
- OD‑3: Build vs. buy for SMS (Twilio) and in‑app inbox.
- OD‑4: Whether to productise Recall/voice now or defer past M3.
- OD‑5: ML ownership — reclaim the Python service behind a provider interface, or grow a Go‑native scorer.
- OD‑6: Retention windows for raw events / LLM logs / message bodies (compliance‑driven).
- OD‑7: Billing model & payment processor (Stripe?), and where entitlements are enforced (ingestion caps vs. dashboard gating) — the tiers are sold but unbuilt (FR‑BIL‑1).
- OD‑8: Erasure semantics — hard delete vs. anonymise‑in‑place — for end‑user PII across runtime + delivery stores (NFR‑PRIV‑4).
End of SRS v1.0. This document is reconstructed from source; verify any specific contract against the code before relying on it for implementation.