Files
baya-monorepo/archive/docs/status/backlog.md
T
2026-08-02 20:01:31 +03:30

307 lines
67 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Backlog — every open item, triaged
> Last verified: 2026-08-02 against commit `51e86a1`.
Reconciled from five ledgers — [hardening/issues.md](../../archive/post-phase/hardening/issues.md) (18 items),
[for-backend.md](../../archive/build-chain/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 a worklist (`appsettings.*.json`, `StartupSecretsGuard.PlaceholderMarkers`, `StartupSecretsGuardTests.cs`, `docs/rules/server/structure.md`); the two `.githooks/*` files in the original worklist no longer exist (phase 7 dropped the pre-commit hook — see [decisions.md](decisions.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 | **Verified 2026-08-02 (phase 5), resolving this row's own trigger:** `ResolveSupportAlert`, `AssignSupportAlert` (`SupportAlertsController.cs`), and nurse suspension (`AdminSuspendVerificationCommand`, feature folder `SuspendVerification`) are all real, built, and code-traced. Only `FlagConcern` — a softer flag-without-suspending action — is genuinely absent from the codebase; not separately filed as it has no UI or caller either. | backend-phase-14 follow-up, phase-5 roadmap verification | none — resolved; the residual `FlagConcern` gap is small enough to pick up opportunistically, not worth its own BL-### | 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 |