ELA · END-TO-END FLOWS · v2 (updated 2026-08-13)

End-to-end flows, by stakeholder

How a live case moves across every party — who is contacted, in what order, with what SLA. v2 refreshes the v1 (June 2026) diagram against the shipped code: watch arming, emirate-based firm matching, the honest "ops handles directly" rung, no-verification trusted contacts, and the current UK-VPS residency reality. Reconciled 2026-08-13 against the shipping branch feat/ui-refresh — the fail-send pre-dispatch inversion, timeline-first active case, real KYC engines, the insurance ops portal, the 2026-08-12 reprice, and the watch as a co-equal mirror. v1 kept for reference →

The whole journey

Four phases — eligibility, activation, dispatch, resolution — with ops and the append-only audit ledger running across all of them. Arming works 24/7 with no human in the loop; ops act only on exceptions.

Client (phone / watch) System / ELA Ops Law firm Consulate Insurer

Master flow

flowchart TB
  subgraph P1["1 · Eligibility (straight-through)"]
    A1[Onboarding questions]-->A2["Capture passport + EID / employment letter"]
    A2-->A3{"On-device quality check — REAL pixel stats
(downscaled JPEG: sharpness / glare / brightness;
corners, MRZ, expiry deferred to the server)"} A3-->|"quality fail — retake"|A2 A3-->|pass|A4["Upload to ELA VPS (filesystem store)"] A4-->A5{"Server pre-screen — REAL OCR
(tesseract WASM, tesseract_wasm_fields_v1;
fail-safe — never auto-rejects)"} A5-->|"pass STP — fields extracted"|A6[Advance to payment] A5-->|"flag — fail-safe, never auto-rejects"|OPS1[("Ops exception queue
+ resubmission loop")] OPS1-->|human clears|A6 end subgraph P2["2 · Activation"] A6-->B1[Choose tier] B1-->B2{"Card (Stripe) / Tabby — no IAP"} B2-->|success|B3["Active · entitlement set (own Postgres table)"] B2-->|fail / cancel|B1 B3-->B4["Shield/Family: ops assigns policy number
30-day Profile Verification window"] end subgraph P3["3 · Emergency"] B3-->C0{"Arm from…"} C0-->|phone|C1["Hold to arm (3s)"] C0-->|Apple Watch|CW["Wrist hold to arm → phone-relay
(co-equal mirror surface;
independent direct path server-ready, unverified)"] CW-->C1 C1-->C2{"Pre-dispatch countdown — 30s
red TAP = 'Send help now'
expiry AUTO-DISPATCHES (deadman)"} C2-->|"slide to cancel — I'm safe"|CSD["Stood down — nothing was sent"] C2-->|"tap to send, or the count reaches zero"|C4["Dispatch fires
+ background location streams (realtime)"] end subgraph P4["4 · Dispatch and resolution"] C4-->D1["Cycle firms in member's emirate
(engine supports N; launch = 1/emirate)"] C4-->D2["Notify consulate(s) for nationality"] C4-->D3["Notify ALL entered trusted contacts"] D1-->D4{Accept within SLA?} D4-->|no|D5["Escalate email+SMS, cc ops
+ grace, then next firm"] D5-->D1 D4-->|none available / exhausted|OPD["Ops handles directly
member sees 'Our team is handling this directly'"] D4-->|yes|D6["Firm confirms client contact (2h)
member sees firm name + 'lawyer calls you'"] OPD-.->|ops can hand back|D1 D6-->D7{Shield/Family insured?} D7-->|yes + policy assigned|E1["Ops sends insurance pack
(gates: firm accepted + contact confirmed +
entitlement + policy — NOT the 30-day window)"] E1-->E2["Ops file FNOL claim with insurer
100k legal + 50k bond
(firm has no claim surface;
in-window filing forfeits the money-back guarantee)"] D7-->|no|E3["Case proceeds — identical dispatch
legal fees are not insured on this tier
(member self-pays)"] end C4-.->LED[("Notification ledger
append-only, ops-viewable
(member sight not wired)")] D2-.->LED D3-.->LED

Live case — timing

sequenceDiagram
  participant U as Client (phone / watch)
  participant S as ELA System
  participant F as Law firm (emirate)
  participant C as Consulate
  participant T as Trusted contacts
  participant O as Ops
  participant I as Insurer
  U->>S: Arm (3s hold) from phone or watch via phone-relay
  Note over U,S: Pre-dispatch is a 30s deadman — the red tap sends now, the count reaching zero SENDS, and the slide ("I'm safe") is the only stand-down
  Note over U,S: Watch Phase-2 wrist arms via phone-relay and mirrors the phone (ledger, two-way chat, settings) — direct path server-ready but hardware-unverified
  S->>F: Notify firm in member emirate (cycles until one accepts)
  S->>C: Notify consulate(s) for nationality (audited email)
  S->>T: Notify ALL entered trusted contacts (SMS/WA) no verification
  S-->>U: Live status timeline plus background location streaming
  Note over S,F: Firm SLA 12h in-week / 24h weekend or holiday plus grace
  alt Firm accepts in SLA
    F->>S: Accept then confirm client contact (2h)
    S-->>U: Lawyer assigned — your lawyer calls you
  else SLA breach or declines
    S->>F: Escalate email+SMS (cc ops) plus grace then next firm
  else None available or all exhausted
    S->>O: Ops handles the case directly
    S-->>U: Our team is handling this directly
  end
  opt Shield / Family insured (policy assigned — the 30-day window gates the money-back guarantee, not the pack)
    O->>F: Send insurance pack (policy + limits + contact)
    O->>I: Ops file FNOL claim (auditable email) — firm has no claim surface
  end
  Note over S,T: Pre-launch — firm and consulate and contact comms run in SIMULATE mode until providers and enable flags land
  

Member-facing sequence is server-authoritative: firm names appear only on a real server-confirmed accept (never invented); no_firm_available renders the honest ops-direct rung, not a fake "searching". Source: packages/cases, apps/api/src/server.ts dispatch handler, apps/mobile/src/services/activeCaseTimeline.ts.

Client & family buyer

The person who may be detained — and the family member who can buy on their behalf. The account holder completes document capture; coverage attaches to a verified identity. The arm can be triggered from the phone or the paired Apple Watch.

flowchart TB
  subgraph ON["Onboarding (11-step funnel)"]
    W[Welcome]-->HOW[How it works]
    HOW-->ACC["Account + OTP"]
    ACC-->PER["Identity: name, DOB, gender"]
    PER-->ADDR["Residence emirate"]
    ADDR-->PRI["Priors (sets insurance eligibility)"]
    PRI-->PLAN["Plan picker"]
    PLAN-->PAY{"Card (Stripe) / Tabby — no IAP"}
    PAY-->|success|PROT["Protection moment"]
    PAY-->|fail|PLAN
    PROT-->REH["Practice mode: 3s hold → 30s countdown
that SENDS a practice case → stand-down slide"] REH-->DOC["Documents offer: passport + EID or letter"] DOC-->CHK{"On-device quality check passes?
(real pixel stats: sharpness / glare / brightness;
a check it cannot honestly run defers to the server)"} CHK-->|retry|DOC CHK-->|"3 tries used"|ESC["Gallery upload or continue → ops"] CHK-->|pass|DONE2[Set up] ESC-->DONE2 end DONE2-->ACT["Active — full dispatch live immediately
(identical on every tier)"] ACT-->PVW["Profile Verification Window 30d
insurance live day 31 if verified + policy"] ACT-->ARM0{"Detained — arm from…"} ARM0-->|phone|ARM["Hold to arm (3s)"] ARM0-->|watch|ARMW["Wrist hold to arm → phone-relay"] ARMW-->ARM ARM-->HOLD{"Pre-dispatch — 30s deadman
red TAP 'Send help now' sends at once;
reaching zero SENDS"} HOLD-->|"slide to cancel — I'm safe"|SDWN["Stood down — nothing was sent"] HOLD-->|tap or expiry|DISP[Dispatch fires] DISP-->LOC["Background location streams (realtime)
until case-end or battery dies"] DISP-->TL["Active-case screen — SHIPPED: timeline-first
(server-fact rungs own the surface;
bottom-pinned capture, chat in a pushed modal)"] TL-->MSG["Two-way chat with ops coordinator
(mirrored on the watch), or ops phone backup"] DISP-->ENDC["'End case' header pill → sheet
(Face ID / Touch ID, PIN fallback)"]

Receives

Live on-device capture feedback; in-app access; policy number once assigned; during a case, the shipped screen is timeline-first — the server-authoritative status rungs own the surface, capture is pinned to the bottom and the full chat thread opens as a pushed modal — plus an ops phone backup. The same case (rungs, messages, states) mirrors on the watch.

Expected of them

Onboard honestly; keep passport/EID/visa current (app reminds + prompts re-upload via the resubmission loop). Choose a tier.

Pricing · 3 tiers

750 Essential · 3,750 Shield (centre-stage) · 7,500 Family Shield. AED/year, no trial (repriced 2026-08-12). Internally each adult is 750 service + 3,000 Sukoon premium; Family Shield prices two adults (1,500 service + 6,000 premium). Server-authored — the checkout price comes from the API, never the device. Paid via card (Stripe) / Tabby — not IAP (insurance carve-out 3.2.1(v)). 30-day money-back.

Failure handling

Junk doc → rejected in-app, resubmit. Doc later flagged → automated resubmit request (Direction-3 sheet); tier not revoked unless fraud. Payment fails → no access until completed.

Likely friction (pre-empt in messaging)

"Why annual, no trial?"

Committed life-safety product; the single arm is the moment of truth.

"Where does my passport live?"

On the ELA server, filesystem storage with custom HMAC-signed URLs, no third-party cloud OCR. Note (internal): the prod VPS is currently Linode London — the UAE-residency requirement was scrapped 2026-06-12; S3 me-central-1 remains pending infra, not deployed. Residency framing is a live decision.

"Arrested on day 3?"

Dispatch is active immediately at payment; only the insurance claim waits out the 30-day window.

"Can I arm from my watch?"

Yes — the Apple Watch arms via the phone (relay-primary), and it is a co-equal mirror, not a remote button: the wrist carries the arm face with server-authored gate states, the 30s deadman countdown with its hold-to-cancel stand-down, the case ledger (server-fact rungs), the full two-way case chat with dictation, a durable pending-verdict ledger that survives force-quit, and its own settings (hold length, haptics, practice). A phone-independent direct arm path is built server-side but not yet hardware-verified.

Trusted contact

Nominated during onboarding. No app, no account. An informational recipient and human rallying point — not a decision-maker. No verification: every entered contact is notified at arm-time; a mistyped number is a known, founder-accepted leak (entry alone is sufficient).

flowchart LR
  NOM["Nominated at onboarding (no verification step)"]-->IDLE["Stands by (no app)"]
  IDLE-->TRIG["Subscriber arms a case"]
  TRIG-->MSG["ALL entered contacts receive SMS / WhatsApp:
subscriber detained, firm + consulate engaged"] MSG-->DEL{Delivered?} DEL-->|yes|ACTC["May contact family or firm"] DEL-->|no|OPSC["Failure logged — OPS-visible (admin delivery states);
member sight of receipts not wired"]

Receives

An outbound alert per triggered case (dark "response arc" framing). Every message logged immutably.

Expected of them

Nothing contractual. An emergency informant, not on the hook to post bail.

No verification (by decision)

Contacts are NOT verified — notify all entered at arm-time. Founder accepts the mistyped-number risk; a life-safety alert should never be blocked on a verification handshake.

Failure handling

Bad number → delivery status recorded on the ops board. The member app does not yet surface delivery receipts (half-wired — see Edge cases).

Law firm

Selected by the cycling engine from firms covering the member's emirate (resolved from the case location → residence emirate → legacy Dubai default; no geocoder yet). The engine supports N firms; launch runs one active firm per emirate. SLA is computed from each firm's own configured working hours.

12h · inside working week 24h · weekend / holiday + escalation grace accept = take the case emirate-matched
flowchart TB
  A["Resolve member's emirate → pick a covering firm"]-->N["Firm notified via portal + client context"]
  N-->SLA{"Respond within SLA?
12h in-week / 24h weekend or holiday"} SLA-->|no|ESCF["Escalate: email + SMS, ops cc, + grace"] ESCF-->NX["Advance to next firm in emirate"] NX-->EX{Firms left?} EX-->|yes|N EX-->|none|OPD["Ops handles directly
member: 'Our team is handling this directly'"] SLA-->|accept|CONF{"Confirm client contact within 2h?"} CONF-->|no|CHASE["Ops chases, then re-dispatch"] CHASE-->NX CONF-->|withdraws|NX CONF-->|yes|WK["Work the case (member sees firm name + 'lawyer calls you')"] WK-->DONE{"Engagement ends how?"} DONE-->|"firm reports resolved"|VP["verification_pending
member pushed to confirm in-app"] DONE-->|"member ends the case (engaged firm)"|VP VP-->|"ops mark_verified"|CR["Closed — resolved (closure comms send now)"] VP-->|"ops close_case, unresolved_* reason"|CF["Closed — unresolved (closed_failed)"] WK-->T{Shield/Family insured?} T-->|yes + policy assigned|PK["Receive insurance pack from ops
(gate is accept + contact-confirm + policy — not the 30-day window)"] PK-->CL["Ops file the FNOL claim with the insurer
(100k legal + 50k bond; the firm has no claim surface)"] T-->|no|WORK["Uninsured case — the firm response is IDENTICAL
the member pays the legal fees themselves"] OPD-.->|ops can hand back|N

Receives

Case notification + client context via portal. On breach: escalation email+SMS (ops cc'd). For accepted insured cases: insurance pack (policy number, 100k/50k limits, claims contact).

Emirate matching

Firm pool is filtered to the member's emirate. No geocoder yet — the location rung passes null, so residence emirate decides (then legacy DXB). 7 per-emirate test firms live on prod.

Single-firm launch

The round-robin engine is real, but launch config is one firm per emirate. Copy simplifies the multi-firm promise; the failover ("finding another firm") screens are kept for when the pool grows.

Failure handling

Miss SLA → auto escalate + grace → next firm. None available → ops handles directly (not a dead end — ops can resolve, or hand back to a firm).

Consulate

Notified automatically when a national triggers a case. View-only. No portal, no required action — awareness and consular duty-of-care.

flowchart TB
  TRIG[Case triggers]-->NAT["For EACH nationality on file
(unbounded multi-select — no primary/second structure)"] NAT-->Q1{Consulate on file for it?} Q1-->|yes|E1["Auditable email: detained, being assisted"] Q1-->|no|NF1["Ops missing-consulate alert (labelled)
(no 'nearest consulate' fallback in prod — that branch is dead code)"] E1-->DEL{Delivered?} DEL-->|yes|STORE["Stored: body + time + message-id (ops-viewable)"] DEL-->|bounce|RETRY["Ops alert + retry + alternate contact"]

Receives

A single auditable email. Full body + timestamp + delivery status + message-ID persisted as evidence.

Expected of them

None enforced by ELA. Strictly informational; ELA does not direct the consulate.

Pre-launch

The consulate email path runs in SIMULATE mode until the email provider + enable flag land — audit rows read "simulated", never "delivered".

Failure handling

No consulate on file for a nationality → the case holds with an ops missing-consulate alert, labelled in the audit trail. (A "nearest consulate" fallback exists in the library but production deliberately does not supply it.)

Insurance company

Provides a policy number per insured subscriber; covers legal costs and bond up to 150,000 AED. Underwriter Sukoon + reinsurer Howden (names feed the admin underwriter config; app copy stays generic). The insurer-side policy-number handoff (API vs manual) is still being defined — but the ELA-side chain is complete and operable from the portal: policy-number assignment and the full FNOL claim console (file / submit / status, forward-only) shipped in the admin portal.

flowchart TB
  SU["Shield / Family Shield purchase"]-->WIN["30-day Profile Verification Window
STARTS AT PAYMENT (not policy issuance)"] SU-->SEND["Ops sends client details + payment"] SEND-->ISSUE["Insurer issues policy number"] ISSUE-->ASSIGN["Ops assigns policy number in ELA
(independent prerequisite — does NOT start the window)"] WIN-->GATE{"Coverage attaches when ALL THREE hold:
day 31 reached · docs verified by ops · policy assigned"} ASSIGN-->GATE GATE-->|all three|COV["Insurance attached"] COV-->CASE["Live insured case"] CASE-->CLAIM["OPS file FNOL claim (auditable email to insurer)
firm has no claim surface"] CASE-->|claim filed inside the window|MBG["Allowed — but forfeits the
30-day money-back guarantee (not blocked)"] MBG-->CLAIM CLAIM-->ADJ["Adjudicate: up to 100k legal + 50k bond"] ADJ-->OVER{Bond over 50k?} OVER-->|yes|CLIENT["Client pays the overage"] OVER-->|no|DONE[Covered]

Provides

Policy number per insured subscriber; processes claims up to 100k legal + 50k bond (150k total — confirm per-incident vs aggregate).

What the window actually gates

The 30-day window (running from payment) gates coverage attachment (day-31 rule) and the money-back guarantee — an in-window claim files normally but forfeits the refund. Claims are not hard-blocked; an excluded verdict still files the FNOL for insurer dispute review.

Open / pinned

How the policy number flows back (API vs manual); who collects bond overage above 50k; required client fields; per-incident vs aggregate. Also open: the internal premium split now assumes 3,000 AED per adult to Sukoon — reconcile against the underwriter paperwork (the code previously carried 1,500/5,250, which may never have matched the real arrangement).

Partners

Sukoon (insurer/underwriter) + Howden (reinsurer). Member-facing copy stays generic; the names live in the admin underwriter config only.

Insurance eligibility & Family Shield

Priors gate insurance, and Family Shield resolves every insured before charging — so an ineligible person is excluded, never refunded. (Verified against shipped code 2026-08-06 — priors gate, essential-only rendering, roster limits, flat-7000 policy, invite handshake, and the day-31 rule all traced to apps/api/src/server.ts / packages/cases.)

flowchart TB
  START["Buyer picks a plan"]-->SOLO{Solo or Family Shield?}
  SOLO-->|solo|P1["Priors knockout (self)"]
  P1-->E1{Recorded prior?}
  E1-->|recorded|NOTE["Essential — same full response,
legal fees uninsured; framed as a match"] E1-->|dismissed or clean|PAYS["Charge Shield at checkout"] SOLO-->|family|ROSTER["Roster: 2 adults + 2 dependants
Family Shield = flat 7,500, covers up to 4 eligible"] ROSTER-->MIN["Guardian declares priors for minors"] ROSTER-->SELF["Primary self-attests"] MIN-->PAYF["Charge flat 7,500 at checkout
(do not wait on a slow 2nd adult)"] SELF-->PAYF NOTE-->PAYE["Charge Essential"] PAYS-->ACT["Active — full dispatch immediately"] PAYF-->ACT PAYF-->|invite 2nd adult|ADULT2 PAYE-->ACTE["Active — full dispatch, uninsured
no insurance, no verification window"] ACT-->WIN["Profile Verification Window 30d
insurance attaches day 31 if verified + policy"] ADULT2["2nd adult self-attests later
via invite handshake (own PDPL consent)"]-->L{Eligible?} L-->|dismissed or clean|ACTV["Insurance activates for them
no extra charge (flat), no blocking"] L-->|recorded|NOTE2["Stays uninsured — dispatch unchanged
flat price unchanged, nothing to refund"] ACTV-->WIN

Why no refunds

Eligibility resolves before the charge, and Family Shield is a flat 7,500 covering up to 4 eligible — a recorded-prior member still gets the identical emergency response, just uninsured legal fees, at no price change, so there's nothing to refund. No auth-hold; charge once at checkout.

PDPL (per-adult)

Each adult self-attests their own priors via an invite handshake with their own consent. The primary may declare for minors only.

Framing

Ineligible = routed to Essential as a match, never a rejection — the shipped words are "Full emergency response — legal fees are not insured on this tier." No "criminal / denied" wording on-screen, and never "notification-only" (a banned phrase: dispatch is identical on every tier).

Needs counsel

CBUAE product class (free-look), per-member activation, PDPL cross-border. No PCC for the dismissed exception.

Insurance status workflow

Insurance status is tracked per named member and — critically — is recomputed statelessly on every read from the entitlement, priors, attestation, KYC, window, and policy facts: there is no persisted per-member state machine to drift. (REBUILT 2026-08-06 from the shipped enum — packages/api-types/src/index.ts:1269 — and the attachment gate planCoverageAttachmentGate, packages/cases/src/index.ts:3760. The v1 tab this replaces used state names and transitions that never shipped.)

flowchart TB
  READ["Compute member's insurance status (on read)"]-->ENT{"Entitlement active?
(Shield / Family Shield)"} ENT-->|"lapsed / refunded"|BLOCKED["blocked_entitlement
(not a persisted terminal — recomputed;
re-activating the entitlement restores the chain)"] ENT-->|"Essential / none"|NR["not_required
Essential — full dispatch, no insurance product"] ENT-->|active insured tier|PRIORS{"Priors attestation for THIS member?"} PRIORS-->|"not yet attested — invited adult"|PSA["pending_self_attestation"] PRIORS-->|recorded prior|NO["notification_only — a MACHINE enum, never member copy
dispatch unchanged, legal fees uninsured
flat price unchanged, no refund path"] PRIORS-->|"clean or dismissed"|KYC{"KYC verdict?"} KYC-->|"rejected — fraudulent / invalid docs"|VOID["void_documents_invalid
(the ONLY void trigger — narrower than 'misrepresentation')"] KYC-->|"ok / pending"|GATE1{"Day 31 reached?
(window runs from PAYMENT)"} GATE1-->|no|PPW["pending_profile_window"] GATE1-->|yes|GATE2{"Docs verified by ops?"} GATE2-->|no|PDV["pending_documents_verification"] GATE2-->|yes|GATE3{"Policy number assigned?"} GATE3-->|no|PPA["pending_policy_assignment"] GATE3-->|yes|ATT["attached
coverage live (day31_docs_verified_policy_assigned)"]

Claim status machine (per filed claim — forward-only, ops-driven)

flowchart LR
  F["filed
(ops file FNOL — gates: case owned,
active insured entitlement, policy matches)"]-->S["submitted_to_insurer
(auditable email to underwriter FNOL address)"] S-->A["acknowledged"] A-->SET["settled
(note required)"] A-->DEN["denied
(note required)"] F-->W["withdrawn"] S-->W

Family Shield

notification_only — an internal wire enum, never words a member sees — can apply to one adult (recorded prior) while the flat 7,500 subscription stays active and every other eligible member proceeds toward attached. That adult still gets the identical dispatch; only their legal fees are uninsured.

Solo knockout

A solo user with a recorded prior never reaches an insured tier — Essential is the only selectable plan, so their status reads not_required.

Window ≠ claim block

An in-window claim is allowed — it forfeits the 30-day money-back guarantee instead of being blocked. Note: even the prior-exclusion check records claim activity against the guarantee ledger (flagged for product review).

No "cancelled" state

There is no persisted cancelled/removed terminal: a lapsed or refunded entitlement simply recomputes to blocked_entitlement, and no member-removal path exists.

ELA ops team (PH) & UAE supervisor

Always-on operators in the admin portal. They never block the happy path — STP auto-passes clean docs. They handle exceptions, monitor dispatch, run insurance setup, and are the direct handler when no firm is available.

flowchart TB
  subgraph KYC["KYC exceptions (async, never blocks STP)"]
    K1[Flagged doc arrives]-->K2["Three-pane: queue / viewer / decision"]
    K2-->K3{Decision}
    K3-->|approve|K4[Cleared]
    K3-->|resubmit|K5["Automated resubmit request (sheet)
membership → verification_pending"] K3-->|"decline — mandatory reason"|K6["Membership paused-for-review
payment stays refundable (MBG)"] end subgraph DIS["Dispatch monitor (UAE 06:00-21:00)"] M1["Live SLA traffic-light countdowns"]-->M2{Breach / exhausted / none?} M2-->|breach|M3["Auto-escalate (SLA tick — NOT enabled on prod yet)
+ manual re-notify / force-escalate"] M2-->|none available|M4["OPS-DIRECT: handle the case; can hand back to a firm"] M4-->M5["Member↔coordinator chat + ops phone"] end subgraph INSU["Insurance ops (portal UI SHIPPED — assign policy + claims console)"] N1["Assign policy number at /coverage/assign
(preview → verbatim read-back ack → commit)"]-->N2["Attachment prerequisite
(window runs from PAYMENT — assignment does not start it)"] N3["Firm accepted + confirmed"]-->N4["Send insurance pack"] N4-->N5["Claims console /coverage/claims
file / submit / status, note required to settle or deny
MBG-forfeit warning shown BEFORE an in-window filing"] end subgraph AUD["Consulate audit"] A1["Append-only: email + time + message-id"]-->A2["Exportable evidence"] end K5 ~~~ M1 M5 ~~~ N1 N4 ~~~ A1

Sees

KYC three-pane viewer; dispatch timelines + traffic-light SLAs (dual UAE/PH clock); consulate audit (append-only, exportable); member 360; audit log; member↔coordinator case chat.

Does

Clear doc exceptions (never reject on OCR alone); monitor + escalate; handle ops-direct cases when no firm is available; assign policy number in the portal (attachment prerequisite); send insurance pack; file and track FNOL claims in the claims console; maintain directories (firm hours, holidays).

Reviewer / canary sandbox

An allowlisted reviewer/canary phone (ELA_REVIEW_PHONE_ALLOWLIST) may open a case even under the production guard — force-routed through the safe sandbox outbox, so NO real firm/consulate is paged (the hourly dispatch canary uses this).

Hours — 24/7 vs desk

The system runs 24/7: arming instantly notifies firms, consulate, and contacts and starts location — no human needed. The desk is staffed UAE 06:00–21:00; only human actions (ops-direct takeover, doc review, insurance-pack send) wait for desk hours.

Edge cases & open gaps

The v1 P0/P1 cluster was resolved in the 2026-05-26 grill and much of it has since shipped. This tab tracks what's done and what's genuinely still open as of v2.

Shipped since v1

✓Ops-direct honesty rung. no_firm_available renders "Our team is handling this directly" — no fake firm search. Ops-direct is not a dead end (may resolve, or hand back to a firm).
✓Emirate firm matching. Firm pool filtered by residence emirate (location rung stubbed null; no geocoder). 7 per-emirate test firms live.
✓Watch Phase-2 arming. Wrist arms via phone-relay; independent direct path + fail-closed jti registry deployed (security-reviewed). WATCH-015 hardware evidence still pending.
✓Trusted contacts — no verification. Notify all entered at arm-time; founder accepts the mistyped-number leak.
✓Member↔coordinator case chat shipped with durable Postgres write-through — and mirrored on the wrist as a full two-way thread.
✓Timeline-first active case shipped. The founder-approved revert off chat-first (Concept C) landed and was re-validated by the 2026-08-08 research: server-fact rungs own the surface, capture is bottom-pinned, the thread opens as a pushed modal, and ProgressSheet/StatusStrip are deleted.
✓Pre-dispatch fail-send inversion (founder law, 2026-08-08). The red button is a TAP — "Send help now"; the countdown is a 30-second deadman that dispatches at zero; the deliberate slide is "Slide to cancel — I'm safe" and is the only stand-down. The watch mirrors the same law. Never re-document this as expire-cancels.
✓Real KYC engines. Server pre-screen runs tesseract WASM OCR (tesseract_wasm_fields_v1, offline langPath, fail-safe to ops review); the on-device precheck runs real pixel statistics on a downscaled JPEG. Receipts name only engines that actually ran — the fixture era is over.
✓Insurance ops portal. Policy-number assignment (/coverage/assign, preview → read-back ack → commit) and the claims console (/coverage/claims) shipped; the dead member-lookup claims link now lands on the console.
✓Watch is a co-equal mirror. Arm face with server-authored gate copy, the 30s deadman countdown + hold-to-cancel, the case ledger from server-fact timelines, the full two-way chat with dictation, a durable pending-verdict ledger that survives force-quit, and its own settings. Ending a case is deliberately absent (it needs biometric/PIN the wrist cannot do).
✓Reprice (2026-08-12). Essential 750 · Shield 3,750 · Family Shield 7,500 AED/year, server-authored from annualTierComponentPricesMinor; component split ratified the same day at 750 service + 3,000 Sukoon premium per adult.
✓Realtime split-brain fix + durable case/session resilience (force-kill survival, cold-start restore).
✓Money path — Stripe/Tabby webhooks → own Postgres entitlement table (RevenueCat dropped); refund-tombstone gate closed.

Still open

LAUNCHComms are dormant. Firm/consulate/contact outbound runs in SIMULATE mode until provider creds (Twilio SMS/WA, email) + enable flags land + public Caddy carve-out for signed links. This is the top launch gate for the dispatch story.
OPSMonitoring not fully wired. Dead-man + dispatch-failure crons exist but need /etc/ela/monitoring.env (in progress); no error/telemetry sink yet (PostHog/Sentry undeployed — box RAM). See docs/strategy/self-validating-loop.md.
DRBackups — offsite + key custody. R2 offsite parked; backup + decryption-key hardening pending. Pre-launch blocker (details in internal ops notes).
PINInsurance-setup handoff — partner API vs manual, policy issuance + return of the policy number. Ops-mediated entry is the v1 placeholder.
BIZSingle-firm launch (1 firm/emirate; 3–5 hoped) · bond-overage collector (>50k) · VAT exemption on the insurance portion · ops vendor + test-recipient swap (prod ops details are currently the founder's).
WATCHWATCH-015 paired-hardware evidence (on-device receive/rotate/tombstone; direct-arm against a live registry). Independent arming is not yet phone-independent-verified on real hardware.
DECData residency framing — prod VPS is Linode London; the UAE-residency requirement was scrapped 2026-06-12 and S3 me-central-1 is pending-not-deployed. Member-facing residency copy needs a decision.

Added by the 2026-08-06 code audit

LAUNCHProduction-runtime dispatch is a code change away. Under ELA_RUNTIME_MODE=production every non-reviewer dispatch 503s behind a deliberate fail-closed guard (dispatch_persistence_not_configured); only the reviewer/canary allowlist reaches the sandbox outbox. Correct fail-closed behaviour — but the go-live sequence must be providers → drain proven → SLA tick → guard removal → runtime flip, or the flip bricks both login and dispatch.
✓KYC honesty closed. The 2026-08-06 finding — fixture engines wearing real names (fast_opencv/ml_kit/paddleocr labels for code that never ran) — is fixed. Real tesseract WASM OCR server-side, real pixel statistics on-device, truthful engine labels and timestamps, checks that cannot honestly run recorded not_run and deferred to the server. Still fail-safe: OCR never auto-rejects, it only routes to ops.
✓Both dormant case states are now produced (2026-08-06 slice). verification_pending is entered automatically when a case with an ENGAGED firm ends — member slide-to-end (no immediate close; family page stands down, closure comms defer) or firm report-resolved — and "Mark verified" closes it resolved. closed_failed ("CLOSED — UNRESOLVED") is produced by close_case with the new unresolved_* reason family; stakeholder closure notices ride the final close with honest unresolved copy.
✓Insurance ops portal shipped. The 2026-08-06 "curl-only" finding is closed: policy-number assignment and the full FNOL claim flow are live routed surfaces, and the dead claims link now lands on the console. (Unrelated Member 360 no-op buttons — billing modal, document view — remain on the smaller-fix list.)
MEMBERDelivery receipts never reach the member. The member case-status contract carries no notification rows; stakeholder rows are locally seeded "queued" and only the firm row is re-derived — copy promising the member sight of consulate/contact delivery cannot be honoured yet (diagram corrected to ops-only).
PINSukoon premium split needs reconciling. The code now assumes 3,000 AED premium per insured adult (up from the historical 1,500/5,250 in the constant, which per the founder may never have matched the real arrangement). Reconcile against the admin underwriter config and the Sukoon paperwork before launch.

Doc provenance

✓Reconciled 2026-08-13 against the shipping worktree feat/ui-refresh (not main). Eight drift items corrected: pre-dispatch semantics (inverted in the prior revision), timeline-first active case, the banned "notification-only" phrasing, the 2026-08-12 reprice, KYC engine honesty, the insurance ops portal, end-case copy, and watch scope.