# Phase 3 — The flow atlas (verified)

**Depends on:** Phase 0, Phase 2 · **Blocks:** Phase 4 · **Size:** 2–4 sessions — the biggest phase

## Goal

Build **the one go-to place** that answers, for every flow in the product:

1. What is it, and who does it?
2. **Is it actually implemented — really, or only mocked?**
3. What screens and endpoints does it use?
4. **How do I test it, step by step, with which account?**
5. What's known to be broken or missing?

This is the phase where "verify the load-bearing claims" earns its keep. The existing
[manual-testing-plan.md](../../dev/post-phase/manual-testing-plan.md) is the closest predecessor and
it is already wrong about bring-up (it tells you to set `user-secrets`, which the code no longer
reads). Copying it forward would reproduce the problem this chain exists to fix.

**Run this phase in slices.** One session does `testing-setup.md` + 4–6 flows. Later sessions pick up
the next slice. The index table tracks which flows are done.

---

## Inputs

- [dev/post-phase/manual-testing-plan.md](../../dev/post-phase/manual-testing-plan.md) — flow
  walkthroughs, test accounts, the seeded world, the mock-vs-real map, the numbers the UI must respect
- [dev/post-phase/refinement/RUNBOOK.md](../../dev/post-phase/refinement/RUNBOOK.md) — bring-up, demo
  accounts, login round-trip, reset, troubleshooting
- [dev/shared-working-context/reports/mocks-registry.md](../../dev/shared-working-context/reports/mocks-registry.md)
- `docs/integration/` — from Phase 2, the endpoint truth
- `product/business/*` — the 14 requirement areas; what "correct" means
- The seeder code (`DemoWorldSeeder`, `DemoLifecycleSeeder`) — **the authority on test accounts and
  seeded state**, over any doc
- `client/src/app/[locale]/**` — 83 routes across `(customer)`, `(customer-focused)`, `nurse`,
  `admin`, `partner`, `(public-routes)`
- `client/src/services/*/` — 22 domains, each with a mock/real flag

## Outputs

```
docs/flows/
  index.md            the status table — every flow, one row
  testing-setup.md    bring-up, accounts, seeded world, OTP, reset, troubleshooting
  <flow>.md           one per flow (see the candidate list)
```

---

## Candidate flow list

Derived from the route groups, the 22 service domains, and the 14 business areas. Confirm and adjust
in the first session; the point is one file per *user-meaningful journey*, not per screen.

| # | Flow | Actor | Primary routes |
| --- | --- | --- | --- |
| 1 | `auth-login-otp` | all | `(public-routes)/login`, `select-role` |
| 2 | `public-front-door` | guest | `/`, `welcome`, `terms`, `privacy` |
| 3 | `onboarding-customer` | customer | onboarding, account |
| 4 | `care-circle-patients` | customer | patients / care-circle |
| 5 | `addresses-and-map` | customer | addresses, Neshan pin |
| 6 | `onboarding-nurse` | nurse | profile, bank account |
| 7 | `nurse-service-areas` | nurse | coverage, whole-city (`districtId = null`) |
| 8 | `nurse-verification` | nurse + admin | verification journey, document upload, review queue |
| 9 | `nurse-catalog-and-pricing` | nurse | services, variant builder |
| 10 | `search-and-discovery` | customer | C1–C3, filters, nurse profile |
| 11 | `booking-request` | customer + nurse | C4/C5, nurse inbox, countdown, accept/reject |
| 12 | `checkout-and-payment` | customer | C6, gateway return, confirmation, escrow |
| 13 | `bnpl-installments` | customer | D1–D5 |
| 14 | `booking-lifecycle-evv` | nurse + customer | check-in/out, two-stage clinical gate |
| 15 | `cancellation-and-refunds` | customer + admin | cancel, policy, refund settlement |
| 16 | `reviews` | customer | post-visit review, moderation |
| 17 | `patient-care-records` | nurse + customer | append-only records |
| 18 | `nurse-earnings-and-payouts` | nurse | earnings, weekly payout run |
| 19 | `messaging-tickets` | all | threads, `is_internal` boundary |
| 20 | `notifications` | all | bell, day-grouped list |
| 21 | `admin-backoffice` | admin | config, holidays, audit, RBAC, queues |
| 22 | `partner-center` | partner | the separately-scoped portal |
| 23 | `account-and-settings` | all | profile, theme/language (settings-only since iteration 1) |

---

## Steps

### 1. Write `testing-setup.md` — and **actually boot it**

Do not transcribe RUNBOOK.md. Follow it, note where it is wrong, and write what really happens.

Must cover:

