cleanup docs phase 4

This commit is contained in:
hamid
2026-08-02 18:08:40 +03:30
parent b876490246
commit cd8144e653
7 changed files with 782 additions and 36 deletions
+2 -2
View File
@@ -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 | | 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 | | 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 | | 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 | | | | 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 | not started | | | | 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 | | | | 5 Roadmap | not started | | |
| 6 Archive & prune | not started | | | | 6 Archive & prune | not started | | |
| 7 Skills & guardrails | not started | | | | 7 Skills & guardrails | not started | | |
+75 -11
View File
@@ -139,20 +139,84 @@ you open when you come back after two weeks away.
## Verification ## Verification
- [ ] Every one of the 18 hardening items has a verdict backed by a code check — none carried over - [x] Every one of the 18 hardening items has a verdict backed by a code check — none carried over
unexamined. unexamined. (3 fixed, 4 partially-fixed, 11 open, 0 obsolete — see `backlog-closed.md`/`backlog.md`.)
- [ ] Every REQ-001…067 is classified; the count of `open` is explicit. - [x] Every REQ-001…067 is classified; the count of `open` is explicit. (28 delivered, 1 obsolete, 6
- [ ] Every "Follow-ups for later phases" section across the 50 reports was read and harvested. deferred, 2 partially-delivered, 30 open.)
- [ ] Both iteration notes are fully accounted for, bullet by bullet. - [x] Every "Follow-ups for later phases" section across the 53 reports was read and harvested. (Split
- [ ] No `BL-###` duplicates another; every one names its origin id(s). across 3 parallel agents by report family; ~211 raw rows extracted, faithfully, with light staleness
- [ ] Every `blocker` names the flow it blocks. flags rather than re-verification — these are historical build-phase notes, not fresh claims.)
- [ ] `implemented.md` covers all 14 business areas and all 23 flows. - [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 ## 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 ## Handoff
_(filled in by the agent that runs this phase — item counts by severity, and anything that turned out **Done 2026-08-02 against `b876490`, in one session using 10 parallel Sonnet subagents for the harvest.**
to be worse than the old ledgers claimed)_
### 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.
+135
View File
@@ -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/`<Money>`/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.
+306
View File
@@ -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 |
+131
View File
@@ -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 (`<UserSecretsId>` 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.
+68
View File
@@ -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.
+65 -23
View File
@@ -1,33 +1,75 @@
# Status — where the project actually is # Status — where the project actually is
> **Populated by phase 4 — not yet written.** Phase 4 owns **149 of the 275 surveyed markdown files**, > Last verified: 2026-08-02 against commit `b876490`. Populated by phase 4 of the
> by far the largest extraction job in the chain. Until then, open work is spread across five ledgers > [documentation clean-up chain](../_plan/README.md).
> that do not reference each other.
## 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 | | Flows: built · partial · mocked · not started | 1 · 15 · 7 · 0 (of 23) |
| `backlog.md` | Every open item as `BL-###`, triaged, each carrying its origin id | | Business areas covered (of 14) | 14 — none orphaned; **area 13 (Tax, Invoicing & Legal) is weakest** |
| `backlog-closed.md` | Items confirmed done, with what closed them | | Backlog items — open + deferred | **262** (`BL-001``BL-262`) |
| `decisions.md` | The distilled decision log from the whole phase chain | | — 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 | ## The five things standing between here and a usable product
| --- | --- | --- |
| [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 |
Plus [mocks-registry.md](../../dev/shared-working-context/reports/mocks-registry.md) (75 K) — the seam-by-seam 1. **Admin RBAC is structurally dead ([BL-001](backlog.md#blockers-18)/[BL-002](backlog.md#blockers-18)).**
mock/real status that `implemented.md` needs, and the only file that tracks it. `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"** | Question | Answer |
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). | 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.