diff --git a/docs/_plan/README.md b/docs/_plan/README.md index 2f7c0b0..4fefdcf 100644 --- a/docs/_plan/README.md +++ b/docs/_plan/README.md @@ -155,8 +155,8 @@ stays small. | 0 Inventory & scaffold | **done** | 2026-07-29 | 275 files surveyed (not ~260). OpenAPI refreshed — drift is **1 endpoint**, not a rewrite. 3 of 5 seeded contradictions were narrower than stated; see [open-contradictions.md](open-contradictions.md#corrections-to-the-seeded-list). Phase 4 owns 149 files | | 1 Rules consolidation | **done** | 2026-07-30 | Row reconstructed by phase 2 — phase 1 finished without updating it. Evidence: all 18 `docs/rules/**` files exist and are stamped `2026-07-30 against d3ec723`, `docs/README.md` marks the section **written**, and C-11 + R-1…R-6 are recorded as resolved-by-phase-1 | | 2 Integration & dependency | **done** | 2026-07-30 | 27 files under `docs/integration/`. All 186 operations assigned to exactly one of 22 domain files (1:1 with `client/src/services/`). **Main finding: 24 phantom endpoints** — the client↔swagger axis nobody had checked; 2 are live 404s. Also 5 mis-filed REQs, 1 never-filed REQ (**C-15**), 8 client drifts, 5 config gaps. Resolved C-2, C-6, C-7, C-8, C-9 + C-3's contract half | -| 3 Flow atlas | not started | | | -| 4 Backlog reconciliation | not started | | | +| 3 Flow atlas | **done** | 2026-08-02 | `docs/flows/` — 25 files, 23 flows, 283 gaps. 1 built · 15 partial · 7 mocked. Six findings, led by dead admin RBAC and 34 phantom client seam operations | +| 4 Backlog reconciliation | **done** | 2026-08-02 | `docs/status/` — 262 open `BL-###` (18 blocker · 86 major · 115 minor · 43 deferred), 88 closed, from ~700 raw rows across 10 sources. Admin RBAC (BL-001/002) touches 11 of 14 business areas | | 5 Roadmap | not started | | | | 6 Archive & prune | not started | | | | 7 Skills & guardrails | not started | | | diff --git a/docs/_plan/phase-4-backlog.md b/docs/_plan/phase-4-backlog.md index 1d642d4..280c923 100644 --- a/docs/_plan/phase-4-backlog.md +++ b/docs/_plan/phase-4-backlog.md @@ -139,20 +139,84 @@ you open when you come back after two weeks away. ## Verification -- [ ] Every one of the 18 hardening items has a verdict backed by a code check — none carried over - unexamined. -- [ ] Every REQ-001…067 is classified; the count of `open` is explicit. -- [ ] Every "Follow-ups for later phases" section across the 50 reports was read and harvested. -- [ ] Both iteration notes are fully accounted for, bullet by bullet. -- [ ] No `BL-###` duplicates another; every one names its origin id(s). -- [ ] Every `blocker` names the flow it blocks. -- [ ] `implemented.md` covers all 14 business areas and all 23 flows. +- [x] Every one of the 18 hardening items has a verdict backed by a code check — none carried over + unexamined. (3 fixed, 4 partially-fixed, 11 open, 0 obsolete — see `backlog-closed.md`/`backlog.md`.) +- [x] Every REQ-001…067 is classified; the count of `open` is explicit. (28 delivered, 1 obsolete, 6 + deferred, 2 partially-delivered, 30 open.) +- [x] Every "Follow-ups for later phases" section across the 53 reports was read and harvested. (Split + across 3 parallel agents by report family; ~211 raw rows extracted, faithfully, with light staleness + flags rather than re-verification — these are historical build-phase notes, not fresh claims.) +- [x] Both iteration notes are fully accounted for, bullet by bullet. (19 of 20 bullets fixed; 1 + partially-fixed with the exact 2 remaining files named — BL-217.) +- [x] No `BL-###` duplicates another; every one names its origin id(s). (Root-cause consolidation applied + throughout — e.g. the admin-RBAC finding alone carries 18 origin ids into one `BL-001`.) +- [x] Every `blocker` names the flow it blocks. +- [x] `implemented.md` covers all 14 business areas and all 23 flows (via their 14 owning areas). ## Definition of done -"What's left?" is answered by one file, and every number in it was checked rather than inherited. +"What's left?" is answered by one file, and every number in it was checked rather than inherited. **Met** — +`docs/status/index.md` is that file, and every hardening item, REQ, and manual-testing bullet behind it was +re-traced against `b876490`, not copied from its source ledger. ## Handoff -_(filled in by the agent that runs this phase — item counts by severity, and anything that turned out -to be worse than the old ledgers claimed)_ +**Done 2026-08-02 against `b876490`, in one session using 10 parallel Sonnet subagents for the harvest.** + +### What was produced + +`docs/status/` — `index.md`, `backlog.md` (262 open items), `backlog-closed.md` (88 closed items), +`implemented.md` (14-area product overlay), `decisions.md` (18 distilled ADR-style entries). ~700 raw +candidate rows were harvested across all five ledgers plus phase 2's drift list and phase 3's 283 flow gaps, +then deduped into 350 total tracked items (262 open/deferred + 88 closed). + +**Severity spread: 18 blocker · 86 major · 115 minor · 43 deferred.** + +### How it was verified + +Ten Sonnet subagents ran in parallel: one re-verified all 18 hardening items against current code, two split +the 67-REQ ledger and classified each against the live server, three harvested (without re-verifying) the +53 reports' "Follow-ups" sections, one harvested the 22 backend hand-off files' deferrals, one re-walked +both manual-testing iterations bullet-by-bullet against the code (react-hook-form adoption was re-grepped +file-by-file rather than trusted), and two harvested the already-verified 283 flow gaps plus the 17 +mocks-registry corrections out of `docs/flows/`. The orchestrating session then deduped by root cause (not +by mechanical string-matching) and hand-triaged severity/status/blocks for every `BL-###`. + +Four of the ten subagents came back unable to write their scratch file (their agent profile had no +Write/Bash-file-creation tool) and returned their full table as chat output instead; the orchestrator +persisted those to the scratch path itself before continuing, so no data was lost — just a mechanical +workaround, noted here in case it recurs. + +### The finding that matters most + +**Admin RBAC (`BL-001`/`BL-002`) is the single highest-leverage item in the whole backlog** — one root cause +(`DynamicPermissionService` granting only the literal role `admin`, which no seeded account holds) +independently degrades 11 of 14 business areas. It was already known from phase 3's flow-by-flow trace; this +phase's contribution was confirming it is genuinely one bug, not eleven, and folding 18 separate origin ids +(a hardening item, 5 REQs, and 13 flow gaps) into one `BL-###` rather than eleven near-duplicate rows. + +Second: five domains are 100% client-mocked while a **working, code-verified server sits behind them** +(verification, refunds, nurse payouts, patient/care records, partner-center) — and three of those five would +*break*, not just show stale data, on a naive flag flip, because the client and server DTO shapes have +independently drifted since the mock was written. + +### What was left undone, and why + +- **Report follow-ups (~211 rows) were harvested but not individually re-verified against code** — that + would have meant re-running phase 3's entire flow-gap sweep at 10x the scope for marginal new information, + since the overwhelming majority either (a) are already superseded by a later, more current phase report, + confirmed by cross-referencing the later report's own "What was built" section, or (b) restate a deferred + decision that the 22 backend hand-off files already state more precisely with a pull-trigger. Genuinely + new, still-open findings from this pool were promoted into `backlog.md`; the rest are either in + `backlog-closed.md` (confirmed done) or folded into the Deferred section (confirmed intentional). +- **The Deferred section (43 items) is consolidated, not 1:1 with its ~150 raw source rows** — the same + "Redis needed for >1 instance" or "Elasticsearch not MVP" statement recurs verbatim across 3-4 separate + hand-off files; each `BL-###` in that section carries every origin id it absorbed. +- **`product/notes/open-questions.md`'s remaining open items were checked directly** (not delegated) since + the file is 86 lines — rate limiting turned out already built for auth/OTP (only search lacks it, already + tracked as REQ-066/`BL-206`); the ToS/Privacy pages exist but are explicitly commented as placeholder + legal copy pending review (`BL-097`); PWA/Workbox caching is untouched and was marked "maybe" in the + original backlog, so it's carried as `BL-251`, deferred. +- **This phase did not touch `dev/`, `product/`, or any code** — pure reconciliation, as scoped. Phase 6 + (archive) is what moves/banners the source ledgers; phase 5 (roadmap) is what turns the Deferred section + into a sequenced plan. diff --git a/docs/status/backlog-closed.md b/docs/status/backlog-closed.md new file mode 100644 index 0000000..dec19e2 --- /dev/null +++ b/docs/status/backlog-closed.md @@ -0,0 +1,135 @@ +# Backlog — closed items + +> Last verified: 2026-08-02 against commit `b876490`. + +Items confirmed done during phase 4's reconciliation, with what closed them. This is what keeps +[backlog.md](backlog.md)'s open count honest — an item only leaves this file if later evidence proves it +regressed. + +--- + +## Hardening ledger (3 of 18 items — see [backlog.md](backlog.md) for the 11 still open + 4 partially-fixed) + +| Origin | Item | Closed by | +|--------|------|-----------| +| H-02 | `isTokenAlive` could never read the real token (JWE) — treated every session as dead. | `baa3cc6` ("manual improvement 1") — a well-formed 5-part JWE is now treated as alive without decoding. | +| H-03 | Anonymous visitor to a private shell hit an infinite splash screen. | `baa3cc6` — `useRoleHydration` now has an explicit `unauthenticated` status; `RoleGuard` redirects to `/login` on it. | +| H-12 | Raw stack-trace error boundary; no route-level error pages. | `370c1be` (ui-phase-1) + `d33568b` (ui-phase-12) — branded `ErrorBoundary` with retry, plus `error.tsx`/`global-error.tsx`. | + +## REQ ledger — delivered (28 of 67; see [backlog.md](backlog.md) for the remaining 39 open/partial/deferred) + +| REQ | Item | Evidence | +|-----|------|----------| +| REQ-001 | `ApiResult` envelope, camelCase casing, `Paginated` shape | `ApiResult.cs`, `PagedResult.cs` | +| REQ-002 | `codeLength`/`expiresInSeconds` on OTP request result | `RequestOtpResult.cs:8` | +| REQ-003 | Machine-readable OTP failure codes + `retryAfterSeconds` | `VerifyOtpCommand.Handler.cs:43,58` | +| REQ-005 | `relation`/`conditions` on `PatientDto` | `Patient.cs:28,33` | +| REQ-006 | Multipart avatar-upload endpoint (nurse + customer) | `UploadCustomerAvatarCommand.cs`, `UploadNurseAvatarCommand.cs` | +| REQ-007 | Customer name + preferred-language update | `UpsertCustomerProfileCommand.cs` | +| REQ-008 | Client map-pin lat/long on address create/update | `CreateAddressCommand.cs:21`, `UpdateAddressCommand.Handler.cs:62-71` | +| REQ-009 | `provinceId` on `CustomerAddressDto` | `CustomerAddressDto.cs:12` | +| REQ-010 | Pagination param name confirmed (`pageSize`) | `PagedResult.cs:4` | +| REQ-011 | Nurse-facing structured credential-details endpoint | `NurseVerificationController.cs:47-51` | +| REQ-012 | Search enrichment + public nurse-profile aggregate | `NurseSearchResultDto.cs:21-23`, `NursesController.cs:33-35` | +| REQ-013 | Variant price on `BookingRequestDto` | `BookingRequestDto.cs:20-21,45` | +| REQ-014 | `variantLabel`+`patientAge` on nurse-inbox list DTO | `BookingRequestListItemDto.cs:23` | +| REQ-015 | Booking/session/EVV enum wire codes confirmed | `BookingStatus.cs:11-30` | +| REQ-016 | Checkout summary read | `BookingRequestsController.cs:64-66` | +| REQ-017 | `bookingId` on converted request | `BookingRequestDto.cs:49` | +| REQ-018 | Invoice reachable right after capture (auto-issue) | `ConfirmPaymentAndPostLedgerCommand.Handler.cs:98` | +| REQ-019 | Customer-initiated cancellation + refund | `BookingsController.cs:67-71` | +| REQ-020 | Pre-cancel policy preview | `BookingsController.cs:74-77` | +| REQ-021 | Refund lookup-by-booking + fee-leg decomposition | `RefundsController.cs:29` | +| REQ-023 | BNPL eligibility accepts D3 KYC inputs | `CheckBnplEligibilityQuery.cs:21-23` | +| REQ-025 | Nurse earnings balance + list + detail + `failureReason` | `NursePayoutsController.cs:27-47` | +| REQ-026 | Review eligibility + my-review-for-booking reads | `BookingReviewsController.cs:33-41` | +| REQ-027 | Family care record + record-access reads | `PatientCareRecordsController.cs:44-59` | +| REQ-028 | Ticket inbox enrichment + message idempotency | `MessagingProjections.cs:13,16-17,62` | +| REQ-029 | `updatedAt`/`updatedBy` on `PlatformConfigDto` | `PlatformConfigDto.cs:11-12` | +| REQ-030 | Audit-trail actor/action/date-range filters | `GetAuditTrailQuery.cs:8-15` | +| REQ-037 | `tagCodes` on the moderation-queue DTO | `ReviewProjections.cs:37-48` | + +**Note:** REQ-029/030's server work is closed, but `client/src/services/admin/constants.ts` still cites both +as reasons the admin mock stays primary — a small residual client cleanup, not tracked separately (fold into +whichever admin-mock item is next touched). + +## REQ ledger — obsolete (1) + +| REQ | Item | Reason | +|-----|------|--------| +| REQ-004 | Confirm multi-role disambiguation (`activeRole`?) on `/me` | Resolved by product decision in refinement-phase-2: the client owns active-role choice; no `MeResult` change was ever needed. | + +## Manual-testing iterations 1 & 2 (19 of 20 bullets — 1 partially-fixed, see [backlog.md](backlog.md) BL-217) + +| Origin | Item | Closed by | +|--------|------|-----------| +| iteration-1 #1 | Login card / all Papers had too large a border radius | `theme.ts:76-84`, `tokens.css:46-51` | +| iteration-1 #2 | Login-page options should move inside the card | `AuthCard.tsx:8-33`, `PhoneStep.tsx:100-117` | +| iteration-1 #3 | No top bar needed on the login page | `PublicLayout.tsx:8-14` | +| iteration-1 #4 | Language/theme switches should live only in Settings | `PublicLayout.tsx:11-13`, `SettingsPanel.tsx:10-16` | +| iteration-1 #5 | Icon set should be modernized with semantic mapping | `AppIcon/config.ts:1-19` — MUI Icons replaced with Lucide | +| iteration-1 #6 | Default route should resolve by role, not spin forever | `RoleGuard.tsx:34-38`, `RoleRouter.tsx:31-46` | +| iteration-1 #7 | React-child console error on phone submit | `OtpStep.tsx:104-115` + regression test `richText.test.tsx` | +| iteration-1 #8 | OTP input spacing broken in `fa` locale | `OtpInput.tsx:119-127` — `gap` not RTL-mirrored `spacing` | +| iteration-1 #9 | Nurse dashboard needed a full visual refactor | `NurseDashboardScreen.tsx:28-45` | +| iteration-1 #10 | Replace drawer nav with a bottom navbar | `NurseLayout.tsx:11-26`, `BottomBar.tsx:18-40` | +| iteration-1 #11 | Each nav group needs a summary root page | `NursePracticeScreen.tsx:13-18`, `NurseFinanceScreen.tsx:10-15` | +| iteration-1 #12 | Horizontal scroll present on nurse dashboard | `AppFrame.tsx:31-33,96-103` — structurally disabled | +| iteration-1 #13 | App should be phone-width at every viewport | `layout/config.ts:5-14` — `APP_FRAME_MAX_WIDTH=480` | +| iteration-2 #1 | Nurse greeting section useless; card borders looked bad | `NurseDashboardScreen.tsx:38-41`, `AccentCard.tsx:15-21` | +| iteration-2 #2 | Bottom nav needed a better, floating form | `BottomBar.tsx:22-34,93-101`, `layout/config.ts:37-44` | +| iteration-2 #3 | No easy way to reach the nurse requests page | `NurseLayout.tsx:16-21,39-44` — now a tab with a badge | +| iteration-2 #4 | Theme-mode toggle buttons had no gap | `ThemeModeSetting.tsx:60-75` | +| iteration-2 #5 | `/nurse/verification` and `/nurse/profile` forms were an undifferentiated list | `nurse/profile/page.tsx:68-80`, `FormSection.tsx:26-40` | +| iteration-2 #6 | `/nurse/services` had the same forms problem | `VariantBuilder.tsx:1-70` — react-hook-form + `StepperHeader` wizard | + +## Report follow-ups confirmed completed by a later phase (curated — the notable ones) + +Verification method: cross-referenced against the later phase's own "What was built" section, not re-traced +against current code (these predate the flow-gap sweep and were folded in only where a later report's own +claim is unambiguous). Treat as **strong evidence**, not the same rigor as the code-verified items above. + +| Origin | Item | Closed by | +|--------|------|-----------| +| backend-phase-0 follow-up | Extend audit interceptor to write `audit_logs`; evolve schema; add `IHolidayCalendar` seed | backend-phase-1 | +| backend-phase-0 follow-up | `ISmsSender` seam + auth/OTP REST surface + rate-limit policies | backend-phase-2 | +| backend-phase-0 follow-up | `WebApplicationFactory` integration-test project | backend-phase-2 (`Baya.Test.Api`) | +| backend-phase-2 follow-up | Profiles/patients/addresses/nurse bank accounts; `/me` completion flags read real tables | backend-phase-3 | +| backend-phase-2 follow-up | Shahkar/KYC populate `NationalId`; `/me` surfaces real `nurseVerificationStatus` | backend-phase-6 | +| backend-phase-3 follow-up | `customer_addresses`+`nurse_service_areas` geography + geocoder | backend-phase-4 | +| backend-phase-3 follow-up | `is_verified` flip transaction; Shahkar/KYC; bank-ownership step coupling | backend-phase-6 | +| backend-phase-3 follow-up | `average_rating`/`total_reviews` aggregate recompute | backend-phase-14 (`total_completed_bookings` recompute unconfirmed — see BL-245-adjacent residue) | +| backend-phase-4 follow-up | `nurse_search_index` fan-out wired to service-area add/remove | backend-phase-7 | +| backend-phase-4 follow-up | EVV distance check consuming address lat/long | backend-phase-9 | +| backend-phase-5 follow-up | `nurse_search_index`, `INurseSearch`, search query, index fan-out | backend-phase-7 | +| backend-phase-5 follow-up | `booking_requests.variant_snapshot_json` persistence | backend-phase-9 (not b8 as originally planned) | +| backend-phase-6 follow-up | Search reads `nurse_profiles.is_verified` for `is_searchable` | backend-phase-7 | +| backend-phase-7 follow-up | Search results feed the booking flow; `required_caregiver_gender` capture | backend-phase-8 | +| backend-phase-8 follow-up | `accepted_awaiting_payment` → real `bookings` row on capture | backend-phase-9/10 | +| backend-phase-8 follow-up | Stage-2 encrypted `booking_care_instructions` | backend-phase-9 | +| backend-phase-8 follow-up | Three-amount split, snapshots, sessions, EVV, dispute window | backend-phase-9 | +| backend-phase-9 follow-up | Real card capture replacing `IPaymentCaptureSimulator` | backend-phase-10 | +| backend-phase-9 follow-up | Reviews-on-completed-booking; `partner_center_id` wiring | backend-phase-14, backend-phase-15 | +| backend-phase-11 follow-up | Real `IBnplProvider` (beyond the b11 revert-only stub) | backend-phase-12 | +| backend-phase-11 follow-up | Clawback netting/recovery + real `INursePayoutStatus` | backend-phase-13 | +| backend-phase-12 follow-up | `settled_at`-gates-payout coupling flag (`require_bnpl_settlement_for_payout`) | backend-phase-13 (shipped, default off) | +| backend-phase-14 follow-up | Ticket system + partner centers | backend-phase-15 | +| frontend-phase-5 follow-up 3 | Admin verification review queue (pass/reject, doc viewer) | frontend-phase-15 | +| frontend-phase-10 follow-up 1 | Admin refund console (`RefundPanel`) | frontend-phase-15 | +| frontend-phase-12 follow-up 2 | Admin payout console (`/admin/payouts`) | frontend-phase-15 | +| frontend-phase-13 follow-up 2 | Review moderation console (`/admin/reviews`) | frontend-phase-15 | +| frontend-phase-14 follow-up 1 | Admin global ticket queue + internal-note composer | frontend-phase-15 | +| frontend-phase-14 follow-up 2 | Support-alert worklist, partner-center console, audit viewer | frontend-phase-15 | +| ui-phase-0/1 follow-up | Core primitives (EmptyState/ErrorState/PageHeader/``/Jalali picker) | ui-phase-1 | +| ui-phase-0/1 follow-up | App-wide motion pass consuming `--bal-motion-*` tokens | ui-phase-12 | +| ui-phase-3 follow-up | Skip-onboarding («بعداً تکمیل می‌کنم») | ui-phase-4 | +| ui-phase-4 follow-up | Public/guest storefront + landing page | ui-phase-13 | +| ui-phase-4/5 follow-up | Zero-case copy sweep; `continue_payment` CTA string sweep | ui-phase-12 | +| ui-phase-7 follow-up 1 | `DashboardActivationSlot`'s full "go live" checklist content | ui-phase-8 | +| ui-phase-8 follow-up | Coverage map visualization | ui-phase-9 | +| handoff-after-refinement-phase-0 | No `USE_*_MOCK` flag had been flipped yet | refinement-phase-4 (14 of 22 domains flipped to real) | + +**Not included above** (checked and found still genuinely open, or not confirmed by a later report): +`support_alerts` FK constraints (BL-not-filed — low-priority schema hygiene, see decisions.md), region +bulk-import feed, `SuspendNurse`/`ResolveSupportAlert`/`FlagConcern` admin actions (BL-245), and everything +else already carried into [backlog.md](backlog.md)'s Deferred section. diff --git a/docs/status/backlog.md b/docs/status/backlog.md new file mode 100644 index 0000000..c54acf7 --- /dev/null +++ b/docs/status/backlog.md @@ -0,0 +1,306 @@ +# Backlog — every open item, triaged + +> Last verified: 2026-08-02 against commit `b876490`. + +Reconciled from five ledgers — [hardening/issues.md](../../dev/post-phase/hardening/issues.md) (18 items), +[for-backend.md](../../dev/shared-working-context/frontend/requests/for-backend.md) (67 REQs), 53 +report "Follow-ups" sections, 22 backend handoff files, two manual-testing iterations — plus +[product/notes/open-questions.md](../../product/notes/open-questions.md), root [CLAUDE.md](../../CLAUDE.md) +§6, phase 2's drift list, and phase 3's **283 flow gaps** (the primary, freshest, code-verified input; see +[docs/flows/index.md](../flows/index.md)). ~700 raw candidate rows were harvested; this file is the deduped, +triaged result. Old ids stay greppable in their source files — nothing was rewritten. + +**Area**: `client` · `server` · `contract` · `ops` · `product` · `docs`. **Sev**: `blocker` (product is wrong +or unusable) · `major` · `minor` · `deferred`. **Status**: `open` · `in-progress` · `blocked (on what)` · +`deferred (trigger)`. **Blocks**: the [docs/flows/](../flows/index.md) journey(s) it degrades. + +Closed items (with what closed them) are in [backlog-closed.md](backlog-closed.md). The distilled decision +log is in [decisions.md](decisions.md). The business-area overlay is in [implemented.md](implemented.md). + +--- + +## Blockers (18) + +| ID | Area | Item | Origin | Status | Blocks | +|----|------|------|--------|--------|--------| +| BL-001 | server | Admin RBAC is structurally dead: `DynamicPermissionService.CanAccess` grants only the literal role `admin`; every seeded admin holds `super_admin`/`finance` and 403s on all `DynamicPermission`-gated controllers. No code path ever writes a `DynamicPermission` claim. | H-04, H-05, admin-backoffice gap-1/3/12, bnpl gap-7, cancellation-and-refunds gap-9, messaging-tickets gap-1/3, nurse-verification gap-3, nurse-earnings-and-payouts gap-7, partner-center gap-8, reviews gap-1/2, booking-request gap-3, booking-lifecycle-evv gap-3/5, nurse-catalog-and-pricing gap-2 | open | admin-backoffice, bnpl-installments, cancellation-and-refunds, messaging-tickets, nurse-verification, nurse-earnings-and-payouts, partner-center, reviews, booking-request, booking-lifecycle-evv, nurse-catalog-and-pricing | +| BL-002 | ops | No seeded account holds the literal `admin` role (`Seed:AdminUsername`/`Password` unset, `SeedDataBase.cs` returns early) — even a fixed BL-001 is untestable out of the box. | admin-backoffice gap-2 | open | admin-backoffice | +| BL-003 | ops | Committed live credentials (DB `sa`, JWE/field-encryption key halves, Kavenegar, Neshan, Finnotech, Telegram bot token) must be rotated and moved out of git before onboarding real users. | CLAUDE.md §6, refinement-phase-0 follow-up 3 | open | — (pre-launch) | +| BL-004 | ops | `GET /api/v1/dev/last_otp/{phone}` is anonymous and Development-only by design, but the deployment runs as Development — live on `api.balinyaar.ir`, lets anyone who knows a phone read its login code. | auth-login-otp gap-9, public-front-door gap-10 | open | auth-login-otp, public-front-door | +| BL-005 | server | Card payment is a dead end everywhere: `MockPaymentProvider` redirects to a non-existent host, and nothing fires the PSP webhook locally, so no new card payment can ever reach `ConfirmPaymentAndPostLedger`. | checkout-and-payment gap-1/2 | open | checkout-and-payment | +| BL-006 | contract | Booking deadline timestamps ship without a timezone (`DateTime` not `DateTimeOffset`); the client reads the offset-less value as local time — in Tehran the 30-minute payment window renders ~4h and expires while the timer still shows time left. | booking-request gap-1 | open | booking-request, checkout-and-payment | +| BL-007 | ops | No `bnpl` payment-gateway row is ever seeded — every BNPL initiate/eligibility call 400s with "No active BNPL gateway is configured" on the live server. | bnpl gap-1 | open | bnpl-installments | +| BL-008 | client | The BNPL wizard is a dead end for every real booking-request id: its mock cross-imports the bookings-mock store (seeded ids 1-2 only), bypassing the real-data flip entirely. | H-07, bnpl gap-2/3 | open | bnpl-installments | +| BL-009 | client | Refunds mock reads the retired bookings-mock store (ids 5001-5005 only), so any real booking id 404s inside the cancel-policy preview; flipping the flag today would also render a 10000% refund (percent-scale bug on an already-0-100 field). | H-06, cancellation-and-refunds gap-1/2/4 | open | cancellation-and-refunds | +| BL-010 | client | Verification is 100% client-mocked under one flag covering nurse + public + admin; the mock resets to empty on every reload, so even a server-verified nurse renders unverified everywhere the app reads it, hiding the real search-visibility gate and "go live" CTA. `submitCredentialDetails` is also a no-op though the real endpoint matches field-for-field. | H-08, nurse-verification gap-1/2/4, onboarding-nurse gap-1/2/3 | open | nurse-verification, onboarding-nurse, search-and-discovery, public-front-door | +| BL-011 | client | Patient/care records are 100% mocked on both the family record and nurse visit-note panel, and the real DTO shapes would break a naive flip: medication/routine fields mismatch server field-for-field, and care-plan ids are `string` client-side vs `long` server-side. | care-circle-patients gap-1..4, patient-care-records gap-1..3, H-17 | open | care-circle-patients, patient-care-records, booking-lifecycle-evv | +| BL-012 | server | Nurse earnings/payouts: mock hides four working endpoints; no `process` operation exists anywhere in the client (the irreversible payout step has no UI); `DeriveEarningsState` marks a booking "paid" whenever it merely has a payout *link* regardless of that payout's actual status; the four earnings buckets don't reconcile against the ledger. | H-09, nurse-earnings-and-payouts gap-1/4/5/9/10/11 | open | nurse-earnings-and-payouts | +| BL-013 | client | Partner center is 100% mocked with zero tenancy (any signed-in caller resolves to center id 1), and 5 core routes (`centers/me`, `/me/nurses`, `/me/bookings`, `/me/bookings/{id}`, `/me/settlement`) don't exist server-side; a center owner has no legal read of its own issued invoices. | partner-center gap-1/2/3/9/10, REQ-032, REQ-033, REQ-064 | open | partner-center | +| BL-014 | server | Search results are index rows, not nurses — one nurse with 3 variants × 3 areas shows as "9 پرستار" with no de-dup; the trust dossier on results is mocked, and the by-id profile page asserts "verified" unconditionally, even for an `in_review` nurse. | search-and-discovery gap-1/3/4 | open | search-and-discovery, public-front-door | +| BL-015 | client | Editing an address through the UI silently destroys data: the form never collects `postalCode`/`recipientName`/`recipientPhone`, and the update handler nulls them unconditionally on every save. | addresses-and-map gap-4 | open | addresses-and-map | +| BL-016 | server | A booking whose remaining sessions get swept to `missed` never reaches a payable state — the `allSettled → Completed` re-check only lives inside the checkout path, so already-checked-out sessions can never enter a payout batch. The "today" session feed also applies no date filter and lists a nurse's whole history. | booking-lifecycle-evv gap-1/2 | open | booking-lifecycle-evv, nurse-earnings-and-payouts | +| BL-017 | server | Reviews can never leave moderation on the live stack: the queue and status-PATCH sit behind BL-001, and `AutoApproveClean` is absent from config — a real review never reaches a nurse's profile except via the seeder. | reviews gap-1/2/3 | open | reviews | +| BL-018 | server | A production database has zero option groups outside Development — the one seeded «نوع شیفت» group only exists because the demo seeder is Dev-only, so every builder collapses to two steps and every variant in a category duplicates every other, deployed. | nurse-catalog-and-pricing gap-3 | open | nurse-catalog-and-pricing | + +## Major (86) + +| ID | Area | Item | Origin | Status | Blocks | +|----|------|------|--------|--------|--------| +| BL-019 | client | Customer without an emergency contact cannot save name or language — the profile validator requires it non-empty but the UI has no field-level pre-check, and the resulting 400 is completely silent (no `onError`). | account-and-settings gap-1/2 | open | account-and-settings | +| BL-020 | client | Partner settings hub shows fabricated identity — `USE_PARTNER_MOCK` resolves the same center for any caller, zero real tenancy on the screen. | account-and-settings gap-6 | open | account-and-settings, partner-center | +| BL-021 | client | Sign-out confirmation is inconsistent — customer hub gates it behind a confirm dialog; nurse/admin/partner fire on the first tap and end every session on the account. | account-and-settings gap-7 | open | account-and-settings | +| BL-022 | client | A customer cannot set an avatar — the real endpoint exists but the client's `uploadAvatar` only targets the nurse route. | account-and-settings gap-8 | open | account-and-settings | +| BL-023 | product | `preferredLanguage` is stored and never consumed anywhere — the UI locale comes only from the URL prefix, so the زبان sheet has two controls that do different things. | account-and-settings gap-3 | open | account-and-settings | +| BL-024 | server | Nurse avatars never load anywhere in the app — `LocalDiskObjectStorage.GetUrl` returns a `file://` URI a browser cannot fetch. | account-and-settings gap-4, onboarding-nurse gap-4, search-and-discovery gap-6, public-front-door gap-2 | open | account-and-settings, onboarding-nurse, search-and-discovery, public-front-door, booking-request | +| BL-025 | ops | `NEXT_PUBLIC_NESHAN_KEY` is unset in every environment, so the real map (tiles, address search, locate-me) never renders anywhere — every environment gets the keyless grid stand-in, and the response shapes are unverified against the live Neshan API. | addresses-and-map gap-1/2 | open | addresses-and-map | +| BL-026 | client | `CITY_CENTROIDS` is keyed on mock-seed city ids; only Tehran matches the real seeded cities, so the map picker opens ~400km away for every other city. | addresses-and-map gap-3 | open | addresses-and-map | +| BL-027 | client | Recipient name/phone/postal-code are never displayed anywhere on an address card either, even where the (broken, BL-015) form would collect them. | addresses-and-map gap-5 | open | addresses-and-map | +| BL-028 | contract | Admin list pagination silently breaks on flip: `admin/apis/clientApi.ts` sends `page_size`; controllers declare `PageSize`. Binding is case- but not separator-insensitive, so every admin list falls back to the server default page size. | admin-backoffice gap-6, search-and-discovery gap-13 | open | admin-backoffice, search-and-discovery | +| BL-029 | server | `/fa/admin/roles` and `/fa/admin/users` have no server — `admin_roles/*` and `admin_users/*` are phantom; REQ-061 (admin user directory) was never given a ledger header despite ten live client files citing it. | admin-backoffice gap-7/8, REQ-031, REQ-061 | open | admin-backoffice | +| BL-030 | client | `admin_cancellation_policies/list|upsert` are unwired — no screen edits the cancellation tiers; they are effectively read-only in production. | admin-backoffice gap-11 | open | admin-backoffice, cancellation-and-refunds | +| BL-031 | client | Reuse-detection cannot tell theft from two tabs: the single-flight refresh guard is per-tab module state, so two tabs 401ing at once race and the loser's classified-as-reuse kills every session with no explanation. `useSelectRole`'s own rotation bypasses the guard the same way. | auth-login-otp gap-2/3 | open | auth-login-otp | +| BL-032 | server | Session revocation does not kill access tokens — `RefreshTokenCommandHandler` revokes sessions but never rotates the security stamp, so "logged out everywhere" is up to 60 minutes late. | auth-login-otp gap-4 | open | auth-login-otp | +| BL-033 | client | The access cookie expires 45 minutes before the token does (900s vs 60min), so 15 idle minutes bounces any full page load to `/login` even though the refresh cookie could have recovered the session. | auth-login-otp gap-5 | open | auth-login-otp | +| BL-034 | client | A `?next=` pointing at `/partner/...` never survives login — `appRoleForPath` has no partner branch, and `/me` carries no signal that the caller administers a partner center. | auth-login-otp gap-11, partner-center gap-4, REQ-038 | open | auth-login-otp, partner-center | +| BL-035 | client | `useLogout` always signs out every device — an absent `refreshToken` means "everywhere"; no UI offers a single-device choice. | auth-login-otp gap-12 | open | auth-login-otp | +| BL-036 | client | `resolveRoleDestination` checks admin before customer/nurse, so a user holding both an admin sub-role and `customer` can never reach the family app from login. | auth-login-otp gap-13 | open | auth-login-otp | +| BL-037 | ops | `POST /auth/request_otp` 500s on a fresh clone — the committed SMS provider is `telegram` with nothing listening; login is unreachable until booted with the mock provider. | auth-login-otp gap-1 | open | auth-login-otp | +| BL-038 | client | Query cache is never cleared on logout/login — only `authKeys` are invalidated, so a device that signs out of one account and into another can render stale, cross-account data from the previous session's cache. | H-11 | open | auth-login-otp, account-and-settings | +| BL-039 | server | `BookingRoles.Admin` over-grants: Support/Moderation roles get clinical care-instruction reads and financial nurse-payable-balance reads, wider than the "admin/finance" comment claims. | H-05 | open | booking-lifecycle-evv, nurse-earnings-and-payouts | +| BL-040 | client | The «پاسخ‌داده» nurse-inbox tab is unpaged — the API filters one status at a time with no group filter, so `nurse/requests` fires three page-1 queries and concatenates; a nurse with >20 answered requests silently loses rows. | booking-request gap-4, REQ-050 (residue) | open | booking-request | +| BL-041 | ops | Nothing seeded is actionable in the booking-request flow — no `pending`/`accepted` request survives the demo seeder's epoch-anchored aging, so the nurse inbox, accept/reject and payment-window handoff are all unwalkable without creating a fresh request. | booking-request gap-2 | open | booking-request | +| BL-042 | server | `nurseAvatarUrl` on a booking request is served as a raw local-disk path — same root cause as BL-024, called out because checkout renders it directly. | booking-request gap-11 | open | checkout-and-payment | +| BL-043 | client | `NEXT_PUBLIC_EVV_MOCK_GPS` now defaults to off (derived from the now-real bookings flag), so a tester away from the seeded Tehran address fires a real GPS mismatch — and a support alert — on every check-in; the doc-comment still describes the old default. | booking-lifecycle-evv gap-6 | open | booking-lifecycle-evv | +| BL-044 | server | The nurse never sees the address on a confirmed booking — `BookingMapper.ToDetailDto` nulls the address snapshot for the nurse role even though the server itself reads it to compute the EVV match. | booking-lifecycle-evv gap-7, REQ-051 | open | booking-lifecycle-evv | +| BL-045 | client | `POST bookings/submit_care_instructions/{id}` is unwired — no customer form exists anywhere; on a booking a tester creates, the nurse's care-instructions card is empty. | booking-lifecycle-evv gap-10 | open | booking-lifecycle-evv | +| BL-046 | client | The nurse booking detail is half real: the bookings/EVV half is server truth, but the mounted visit-notes panel (task checklist, continuity history, note-save) is entirely `patientRecords`-mocked — same root cause as BL-011. | booking-lifecycle-evv gap-12 | open | booking-lifecycle-evv | +| BL-047 | client | H-06's exact mocked-cross-import defect also shows up as: `CHANNEL_BY_BOOKING` pins the BNPL-revert refund channel to one fixture id, so every real booking silently demos as a card refund. | cancellation-and-refunds gap-3 (folded severity note) | open | cancellation-and-refunds | +| BL-048 | contract | The client discards six fields the server now serves on refund status (`platformFeeRefundedIrr`, `nursePayoutRefundedIrr`, `refundPercentageApplied`, `cancellationPolicyCode`, `createdAt`, `completedAt`) — the fee-split transparency section can never render even once BL-009 is fixed. | cancellation-and-refunds gap-5 | open | cancellation-and-refunds | +| BL-049 | contract | Cancellation enum drift on three fields (`CancellationPolicyCode`, `CancellationLeadTime`, `appliesTo`) between client and the seeded/wire values — every one drives an i18n lookup that will miss on flip. | cancellation-and-refunds gap-6 | open | cancellation-and-refunds | +| BL-050 | server | `POST admin_refunds` creates and executes in one call — no preview/approve/reject route exists, so the admin `RefundPanel`'s three-step console has no real backend. | cancellation-and-refunds gap-10, REQ-035 | deferred (trigger: admin refund console prioritized) | cancellation-and-refunds | +| BL-051 | client | No admin console exists for refund/clawback settlement — `confirm_settlement`, `mark_failed`, `write_off` all have server handlers but zero client screens. | cancellation-and-refunds gap-12 | open | cancellation-and-refunds | +| BL-052 | server | `expectedCustomerRefundEta` is hardcoded null in the cancellation preview — the BNPL 10-business-day window is invisible before the customer confirms, exactly where it matters most. | cancellation-and-refunds gap-14 | open | cancellation-and-refunds | +| BL-053 | ops | `customer_no_show` has no seeded cancellation-policy row — the product doc's "up to 100% charge" tier does not exist; a customer no-show falls through to the 50%-back tier. | cancellation-and-refunds gap-15 | open | cancellation-and-refunds | +| BL-054 | server | No credit note / invoice reversal on refund — a refunded booking's VAT-bearing commission invoice stays as originally issued. | cancellation-and-refunds gap-16 | open | cancellation-and-refunds | +| BL-055 | server | Seeded visit-note `taskResults` labels are lost — the handler deserializes with case-sensitive options against camelCase seeded JSON; any externally-written JSON in that column silently degrades to blank labels. | care-circle-patients gap-5, patient-care-records gap-7 | open | care-circle-patients, patient-care-records | +| BL-056 | client | Editing a patient destroys their birth date — the form always submits `ageToBirthDate(age)` = `YYYY-01-01`, overwriting the real recorded date on every save even with no change. | care-circle-patients gap-10 | open | care-circle-patients | +| BL-057 | server | Tenancy leak on patient records: a foreign patient returns 403, not 404, across all three handlers — confirms the patient exists to an unauthorized caller, violating the repo's 404-not-403 invariant. | patient-care-records gap-8 | open | care-circle-patients, patient-care-records | +| BL-058 | contract | A verified nurse is shown as unverified on the checkout screen — the checkout summary DTO declares `nurseVerified` but the server never constructs it. | checkout-and-payment gap-3, REQ-046 | open | checkout-and-payment | +| BL-059 | client | Payment confirmation cannot deep-link to the booking — the server does serve `bookingId` but the client types it away and hardcodes null; the receipt also always hides the tracking code and paid-at. | checkout-and-payment gap-5, H-10, REQ-046 | open | checkout-and-payment | +| BL-060 | client | The invoice screen derives a money row client-side (`gross − commission − vat`) that is wrong by exactly the VAT amount, and never shows the server's real `totalIrr` — violates the "client never computes money" rule. | checkout-and-payment gap-7 | open | checkout-and-payment | +| BL-061 | server | Customer payment history is a live 404 — `bookings/payment_history` doesn't exist; the wallet «پرداخت‌ها» tab is permanently empty for a card-paying customer. | checkout-and-payment gap-8, REQ-047 | open | checkout-and-payment | +| BL-062 | server | The escrow ledger has no read surface at all — a customer, admin or tester has no way to see the balanced capture group the flow's core money invariant depends on. | checkout-and-payment gap-10 | open | checkout-and-payment | +| BL-063 | client | `getUnreadTotal` is a hardcoded `null` on the real path — the chrome support badge can never light up. | messaging-tickets gap-4, account-and-settings gap-10, REQ-059 | open | messaging-tickets, account-and-settings | +| BL-064 | client | `mapSummary` hardcodes `lastMessagePreview`/`lastAuthorRole` to null on the real path — the ticket inbox card shows no message preview. | messaging-tickets gap-5, REQ-059 | open | messaging-tickets | +| BL-065 | client | `POST /tickets/emergency` has zero client callers — the nurse's emergency banner only dials `tel:`; the "then opens a ticket" half of the business playbook is manual. | messaging-tickets gap-6 | open | messaging-tickets | +| BL-066 | contract | 11 of 14 server notification types are unknown to the client's parser — only 3 deep-link; the nurse's most important notification (`booking_confirmed_nurse`) and the entire `booking_request_*` lifecycle are non-navigable. | notifications gap-1/2/3 | open | notifications, booking-request | +| BL-067 | client | The client handles 10 notification types the server never emits, while the server's real payout-paid/payout-failed events have no client branch at all — a nurse is never told a payout paid or failed. | notifications gap-6 | open | notifications, nurse-earnings-and-payouts | +| BL-068 | server | Every notification title/body is an English literal rendered verbatim into the Persian RTL feed — no i18n path for notification content, only for chrome. | notifications gap-7 | open | notifications | +| BL-069 | client | No catalogue-authoring UI exists at all — all seven `admin_catalog/*` routes have zero client callers; an admin cannot add a pricing dimension without SQL, compounding BL-018. | nurse-catalog-and-pricing gap-1 | open | nurse-catalog-and-pricing | +| BL-070 | server | ZWNJ is stripped from every stored Persian string via a global `FixPersianChars` normalizer, contradicting the repo's "with a ZWNJ, always" naming rule. | nurse-catalog-and-pricing gap-4 | open | nurse-catalog-and-pricing | +| BL-071 | server | A nurse's price edit is not shielded from an in-flight booking request — the conversion reads the live variant price, not a request-time snapshot, so editing price inside the 30-minute payment window changes what the customer pays. | nurse-catalog-and-pricing gap-5 | open | nurse-catalog-and-pricing, booking-request | +| BL-072 | client | The «همراهی و مراقبت روزمره» category is data-only per its own seed comment, but the builder offers it like any real pricing path. | nurse-catalog-and-pricing gap-8 | open | nurse-catalog-and-pricing | +| BL-073 | client | `toHistoryItem` hardcodes `failureReason: null` though the live wire carries it — a failed payout loses its reason in the nurse's history on flip. | nurse-earnings-and-payouts gap-3 | open | nurse-earnings-and-payouts | +| BL-074 | client | `previewPayoutBatch` sums net amounts and fabricates a processing date client-side — the client computing money and a payout date, violating the "client never computes money" rule. | nurse-earnings-and-payouts gap-6 | open | nurse-earnings-and-payouts | +| BL-075 | contract | `recordTransferReference` targets a route that doesn't exist — the batch-detail reconcile field 404s on flip. | nurse-earnings-and-payouts gap-12, REQ-036 | deferred (trigger: payout reconciliation console prioritized) | nurse-earnings-and-payouts | +| BL-076 | client | `POST admin_payouts/{id}/mark_failed` exists server-side but has no client op — a reconciled bank rejection cannot be recorded from the console. | nurse-earnings-and-payouts gap-14 | open | nurse-earnings-and-payouts | +| BL-077 | server | Whole-city + a specific district in the same city are both accepted for one nurse, and search then returns her twice with no de-dup on `(nurseId, variantId)`. | nurse-service-areas gap-1 | open | nurse-service-areas, search-and-discovery | +| BL-078 | client | The coverage screen has no error state — `isError` is dropped, so a failed list load renders the "no area registered" empty state, telling a nurse with real coverage she is invisible in search. | nurse-service-areas gap-3 | open | nurse-service-areas | +| BL-079 | contract | No edit and no deactivate for a service area — changing a district means remove-then-add, which silently drops the nurse from search between the two calls. | nurse-service-areas gap-5 | open | nurse-service-areas | +| BL-080 | client | `nurse/verification/page.tsx` unconditionally imports the verification mock module — the one production seam breach in the client; the mock ships in every build regardless of the render gate. | nurse-verification gap-4 | open | nurse-verification | +| BL-081 | client | `foldQueueRows` folds the per-step admin verification queue to per-nurse items but leaves the step-progress counts at zero and the pager count wrong — a nurse's steps can straddle a page boundary. | nurse-verification gap-6, REQ-062 | open | nurse-verification | +| BL-082 | contract | `approveVerification`/`rejectVerification`/`getDocumentSignedUrl` target routes that don't exist — the admin case page's visible approve/reject CTAs would 404 the moment BL-001 is fixed. | nurse-verification gap-7, REQ-034 | deferred (trigger: verification-admin console prioritized) | nurse-verification | +| BL-083 | client | No admin UI exists for `suspend`/`scan_expiring` or the verification step-type catalog — the "data-driven catalog" design rule has no admin surface. | nurse-verification gap-8 | open | nurse-verification | +| BL-084 | client | Admin verification case URLs are mistyped — the route folder is `[nurseId]` but the value passed is a `nurseVerificationId`; typing a nurse id into the URL opens the wrong case. | nurse-verification gap-11 | open | nurse-verification | +| BL-085 | product | Onboarding never creates a `customer_profile` — it creates a patient and stops; nothing blocks a customer from booking without ever setting an emergency contact. | onboarding-customer gap-1 | open | onboarding-customer | +| BL-086 | contract | A customer's own `gender` is unsettable through the app — no client call and no command field carries it; a fresh account's `/me.gender` stays null forever. | onboarding-customer gap-3 | open | onboarding-customer | +| BL-087 | client | The customer profile upsert has no PATCH semantics — every sheet save rewrites the whole profile including name, so a stale form on one device can silently overwrite a name changed on another. | onboarding-customer gap-5 | open | onboarding-customer, account-and-settings | +| BL-088 | client | `/fa/select-role` is reachable by any authenticated user with no guard, letting a customer permanently self-grant the `nurse` role by typing a URL, with no confirmation step for an irreversible grant. | onboarding-customer gap-7 | open | onboarding-customer | +| BL-089 | client | `useSelectRole` swallows a failed post-select token rotation — the role persists server-side but the client keeps a stale token until the next silent refresh happens to fire, so a role-gated call in between 403s. | onboarding-customer gap-9 | open | onboarding-customer | +| BL-090 | client | The duplicate-IBAN error message never reaches the nurse — the bank page discards the server's field error and shows a generic "registration failed" toast. | onboarding-nurse gap-7 | open | onboarding-nurse | +| BL-091 | server | Bank-ownership inquiry ignores the national id entirely — a nurse who never did identity KYC (`national_id = NULL`) still gets `matchedNationalId = true`, so the payout gate opens on a claim nothing actually checked. | onboarding-nurse gap-8 | open | onboarding-nurse, nurse-earnings-and-payouts | +| BL-092 | client | `verifyOwnership` is a dead seam op — no hook calls it, so a `mismatch` bank account has no re-inquiry affordance; the UI only offers "re-enter the IBAN". | onboarding-nurse gap-9 | open | onboarding-nurse | +| BL-093 | contract | Shape mismatch blocks the partner portal even once mocked data is off: the server serves one capped/inline dashboard aggregate; the portal needs paginated `/me`-scoped splits it cannot get from that shape. | partner-center gap-5 | open | partner-center | +| BL-094 | client | `GET /centers/{id}/dashboard` — the one portal endpoint that *is* real — needs a center id the portal has no way to discover, so it goes unused. | partner-center gap-6 | open | partner-center | +| BL-095 | server | Structured `taskResults` are discarded in both directions on the real patient-records path — accepted on write and returned on read by the server, but the client hardcodes them away; the "wire has no structured field" client comments are simply wrong. | patient-care-records gap-6 | open | patient-care-records | +| BL-096 | product | Tier (c) of the public front door (guest search + public nurse profiles) is unbuilt — REQ-066/067 need a backend phase plus an explicit privacy sign-off before any guest-facing search/profile screen is built. | public-front-door gap-6, ui-phase-13 follow-up | deferred (trigger: privacy sign-off + REQ-066/067 delivered) | public-front-door, search-and-discovery | +| BL-097 | product | `/terms` and `/privacy` ship placeholder legal copy behind a draft banner, flagged for human/legal review since ui-phase-3 and still unreviewed — a real pre-launch item. | public-front-door gap-5, ui-phase-3 follow-up 1 | open | public-front-door | +| BL-098 | server | `GET /api/v1/nurses/{id}/profile` returns 200 anonymously for an unverified (`in_review`) nurse — the persona that "must never appear in search" is reachable and enumerable by id, even though search itself correctly hides her. | public-front-door gap-3 | open | public-front-door, search-and-discovery | +| BL-099 | client | The nurse cannot see their own reviews anywhere in the app — the only signal is a notification the (BL-001-blocked) moderation handler dispatches. | reviews gap-8 | open | reviews | +| BL-100 | client | The client hardcodes the five review tag codes instead of reading the server's tag master — a newly seeded tag is invisible to the UI and an unseeded code fails submit outright. | reviews gap-7 | open | reviews | +| BL-101 | server | `ReviewModerationStatus.Rejected` is unreachable end to end — the submit-time banned-word path maps to `Hidden`, not `Rejected`, and the only other producer is the BL-001-blocked admin PATCH; the client still renders a `rejected` chip nobody can ever trigger. | reviews gap-4 | open | reviews | +| BL-102 | server | Review eligibility distinguishes "booking not found" from "not your booking" with a 200, letting any authenticated caller probe whether an arbitrary booking id exists — violates the repo's 404-not-403 tenancy invariant. | reviews gap-9 | open | reviews | +| BL-103 | product | No free-text search exists — a customer who knows a nurse's name by name cannot find her; category/city/gender/price filters only. | search-and-discovery gap-10, REQ-041 | open | search-and-discovery | +| BL-104 | client | Guests cannot reach search at all — every search route sits behind a customer-only route guard and `/search` is not in the public-path list, even though the underlying endpoints are already anonymous. | search-and-discovery gap-11 | open | search-and-discovery, public-front-door | + +## Minor (115) + +| ID | Area | Item | Origin | Status | Blocks | +|----|------|------|--------|--------|--------| +| BL-105 | client | Admin hub renders a raw i18n key as the role label — the translation keys were never added. | account-and-settings gap-5 | open | account-and-settings | +| BL-106 | client | Customer hub navigates with a hand-built locale prefix instead of the shared navigation helper — works today, breaks silently if `localePrefix` ever changes. | account-and-settings gap-11 | open | account-and-settings | +| BL-107 | client | UNVERIFIED: a system-theme user may see one light/dark flip after hydration (SSR cookie vs. post-hydration MUI resolution can disagree). | account-and-settings gap-12 | open | account-and-settings | +| BL-108 | product | No notification-preference surface exists anywhere — the settings row only deep-links to the notification centre. | account-and-settings gap-9 | open | account-and-settings, notifications | +| BL-109 | client | The pin is a required address-form field, so the API's "no pin" state and its UI badge are unreachable except via direct API calls or seed data. | addresses-and-map gap-6 | open | addresses-and-map | +| BL-110 | ops | Geography is seeded one city per province; districts exist only for Tehran, so the region cascade is a two-step formality everywhere else. | addresses-and-map gap-7 | open | addresses-and-map | +| BL-111 | client | `GET geo/tree` is live but unused — the region cascade makes three round trips instead of one. | addresses-and-map gap-8 | open | addresses-and-map | +| BL-112 | client | Map-pin RTL geometry is dodged via inline style, untested for RTL drift beyond the keyless fallback path. | addresses-and-map gap-9 | open | addresses-and-map | +| BL-113 | docs | No `product/` doc describes address entry, the map-pin picker, or the Neshan integration. | addresses-and-map gap-10 | open | addresses-and-map | +| BL-114 | client | `/fa/admin/notifications` is a placeholder stub and a true orphan — no link reaches it and no bell is mounted in the admin shell. | admin-backoffice gap-9, notifications gap-8 | open | admin-backoffice, notifications | +| BL-115 | client | `POST holidays/delete_holiday` is unwired — the admin console offers no delete affordance for a holiday row. | admin-backoffice gap-10 | open | admin-backoffice | +| BL-116 | ops | `admin_search/rebuild_index` and `admin_booking_requests/expire` are reachable only by curl, and both 403 for a seeded admin anyway. | admin-backoffice gap-12 | open | admin-backoffice, booking-request | +| BL-117 | client | `useAdminCapabilities` hides tabs but never blocks the route itself — every admin console stays URL-reachable regardless of capability; server enforcement is currently just the blanket BL-001 403. | admin-backoffice gap-13, H-14 | open | admin-backoffice | +| BL-118 | client | A 429 on OTP verify renders as "wrong code" — the empty error body leaves nothing to branch on, unlike the phone-step's correct 429 handling. | auth-login-otp gap-6 | open | auth-login-otp | +| BL-119 | contract | The client ignores the server's OTP metadata (`codeLength`, `expiresInSeconds`) and hardcodes its own — the 60-second code expiry is never shown, only the 120-second resend cooldown. | auth-login-otp gap-7 | open | auth-login-otp | +| BL-120 | docs | Stale client comments claim the server exposes no OTP code length or machine-readable failure code — it exposes both. | auth-login-otp gap-8 | open | auth-login-otp | +| BL-121 | client | A signed-in user visiting `/login` sees the phone form again instead of being redirected away, unlike `/welcome`. | auth-login-otp gap-10 | open | auth-login-otp | +| BL-122 | product | The OTP SMS template is not WebOTP-conformant, so the client's WebOTP autofill ships but never fires; landing this is a Kavenegar-dashboard template edit (ops action), not a code change. | auth-login-otp gap-14, REQ-039 | open | auth-login-otp | +| BL-123 | ops | No seeded account is role-less, so `/fa/select-role` has no natural path in the demo world. | auth-login-otp gap-15, onboarding-nurse gap-10 | open | onboarding-customer, onboarding-nurse | +| BL-124 | client | Client hardcodes English toasts for 401/403/5xx/network errors — no i18n dictionary. | H-13 | open | (all authenticated flows) | +| BL-125 | client | Admin read-only consoles are inconsistently client-guarded — some pages check capabilities only to gate mutation buttons, `admin/audit` has zero check, and no shared `CapabilityGuard` component exists. | H-14 | open | admin-backoffice | +| BL-126 | docs | `bnpl/apis/clientApi.ts` and its header comment both claim the by-request BNPL read 404s — it exists and returns 200, a stale comment blocking a partial de-mock. | bnpl gap-5 | open | bnpl-installments | +| BL-127 | client | `acceptBnplSchedule` derives request status client-side from the order status, mislabelling a failed/reverted/cancelled order as awaiting-payment — the expired-window branch can never fire on the real path. | bnpl gap-6 | open | bnpl-installments | +| BL-128 | product | `providerCommissionReversedAmount` is left null on a reverted BNPL order — nothing in the UI or admin surfaces the resulting commission shortfall. | bnpl gap-8 | open | bnpl-installments | +| BL-129 | ops | Seeded BNPL eligibility status (`"approved"`) is not in the closed status vocabulary on either side of the wire. | bnpl gap-9 | open | bnpl-installments | +| BL-130 | server | D3 KYC inputs are half-wired — the client sends `nationalId`/`mobile`/`consent`, but eligibility only reads the mobile; legal consent is collected and silently discarded. | bnpl gap-10, REQ-023 (residue note) | open | bnpl-installments | +| BL-131 | ops | Nothing fires the BNPL provider webhook in dev, so even a successfully initiated real order would never reach `settled` — settlement is webhook-driven by design. | bnpl gap-11 | open | bnpl-installments | +| BL-132 | client | `/fa/bookings/checkout/bnpl/gateway` is an orphan route on the real path, reachable only from the mock and gated outside development. | bnpl gap-12 | open | bnpl-installments | +| BL-133 | client | No client path produces a `disputed` booking status — it renders in the status map but nothing ever raises one. | booking-lifecycle-evv gap-4 | open | booking-lifecycle-evv | +| BL-134 | contract | The nurse's today-feed session row carries no service/variant label — every row says patient name + visit index only. | booking-lifecycle-evv gap-8, REQ-052 | open | booking-lifecycle-evv | +| BL-135 | contract | A booking list row has no `patientId` — a booking card can never deep-link to the care record. | booking-lifecycle-evv gap-9, care-circle-patients gap-13, REQ-057 | open | booking-lifecycle-evv, care-circle-patients | +| BL-136 | client | `POST booking_sessions/cancel/{id}` is unwired — a nurse cannot cancel a single visit from the UI. | booking-lifecycle-evv gap-11 | open | booking-lifecycle-evv | +| BL-137 | docs | `services/bookings/apis/serverApi.ts` is dead code with no importer, and its own doc-block still claims the domain is mock-primary. | booking-lifecycle-evv gap-13 | open | booking-lifecycle-evv | +| BL-138 | contract | No structured rejection-reason code exists — the client runs a seven-keyword heuristic on free text to decide whether to offer "retry with the same nurse," and it silently misses any other wording. | booking-request gap-5, REQ-044 | open | booking-request | +| BL-139 | contract | `variantLabel` is typed as client-augmented/optional though the server always serves it, and the list row still lacks `variantPrice`/`variantPriceUnit`, so the nurse inbox card can't show the money without opening the detail. | booking-request gap-6/7, REQ-050 (residue) | open | booking-request | +| BL-140 | contract | `patientAge` is served on every nurse-inbox row but never modelled client-side — a pure client-side type-widening fix. | booking-request gap-8 | open | booking-request | +| BL-141 | client | The customer's pending-request list doesn't poll while the detail view does, so countdowns on `/bookings` go stale until a manual refresh. | booking-request gap-9 | open | booking-request | +| BL-142 | client | `useBookingRequest` has no auth gate (unlike both list hooks) — a hard reload before hydration can flash an error card. | booking-request gap-10 | open | booking-request | +| BL-143 | product | The nurse sees the patient's display name pre-accept, wider than the integration doc's "notes + coarse address only" — confirm this is the intended stage-1 boundary. | booking-request gap-12 | open | booking-request | +| BL-144 | client | The client's 22 in-memory mocks are module-scoped and reset on reload/HMR — a mocked cancel demo doesn't survive navigating to the refund-status screen. | cancellation-and-refunds gap-17 | open | cancellation-and-refunds | +| BL-145 | contract | `preview.refundableSessionIds` doesn't exist on the wire (only `sessions[]`) — harmless today, but the client type lies. | cancellation-and-refunds gap-7 | open | cancellation-and-refunds | +| BL-146 | docs | The refunds `clientApi.ts` doc-block is stale — claims REQ-019/020/021 are contract gaps; all three routes exist and return 200. | cancellation-and-refunds gap-8 | open | cancellation-and-refunds | +| BL-147 | server | No `GET refunds/my` — the wallet «استردادها» tab renders empty on the real path. | cancellation-and-refunds gap-11, REQ-048 | open | cancellation-and-refunds | +| BL-148 | client | `/fa/admin/finance` is a hub with a single payouts tile — no refunds, clawbacks, or invoice-issue surface. | cancellation-and-refunds gap-13 | open | cancellation-and-refunds | +| BL-149 | client | Client discards a field the server does serve on the visit-note read — the "N of M tasks" summary chip can never render on the real path. | care-circle-patients gap-6 | open | care-circle-patients | +| BL-150 | contract | `deniedReason` enum mismatch on patient-record access denial — server sends `not_authorized`, client/docs declare `no_access`/`not_found`. | care-circle-patients gap-7, patient-care-records gap-5 | open | care-circle-patients, patient-care-records | +| BL-151 | docs | Stale client comments assert patient-record endpoints "have no backend" — all exist, are authorized, and were probed 200. | care-circle-patients gap-8/9, patient-care-records gap-12 | open | care-circle-patients, patient-care-records | +| BL-152 | ops | Seeded care plans are empty and seeded patients have no relation/conditions — three of four record tabs would be blank the moment the mock flag flips. | care-circle-patients gap-11/12, patient-care-records gap-9 | open | care-circle-patients, patient-care-records | +| BL-153 | docs | No `product/business/` file covers patient care records at all — no documented rule for append-only, encryption, or the clinical-access gate. | care-circle-patients gap-14, patient-care-records gap-10 | open | care-circle-patients, patient-care-records | +| BL-154 | client | Archiving a patient is optimistic with no undo — a stale/cross-tenant id 404s and the card silently reappears with a generic toast. | care-circle-patients gap-15 | open | care-circle-patients | +| BL-155 | client | The only way to demo patient-record access-denial uses a hardcoded sentinel patient id — untested against how the real server denies. | patient-care-records gap-11 | open | patient-care-records | +| BL-156 | contract | `sessionCount` is served nullable but typed non-null client-side; UNVERIFIED how the i18n label renders a null count. | checkout-and-payment gap-4 | open | checkout-and-payment | +| BL-157 | docs | Payment client constants/comments are stale — claim a live endpoint 404s, claim the mock is still primary, and hardcode a platform-fee rate that no longer matches the server's seeded value. | checkout-and-payment gap-6 | open | checkout-and-payment | +| BL-158 | server | Invoice carries no payment method, transaction reference, or seller fiscal identity — those rows never render on the real path. | checkout-and-payment gap-9, REQ-049 | open | checkout-and-payment | +| BL-159 | server | `GET invoices/{bookingId}` tenancy is enforced only inside the query handler; UNVERIFIED whether a foreign customer actually gets 404. | checkout-and-payment gap-11 | open | checkout-and-payment | +| BL-160 | server | Staff authorization is inconsistent by layer for tickets — the handler-level role set includes Support/Finance, but the controller attribute blocks them; two different definitions of "staff" in one request path. | messaging-tickets gap-3 | open | messaging-tickets | +| BL-161 | client | `POST`/`DELETE tickets/{id}/participants` are unwired — no UI can attach a third party to a thread. | messaging-tickets gap-7 | open | messaging-tickets | +| BL-162 | client | `close`/`reopen` are live and working server-side but unreachable client-side — one shared flag gates them alongside the genuinely-missing `assign`, so two already-shippable features stay dark. | messaging-tickets gap-8, REQ-063 (residue) | open | messaging-tickets | +| BL-163 | server | `POST tickets/{id}/assign` is a phantom endpoint the client already targets. | messaging-tickets gap-9, REQ-063 | deferred (trigger: ticket assignment prioritized) | messaging-tickets | +| BL-164 | docs | Stale client comment claims the server has no field for `clientMessageId` echo — it is sent and echoed today. | messaging-tickets gap-10 | open | messaging-tickets | +| BL-165 | contract | Client `TicketAuthorRole` declares `system`; the server's role vocabulary doesn't — one side must move. | messaging-tickets gap-11 | open | messaging-tickets | +| BL-166 | product | Auto-created coordination tickets open with no first message — both actors see an empty thread with no explanation of what it's for. | messaging-tickets gap-12 | open | messaging-tickets | +| BL-167 | contract | Ticket message history is unpaginated by contract — a long thread has no incremental read. | messaging-tickets gap-13 | open | messaging-tickets | +| BL-168 | client | Ticket attachments are fully designed but gated off behind a flag. | messaging-tickets gap-14, REQ-060 | deferred (trigger: attachment upload prioritized) | messaging-tickets | +| BL-169 | contract | `refund_issued`/`refund_processed` and `review_moderated`/`review_published` are near-miss names on either side of the same contract. | notifications gap-4 | open | notifications | +| BL-170 | server | `verification_expiry_prompt` is dispatched with a null data payload — even a matching client branch couldn't route it. | notifications gap-5 | open | notifications, nurse-verification | +| BL-171 | client | `NotificationBellPopover` (full mark-read + deep-link logic) is exported but mounted nowhere — dead UI. | notifications gap-9 | open | notifications | +| BL-172 | client | Notification "load more" isn't real pagination — it grows the page size and refetches the whole feed every time. | notifications gap-10 | open | notifications | +| BL-173 | server | Notification retention (90d/24h) is a hardcoded constant rather than a `platform_configs` row, against the repo's config-is-rows convention. | notifications gap-11 | open | notifications | +| BL-174 | client | Both notification pages are client components with no `generateMetadata`, departing from the thin-RSC-page convention. | notifications gap-12 | open | notifications | +| BL-175 | product | No unread-notification cap or archive — the feed grows until the 90-day sweep; unread rows are never separately swept. | notifications gap-13 | open | notifications | +| BL-176 | docs | `IVariantSnapshotSerializer`'s doc-comment names the wrong table for the frozen JSON snapshot. | nurse-catalog-and-pricing gap-6 | open | nurse-catalog-and-pricing | +| BL-177 | server | Category `iconKey` and description fields are null for all seeded categories — the catalog grid falls back to a generic icon with no explainer copy. | nurse-catalog-and-pricing gap-7 | open | nurse-catalog-and-pricing | +| BL-178 | server | `sessionCount` is unvalidated against `priceUnit` — nothing stops an invalid combination from being saved. | nurse-catalog-and-pricing gap-9 | open | nurse-catalog-and-pricing | +| BL-179 | client | The mock payout store's fixture booking ids deep-link into the (now real) bookings screens and 404. | nurse-earnings-and-payouts gap-2 | open | nurse-earnings-and-payouts | +| BL-180 | contract | The client sends `Idempotency-Key` on payout generate/retry, which the controller never reads — decorative and misleading to a reader. | nurse-earnings-and-payouts gap-13 | open | nurse-earnings-and-payouts | +| BL-181 | server | `holidayShifted` is always false — `PayoutBatchDto` carries no such flag, so a bank-holiday date shift can never be shown. | nurse-earnings-and-payouts gap-15 | open | nurse-earnings-and-payouts | +| BL-182 | product | No payout forecast — the nurse dashboard's "next batch" line renders nothing on the real path. | nurse-earnings-and-payouts gap-16, REQ-053 | open | nurse-earnings-and-payouts | +| BL-183 | client | Only one bank-rail failure reason is mapped to a label; every other reason falls back to a generic message. | nurse-earnings-and-payouts gap-17 | open | nurse-earnings-and-payouts | +| BL-184 | docs | Stale payouts doc-blocks assert three live endpoints don't exist server-side — all three return 200. | nurse-earnings-and-payouts gap-18 | open | nurse-earnings-and-payouts | +| BL-185 | client | Duplicate React keys on the nurse-service-areas search results whenever the BL-077 duplicate-listing bug fires. | nurse-service-areas gap-2, search-and-discovery gap-2 | open | nurse-service-areas, search-and-discovery | +| BL-186 | docs | The service-areas integration doc states a conflict rule that the live server doesn't enforce (probed 200 where it claims a conflict). | nurse-service-areas gap-4 | open | nurse-service-areas | +| BL-187 | client | No warning that removing a nurse's last service area de-lists her from search — the confirm-dialog copy doesn't distinguish this case. | nurse-service-areas gap-6 | open | nurse-service-areas | +| BL-188 | ops | Remove-service-area was left end-to-end unverified this pass (token expired mid-probe); code-traced only. | nurse-service-areas gap-7 | open | nurse-service-areas | +| BL-189 | ops | The shared demo DB has drifted from the seeder's own definitions (nurse 1 now has 5 service areas, not the seeded 3) and the idempotent seeder will never correct it. | nurse-service-areas gap-8 | open | nurse-service-areas | +| BL-190 | client | Verification step model omits `isRequired` though the wire serves it, so the "X of Y" progress meter would be wrong the moment an optional step type is added. | nurse-verification gap-9 | open | nurse-verification | +| BL-191 | contract | No `submittedAt` on the nurse verification status, and no per-step detail on the public trust-badge read. | nurse-verification gap-10, REQ-043, REQ-055 | open | nurse-verification, search-and-discovery | +| BL-192 | ops | Seeded verification data leaves two of six catalog step types unexercisable in the demo world. | nurse-verification gap-12 | open | nurse-verification | +| BL-193 | client | `POST customer_profiles/avatar` is live and implemented but no client code calls it — a customer can never set a photo. | onboarding-customer gap-2 | open | onboarding-customer, account-and-settings | +| BL-194 | client | `preferredLanguage` write silently commits a default `'fa'` the customer never chose, the first time they open and save the language sheet. | onboarding-customer gap-4/6 | open | onboarding-customer | +| BL-195 | client | `avatarUrl` on the nurse-profile upsert input is dead on the real path — populated by the form but never sent (harmless today, since the multipart route already persisted it separately). | onboarding-nurse gap-5 | open | onboarding-nurse | +| BL-196 | docs | The onboarding integration doc states duplicate-IBAN returns 409; the handler actually returns 400 with a field error. | onboarding-nurse gap-6 | open | onboarding-nurse | +| BL-197 | client | The bank-account page polls a stuck ownership inquiry forever, with no ceiling and no timeout copy. | onboarding-nurse gap-11 | open | onboarding-nurse | +| BL-198 | server | The HTTP merchant-of-record resolver (`internal/bookings/{id}/center`) is `DynamicPermission`-gated and 403s for both seeded admins — reachable only in-process today. | partner-center gap-7 | open | partner-center | +| BL-199 | server | Two merchant-of-record resolvers can disagree — BNPL reads a platform config value while invoicing resolves the actual center, so a MoR center's BNPL order and invoice could name different sellers. | partner-center gap-10 (folded severity, see BL-013) | open | partner-center, bnpl-installments | +| BL-200 | ops | Seeded partner-center invoices carry null Moadian reference/PDF fields — the settlement view's document columns have nothing real to render. | partner-center gap-11 | open | partner-center | +| BL-201 | product | Center self-onboarding (write-then-masked IBAN) is deferred and has never been exercised on a real route. | partner-center gap-12 | deferred (trigger: center self-service onboarding prioritized) | partner-center | +| BL-202 | client | Welcome page's Open-Graph image is Latin-only — no Persian glyph rendering for the `fa` share card. | public-front-door gap-7 | open | public-front-door | +| BL-203 | client | `/fa/welcome` has no in-app link at all — reachable only as the `/` rewrite body or by typing the URL. | public-front-door gap-8 | open | public-front-door | +| BL-204 | ops | `NEXT_PUBLIC_SITE_URL` is unset in dev, so `robots.txt`/`sitemap.xml`/OG tags fall back to localhost (dev-only caveat, `.env.production` is correct). | public-front-door gap-9 | open | public-front-door | +| BL-205 | ops | `next dev` misrepresents this flow entirely — stale prerenders serve ahead of the middleware; only a production build reproduces real behavior. | public-front-door gap-1 | open | public-front-door | +| BL-206 | server | No anonymous rate limit on the public search/profile reads — falls back to the shared global per-IP limiter. | public-front-door gap-4, search-and-discovery gap-12, REQ-066 (residue) | open | public-front-door, search-and-discovery | +| BL-207 | client | `GET nurses/{id}/review_tags` (the "% of reviewers said X" rollup) and `POST reviews/{id}/tags` are both unwired — no chip-aggregate view and no post-submit tag amendment. | reviews gap-5/6 | open | reviews | +| BL-208 | server | The low-rating support alert is raised on submit but has no reachable triage surface — reads sit behind BL-001. | reviews gap-10 | open | reviews | +| BL-209 | docs | Stale review doc comments claim the mock is still primary; the real client is live and the 14.6KB mock module is dead behind the flag. | reviews gap-11 | open | reviews | +| BL-210 | server | `distanceKm` is always null on search results — the covering index carries no coordinate, so the distance chip can never render. | search-and-discovery gap-7 | open | search-and-discovery | +| BL-211 | contract | `topReviewTag` and `variantDisplayName` are never served on search results — dead code on the client's tag chip and no multi-variant collapse. | search-and-discovery gap-8, REQ-040 | open | search-and-discovery | +| BL-212 | client | `nurseGender` is hardcoded `'female'` in the public-profile mapping because the DTO carries no gender field — unused today, but a live lie in the typed model. | search-and-discovery gap-9, REQ-042 | open | search-and-discovery | +| BL-213 | server | `attributeChips` is empty for every seeded nurse, and a masked reviewer name maps to an empty string — the profile snippet renders a bare separator with nothing around it. | search-and-discovery gap-14 | open | search-and-discovery | +| BL-214 | contract | No nurse-facing read-back of submitted credential details (INO-on-file, specialties) — the returning-nurse form can't hydrate from the server. | REQ-056 | open | nurse-verification | +| BL-215 | contract | Policy numbers (dispute-window hours, cancellation lead-time, refund ETA) have no public/authenticated read — the client keeps them single-sourced in a local constants file. | REQ-065, ui-phase-12 follow-up 3 | open | cancellation-and-refunds | +| BL-216 | contract | No typed address/variant snapshot objects — `BookingDetailDto` still carries opaque JSON strings, forcing the client's defensive multi-key parse in three separate places. | REQ-045, ui-phase-6 follow-up 2, ui-phase-7 follow-up 2 | open | booking-lifecycle-evv, checkout-and-payment | +| BL-217 | client | Two admin-only multi-field dialogs (`GrantRoleDialog` in `admin/roles`, `PreviewBatchDialog` in `admin/payouts`) still hold raw `useState` instead of react-hook-form — the only genuine residue of the app-wide form migration. | iteration-2 #7 | open | admin-backoffice | +| BL-218 | client | `H-01` residue: the auth-gate root-cause fix landed, but `client/middleware.ts` was never migrated to Next 16's `src/proxy.ts` as the original fix prescribed, and `outputFileTracingRoot` was never added alongside `turbopack.root` — a production build may still warn from the stray root-level lockfile. | H-01 | open | auth-login-otp | +| BL-219 | docs | Rename the `SET_VIA_USER_SECRETS_OR_ENV` placeholder sentinel now that `user-secrets` is confirmedly removed — filed by phase 2 with an exact 7-file worklist (`appsettings.*.json`, `StartupSecretsGuard.PlaceholderMarkers`, `.githooks/pre-commit`, `.githooks/README.md`, `StartupSecretsGuardTests.cs`, `docs/rules/shared/git-and-gates.md`, `docs/rules/server/structure.md`). Optional cleanup, not urgent — the name is a load-bearing sentinel and the mechanism is already documented correctly elsewhere. | C-2 (open-contradictions.md) | deferred (trigger: none — cleanup-of-convenience) | — | + +## Deferred (43) + +These are recorded decisions with pull-triggers, not bugs — carried here (per this phase's Status vocabulary) +for phase 5's `roadmap/deferred.md` to pick up. Grouped by theme; each origin id is greppable in the source +handoff/report files for the full context. + +| ID | Area | Item | Origin | Trigger | Blocks | +|----|------|------|--------|---------|--------| +| BL-220 | ops | Redis (shared cache, cross-instance scheduler/money lock) is not deployed — every current seam is correct only for a single instance. | handoff-after-refinement-phase-7/8, refinement-phase-7/8/9 follow-ups | running more than one instance | — | +| BL-221 | server | Elasticsearch `INurseSearch` backend + outbox feeder not built — SQL search is the real MVP implementation. | handoff-after-backend-phase-7, handoff-after-refinement-phase-7/8/9, backend-phase-7 follow-up | SQL search shows real strain | search-and-discovery | +| BL-222 | product | `IAnalyticsSink` / an analytics warehouse is not built — events aren't routed anywhere durable yet. | handoff-after-backend-phase-1, handoff-after-refinement-phase-9 | product pulls it | — | +| BL-223 | server | A real holiday-calendar feed (vs. the seeded/admin-CRUD calendar) is not built. | handoff-after-backend-phase-1, handoff-after-refinement-phase-9 | product pulls it | — | +| BL-224 | server | `organizations`/`organization_nurses` (employer model) tables are not built. | handoff-after-refinement-phase-9, frontend-phase-15 follow-up 2 | product pulls it | — | +| BL-225 | product | `fraud_flags` / ML fraud scoring is not built — manual suspension + support alerts cover this for now. | handoff-after-backend-phase-6/14, handoff-after-refinement-phase-9 | product pulls it | — | +| BL-226 | server | `recurring_booking_schedules` is not built. | handoff-after-refinement-phase-9, frontend-phase-15 follow-up 2 | product pulls it | — | +| BL-227 | server | `bnpl_settlement_entries` (tranched settlement) is modelled but not built. | handoff-after-backend-phase-12, handoff-after-refinement-phase-9 | product pulls it | bnpl-installments | +| BL-228 | product | Nurse availability slots/exceptions (soft scheduling guidance) are not built. | handoff-after-backend-phase-5/7, handoff-after-refinement-phase-9 | product pulls it | booking-request | +| BL-229 | product | Customer national-ID KYC collection is deliberately not built — the product decision is to never gate browsing/booking on it. | handoff-after-backend-phase-3, handoff-after-refinement-phase-9 | never, by design (informational) | — | +| BL-230 | server | A geography bulk-import feed (`IGeoDataImporter`) is deferred — the idempotent seed + admin CRUD is sufficient for MVP. | handoff-after-backend-phase-4, backend-phase-4 follow-up | product pulls it | addresses-and-map | +| BL-231 | product | Holiday/surge pricing, a distinct Companionship pricing tier, and tiered per-category commission are all deferred. | handoff-after-backend-phase-5, backend-phase-5 follow-up | product pulls it | nurse-catalog-and-pricing | +| BL-232 | product | GPS-radius "nurses near me" map discovery is explicitly not planned — coverage stays named-district-only. | handoff-after-backend-phase-4/7 | none — permanent product decision | search-and-discovery | +| BL-233 | server | Automated MoH/INO license lookup and a professional-liability-insurance verification step type are both deferred. | handoff-after-backend-phase-6 | a lookup portal exists / product pulls it | nurse-verification | +| BL-234 | ops | The credential-expiry scan and the EVV no-show sweep both run only via manual admin endpoints — no cron calls either yet. | handoff-after-backend-phase-6/9, backend-phase-6 follow-up | scheduled-ops phase | nurse-verification, booking-lifecycle-evv | +| BL-235 | server | SMS.ir/Ghasedak SMS adapters are not built — only Kavenegar's real-SMS path is wired (attempting either throws at startup by design, never silently mocks). | handoff-after-refinement-phase-8, refinement-phase-8 follow-up | a second SMS vendor is needed | auth-login-otp | +| BL-236 | ops | Finnotech/Moadian token-exchange refresh and the Moadian signing certificate are not wired — both are deploy-time actions once real credentials exist. | handoff-after-refinement-phase-8 | going to a real Moadian integration | partner-center | +| BL-237 | ops | The Moadian reconciliation poll and the refund-settlement (BNPL processing→succeeded) poll are both thin/manual — no scheduled cron for either. | handoff-after-backend-phase-11, handoff-after-refinement-phase-7/8, refinement-phase-6/7/8 follow-ups | scheduled-ops phase | cancellation-and-refunds, bnpl-installments | +| BL-238 | server | Per-provider-code BNPL revert is incomplete — the refund path only drives the SnappPay default regardless of the transaction's actual provider. | handoff-after-refinement-phase-8, refinement-phase-8 follow-up | a second BNPL provider goes live | bnpl-installments | +| BL-239 | product | A dedicated merchant-of-record center-settlement payout path (تسهیم split leg) is deferred per decision 6.6. | handoff-after-refinement-phase-8, refinement-phase-6/8 follow-ups | product pulls it | partner-center | +| BL-240 | ops | The weekly payout-batch cron generates automatically, but processing a batch stays a deliberate, explicit admin action by design (not a bug). | handoff-after-refinement-phase-7, backend-phase-13 follow-up | none — permanent product decision | nurse-earnings-and-payouts | +| BL-241 | product | On-demand/instant nurse payout withdrawal and per-nurse payout-frequency configuration are both deferred (MVP is one fixed weekly cadence for everyone). | handoff-after-backend-phase-13, frontend-phase-12 follow-up 4 | product pulls it | nurse-earnings-and-payouts | +| BL-242 | server | Automated clawback recovery beyond simple next-batch netting is not built. | handoff-after-backend-phase-13 | product pulls it | nurse-earnings-and-payouts | +| BL-243 | server | Two-way (nurse-reviews-customer) double-blind reviews with timed reveal are not built. | handoff-after-backend-phase-14 | product pulls it | reviews | +| BL-244 | server | A first-class `incidents` entity is not built — manual suspension + support alerts stand in for now. | handoff-after-backend-phase-14 | product pulls it | admin-backoffice | +| BL-245 | server | `SuspendNurse`/`ResolveSupportAlert`/`FlagConcern` admin actions were never confirmed built in b15 — worth a direct re-check before relying on them. | backend-phase-14 follow-up | phase 5 verification pass | admin-backoffice | +| BL-246 | server | Partner-center license verification is mocked to manual-approve at MVP rather than an automated eNamad/MoH check. | handoff-after-backend-phase-15 | product pulls it | partner-center | +| BL-247 | product | There is no telephony seam for emergencies — the emergency contact is an out-of-platform `tel:` link by deliberate design. | handoff-after-backend-phase-15 | none — permanent product decision | messaging-tickets | +| BL-248 | client | SMS/push notification channels are not built — only in-app notifications are real. | handoff-after-refinement-phase-9 | notification UX demands out-of-app reach | notifications | +| BL-249 | server | Legacy `UserRefreshTokens` (gRPC auth path) still exists alongside the real session-based REST auth. | backend-phase-2 follow-up | gRPC moves to sessions, or is dropped | auth-login-otp | +| BL-250 | client | The ESLint unused-vars gate is a repo-wide no-op — the config patches an export path that doesn't carry the rule, so `@typescript-eslint/no-unused-vars` never actually runs despite `client/CLAUDE.md`'s golden rule 11 claiming it does. | frontend-phase-13 follow-up 1 | dedicated infra task | — | +| BL-251 | client | PWA/offline caching (Workbox) is unbuilt — marked "maybe" in the original product backlog, i.e. optional. | product/notes open-questions.md | product pulls it | — | +| BL-252 | server | Optional short-TTL cache over hot search result pages was shipped no-cache at MVP. | backend-phase-7 follow-up | search read latency becomes a real problem | search-and-discovery | +| BL-253 | server | A single explicit DB transaction for the payment-webhook confirm path (currently two commits, kept safe via idempotency + a forward-only guard) is flagged as future hardening. | backend-phase-10 follow-up | `IUnitOfWork` grows a transaction scope | checkout-and-payment | +| BL-254 | server | The partner-center dashboard's sponsored-nurse list is capped at 50 with an exact count — needs pagination if a center's roster grows past that. | backend-phase-15 follow-up | a center exceeds 50 sponsored nurses | partner-center | +| BL-255 | server | `Bookings`/`Invoices.partner_center_id` columns exist with no DB-level FK — only `nurse_profiles.partner_center_id` got one, per the shipping phase's own Definition of Done. | backend-phase-15 follow-up, backend-phase-11 follow-up | a data-integrity pass on partner-center columns | partner-center | +| BL-256 | client | Skeleton→content crossfade exists as a one-line-per-screen pattern but was never retrofitted onto every list/detail page. | ui-phase-12 follow-up 1 | a dedicated visual-polish pass | — | +| BL-257 | client | No true desktop search layout — a full responsive pass beyond the phone-width frame was explicitly deferred post-chain. | ui-phase-12 follow-up 2 | product decides to support desktop | search-and-discovery | +| BL-258 | client | Persian OG image variant is Latin-only by deliberate scope cut on `/welcome` and other share cards. | ui-phase-13 follow-up, public-front-door gap-7 | Persian social sharing becomes a priority | public-front-door | +| BL-259 | server | List-row EVV presence indicator (a lightweight "currently checked in" flag on the booking list DTO) was never built — needs a product/API decision to avoid an N+1 read. | ui-phase-5 follow-up 1 | product prioritizes a list-level EVV signal | booking-lifecycle-evv | +| BL-260 | client | Web-push for new nurse booking requests remains deferred — the 15-second poll is the only freshness mechanism. | ui-phase-7 follow-up 5, REQ-054 | push infra (service worker + VAPID + dispatch rail) is built | booking-request | +| BL-261 | client | Ticket attachments' composer button is fully built but gated off pending the upload/signed-URL backend. | REQ-060 (see also BL-168) | attachment backend delivered | messaging-tickets | +| BL-262 | product | Real payment-gateway/Shaparak logos near the pay CTA are deferred — no licensed assets yet; a generic lock-icon trust notice stands in. | ui-phase-6 follow-up 5 | licensed gateway assets obtained | checkout-and-payment | diff --git a/docs/status/decisions.md b/docs/status/decisions.md new file mode 100644 index 0000000..438e606 --- /dev/null +++ b/docs/status/decisions.md @@ -0,0 +1,131 @@ +# Decisions — the distilled engineering decision log + +> Last verified: 2026-08-02 against commit `b876490`. + +Non-obvious decisions with a reason, extracted from `dev/`'s ~3MB of build history so they survive +`dev/`'s move to `archive/` in phase 6. **`product/` wins for business rules** — this file is for +*engineering* decisions, and for business decisions made **during** the build that never made it back into +`product/` (each of those also gets a note filed in [product/notes/](../../product/notes/open-questions.md)). +Each entry: what was decided, when, why, where it binds. + +--- + +## Business rules made during the build (candidates for `product/` too) + +**Commission 0.15 / VAT 0.10, VAT on commission only.** The canonical fee model as of refinement-phase-3: +platform commission is 15% of the gross booking amount; VAT is 10% of the *commission*, not of gross. This +became the single source of truth after the pre-refinement code computed VAT two different ways on two +surfaces (carved out of commission at checkout vs. added to it on the invoice, a 341 IRR disagreement on one +seeded booking — see [backlog.md](backlog.md) BL-060, still open on the invoice screen specifically). Binds: +`server/src/Core/Baya.Application/Features/Payments/`, every invoice/checkout DTO. + +**`district_id = NULL` means whole-city, in both directions.** A nurse service area with no district covers +every district in the city; a search with no district filter matches both district-scoped and whole-city +rows. Binds: `nurse_service_areas`, `SqlNurseSearch`, the coverage-picker UI. See +[backlog.md](backlog.md) BL-077 for the one place this invariant currently double-counts a nurse. + +**Verification `status` is the source of truth; `is_verified` is a guarded flip.** The nurse-facing status +enum drives all business logic; `nurse_profiles.is_verified` only flips inside the same transaction that +moves status to `approved`, never independently. Decided backend-phase-6. Binds: `NurseVerificationService`, +`nurse_search_index.is_searchable`. + +**Booking status is forward-only; the three-amount split has a DB CHECK constraint.** No booking status +transition may move backward, enforced in `BookingTransitions`/`BookingSessionTransitions` +(CONVENTIONS §6 pattern). The `gross`/`platform_commission`/`nurse_payout` three-way split on a booking is +CHECK-constrained to balance at the DB layer, not just in application code. Decided backend-phase-9. + +**The two-stage clinical-disclosure gate; EVV is advisory, never a block.** Care details are disclosed in two +stages — coarse notes pre-acceptance, full encrypted `booking_care_instructions` only to the assigned nurse +post-confirmation. A geofence mismatch on check-in/out (EVV) raises a support alert but never blocks the +visit from proceeding — decided explicitly to avoid a GPS false-positive stranding a nurse mid-shift. Binds: +`booking_requests` vs `booking_care_instructions`, `CheckInVisitCommand`/`CheckOutVisitCommand`. + +**Webhook idempotency is upsert-first; ledger postings must balance.** Every external webhook handler +(payment PSP, BNPL, payout transfer) upserts on the provider's idempotency key *before* any side effect, so a +replayed webhook is a no-op rather than a double-post. Every `LedgerPosting` is validated to balance (debits += credits) before commit — decided backend-phase-10, the same phase that found the payment-webhook confirm +path needs two DB commits (booking creation, then transaction+ledger) because ledger legs need the +DB-generated `booking_id`; flagged as future hardening once `IUnitOfWork` grows a transaction scope (see +[backlog.md](backlog.md) BL-253). + +**Reviews recompute from source; nurse care records are append-only.** A nurse's `average_rating`/ +`total_reviews` are always recomputed from the live review rows, never incrementally maintained, to avoid +drift. Visit-note/care-record writes are append-only — no record is ever edited or deleted, only superseded +by a newer entry. Decided backend-phase-14. + +**`is_internal` ticket messages never appear in user-facing types.** The admin-only internal-note boundary on +a support ticket is enforced by keeping `isInternal` out of every client-facing TypeScript type entirely — +not by a runtime filter that could be bypassed. Decided frontend-phase-14, re-confirmed frontend-phase-15 +when the admin console was built on top of the same domain. + +**One payout per booking (UNIQUE); whole-clawback greedy netting.** A `nurse_payouts` row is +UNIQUE-constrained to one per booking — a booking can never be paid out twice. Clawback recovery uses +greedy whole-amount netting against the next batch rather than partial installments. Decided +backend-phase-13; automated recovery beyond simple netting is deferred (see [backlog.md](backlog.md) +BL-242). + +**Escrow releases after a confirmed check-out; weekly payout generation is automatic, processing stays +manual.** Funds move from `escrow_held` to `nurse_payable` only after a checked-out session passes its +dispute window. Payout *batch generation* runs on a weekly cron (refinement-phase-7); actually *transferring* +money in a batch stays a deliberate, explicit admin action by design — decided refinement-phase-7, +re-affirmed in the backend-phase-13 handoff. Not a bug; see [backlog.md](backlog.md) BL-240. + +**Config lives in files, not a secret store — deliberate pre-launch trade.** `dotnet user-secrets` was +removed (`` dropped from the `.csproj`); all configuration, including live credentials, lives +in `appsettings.*.json` / `.env.*` / `docker-compose.yml`. This is explicitly temporary — root +[CLAUDE.md](../../CLAUDE.md) §6 requires rotating every credential and moving the secret half out of git +before real users (see [backlog.md](backlog.md) BL-003). The one value that must **never** change once real +data exists: `Seams:FieldEncryption:Key`/`:HashKey`. + +**Error state is never an empty state (the client convention).** A failed query must never silently render +the same UI as "no data" — decided in the frontend-phase-1 primitives pass. Still occasionally violated; see +[backlog.md](backlog.md) BL-078 for the one live regression found this phase (`nurse-service-areas`'s +coverage screen drops `isError`). + +**Root `/` forks by auth via a middleware rewrite, never a redirect.** An anonymous visitor to `/` gets the +public landing page's content rewritten in at the same URL; the canonical URL and address bar never change. +Decided ui-phase-13, chosen specifically so `/` stays a stable, shareable, indexable URL for both audiences. + +--- + +## Engineering decisions + +**Phase 2 → Phase 4 handoff, "the rename is filed."** The placeholder secret sentinel +`SET_VIA_USER_SECRETS_OR_ENV` keeps its name — renaming it needs a server-code + git-hook + test change that +is out of a documentation phase's scope, and the string is load-bearing across 7 live files. Filed as +[backlog.md](backlog.md) BL-219, deferred (cleanup-of-convenience, no urgency). + +**Hardening ledger re-verification (C-10) confirms the ledger was right to distrust its own checkboxes.** +All 18 hardening items were re-traced against `b876490` rather than trusted as-filed: 3 were already fixed +(client-only, landed in the "manual improvement" commits well before this doc chain started), 4 are +partially fixed (real progress with concrete residue), and 11 are unchanged since 2026-07-16 despite 14 UI +phases, 2 manual-testing iterations, and a deploy running on top of them. See +[backlog-closed.md](backlog-closed.md) and [backlog.md](backlog.md) for the full disposition of each. + +**REQ-061 (admin user directory) is filed for real, despite never getting a ledger header (C-15).** +`ui-phase-11-report.md` claimed "REQ-061…064 appended," but the append-only ledger's numbering jumps +060→062 — REQ-061's body survived as an orphaned, headerless block. Ten live client files depend on the +endpoints it describes. Ruling: it is a genuine, currently-undelivered backend gap and is carried into +[backlog.md](backlog.md) as BL-029, not treated as a documentation artifact to discard. + +**Five REQs were filed narrower or wrong relative to the live server (C-16).** REQ-050, REQ-063, REQ-066, +REQ-067 and the REQ-029/030 admin-mock justification were all checked against the live swagger rather than +against another document, during phase 2. `variantLabel` already shipped before REQ-050 was filed; +`tickets/close`+`/reopen` already ship (only `assign` is missing); `search/nurses` and +`nurses/{id}/profile` are already anonymous (only rate-limiting and a privacy sign-off are missing, +respectively). Ruling: REQ status is derived from code, never copied from the ledger's own prose — this +phase's REQ classification (see [backlog.md](backlog.md) and [backlog-closed.md](backlog-closed.md)) +followed the same rule throughout, and found one further instance on its own: REQ-050's "neither field +exists" claim was stale even for the field it explicitly named (`variantLabel`), and REQ-057's residue was +found duplicated across two unrelated flows independently citing the same missing DTO field. + +**A `true` mock flag does not mean the domain is fully fake, and a `false` flag does not mean it's fully +real.** `verification` has 10 of 14 operations live and probed while mocked; `payment` is flag-real with only +2 of 6 operations actually working. Decided as the framing for this whole reconciliation (phase 3's finding) +— every BL item in this backlog that touches a `USE_*_MOCK` flag states the *specific* operations affected, +never just cites the flag. + +**Deferred items keep their pull-trigger, not a target date.** Following the phase-chain convention set in +`dev/shared-working-context/backend/handoff/`, every item in [backlog.md](backlog.md)'s Deferred section +carries the condition that should cause it to be picked up (a specific product decision, a scale threshold, +a second integration) rather than a scheduled date. Phase 5 owns turning these into a sequenced roadmap. diff --git a/docs/status/implemented.md b/docs/status/implemented.md new file mode 100644 index 0000000..6c9f30f --- /dev/null +++ b/docs/status/implemented.md @@ -0,0 +1,68 @@ +# Implemented — the product overlay + +> Last verified: 2026-08-02 against commit `b876490`. + +The mapping [`product/`](../../product/index.md)'s 14 business areas need and never had: which flow builds +each area, and what state it's actually in. `product/` itself stays untouched — this file is the bridge +between "what the business is" and "what we built." States: `built` (end-to-end real) · `partial` (real with +named gaps) · `mocked` (UI real, data fake) · `not started` · `deferred`. + +Every row's Gaps column links into [backlog.md](backlog.md); the full evidence is in the linked +[docs/flows/](../flows/index.md) file, not restated here. + +## Business area → build state + +| # | Business area | Doc | Primary flow(s) | State | Gaps | +| --- | --- | --- | --- | --- | --- | +| 01 | Actors & Onboarding | [product/business/01-…](../../product/business/01-actors-and-onboarding.md) | [auth-login-otp](../flows/auth-login-otp.md) **(built)**, [onboarding-customer](../flows/onboarding-customer.md), [onboarding-nurse](../flows/onboarding-nurse.md) | partial | BL-023, BL-031..038, BL-085..092, BL-193..197, BL-249 | +| 02 | Nurse Verification | [product/business/02-…](../../product/business/02-nurse-verification.md) | [nurse-verification](../flows/nurse-verification.md) | **mocked** | BL-010 (blocker), BL-080..084, BL-190..192, BL-214, BL-233 | +| 03 | Catalog & Pricing | [product/business/03-…](../../product/business/03-service-catalog-and-pricing.md) | [nurse-catalog-and-pricing](../flows/nurse-catalog-and-pricing.md) | partial | BL-018 (blocker — zero option groups outside Development), BL-069..072, BL-176..178, BL-231 | +| 04 | Search & Matching | [product/business/04-…](../../product/business/04-search-and-matching.md) | [search-and-discovery](../flows/search-and-discovery.md) | partial | BL-014 (blocker), BL-103, BL-104, BL-210..213, BL-221, BL-232 | +| 05 | Booking & Scheduling | [product/business/05-…](../../product/business/05-booking-and-scheduling.md) | [booking-request](../flows/booking-request.md) | partial | BL-006 (blocker), BL-040..044, BL-071, BL-138..143, BL-228, BL-260 | +| 06 | EVV / Service Delivery | [product/business/06-…](../../product/business/06-evv-and-service-delivery.md) | [booking-lifecycle-evv](../flows/booking-lifecycle-evv.md) | partial | BL-016 (blocker), BL-039, BL-043..046, BL-133..137, BL-259 | +| 07 | Cancellation & Refunds | [product/business/07-…](../../product/business/07-cancellation-and-refunds.md) | [cancellation-and-refunds](../flows/cancellation-and-refunds.md) | **mocked** | BL-009 (blocker), BL-047..054, BL-144..148, BL-215, BL-237 | +| 08 | Payments & Escrow | [product/business/08-…](../../product/business/08-payments-and-escrow.md) | [checkout-and-payment](../flows/checkout-and-payment.md) | partial | BL-005 (blocker), BL-058..062, BL-156..159, BL-253, BL-262 | +| 09 | Installments / BNPL | [product/business/09-…](../../product/business/09-installments-bnpl.md) | [bnpl-installments](../flows/bnpl-installments.md) | **mocked** | BL-007, BL-008 (blockers), BL-126..132, BL-227, BL-238 | +| 10 | Payouts | [product/business/10-…](../../product/business/10-payouts.md) | [nurse-earnings-and-payouts](../flows/nurse-earnings-and-payouts.md) | **mocked** | BL-012 (blocker), BL-073..076, BL-179..184, BL-240..242 | +| 11 | Reviews, Trust & Safety | [product/business/11-…](../../product/business/11-reviews-trust-and-safety.md) | [reviews](../flows/reviews.md) | partial | BL-017 (blocker), BL-099..102, BL-207..209, BL-243 | +| 12 | Messaging & Emergencies | [product/business/12-…](../../product/business/12-messaging-and-emergencies.md) | [messaging-tickets](../flows/messaging-tickets.md) | partial | BL-063..068, BL-160..168, BL-247, BL-261 | +| 13 | Tax, Invoicing & Legal | [product/business/13-…](../../product/business/13-tax-invoicing-and-legal.md) | [partner-center](../flows/partner-center.md) (+ invoice half of `checkout-and-payment`, VAT — see BL-060) | **mocked — weakest area** | BL-013 (blocker), BL-093..095, BL-198..201, BL-236, BL-239, BL-246, BL-254, BL-255 | +| 14 | Notifications & Admin | [product/business/14-…](../../product/business/14-notifications-and-admin.md) | [notifications](../flows/notifications.md) (partial), [admin-backoffice](../flows/admin-backoffice.md) (**mocked**) | **mocked (admin half)** | BL-001, BL-002 (blockers — the RBAC finding that touches 11 of 14 areas), BL-114..117, BL-124, BL-125, BL-169..175, BL-244, BL-245 | + +**No area is orphaned** — all 14 map to a primary flow, matching [docs/flows/index.md](../flows/index.md)'s +coverage accounting. **Area 13 is confirmed the weakest**: invoicing and VAT are split across two flows with +no single owner, the entire partner-center portal is mocked with zero tenancy, and BNPL/invoicing can name +two different merchant-of-record centers for the same booking (BL-199). + +**One flow has no `product/` source at all**: `account-and-settings` is a ui-phase-9 UI decision with no +business-area file behind it — not a gap in this table, just a fact worth knowing before editing it. + +## Cross-cutting: the RBAC finding + +[BL-001](backlog.md#blockers-18) (admin RBAC grants only the literal role `admin`, which no seeded account +holds) is the single defect with the widest blast radius in this table — it independently degrades the admin +half of areas 02, 07, 09, 10, 11, 12, 13 and 14, plus the admin-only surfaces inside 03, 05 and 06. Fixing it +does not fix any area's `mocked` state on its own (most of those are separately blocked by their own +`USE_*_MOCK` flag or missing route — see each row above), but it is the single highest-leverage fix in the +whole backlog: [docs/flows/index.md](../flows/index.md#the-six-things-that-surprise-everyone) independently +found the same root cause from the code side. + +## Data model — never-built product tables + +The STATUS logs and [product/data-model/index.md](../../product/data-model/index.md) name **8 tables** +modeled in the product data model that have no implementation today, all `[DEFERRED]` by product decision +(not oversight — see [decisions.md](decisions.md)): + +| Table | Backs | Product doc | +| --- | --- | --- | +| `organizations` | Employer/company account model | [13-partner-centers-and-future.md](../../product/data-model/13-partner-centers-and-future.md) | +| `organization_nurses` | Employer↔nurse membership | same | +| `fraud_flags` | ML-scored fraud signals (manual suspension covers this today) | same | +| `recurring_booking_schedules` | Recurring/subscription bookings | same | +| `bnpl_settlement_entries` | Tranched BNPL settlement (one settlement row covers it today) | [08-bnpl.md](../../product/data-model/08-bnpl.md) | +| `nurse_availability_slots` | Soft scheduling-guidance windows | [05-booking-and-scheduling.md](../../product/data-model/05-booking-and-scheduling.md) | +| `nurse_availability_exceptions` | Time-off exceptions to the above | same | +| `incidents` | First-class incident entity (support alerts cover this today) | [11-reviews-trust-and-safety.md](../../product/business/11-reviews-trust-and-safety.md) | + +None of these are backlog items — they're recorded product decisions to not build yet, carried in +[backlog.md](backlog.md)'s Deferred section (BL-222 through BL-247) with their pull-triggers. diff --git a/docs/status/index.md b/docs/status/index.md index 9eec607..3a96e57 100644 --- a/docs/status/index.md +++ b/docs/status/index.md @@ -1,33 +1,75 @@ # Status — where the project actually is -> **Populated by phase 4 — not yet written.** Phase 4 owns **149 of the 275 surveyed markdown files**, -> by far the largest extraction job in the chain. Until then, open work is spread across five ledgers -> that do not reference each other. +> Last verified: 2026-08-02 against commit `b876490`. Populated by phase 4 of the +> [documentation clean-up chain](../_plan/README.md). -## Planned contents +This is the page you open after two weeks away. "What's left?" now has one honest answer instead of five +unreconciled ledgers. -| File | Covers | +## The picture in one table + +| | Count | | --- | --- | -| `implemented.md` | The overlay: `product/`'s 14 business areas → build state. `product/` itself stays untouched | -| `backlog.md` | Every open item as `BL-###`, triaged, each carrying its origin id | -| `backlog-closed.md` | Items confirmed done, with what closed them | -| `decisions.md` | The distilled decision log from the whole phase chain | +| Flows: built · partial · mocked · not started | 1 · 15 · 7 · 0 (of 23) | +| Business areas covered (of 14) | 14 — none orphaned; **area 13 (Tax, Invoicing & Legal) is weakest** | +| Backlog items — open + deferred | **262** (`BL-001`…`BL-262`) | +| — blocker | 18 | +| — major | 86 | +| — minor | 115 | +| — deferred (has a pull-trigger) | 43 | +| Backlog items — closed this phase | 88 | +| Raw candidate rows harvested (pre-dedup, across 10 sources) | ~700 | -## The five ledgers phase 4 reconciles +Full detail: [backlog.md](backlog.md) (open) · [backlog-closed.md](backlog-closed.md) (closed) · +[implemented.md](implemented.md) (business-area overlay) · [decisions.md](decisions.md) (the ADR log). -| Source | Size | Note | -| --- | --- | --- | -| [frontend/requests/for-backend.md](../../dev/shared-working-context/frontend/requests/for-backend.md) | 67 REQs, 104 K | Append-only; dispositions must be re-checked, not copied | -| [hardening/issues.md](../../dev/post-phase/hardening/issues.md) | 18 items | **18 of 18 unticked**, last touched 2026-07-17 — while four later chains shipped. Re-verify each against code (**C-10**) | -| `reports/*.md` "Follow-ups for later phases" | 53 reports | Every report has one. Confirmed present in all 53 | -| `backend/handoff/*.md` | 22 files | Deferrals with pull-triggers | -| [product/notes/open-questions.md](../../product/notes/open-questions.md) + [manual-testing/](../../dev/manual-testing/) ×2 | — | Business questions and two raw human feedback notes | +## The five things standing between here and a usable product -Plus [mocks-registry.md](../../dev/shared-working-context/reports/mocks-registry.md) (75 K) — the seam-by-seam -mock/real status that `implemented.md` needs, and the only file that tracks it. +1. **Admin RBAC is structurally dead ([BL-001](backlog.md#blockers-18)/[BL-002](backlog.md#blockers-18)).** + `DynamicPermissionService` grants only the literal role `admin`, which no seeded account holds — every + admin surface 403s. This one root cause independently degrades **11 of 14 business areas** + ([implemented.md](implemented.md#cross-cutting-the-rbac-finding)). Highest leverage fix in the backlog. +2. **The two real money rails both dead-end before completion.** Card payment redirects to a host that + doesn't exist and nothing fires the PSP webhook locally ([BL-005](backlog.md#blockers-18)); no BNPL + gateway is ever seeded, so every BNPL call 400s, and the wizard's own mock cross-imports a store that + 404s on any real booking id ([BL-007](backlog.md#blockers-18)/[BL-008](backlog.md#blockers-18)). Neither + money path can be walked end-to-end from a browser today. +3. **Five domains are 100% client-mocked while a working server sits behind them** — verification, refunds, + nurse payouts, patient/care records, and the entire partner-center portal + ([BL-009](backlog.md#blockers-18), [BL-010](backlog.md#blockers-18), [BL-011](backlog.md#blockers-18), + [BL-012](backlog.md#blockers-18), [BL-013](backlog.md#blockers-18)). Three of the five would also *break* + on a naive flip — the client and server DTO shapes have drifted. +4. **The booking payment window lies to the customer.** Deadline timestamps ship with no timezone, so in + Tehran a 30-minute countdown renders as ~4 hours and the request silently expires while the timer still + shows time left ([BL-006](backlog.md#blockers-18)). +5. **Two pre-launch security items are live right now.** The repo's committed credentials (DB `sa`, both + encryption-key halves, three third-party API keys) have never been rotated + ([BL-003](backlog.md#blockers-18)), and a Development-only OTP-read endpoint is reachable on the + production domain because the deployment runs as Development + ([BL-004](backlog.md#blockers-18)). -## Where decisions come from +## Where to go next -Every `dev/phases/*` prompt shares a fixed structure, and **§5 "Critical rules you must not get wrong"** -is the section that states constraints *not visible in the code*. Verified present in all 32 prompts — -that is the raw material for `decisions.md`. See [the inventory](../_plan/inventory.md). +| Question | Answer | +| --- | --- | +| Is flow X built? | [docs/flows/index.md](../flows/index.md) — the status table, 23 flows | +| How do I test it? | [docs/flows/testing-setup.md](../flows/testing-setup.md) | +| What's the client↔server contract? | [docs/integration/index.md](../integration/index.md) | +| What are the hard rules? | [docs/rules/index.md](../rules/index.md) | +| What's open, and how bad is it? | [backlog.md](backlog.md) | +| What's already closed? | [backlog-closed.md](backlog-closed.md) | +| Which business area is built vs. mocked? | [implemented.md](implemented.md) | +| Why was X built that way? | [decisions.md](decisions.md) | +| What's next, in order? | [docs/roadmap/index.md](../roadmap/index.md) — phase 5, not yet written | + +## How this file's numbers were produced + +Five ledgers — the [hardening issues list](../../dev/post-phase/hardening/issues.md) (18 items, all +unticked since 2026-07-16), the [67-REQ contract ledger](../../dev/shared-working-context/frontend/requests/for-backend.md), +53 phase reports' own "Follow-ups" sections, 22 backend hand-off files' deferrals, and two rounds of raw +manual-testing notes — were harvested in full, then every hardening item, every REQ, and every manual-testing +bullet was **re-verified against the code at `b876490`**, not trusted as filed. Phase 3's **283 +already-verified flow gaps** (each with a live code trace, several walked against a booted server) are this +phase's primary, freshest input and form the backbone of [backlog.md](backlog.md). Nothing here is copied +from a stale document without a fresh check — the phase-chain's own rule (["verify, don't +copy"](../_plan/README.md#non-negotiables-for-every-phase)) held throughout.