- Prerequisites and the two-terminal run (`dotnet run` + `npm run dev`).
- **Configuration** — the current, correct story: config in `appsettings.*.json` and `.env.*`;
  `user-secrets` **is not used and is not read** (`<UserSecretsId>` was removed). The four crypto
  values still matter and must match whatever the target DB was encrypted under — booting with
  different `Seams:FieldEncryption` keys makes every phone lookup miss and every PII read throw
  `Padding is invalid`. Say where they live now.
- **Which database.** The dev config has pointed at a *remote* SQL Server. Confirm the current
  target, and give the local-DB alternative.
- **Test accounts** — read them out of the seeder, not the old doc. Note the phone-OTP admins
  (`09120000020` super_admin, `09120000021` finance) and what each demo account is set up to
  demonstrate.
- **Getting the OTP** — server console (`MOCK SMS — OTP code …`), `GET /api/v1/dev/last_otp/{phone}`,
  or the Telegram relay. Note the limits: 120 s resend window, 5 wrong attempts, 60 s validity, and
  that the OTP endpoints are IP rate-limited (scripted logins will 429).
- **The seeded world** — what `DemoLifecycleSeeder` builds (the 8-booking world), and the fact that
  **time-relative scenarios age out** and need a reseed.
- **Reset** — the drop-and-reseed procedure, verified.
- **Troubleshooting** — the failures you actually hit while doing the above.

### 2. Build the mock-vs-real map — from code

The single most important input to every flow file, and the thing most likely to be stale in the
docs. For each of the 22 client `services/` domains, read the code:

- Is the mock flag on or off?
- If real, does the whole domain hit the API, or only part of it?
- Server-side: which seams are mocked (SMS, payment gateway, BNPL provider, object storage,
  geocoder, search) and which have a real adapter available behind config (refinement-phase-8)?

Cross-check `mocks-registry.md` and **correct it** — Phase 0 already flagged that the registry has
had stale rows before. Put the result in `docs/flows/index.md` as a column, and in each flow file as
a header block.

Known suspects from the hardening audit (2026-07-16, never ticked off — verify each against current
code, do not assume): verification fully mocked while catalog/search are real; the refunds mock
reading a retired store; the BNPL wizard on a disconnected store; nurse earnings fabricated despite
live endpoints; `patientRecords` id-type mismatch.

### 3. Write one file per flow

Template — keep each under ~200 lines:

```markdown
# Flow — <name>

> Last verified: <date> against <commit>

**Actor(s):** · **Status:** built | partial | mocked | not started
**Business source:** product/business/NN-….md

## What it does
Two or three sentences. The user's intent, not the implementation.

## Screens
| Step | Route | Component/notes |

## API
| Call | Endpoint | Notes |
Link to docs/integration/domains/<domain>.md — don't restate shapes here.

## Rules that must hold
The load-bearing numbers and invariants, with their product/ source.
(e.g. commission 0.15, VAT 0.10 on commission only; forward-only status;
 escrow released after confirmed check-out; whole-city = districtId NULL)

## How to test
1. Log in as <account> (see testing-setup.md)
2. …
**Expect:** …

## Known gaps
- [BL-xxx] … (filled by Phase 4; leave a plain list here for now)
```

**Verification standard per flow.** For each, do at least the cheap check — trace the route → service
→ endpoint → handler and confirm the chain is real. For the money flows (11–15, 18) and auth (1), do
the expensive check too: run the walkthrough against a booted app. If you cannot run it, mark the
flow's status `UNVERIFIED` in the index and say why — an honest gap beats a confident guess.

### 4. Write `index.md`

The status table, and nothing else of substance:

| Flow | Actor | Status | Client | Server | Verified | File |
| --- | --- | --- | --- | --- | --- | --- |
| booking-request | customer + nurse | built | real | real | 2026-…-… | [link] |

Statuses: `built` (end-to-end real) · `partial` (real but with gaps) · `mocked` (UI real, data fake) ·
`not started`. `Client`/`Server` columns say `real`/`mock` independently — a flow can have a real UI
on a mocked service, which is exactly the trap the hardening audit found.

---

## Verification

- [x] `testing-setup.md` was executed, not transcribed — the API and the client were booted, all 8 demo
      accounts logged in over the real phone-OTP round-trip, and a production client build was run to settle
      the root-path question. 10 stale instructions in the predecessor docs were found and corrected.
- [x] Every flow file has a status backed by a code trace. The 7 money/auth flows were additionally walked
      against the running API by an adversarial second agent. **0 flows are `UNVERIFIED`.**
- [x] The mock-vs-real map was derived from code (22 domains, 153 seam operations; 26 server seams) and
      **17 disagreements with `mocks-registry.md` are logged** in `docs/flows/index.md`. That file now
      carries a banner pointing at the correction.
- [x] No flow file restates an API shape. 126 links into `docs/integration/` instead; the only JSON block
      anywhere is one `request_otp` response in `testing-setup.md`, used as a bring-up example.
