MVP Event Scope
Source of truth: ../simiaroom-cx-v5.md Event Dictionary.
MVP does not require every dictionary event on day one. Prioritize events that power funnel, retention, risk, fit, and light actions.
Initial integration slice — first 8–12 authoritative events
Before expanding across the full P0 target set, prove collection, reconciliation, state derivation, and reporting with this backend-first slice:
| Event | Producer of record |
|---|---|
authentication_succeeded / identity link |
Authentication backend |
assessment_completed |
Assessment backend |
therapist_selected |
Matching/selection backend |
booking_confirmed |
Booking backend |
booking_cancelled |
Booking backend |
payment_succeeded |
Payment verification backend |
payment_failed |
Payment verification backend |
session_completed |
Session backend / approved staff workflow |
session_no_show_recorded |
Session backend / approved staff workflow |
therapy_pause_declared |
Approved product/staff workflow |
treatment_completed |
Approved clinical/operational workflow |
internal_case_resolved |
CX Orbit operational store |
Expansion is allowed only after the slice meets the agreed completeness, duplicate, latency, and invalid-event targets.
Priority P0 target set — introduce incrementally
| ID | Event | Why |
|---|---|---|
| AUTH-01 / AUTH-04 | auth succeeded / identity link | Identity continuity |
| ASM-02 / ASM-04 / ASM-09 | assessment started / abandoned / completed | Funnel friction |
| THR-01 / THR-02 / THR-05 | options viewed / profile viewed / selected | Matching funnel |
| BKG-01 / BKG-05 / BKG-06 / BKG-S03 / BKG-S04 | booking start/submit/abandon/confirm/cancel | Booking outcomes |
| PAY-04 / PAY-06 / PAY-07 | payment started/succeeded/failed | Revenue + friction |
| SES-04 / SES-06 / SES-08 / SES-S01 | joined / completed / no-show / therapist attendance issue | Session spine |
| CON-04 / CON-S01 | pause declared / unplanned gap | Retention risk |
| CHG-01 / CHG-02 | therapist change requested / reason | Fit quality |
| CHR-02 / CHR-06 / CHR-S01–S03 | stop / churn reason / risk & churn states | Churn analytics |
| RET-01 / CON-07 | return / resumed therapy | Reactivation |
| FDB-02 / FDB-04 / FDB-07 | baseline / session feedback / dissatisfaction | Quality signals |
| INT-01 / INT-03 / INT-09 | case created / contact attempted / resolved | Action loop |
| SYS-01 | profile recomputed | Derived freshness |
Priority P1 — Month 2–3 if capacity
| Area | Events |
|---|---|
| Entry / GA | ENT-01…ENT-10 (attribution + entry drop-off) |
| Assessment detail | ASM-03, ASM-05–08, ASM-S* |
| Booking detail | BKG-02–04, BKG-07–08, BKG-S01–02 |
| Payment extras | PAY-01–03, PAY-08–09, PAY-S* |
| Feedback extras | FDB-01, FDB-03, FDB-05–06, FDB-S* |
| Continue | CON-01–03, CON-05–06 |
| Internal ops | INT-02, INT-04–08, INT-S01 |
Priority P2 — post-MVP / nice-to-have
Fine-grained UI clicks that do not change state (many ENT/GA micro-events), optional insurance/refund deep paths, advanced quality review events — keep schema-ready but do not block MVP screens.
Event envelope (canonical) — locked v1
Status: locked for SDK + collector (T0.4). Delivery, retries, and validation live in SDK-AND-DATA-INGESTION.md and must not contradict this section.
Every ingested event maps to envelope v1 below. SDKs never write storage; they only send this envelope to the collector.
Required sender fields
| Field | Type | Meaning |
|---|---|---|
event_id |
UUID string | Generated before first send; stable across all retries of the same payload |
canonical_type |
string | Dictionary event name (booking_confirmed, payment_succeeded, …). Wire alias: event_name (see compatibility) |
source |
string | Stable producer identifier (not a coarse channel enum) |
source_event_id |
string | Stable ID of this fact in the source system |
occurred_at |
ISO-8601 UTC | When the fact happened (…Z) |
identity |
object | Identity refs (anonymous_id, user_id, optional analytics session_id) |
entity |
object | Primary entity refs (type, id) |
properties |
object | Type-specific payload (allowlisted per event contract) |
Also required on the wire (already in the SDK contract): event_version, project_id, environment.
Compatibility with the SDK wire shape
Do not treat these as different events:
| Locked field | Existing SDK / sketch alias | Rule |
|---|---|---|
canonical_type |
event_name |
Same value. If both are present they must be identical. Collector stores canonical_type. Senders may send only event_name; collector copies it to canonical_type. |
identity.anonymous_id / identity.user_id |
top-level anonymous_id / user_id |
Nested identity is canonical. Flat aliases, if sent, must match. |
actor.type |
top-level actor_type |
Nested actor is canonical. |
entity + properties.*_id |
top-level booking_id / session_id / payment_id / therapist_id |
entity is the primary ref. Related IDs belong in properties and must not disagree with entity. |
source is a producer ID (simiaroom_backend, simiaroom_booking, cx_orbit_browser, porsline, import, cx_orbit_system, …). The older sketch enum browser\|backend\|ga\|… is a channel hint only; do not use it as the uniqueness key.
Identity refs
"identity": {
"anonymous_id": "string|null",
"user_id": "string|null",
"session_id": "string|null"
}
session_idhere is the analytics/web session, not the therapy session. Therapy sessions useentity.type = "session"and/orproperties.session_id.- Authoritative backend P0 outcomes require
user_id. - Browser behavioral events may send
anonymous_idwithuser_id: nulluntil identify/link. - At least one of
anonymous_idoruser_idis required except for approvedactor.type = systemevents that are not client-scoped (rare; must be in the event contract). - Browser consent, ID storage, and send-minimization:
BROWSER-CONSENT-POLICY.md(T1.14). Backend P0 events are not consent-gated.
actor (required): { "type": "user|system|staff|internal", "id": "string" }.
Entity refs
"entity": { "type": "booking|session|payment|assessment|client|therapist|case|identity_link|…", "id": "string" }
Related entities go in properties (e.g. therapist_id on a booking event). properties must not repeat a different ID for the same entity.type.
Collector-owned fields (never trusted from sender)
received_at, ingestion / request ID, authentication result, validation status, collector version.
Idempotency (locked)
Canonical idempotency key = project_id + source + source_event_id.
This is the DoD key source + source_event_id, scoped by project_id (envelope already includes project_id; MVP project is simiaroom).
source_event_id must uniquely identify one fact within that source. Do not reuse a bare entity PK across different event types (a booking can be confirmed and later cancelled). Prefer the source outcome row ID. If the source has no outcome ID and the type can fire at most once per entity, use {canonical_type}:{entity.id}.
Transport dedup (SDK retries of the same envelope, unchanged from the ingestion doc) = project_id + event_id. This does not replace the canonical key. Same event_id retry → one raw row. New event_id with the same canonical key → no second canonical fact (see duplicate behavior).
Duplicate / late / out-of-order
| Case | Behavior |
|---|---|
| Duplicate (same canonical key, compatible payload) | Accept; do not insert a second canonical fact. Response may flag duplicate. First accepted envelope remains the raw record. |
Duplicate conflict (same key, different canonical_type or material identity/entity/occurred_at/required properties) |
Quarantine as IDEMPOTENCY_CONFLICT. Do not overwrite the canonical fact. |
| Retry / batch resubmit | Same event_id and/or same canonical key must be safe. Replay of a batch must not double-count P0 outcomes. |
Late (received_at well after occurred_at, or after later facts already applied) |
Store on the canonical log using occurred_at as event time. Recompute derived state from history. Flag for data-quality if later than the late threshold (P0 operational default 24h; imports/CDC default 7d; still stored if older). |
| Out-of-order | Arrival order is not event order. Interpretation and derived state (Current Event + Current State + History) order by occurred_at then event_id. An earlier fact arriving after a later one is inserted into history and triggers recompute (SYS-01 path). Do not rewrite prior canonical payloads; correct via compensating events. |
Exactly-once delivery is out of scope. At-least-once + these keys is the contract.
Collector HTTP success/partial/reject shapes, retryable vs permanent codes, quarantine fields, and resubmit item codes (DUPLICATE_EVENT_ID, DUPLICATE_CANONICAL_KEY, IDEMPOTENCY_CONFLICT, EVENT_ID_REUSED) are locked in SDK-AND-DATA-INGESTION.md §11 (T1.13).
Timezone (locked)
- Store
occurred_at,sent_at, and collectorreceived_atas UTC (Z). - If the sender supplies offset/local time, convert to UTC; keep the sender zone in
context.timezonewhen known. properties.user_timezone/ slot local times are scheduling display fields, not the storage clock.- Display in CX Orbit UI and calendar-day metrics: Asia/Tehran (IANA; DST handled by the zone), unless a report explicitly says otherwise.
Locked envelope shape
{
"event_id": "0198a1c2-3b4d-7e8f-9a0b-1c2d3e4f5a6b",
"canonical_type": "booking_confirmed",
"event_name": "booking_confirmed",
"event_version": 1,
"project_id": "simiaroom",
"environment": "production",
"source": "simiaroom_backend",
"source_event_id": "bkg_evt_confirm_789",
"source_version": "3.18.0",
"occurred_at": "2026-08-03T10:20:30.123Z",
"sent_at": "2026-08-03T10:20:31.004Z",
"sdk": { "name": "@cx-orbit/node", "version": "0.2.0" },
"actor": { "type": "system", "id": "booking-service" },
"identity": {
"anonymous_id": null,
"user_id": "usr_123",
"session_id": null
},
"entity": { "type": "booking", "id": "bkg_789" },
"journey_ref": "J-04",
"context": {
"request_id": "req_123",
"timezone": "Asia/Tehran",
"locale": "fa"
},
"properties": {
"therapist_id": "thr_456",
"payment_id": "pay_456",
"slot_id": "slot_1001",
"session_number": 1,
"user_timezone": "Asia/Tehran",
"booking_context": "first_session",
"scheduled_start_utc": "2026-08-10T14:00:00Z"
}
}
P0 examples
Full JSON for three initial-slice types is in SDK-AND-DATA-INGESTION.md §7.6. Summary:
canonical_type |
Dict ID | source (example) |
source_event_id (example) |
entity |
Identity |
|---|---|---|---|---|---|
booking_confirmed |
BKG-S03 | simiaroom_backend |
bkg_evt_confirm_789 |
booking / bkg_789 |
user_id required |
payment_succeeded |
PAY-06 | simiaroom_backend |
pay_ver_456 |
payment / pay_456 |
user_id required |
session_completed |
SES-06 | simiaroom_backend |
ses_complete_321 |
session / ses_321 |
user_id required |
Producer-of-record rules
| Event class | Authoritative producer | Notes |
|---|---|---|
| Page, view, click, form exposure | Browser SDK / approved analytics source | Behavior and intent only |
| Booking/payment/session/refund outcomes | Simiaroom backend or reconciled source table | Browser success screens are not proof |
| Pause/completion/change outcomes | Backend or approved staff workflow | Must preserve actor and policy version |
| Feedback | Form/MyOutcomes source or backend receipt | Sensitive fields follow restricted access policy |
| Internal case/action | CX Orbit operational store | Emit analytical events after committed state change |
Every canonical event must have one producer of record. Duplicate observations from other sources may be retained as diagnostic events but must not create duplicate business facts.
State update rule
Current Event + Current State + Accumulated History + Global Flags → Interpretation → Next State / Action
Not every click changes state. Prefer PRODUCT/status events for stage transitions; use GA for conversion diagnostics.
Mapping checklist (engineering)
- Publish mapping table: Simiaroom source field → canonical event + properties —
SOURCE-EVENT-MAPPING.md(T1.3) - Define idempotency keys per source — locked:
project_id + source + source_event_id(T0.4) - Define late/duplicate event handling — locked in envelope section above (T0.4)
- Define timezone policy (store UTC, display Asia/Tehran) — locked (T0.4)
- Define which events trigger
SYS-01recompute — initial list inSOURCE-EVENT-MAPPING.md§ SYS-01 recompute triggers (T1.3) - Assign producer of record and owner to every P0 event —
SOURCE-EVENT-MAPPING.md+SOURCE-OWNERSHIP.md(T1.3) - Add event version, privacy class, required fields, and allowed enums — initial 12-event slice in
fixtures/events/contracts.yaml(T1.12) - Add schema fixtures to CI for backend and browser SDKs — backend 12-event slice:
docs/fixtures/events/,packages/shared/schemas/events/,pnpm --filter @cx-orbit/shared test(T1.12; browser SDK deferred T2.13) - Define retryable/non-retryable collector responses — locked T1.13 in
SDK-AND-DATA-INGESTION.md§11.5–§11.6 - Define reconciliation query for every authoritative P0 event — per-type queries in
SOURCE-EVENT-MAPPING.md§ Initial 12-event slice (T1.3); remaining authoritative rows include CDC target table