- [x] The index accounts for all 22 client service domains, all 14 business areas and all 83 routes. Area
      13 (Tax, Invoicing & Legal) is flagged as the weakest coverage and is a Phase 4 item.
- [x] Every flow file carries a `Last verified:` line. 477 relative links checked, 0 broken.

## Definition of done

You can hand someone `docs/flows/testing-setup.md` and `docs/flows/index.md` and they can test the
product without asking you a single question — and without hitting an instruction that no longer works.

## Handoff

**Done 2026-08-02 against `c841bde`, in one session rather than the planned 2–4 slices.**

### What was produced

`docs/flows/` — 25 files, ~3 500 lines: `index.md`, `testing-setup.md`, and **all 23 flow files**. The
candidate list needed no adjustment; every one turned out to be a real, distinct journey.

**Status spread: 1 `built` · 15 `partial` · 7 `mocked` · 0 `not started` · 0 `UNVERIFIED`.**
Only `auth-login-otp` is end-to-end real and observed working.

### How it was verified

The stack was booted and stayed up throughout. Every flow got the cheap check (route → hook → seam →
endpoint → controller → handler, cited `file:line`). The 7 money/auth flows got the expensive check *twice*:
once by the author, then by an independent adversarial agent told to refute the status claim. That second
pass was worth its cost — it caught a fabricated test count, five wrong `file:line` refs, two factually
wrong gaps, a business-rule citation that asserted the **opposite** of the claim it was supporting, a
fabricated source for a money constant, a self-contradicting headline, and a test step that was false
against the running world. All were corrected in place.

Verification also **mutated the shared demo DB**: booking requests 21–24 were created and driven through
accept / reject / cancel / convert to prove those paths work. They carry fresh dates (2026-08-06…08-12) and
are the only non-aged requests in the world.

### The six findings that matter most

1. **The admin backoffice cannot reach its own server.** All 16 admin GET operations `403` for both seeded
   admin accounts. `DynamicPermissionService.CanAccess` grants on the literal role `"admin"` or a
   per-controller `DynamicPermission` claim; the demo admins hold `super_admin`/`finance`, and **no code
   anywhere writes that claim**. `USE_ADMIN_MOCK = true` hides it completely.
2. **34 of 153 client seam operations target routes that do not exist server-side** — they would `404` the
   day a flag is flipped. 12 more fabricate or client-derive a value. The flag is not the honesty signal.
3. **No card payment can complete from a browser.** `MockPaymentProvider` redirects to a non-existent host.
   The server money path is correct and idempotent; only the last hop is missing.
4. **VAT is computed two different ways on two surfaces** — carved out of the commission at checkout,
   added to it on the invoice — differing by 341 IRR on one seeded booking. No `product/` file resolves it.
5. **Booking deadline timestamps ship without a timezone** (`DateTime`, not `DateTimeOffset`), so every
   countdown the client renders is wrong by the UTC offset — 3.5 h in Tehran.
6. **The real BNPL rail is dead.** `SeedPaymentGatewaysAsync` seeds only a `Standard` gateway, never a
   `Bnpl` one, so `checkout_bnpl/initiate` returns `400 "No active BNPL gateway is configured."` for every
   request. The mocked UI hides it entirely.

Plus: the demo world is dated 2026-07-26 and has aged out (no `pending`/`accepted` request survived the
60-second expiry job); `/healthz/ready` is a genuine Windows-only code defect; and a re-login silently
invalidates every token previously issued to that account, so two people cannot share a demo phone.

### Input to Phase 4

**283 gaps** are recorded across the 23 files, each a plain bullet naming what breaks, for whom, and where
in the code. That list plus the 17 `mocks-registry.md` corrections is Phase 4's primary input. No `BL-###`
ids were invented — Phase 4 assigns them.

### What was left undone, and why

- **The local-Docker database path is documented but `UNVERIFIED`** — Docker is not installed on the
  verification machine. The remote path was fully exercised instead.
- **The reset/reseed procedure was not executed.** It requires dropping a database shared with the
  `balinyaar.ir` demo deployment; that is the owner's call, not a doc-writing side effect.
- **No screen was driven in a browser.** Everything client-side is a code trace plus HTTP-level probing of
  the dev and production servers. Where that limits a claim, the file says so.
- **Backend seam rows in `mocks-registry.md` were not re-audited** row-by-row; the current server picture is
  the seam table in `docs/flows/index.md` instead.
- `dev/post-phase/manual-testing-plan.md`, `dev/post-phase/refinement/RUNBOOK.md` and
  `dev/shared-working-context/reports/mocks-registry.md` now carry supersession banners pointing at
  `docs/flows/`. They were otherwise left intact, because `dev/` is history.
