# Decisions Register

The single place where open business questions get answered and where technical
choices are recorded. Code that depends on an entry here must cite its ID.

- **D1–D4** — decisions confirmed with the client, recorded in the header of
  [`database/db-schema.sql`](database/db-schema.sql). They override the BRD.
- **DEC-xx** — business decisions inherited from the BRD Project Guide §13. All
  were *Open* at BRD v1.3; many are now answered by the schema.
- **TD-xx** — technical decisions made by the engineering team.

**Statuses:** `Open` (no answer — stop and ask before building behavior that
depends on it) · `Partly resolved` (build what is decided; the remainder is a
setting or still open) · `Working assumption` (build with this, keep it
configurable) · `Resolved` (final) · `Proposed` (TD awaiting approval) ·
`Accepted` (TD in force) · `Superseded`.

> Agents: when you resolve or assume anything, add a row to the
> [resolution log](#resolution-log) and update the status here. Never edit a
> resolved decision silently — supersede it with a new log entry.

---

## Client-confirmed decisions

Source: `db-schema.sql` header ("Decisions confirmed with the client where BRD
and design differ"). Status: **Resolved**.

| ID | Decision | Overrides | Affects |
|----|----------|-----------|---------|
| D1 | **Packages are prepaid multi-session bundles** with a validity period (30/45/60/90 days), reviewed by Joshen before they go live. Customers buy a package, then redeem sessions as separate orders. | BR-07 (admin can only hide) — admin now also approves/rejects | PROV-011, ADMIN-011, CUST-008 |
| D2 | **Group booking:** one order, one provider, one place/time, several people each with their own services, **one delivery fee**. | New scope | CUST-012/013/014, PROV-014 |
| D3 | **Instant booking:** customer pays first, then the provider has an **accept window** (default 20 s, setting `provider_instant_response_seconds` in `app_configs`, group `response_timers`, editable from the dashboard). Reject or timeout ⇒ automatic refund. | BR-12 / DEC-01 working assumption (no accept/reject) | CUST-012, CUST-015, PROV-012/014/015 |
| D4 | **An establishment is a single provider account** (no staff accounts). `specialists_count` is informational only. | — | PROV-001/002 |

**Design additions adopted** (superset of the BRD, same source — treated as
approved scope): service duration, notes to the specialist, customer city /
birth year / photo, notification preferences, rating tags, provider radius +
districts, default transport preference, document expiry dates, coupons, saved
cards / Apple Pay / Tabby, several Featured plans + VAT, cancellation fees as
admin-editable rules.

**Screen additions adopted** (2026-10-20, project owner — UI scope from
`docs/Joshen_screens_by_feature/`, business rules unchanged): the items
`SCR-08`…`SCR-25` in [Project Guide §17.3](brd/BRD-Khadami-Project-Guide-en.md#screen-alignment)
— intro slides, home header, "from" price on service cards, Finder lists and
budget brackets, provider profile page, 45 min instant-arrival default, re-order,
live tracking map, two-step provider/driver registration, document list,
provider home and earnings views, rating/review screens, support entry points,
extra notifications. Screens that contradict a decision or a business rule are
listed in §17.1, §17.2 and §17.4.

## Business decisions (from BRD v1.3)

| ID | Topic | Status | Decision / working assumption | Affects |
|----|-------|--------|-------------------------------|---------|
| DEC-01 | Instant booking: does the provider accept/reject? | **Resolved** by D3 | Instant: pay → provider accept window → confirmed; reject/timeout ⇒ auto refund. Scheduled: provider accepts → customer pays → confirmed. | CUST-012/013/015, PROV-012/014/015 |
| DEC-02 | Joshan: special requests vs guided search | **Resolved** by schema | "Joshen finder" = guided search (category or package, occasion, address/district, date/time, budget) logged in `finder_searches`. No admin-handled special requests. | CUST-009, ADMIN-012 |
| DEC-03 | "Courses", "live sessions", 99.9% uptime wording | Open | Treated as out of scope; NFR boundary undefined. | NFRs |
| DEC-04 | Capacity, overlapping appointments, durations, slot holds | Partly resolved | Durations exist (`services.default_duration_min`, option `extra_duration_min`, `orders.estimated_duration_min`); working hours + time off per provider. TD-92 supplies the Phase 2 overlap working assumption; capacity and slot holds remain open. | PROV-012/013, CUST-012/013 |
| DEC-05 | Provider response deadline, payment deadline, expiry | Partly resolved | Deadlines are per-order timestamps (`provider_response_due_at`, `payment_due_at`) with status `expired`. TD-92 supplies configurable working values; client confirmation of the numbers remains open. | CUST-013, PROV-015 |
| DEC-06 | Payment gateway vendor, callbacks, retries, duplicates | Partly resolved | Methods: card, mada, Apple Pay, Tabby, wallet, wallet + gateway; unique `(gateway, gateway_reference)` blocks duplicates; split amounts recorded. **Open:** vendor (Moyasar / HyperPay / Tap…), callback verification. | CUST-015, PROV-007, ADMIN-015 |
| DEC-07 | Driver search duration; post-acceptance failures | Partly resolved | Search window = setting (`trips.search_expires_at`); `no_driver` status; one live trip per order, retries allowed. **Open:** driver cancel / no-show after acceptance. | PROV-018, DRIV-008/009 |
| DEC-08 | Who confirms driver arrival | Resolved 2026-11-01 (TD-149) | Any one of driver, provider or customer confirms an `arrived` trip; the first confirmation completes it and is stored in `trips.arrival_confirmed_by`; the provider starting the service confirms implicitly. | DRIV-015, earnings |
| DEC-09 | Discounts, commission base, rounding, VAT, gateway fees | Partly resolved | Total = `services_amount + package_amount + delivery_fee + vat_amount − discount_amount`; coupons funded by platform or provider; VAT 15% on Featured plans; commission rules percent/fixed with `effective_from`; gateway fee recorded. **Open:** VAT base on orders, rounding. | CUST-011, ADMIN-015/016 |
| DEC-10 | Wallet and refunds | Partly resolved (wallet only, TD-152) | Wallet = customers only, refund credits spent on new orders; **no top-up, no withdrawal**. Refund destination `wallet` (default) or `original_method`. **Resolved for now (TD-152, owner 2026-11-01):** every refund goes to the wallet; `original_method` stays unused until the client says when it applies. | CUST-016/020, ADMIN-018 |
| DEC-11 | Payout cycle, approval, evidence | Resolved 2026-11-02 (TD-158) | Earnings `pending → available → in_payout → paid`; payouts are never automatic: the provider or driver **asks** for a transfer, the admin reviews it, sends the money by hand outside the system to the saved IBAN, records the bank reference and marks it done. **Owner answers 2026-11-02:** an earning is `available` as soon as it is written (service completion / arrival confirmation) — no hold period, no scheduler; a request claims all her available earnings; one open request at a time; a minimum from the `payout_min_amount` setting (default none). The weekly Sunday cycle of SCR-29 is not built. | PROV-023, DRIV-017, ADMIN-017 |
| DEC-12 | Featured plan details | Partly resolved | Several plans (monthly/yearly), price + VAT, priority level, badges, auto-renew, one active subscription per provider, pay by card or earnings deduction. Badge display: DEC-23. **Open:** refunds on cancellation, suspension effects. | PROV-007, ADMIN-005 |
| DEC-13 | Notification channels | **Resolved** by schema | Per event × audience template with in-app / push / SMS / WhatsApp toggles; user preferences per category (orders, payments, chat, offers, account) for push and SMS; delivery log per channel. | All notification features |
| DEC-14 | Admin permission matrix | Partly resolved | Matrix = 21 sections × view / create / update / delete / approve, implemented on base RBAC (TD-08). Final sections (TD-27): dashboard, customers, providers, drivers, registration_review, service_areas, services (+ Joshen finder), packages, orders, transportation, payments (+ coupons), payouts (+ commissions), cancellations_refunds, featured_subscriptions, ratings, banners_content (+ FAQs, static pages, onboarding), notifications, support, reports, settings (+ feature flags), administrators (+ audit logs). Grant rules: TD-28. Owner approved participant order chat in TD-144; **open:** admin chat-monitoring access, sensitive-record access, and general conversation retention period. No automatic deletion schedule is approved. | ADMIN-024, ADMIN-003/013, CUST-018, PROV-021 Chat monitoring (2026-11-01, TD-150): read-only for the `orders` section, every history read audited, no deletion; general retention period still open. |
| DEC-15 | OTP and session policy | **Resolved** 2026-10-20 | **Customers, providers, drivers have no password** (`users.password` stays `NULL`). Login and registration = `phone_code` + `phone` → SMS OTP → token. **Admins** log in with email + password. OTP via the base Verification module (`send('phone', …, $phone_code)`): 6 digits, 10 min expiry, 5 attempts (`modules/Verification/config/verification.php`). *Code length/generation temporarily superseded by TD-24; session policy (token lifetimes, refresh window) in TD-29.* **Open:** SMS provider — the SMS channel only logs today; pick a Saudi gateway (e.g. Unifonic, Taqnyat, Msegat) before Phase 1 ships. | CUST-002/003, PROV-001/005, DRIV-001/004, ADMIN-001 |
| DEC-16 | Platforms and architecture | **Resolved** 2026-10-20 | Customer, provider, driver apps = Flutter; admin dashboard = React JS; backend = Laravel 13 API (this repo). | All |
| DEC-17 | Languages, currency, timezone | **Resolved** 2026-10-20 | All four apps ship in **Arabic and English** (`Accept-Language: ar` default, `en`), RTL for Arabic. Currency **SAR**; times stored in **UTC**, shown in Asia/Riyadh. Content: Arabic translation required, English optional with Arabic fallback, stored in `{entity}_translations` (TD-12). `config/geo.php` still defaults to `EG`/`EGP` — fix with the first geo/booking feature. | All |
| DEC-18 | Female-user eligibility | Working assumption | Declaration stored per profile as `customers` / `providers` / `drivers`.`female_declared_at` (+ terms/privacy timestamps, TD-13). No further verification. | CUST-002, PROV-001, DRIV-001 |
| DEC-19 | Ratings | **Resolved** by schema | 1–5 stars, one review per order per direction, optional public comment + private note for Joshen, tags, admin can hide. Directions: customer→provider, provider→customer, **provider→driver** (new). | CUST-021, PROV-024, ADMIN-019 |
| DEC-20 | KPIs and performance targets | Partly resolved 2026-11-02 (TD-161) | KPI list = the BRD's ADMIN-002 / ADMIN-022 summaries and rankings (owner decision). **Open:** performance targets and NFR KPIs. | ADMIN-022, NFRs |
| DEC-21 | Support | **Resolved** by schema | Reasons per app (with urgent flag), threaded admin/user messages, attachments, statuses `under_review → replied → closed`, optional link to order/trip/change request. | CUST-024, PROV-026, DRIV-019, ADMIN-026 |
| DEC-22 | Config changes vs existing records | **Resolved** by schema | Snapshot on the order: names, prices, options, commission rate, address. Commission rules carry `effective_from`. | All money features |
| DEC-23 | "Verified" badge vs featured plan (screens, SCR-07) | Partly resolved 2026-10-20 | Customer and driver apps show **"Verified" from verification status** (approved account + verified documents), never from the plan. The provider app shows the plan she subscribed to: the provider user resource returned at login carries `is_subscribed` (bool) and a mini resource of the subscribed plan. Plan effect for customers = ranking priority (BR-09). **Open:** customer-facing "Featured" label, home-page package highlighting, monthly performance report (shown in screens). | PROV-007, CUST-010, DRIV-005, ADMIN-005 |
| DEC-24 | Tiered cancellation fees (screens, SCR-06) | **Resolved** 2026-10-20 | **BRD policy kept.** Default `cancellation_rules` = full refund to wallet before `on_the_way`, no customer cancellation after execution starts, no provider/driver earnings, provider cancels directly (no "request"), no ranking penalty. The screens' 2 h / 5 min / 50 % tiers are not adopted. | CUST-020, PROV-016, ADMIN-018 |
| DEC-25 | Profile, document, bank changes (screens, SCR-26) | **Resolved 2026-11-10 (TD-184)** | Structured in-app request with admin review (the BRD reading); the old data stays effective until approval. Matrix and rules: TD-184. Direct edits for the first set and the photo are unchanged. | PROV-008, DRIV-006 |
| DEC-26 | Driver offer countdown and address (screens, SCR-27) | Partly resolved 2026-10-20 | **Countdown adopted:** each driver offer has a response countdown, setting `driver_offer_response_seconds` (default 12 s) in `app_configs`, group `response_timers`, editable from the dashboard; an unanswered offer counts as not accepted and the search continues within the search window (DEC-07). **Open:** showing the full drop-off address before acceptance — until decided, brief trip info before acceptance, full details after (DRIV-010). | DRIV-008/009/010 |
| DEC-27 | Customer ↔ provider phone calls (screens, SCR-28) | Open | Screens add call buttons; BRD has order chat only (deferred, TD-16). Decide whether calls are allowed and whether numbers are masked. **Until decided:** no call feature is built. | CUST-018, PROV-021 |
| DEC-28 | Unmutable notification messages | Open (working assumption TD-166, 2026-11-04) | Which push messages a user may never switch off (candidates: `account` review results, payment failures, order cancellation). **Until decided:** every category can be muted; the inbox row is always written, so nothing is lost. The code constant `NotificationCategoryMap::ESSENTIAL` is empty and is the only place to change once decided. | CUST-022, PROV-025, DRIV-018 |
| DEC-29 | Push token on logout and on a shared device | Open (2026-11-04) | `POST …/auth/logout` detaches this app's token only when the app sends `device_token`; without it the token keeps receiving pushes for that user, and a second account on the same phone does not remove the first account's row. Decide whether logout must detach by itself (for example by current device) and whether registering a token under a new user removes it from the previous user. **Until decided:** unchanged; the apps should send `device_token` on logout. | CUST-022, PROV-025, DRIV-018 |

### BRD rules changed by the decisions above

| Rule | Change |
|------|--------|
| BR-07 | Packages also need Joshen review (`pending_review → approved`) before customers see them (D1). |
| BR-12 | Instant booking has a provider accept window after payment (D3). |
| BR-20 | Cancellation can carry a fee via admin-editable `cancellation_rules` (design addition). Default rule set must reproduce the BRD: full refund before execution. Execution starts at `on_the_way`. |

## Technical decisions

| ID | Decision | Status | Detail |
|----|----------|--------|--------|
| TD-01 | Per-app authentication | **Accepted** 2026-10-20 | One `users` row per phone (`phone_code` + `phone`); the same person can be a customer, a provider, and a driver through the 1:1 profile tables. Shared `api` JWT guard; each app's OTP login issues a token with an `app` claim (`customer` / `provider` / `driver`) and creates that app's profile row on first sign-up. `app:{name}` middleware rejects tokens from other apps (403) and requires the matching profile; `approved` middleware gates operational provider/driver routes on `account_status = approved` (BR-03). Admin tokens are issued only to admin user types (email + password). |
| TD-02 | Per-field validation errors on 422 | **Accepted 2026-10-25, implemented** | Every 422 from a Form Request or the framework handler carries `data.errors: {field: [messages]}` beside the unchanged combined `message`; localized by `Accept-Language`. Additive (MINOR): `API_CONVENTIONS` 2.7.0. |
| TD-03 | Date format in responses | **Proposed** | ISO 8601 with offset everywhere (stored UTC, rendered Asia/Riyadh). `AdminResource::date()` currently returns `Y-m-d H:i:s`. |
| TD-04 | Documentation system | Accepted 2026-10-20 | Per-feature, per-surface docs with SemVer; registry in `PROJECT_BRAIN.md`; timeline in `CHANGELOG.md`. |
| TD-05 | Money representation | Accepted 2026-10-20 (amended) | `DECIMAL(12,2)` SAR (schema convention); JSON as 2-decimal strings; ledger row for every movement. |
| TD-06 | Enum representation | Accepted 2026-10-20 | PHP backed enums mirror the schema `ENUM` values exactly; API returns `{value, label}`. |
| TD-07 | Business rule placement | Accepted 2026-10-20 | `app/Domain/{Module}/Rules` + `RuleChain`; typed `ErrorCode`. |
| TD-08 | Schema vs Laravel base | **Accepted** 2026-10-20 | **Mixed.** Business domain (schema §4–§21) follows `db-schema.sql` exactly; identity (§2), admin users & RBAC (§3), and geography (§1) stay on the base for now. Mapping: [database/README.md §2](database/README.md#2-schema--base-mapping-td-08). |
| TD-09 | Test database | **Accepted** 2026-10-20 | Tests run on MySQL (`joshen_testing`) so constraints, generated columns, and triggers are tested. |
| TD-10 | Product name | **Accepted** 2026-10-20 | **Joshen (جوشن)**. "Khadami / تطبيق خدمي" is the BRD's working title; BRD files keep their names. The guided search is the "Joshen finder". |
| TD-11 | Triggers are a safety net | Accepted 2026-10-20 | Rules enforced by schema triggers (price ranges, wallet balance, review-after-completion) are also checked in the Domain layer first, so the API returns typed 422 errors instead of database errors. |
| TD-12 | Translation storage | Accepted 2026-10-20 | Schema change. Translatable admin-managed text moves from `name_ar` / `name_en` / `*_ar` columns into `{entity}_translations` tables (`{entity}_id`, `locale`, fields; `UNIQUE ({entity}_id, locale)`; cascade delete), matching the base `role_translations` pattern. 14 tables: category, service, service_option_group, service_option_value, occasion, featured_plan, cancellation_rule, review_tag, notification_template, support_reason, faq, slider, onboarding_slide, static_page. Geography uses the base `governorate_translations` / `region_translations` (TD-13). `ar` required, `en` optional (application rule). Parent-scoped name uniqueness is checked by the application. User-written text and checkout snapshots stay single-language (Arabic) on the row. API: one translation style for all entities (`API_CONVENTIONS` 1.2.0). |
| TD-13 | Base tables used without changes | Accepted 2026-10-20 | Refines TD-08. Schema §1–§3 are a copy of the base migrations (`countries`, `governorates`, `regions`, `users`, `roles`, `permissions`, pivots, `devices`, `verifications`, `audit_logs`) and are never altered for Joshen. Domain FKs reference them directly: `city_id` → `governorates`, `district_id` → `regions`, admin columns → `users`. Data the base lacks lives in schema tables: consent timestamps (BR-01) on `customers` / `providers` / `drivers`; push-token app info in `device_apps`. Removed schema tables: `cities`, `districts` (+ translations), schema `users`, `otp_codes`, `auth_sessions`, `user_devices`, `admin_roles`, `admin_role_permissions`, `admins`, `admin_activity_log`. |
| TD-14 | File storage | Accepted 2026-10-20 | All files use the base Media module (`media` table, `HasMedia`, `$media_keys`), never URL columns. Collections: `categories.image`, `providers.portfolio` (multiple), `user_documents.file`, `profile_change_requests.files` (multiple), `packages.image`, `invoices.pdf`, `messages.attachment`, `support_tickets.attachments` and `support_messages.attachments` (multiple), `sliders.image`, `onboarding_slides.image`; profile photos = `users.image` (base). Removed: `provider_portfolio_images`, `support_attachments`, and every `*_url` column. Files sent with a change request attach to the request so the orphan cleanup does not delete them before review. |
| TD-15 | Banners on the base Slider; no shipping zones | Accepted 2026-10-20 | BRD banners (ADMIN-023, BR-30) use the base `sliders` table and `App\Models\Slider` (schema table renamed `banners` → `sliders`, `banner_translations` → `slider_translations`; base column names `link`, `sort_order`, `is_active` kept). The banners feature evolves the base migration into the schema shape. Shipping zones are not in the BRD or the screens, so they are removed from the project: migration `2026_10_20_081852_drop_shipping_zones_tables` drops `shipping_zones`, `shipping_zone_translations`, `regions.shipping_zone_id`, and the `shipping-zones` permissions; the model, admin CRUD, seeder, and Postman entries are deleted. Delivery fees stay on `delivery_fee_rules` (BR-11). |
| TD-16 | Order chat deferred | Accepted 2026-10-20 | Order chat (CUST-018, PROV-021) and the `socket-io/` realtime server are **deferred to the production phase**. The schema keeps `conversations` / `messages`; until chat ships, order resources return `"can_chat": false` and no chat endpoints exist. BR-15 applies when it is built. |
| TD-17 | Registration consent flags on `users` | Accepted 2026-10-20 | Exception to TD-13, requested by the project owner. Base `users.gender` (male/female) is dropped: every app user is female (BR-01), so it carries no information. Replaced by three `tinyint(1) NOT NULL DEFAULT 0` columns on `users`: `guarantees_female_gender` (female declaration), `accept_policies` (privacy policy), `accept_terms` (terms and conditions). The per-app acceptance timestamps on `customers` / `providers` / `drivers` stay. |
| TD-18 | Service tree replaces option tables | Accepted 2026-10-20 | Refines BR-05/BR-06 wording, not their intent. `services` is a tree via `parent_id` + `type` (1 service, 2 subservice = bookable, 3 addon); depth capped at 3, sub-addons later = a new `type`. A node's `children_selection` (`single`/`multiple`) and `children_required` replace `service_option_groups.selection` / `is_required`. Providers price subservices **and** addons in `provider_services` (range trigger on `services.min/max_price`); an order's service total = the subservice price + the chosen addon prices. The BRD's "subservice" is the type-2 node; type 1 is an extra grouping level above it. Removed: `service_option_groups`, `service_option_values`, their translations, `provider_service_option_prices`. Parent rows are never hard-deleted (FK restrict; use `is_active`). |
| TD-19 | Public uuid on every addressable table | Accepted 2026-10-20 (exclusions superseded by TD-20) | Refines TD-13 (adds a column to base tables). Every table with a surrogate `id` also has `uuid CHAR(36) NOT NULL UNIQUE` (UUIDv7, generated by the app). The integer `id` stays the primary key and the target of all foreign keys; **clients only ever see the uuid**: response `id`, route parameters, request fields (`country_id` carries the country's uuid), query filters, and headers (`X-City`). Requests validate `exists:{table},uuid` and swap uuids for integer ids after validation (`ApiMasterRequest::uuidReferences`). Excluded: `*_translations`, pivots, and tables keyed by `user_id` (public id = `users.uuid`). |
| TD-20 | Integer keys and public UUIDs | Amended by TD-22, 2026-10-20 | Every schema table retains its integer `id` primary key. Addressable records retain a public `uuid`; translations and pure link tables are excluded under TD-22. Former natural/composite keys stay as their existing UNIQUE keys. References to `providers(user_id)`, `customers(user_id)`, `drivers(user_id)` and `wallets(user_id)` continue to use those unique FK targets. A profile is addressed by its own UUID; `users.uuid` identifies the account. The original decision is preserved in the resolution log. |
| TD-21 | Full-schema fresh-install baseline | Accepted 2026-10-20 | Project owner requested all schema migrations, original create-table updates instead of add-column migrations, and models/factories/seeders. Consolidate the original definitions and build all 95 tables, including chat storage; TD-16 still defers chat endpoints/realtime behavior. This overrides feature-by-feature table creation timing for this baseline only. Existing populated databases are not upgraded by replaying edited migrations. TD-22 restores the original SQL and excludes translations/pure links from UUIDs; the Laravel order-history triggers still supply UUIDs for their addressable history rows. Demo seeders are local/testing fixtures, not production defaults. |
| TD-22 | No UUIDs on translations or pure link tables | Accepted 2026-10-20 | Project owner requested UUID removal from translations and pivot tables because they are managed through parent foreign keys rather than independent CRUD. Restore `docs/database/db-schema.sql` byte-for-byte to its original version. All 17 translation tables and seven pure links (`permission_role`, `permission_user`, `service_occasions`, `provider_districts`, `driver_districts`, `package_occasions`, `review_tag_links`) keep their existing integer IDs, FK columns and schema indexes, without UUID columns, defaults, traits or factory values. Business association records with their own state/pricing retain the UUIDs present in the original SQL. Amend TD-20; retain its profile `user_id` FK targets and independent profile UUIDs. |
| TD-23 | Authentication delivery contracts | Accepted working assumptions 2026-10-20 | Per-app OTP contract, Saudi phones, pending state, identity/device rules, configurable abuse defaults and admin compatibility. Assumptions requested by the auth task are individually recorded below. |
| TD-24 | Temporary fixed OTP pending gateway | Accepted 2026-10-20 | Owner requested shared OTP generation to return 1234 directly until a gateway is provided. Four digits for send/resend and request validation; temporarily supersedes DEC-15 code generation/length. Existing expiry, attempts, cooldowns and app-bound verification remain. |
| TD-25 | `users.user_type` for app users | Accepted 2026-10-20 | Every customer, provider, and driver identity is stored with `user_type = client`, meaning "app user". The column only separates admins (`super_admin` / `supervisor`) from app users (schema §2). Which apps a person uses comes from the `customers` / `providers` / `drivers` rows and the token's `app` claim (TD-01), never from `user_type`. `UserType::PROVIDER` / `DELIVERY` stay in the enum for legacy rows only. The notifications rebuild must scope the inbox by `notifications.app` / the token claim, not by `user_type`. |
| TD-26 | Auth rate limits and carrier CGNAT | Accepted 2026-10-20 | Supersedes the per-IP values in TD-23 "Abuse defaults". Per-identity limits stay strict (otp-send 5 per phone **per app** per 10 min with a ceiling of 12 per phone across apps — amended 2026-10-21, otp-verify 10 per token per min, admin-login 5 per email per min). Per-IP limits for the apps are raised because Saudi carriers share public IPs: otp-send 300 per IP per 10 min, otp-verify 300 per IP per min. Admin login keeps 30 per IP per min. All values are env-configurable (`AUTH_*`, `config/auth_limits.php`; ceiling `AUTH_OTP_SEND_PER_PHONE_ALL_APPS`). |
| TD-27 | Admin permission catalog | Accepted working assumption 2026-10-20 | `config/rbac.php` defines the 21 DEC-14 sections and 5 actions once. One `permissions` row per non-exempt `admin.*` route (`name` = `route_name`, `resource` = section, `action`, ar/en `title`). Section from the route group (`admin.{group}.…`) or an exact-name override; action from the last segment (`index`/`show`/`view`/`export*` → view, `store`/`create` → create, `update`/`toggle*`/`reorder`/`reply` → update, `destroy`/`delete` → delete, `approve`/`reject`/`review` → approve). Granting a cell grants all its routes. `php artisan rbac:sync-permissions` upserts rows, detaches and deletes rows of removed routes, is idempotent, and fails when a route has no section; the permission seeder calls it. Exempt: `admin.auth.*`, `admin.profile.*`. |
| TD-28 | Admin grant rules and security | Accepted working assumption 2026-10-20 | RBAC is enforced in every environment (bypass removed). `super_admin` passes every route and gets the whole catalog in `auth/me`. Supervisors: role's own ∪ **active descendant roles'** ∪ direct grants. **Hierarchy direction: a senior (parent) role inherits its juniors; a child never inherits its parent.** Security: (1) super admins are unreachable through `/supervisors` and `/users`; (2) no privilege escalation: grant, assign, or manage (role or account) only within your own effective grants, `PRIVILEGE_ESCALATION` 403; (3) no self-modification of your account via `/supervisors` or of the role you hold, `SELF_MODIFICATION_FORBIDDEN` 403; (4) disabling a role never leaves zero working super admins, `LAST_SUPER_ADMIN` 409; (5) disabled/deleted accounts are refused by `active` on the next request; (6) every role/account grant change invalidates cached grants (global version counter, no `Cache::flush()`); (7) passwords hashed, never returned or audited. The `super_admin` role is a locked system role (`SYSTEM_ROLE_LOCKED` 403); app roles are hidden from `/roles`. A role is deleted only with no live users and no child roles (`ROLE_IN_USE` 409). Audit entries are written in the save transaction. |
| TD-29 | Session policy (JWT lifetimes) | Accepted working assumption 2026-10-21 | Per-app access-token TTL set at issue time: admin 720 min (12 h), customer / provider / driver 10080 min (7 days). Refresh window counted from the **original sign-in** (`iat` is kept on refresh): admin 10080 min (7 days), apps 86400 min (60 days). `auth/refresh` accepts an expired token inside the window (route middleware `jwt.refreshable:{app}` instead of `auth:api`), keeps the `app` claim and blacklists the old token; the blacklist keeps a revoked token for the whole window. `expires_in` = the app's access TTL in seconds. Env: `AUTH_{ADMIN,CUSTOMER,PROVIDER,DRIVER}_ACCESS_TTL`, `AUTH_{…}_REFRESH_TTL` (`config/auth_sessions.php`). `JWT_TTL` / `JWT_REFRESH_TTL` are no longer used for issued tokens. Final values pending product owner. |
| TD-30 | Legacy base routes unregistered until their feature ships | Accepted 2026-10-21 | Base routes that returned 500 or contradicted the new auth are unregistered (classes kept as reference): client (`/client/notifications`, `/client/notifications/read`) and admin (`/admin/notifications`, `/admin/notifications/send`) notification routes, `PUT`/`DELETE /client/profile`, `/client/addresses…` (integer ids, base table), `/client/wallet…` (500, `withdraw` vs DEC-10), guest `/client/faqs` and `/client/pages/{type}` (500). `GET /client/profile` returns the `auth/me` resource; `PUT /client/settings` returns the settings payload. `is_verified` is not an app response field: an app token exists only after OTP, and verify sets `users.phone_verified_at`. Push tokens: `POST /{client,provider,delivery}/notifications/device` upserts through the verify device service (TD-23); no `device_id`. The shared inbox `/api/v1/notifications` (list, show/read, delete, mark-all-read, clear-all) stays: rebuilt on the schema `notifications` table, scoped to the user and the token's `app` claim (TD-25), uuid ids; amended 2026-10-21 at the project owner's request. |
| TD-31 | Developer docs viewer, API console and docs MCP server | Accepted 2026-10-21 | Project owner requested the docs viewer be secured and exposed as an MCP server for the Flutter/React teams' coding agents, reusing the same view. One gate (`docs.access`, `config/docs.php`) covers `/docs` (viewer), `/docs/proxy`, `/docs/quick-token` and `POST /mcp/docs`: open without a token in `local`/`dev`/`development`/`testing`; everywhere else 404 unless `DOCS_ENABLED=true` **and** `DOCS_TOKEN` is set, then the token is required (`Authorization: Bearer`, `X-Docs-Token`, or `?token=` once in the browser → HttpOnly HMAC cookie). The proxy only replays this app's own `/api/` routes in-process (no outbound HTTP, no docs routes). Quick token issues real JWTs (`JwtSessionPolicy`) for the seeded `UserSeeder` demo accounts only, never creates users, never runs in production. The viewer moved from `/api/v1/docs` to the web routes (`/docs`) and is no longer in the Postman collection. MCP server `joshen-docs` (`laravel/mcp` ^1.0, now a direct dependency) is read-only: `get-integration-brief` (call first: reading checklist + app README + registry row + BRD sections with AC/BR/SCR rows + decision rows), `list-features`, `list-docs`, `read-doc`, `search-docs`, `list-endpoints`, `get-endpoint`, and the `integrate-feature` prompt; app teams connect and add the rules block per `MCP_AGENT_GUIDE.md`; also `php artisan mcp:start joshen-docs` over stdio. Viewer and MCP read `docs/` through one `DocsLibrary`. |
| TD-32 | Base (pre-BRD) endpoints documented at 0.x | Accepted 2026-10-21 | Project owner requested docs for the admin routes inherited from the base project. Each route group is documented in the doc its BRD feature already reserves (the `config/rbac.php` section comments): `admin/settings.md` (settings + feature flags), `content.md` (FAQs, static pages), `banners.md` (sliders), `services.md` (categories), `service-areas.md` (countries, governorates, regions), `customers.md` (`/users`), `support.md` (contact messages), `dashboard.md`. Admin profile stays in `admin/auth.md`, audit logs in `admin/admins-permissions.md`. Such a doc starts at **0.1.0**, registry status **`base (TD-32)`**: the contract is what the code does today, examples come from real test responses, and known failures are listed instead of hidden. A 0.x doc follows the same SemVer bump rules, but app teams treat the contract as provisional. When the BRD feature ships, its doc moves to **1.0.0** (MAJOR, with old → new notes) and the status to `ready`. No code changed: failing base endpoints stay registered until their feature fixes or replaces them (TD-30 is the precedent for unregistering). |
| TD-33 | Catalog ancestor visibility | Accepted 2026-10-21 | ADMIN-008/009: category, group, subservice and addon flags remain independent. `v_bookable_provider_services` requires an active category and type-1 parent group as well as an active type-2 node. `v_selectable_provider_addons` joins active provider-priced type-3 nodes to their same-provider bookable parent; inactive subservice/group/category excludes the addon. Active addons also require active provider-price rows before the parent is bookable (BR-06). Reactivation restores eligibility without rewriting descendants. Admin lists/tree include inactive records. Schema SQL, baseline view migration and an upgrade migration change together; no table columns change. |
| TD-34 | Catalog integrity and price-range edits | Accepted working assumption 2026-10-21 | ADMIN-008…010: reject a range edit when any existing provider price (active or inactive) would fall outside it: HTTP 409 `PRICE_RANGE_CONFLICT` with public `affected_count`; never rewrite provider prices. Reject deletion of any referenced category/service with HTTP 409 `CATALOG_IN_USE` (deactivate instead), including service-occasion links. `type`/`category_id` are immutable when children or provider prices exist (409 `INVALID_STATE_TRANSITION`, existing code). Range/tree violations use existing 422 `BUSINESS_RULE_VIOLATED`. Service images are omitted: only categories have a schema-owned media collection. Catalog date-times use `Y-m-d H:i:s` per owner's task override of proposed TD-03. |
| TD-35 | Shared provider/driver review transition map | Accepted 2026-10-21; implementation scheduled ADMIN-004/006 | Admin: `pending_review → approved/rejected/needs_changes`; `approved → suspended`; `suspended → approved` only through reactivate; all other admin transitions 409 `INVALID_STATE_TRANSITION` (existing code). Approve requires `submitted_at` and required non-expired documents, otherwise a business error identifies omissions. Reject/request-changes/suspend require a reason; reactivate accepts an optional note. Every action updates profile status/reason and writes an account review event atomically; approval records actor/time. No `approved → needs_changes`: post-approval edits use profile_change_requests. Onboarding resubmission is user-only (`rejected/needs_changes → pending_review`); PROV-004/DRIV-003 permit rejection edits/resubmission according to settings, whose policy remains with onboarding. Same shared service for both roles and future ADMIN-007. |
| TD-36 | Profile suspension and confirmed orders | Accepted 2026-10-21; implementation scheduled ADMIN-004/006 | Provider/driver suspension is profile-level, never users.is_active; it blocks new bookings and customer visibility/ranking only. Already-confirmed order handling is deferred to the orders feature (record in providers/drivers §13). Account-level customer blocking remains TD-28. |
| TD-37 | Catalog media hash forms on every HasMedia model | Accepted 2026-10-21 | Owner clarification: both categories and services support single `image` collections through `Modules\Media\Traits\HasMedia` and `$media_keys`. Upload `file` + `collection=image` to POST /api/v1/media, then submit its `data.hash` as the form's `image`. Responses expose the media URL through the model accessor. Create, replace and clear use the shared catalog media hook backed by HasMedia; no raw file/path/URL form fields or URL columns. Adds the versioned shared POST /api/v1/media alias to the existing package upload controller; /media remains available. Supersedes only TD-34's original service-image omission; the polymorphic media table already supports services, so only the schema's collection comment changes (no migration or columns). Applies to type-1/2/3 service nodes. |
| TD-38 | Timezone of offsetless legacy timestamps | Accepted 2026-10-21 | Current app timezone/storage is UTC. Catalog and admin customer date-times retain `Y-m-d H:i:s` for compatibility and must be interpreted as UTC, never browser/device local time. Convert to ISO by replacing the space with T and appending Z before parsing/display conversion. Date-only fields remain calendar dates. ISO 8601 with an explicit offset remains the TD-03 target and existing ISO endpoints keep their format; adopting it on legacy contracts requires a separately versioned change. This clarifies TD-34 without changing serialized values. |
| TD-39 | Admin customer contract and refund-credit history | Accepted 2026-10-21 | ADMIN-003 retains users UUID routes/names. Only accounts with a customer profile and no admin type are reachable, including multi-profile people (BR-29/TD-28). List key becomes users with standard filters/sorts, capped unpaginated dropdowns; created_at and registration filters refer to users.created_at (existing account registration field), while customer_profile.created_at exposes per-app registration. Addresses come only from non-deleted customer_addresses via customer user_id. Wallet/history are read-only; the owner explicitly confirmed this endpoint returns refund_credit entries only, excluding spending, reversals and adjustments. Balance remains the true current wallet balance from all ledger movements, so it need not equal the refund-history sum. Details count active saved addresses and refund-credit transactions only; unbuilt orders/payments/ratings/chat details are omitted, not fabricated. Wallet history is always paginated. Toggle users.is_active is account-wide, transactional and audited; it never changes provider/driver profile status or deletes an account. |
| TD-40 | UTC database sessions for generated timestamps | Accepted implementation decision 2026-10-21 | Live verification while implementing TD-38 found MySQL inherited SYSTEM (+03:00), while Laravel uses UTC. Pin application MySQL/MariaDB connections to +00:00 so DATETIME defaults, CURRENT_TIMESTAMP triggers, TIMESTAMP conversions and application-generated dates share UTC. No schema/migration/global-server setting change and no historical date rewrite. Existing naive DATETIME rows may contain mixed historical timezones; audit provenance before any correction. Long-lived workers/config caches must reload this connection configuration on deployment. |
| TD-41 | Owner-approved category slugs | Accepted 2026-10-21; compatibility details working assumption | Owner explicitly approved a unique category slug, superseding only the original task's slug prohibition. categories.slug is one optional, nullable, non-translated VARCHAR(255), globally unique under the existing utf8mb4_unicode_ci collation (case/accent-insensitive); multiple nulls are allowed. Accept any string within the limit, with standard request whitespace trimming/empty-to-null normalization; no ASCII-only format or automatic generation. Omission on update preserves the value, null clears it; duplicate values return 422 and a database unique index also enforces integrity. UUIDs remain the only route identifiers, with no slug lookup/filter/search contract. Baseline SQL/migration are updated together; an upgrade migration preserves existing categories with null slugs. |
| TD-42 | Bookable services with independent option groups | Accepted 2026-10-21 by owner after screen review | Supersedes TD-18 type meanings and the affected TD-33/34 pricing/visibility rules. Categories remain flat/non-bookable. services.type 1 is a bookable root directly under its category, with positive duration and 0 < min <= max; type 2 is an unpriced option group under that service, duration/ranges zero, whose own required/single/multiple flags govern its choices independently; type 3 is a choice under a group, extra duration >=0 and 0 <= min <= max (0–0 is free). Reference price if supplied must fit the range. Admin owns definitions/ranges only. Reuse existing provider_services (2026_10_20_110012 migration): providers select services/choices and enter their prices within admin ranges; option groups cannot have provider price rows. No new provider-pricing table or admin price-writing endpoint. Bookability requires active category/service, approved provider/category and active provider-owned prices for the base and every active choice in active groups, including zero prices for free choices. Inactive groups hide their choices without changing stored child flags; active root may remain bookable. Existing convention: no active choices means selection flags have nothing to require; empty/inactive groups are not selectable. Checkout must enforce each nonempty active group's own selection rule (future booking feature). Existing populated TD-18 catalogs need an explicit mapping before upgrade; no automatic reinterpretation/deletion of records or order references. |
| TD-43 | Account review history upgrade | Accepted 2026-10-21, owner authorized | New additive migration adds nullable from_status / to_status ENUMs matching account_status to account_review_events. Existing/non-transition events remain null; no backfill is invented. Original create migration stays immutable. Each new admin transition stores both states, actor and note atomically with profile, audit and inbox notification. ADMIN-007 will reuse the shared review service. |
| TD-44 | Important profile-change requests read-only | Accepted deferral 2026-10-21, owner authorized | ADMIN-004 change-request review is partially covered: show pending request metadata only; do not apply or approve/reject requests. Before implementation define allowed fields per change_type/role, per-field validation, UUID/media resolution, stale-old-value checks and transactional apply semantics for city, coverage (radius/base/district replacement) and payout (primary-account verification/replacement). Generic JSON is not an apply contract. Uncontracted payloads are omitted to prevent sensitive/internal-ID leaks. |
| TD-45 | Sensitive account identity review | Accepted 2026-10-21, owner authorized | Provider/driver lists and default details mask identifiers; IBAN always last four, never revealed. Dedicated GET /providers/{provider UUID}/identity and later /drivers/{driver UUID}/identity require the section approve action and audit each successful read with actor, target UUID and field names, never the identity values. Provider identity includes national_id (also iqama), commercial_register_no and vat_number. No full-text identity requirement elsewhere. |
| TD-46 | Provider management delivery boundaries | Accepted implementation choice 2026-10-21 | Provider route IDs are providers.uuid; user/city/nested records use their own UUIDs. Approval documents follow adopted SCR-18: individual national_id/professional_certificate/health_certificate; establishment commercial_register/business_license/staff_health_certificates; pending or verified documents require attached file and non-expired date (null expiry permitted). Approval requires submitted_at; approval sets approver/time, reactivate preserves original approval and clears status_reason. Status notices are transactional in-app only; external delivery remains ADMIN-020. Rejection resubmission stays user-only under PROV-004 settings. Confirmed-order/trip effects remain TD-36. |
| TD-47 | Review validation and development fixtures | Accepted 2026-10-21, live review fixes authorized | Provider/driver city filters use normal 422 UUID/exists validation naming filters.city_id; profile routes keep 404. Invalid transitions interpolate localized action/status labels; sort errors name concrete keys. UserSeeder previously bypassed review for demo provider/driver; future default profiles start incomplete. App registration already starts incomplete; no auto-approval app flow found. Preserve existing legacy/live-reviewed seed rows without fabricated approval backfill. Opt-in AdminReviewDevSeeder runs only local/dev, outside DatabaseSeeder/tests, creates submitted pending-review synthetic document fixtures and never resets reviewed profiles. |
| TD-48 | Driver management delivery boundaries | Accepted implementation choice 2026-10-21 | ADMIN-006 routes use drivers.uuid and share AccountReviewService/AccountReviewRequest with Providers and future ADMIN-007. Details include all saved vehicles; list summary is the active vehicle or null. Plate/document numbers and default national_id are masked; audited identity reveals national_id/iqama only, never IBAN. Current coordinates appear only when is_online=true, as nullable numeric lat/lng plus updated_at; last location_updated_at remains visible in list/details even offline. Vehicle fields remain the schema's make_model, not invented make/model columns. Change-request metadata read-only per TD-44; trip/earnings/commission/payout workflows and confirmed-trip suspension effects deferred. Dev fixtures add car/motorbike ready and missing-driving-license drivers. |
| TD-49 | Customer validation consistency | Accepted 2026-10-21, owner requested | Extend TD-47 filter/error fixes to admin/users: malformed/integer/unknown city UUID filters are 422 naming filters.city_id, route identity errors remain 404. Customer and refund-history sort errors name the exact key, never sort.*. No changes to TD-39 refund-only wallet history, account-wide blocking, response fields or permissions. |
| TD-50 | Approval verifies qualifying documents | Accepted 2026-10-21, owner requested | Same role/required-type/pending-or-verified/non-expired/attached-file rows used by ReadyForApproval are locked and reused within approval. Qualifying pending rows become verified with reviewed_by/reviewed_at; existing verified rows retain their attribution. Non-required, rejected/expired/replaced, wrong-role and missing-file rows are untouched. Event note summarizes qualifying/newly-verified counts; profile audit links review_event_id and qualifying/newly-verified document UUIDs, plus individual document change audits. All writes roll back together. Existing note/audit JSON suffice; no schema change. Missing prerequisites use localized labels. |
| TD-51 | Expanded identity review and public vehicle plates | Accepted 2026-10-21, owner requested; supersedes affected TD-45/48 masking | Both audited identity endpoints add role-scoped documents [{id UUID,type enum,doc_number full or null}], including saved licence/registration/CR/other numbers. Audit field names and document UUIDs only, never numbers. Default document/profile identity remains masked; vehicle plate_number is public and unmasked in driver list/details, including historical vehicles. IBAN never revealed. |
| TD-52 | Active vehicle required for initial driver approval | Accepted 2026-10-21, BRD DRIV-002/003 + SCR-19 | Vehicle type/model/plate and vehicle documents form registration information. Initial approve requires at least one is_active vehicle belonging to that driver; absence gives 422 BUSINESS_RULE_VIOLATED naming the localized Active vehicle prerequisite. Reactivate restores existing approval under TD-35 without rerunning initial submission checks. Adds a local submitted complete-document driver with no active vehicle; no automatic approval or rewriting existing fixtures. |
| TD-53 | Super admin answer of `auth/my-permissions` | Accepted 2026-10-22, owner requested | `GET /admin/auth/my-permissions` returns `is_super_admin: true` with empty `permissions` and `sections` for a `super_admin`: she is allowed every admin route implicitly (TD-28), so the flag, not a route list, carries her access. Supervisors get their effective route names and granted section actions. `auth/me`, `auth/login`, `auth/refresh` and `GET profile` keep listing the whole catalog in `profile.permissions` (TD-28, unchanged). |
| TD-54 | Identity numbers are not searchable | Accepted 2026-10-22, owner requested | Admin provider/driver list `search` matches name, phone and (drivers) vehicle plate only. `national_id`, `commercial_register_no` (and any document number) are never matched, exactly or partially: list search needs only the section view grant, so matching would let a view-only admin confirm or rebuild a masked value without the approve-gated, audited identity read (TD-45/51). Applies to every future admin list that holds masked identifiers (e.g. ADMIN-007). |
| TD-81 | Examples and payloads live only in the Postman collection | Accepted 2026-10-22, owner requested; supersedes the `docs/postman/examples/` folder of TD-56 | `docs/postman-collection.json` is the single store of payload docs, saved responses (each with the exact request payload that produced it) and variables; the per-example files are removed. Recording tests (`recordExample()` / `recordSharedExample()`, `RECORD_API_EXAMPLES=1`) buffer examples in memory and merge them into the collection with one `postman:generate --merge-examples` run when the test run ends (file-locked; run serially). Every captured response carries a `_joshen` block (route, name, code, request, source test, `applies_to` for surface-wide answers), so `postman:generate` reads examples back from the collection (`--source`) and never loses one; generated illustrative responses carry no block and are rebuilt. Merge key = route + code + name; `--forget-example="route|code|name"` removes one. Feature docs cite examples as `Postman: route › "code name"`, not as file links. The coverage baseline stays in `docs/postman/coverage-baseline.json`. Migration: 562 stored example files merged losslessly (956 captured responses before and after). |
| TD-57 | Request chaining through saved variables | Accepted 2026-10-22, owner requested | Running a request saves values from its 2xx response into collection variables that related requests read as `{{name}}`: `{surface}_token` + `token` from sign-in/refresh `data.access_token`; `verification_token` from app register/login/resend; `media_hash` from the media upload; `{resource}_id` from a list's first row (`data.*.id`) and from create/show/update (`data.id`). Protected routes send their own app's token (`admin_token`, `client_token`, `provider_token`, `delivery_token`; shared routes `token`) with `auth: noauth`, so one session drives all apps. Derived from routes by `PostmanVariableMap`; a save is kept only when captured 2xx examples hold a value at its path. One `_joshen_variables` block per request feeds the Postman scripts, the API console (browser storage, Quick Auth sets the app token) and MCP `get-endpoint`/`list-endpoints`. `otp_code` stays manual: no fixed development OTP. |
| TD-56 | Postman collection carries documented keys and real response examples | Accepted 2026-10-22, owner requested | Every task that touches an endpoint leaves its Postman entry complete, for every agent and model. Request keys: type/required/allowed values/referenced record derive from Form Request `rules()`; meaning and example come from `bodyParameters()` (POST/PUT/PATCH) or `queryParameters()` (GET) on the same request. Responses: only real ones, recorded by feature tests with `recordExample()` / `recordSharedExample()` into `docs/postman/examples/` when `RECORD_API_EXAMPLES=1` (tokens redacted); success and every error the route can return. The generator's typed-in responses remain only as labelled "illustrative, not captured" fallbacks. `postman:generate --check` and `PostmanCoverageTest` fail on any gap (success, validation, unauthenticated, forbidden, rate_limited, field) outside `docs/postman/coverage-baseline.json`, which records the 457 pre-TD-56 gaps and only shrinks. Rules: `.kiro/steering/postman-collection.md`, skill `joshen-postman`. |
| TD-55 | Featured subscription status in the provider list | Accepted 2026-10-22, owner requested; working assumption for DEC-12 | ADMIN-004 list/details add `featured_status` {active, expired, not_subscribed} read from `provider_subscriptions`, independent of account status, so a suspended provider with a paid unexpired subscription reads `active` (AC-ADMIN-004). `is_featured` keeps meaning "ranking priority applies now" (`v_provider_featured`, approved only, BR-27). active = status active and ends_at > now (UTC); expired = none active, but an earlier status active (past ends_at) or expired row; not_subscribed = otherwise. pending_payment / payment_failed never activated (BR-14); cancelled / suspended are not counted as history until DEC-12 decides their meaning. |
| TD-58 | Provider step-2 submission boundary (A) | Accepted working assumption 2026-10-22, owner supplied | PROV-002/003/004 + SCR-18: individual requires national_id, years_experience, experience_note, at least one active category and national_id/professional_certificate/health_certificate files; establishment requires commercial_register_no, authorized_person_name/phone, years_experience, specialists_count, category and commercial_register/business_license/staff_health_certificates files; VAT optional. City, coverage, photo, portfolio, hours and payout are separate missing_for_bookability items, never submission blockers. D4: specialists_count is information only, no staff accounts. |
| TD-59 | One review-document policy (B) | Accepted 2026-10-22, owner supplied | Extract required types and qualifying role/status/expiry/file query into Domain AccountReview; both provider submission and ReadyForApproval use it. Preserve TD-46/50: pending or verified, attached file, null expiry allowed, expiry today valid in UTC; rejected/expired/replaced do not qualify. |
| TD-60 | Registration categories approved atomically (B2) | Accepted 2026-10-22, owner supplied | On initial provider approve, requested provider_categories become approved with reviewed_by/at inside AccountReviewService's existing document/profile/event/audit/inbox transaction. Other category statuses remain unchanged; reactivate preserves previous review attribution. Category changes are audited. No historical backfill. Supersedes the deferred category note in admin/providers.md. |
| TD-61 | Provider resubmission setting (C) | Accepted working assumption 2026-10-22, owner supplied | needs_changes always permits edits and resubmission; rejected permits both only when platform_settings provider_resubmission_after_rejection is JSON true (absent defaults true). Explicit false disables both. Every submission returns to pending_review and refreshes submitted_at, clears reason, writes submitted event with provider actor using existing admin_id users FK and from/to statuses (TD-43); no schema change. |
| TD-62 | Provider onboarding edit states (D) | Accepted working assumption 2026-10-22, owner supplied | Draft and submit allowed only incomplete/needs_changes/rejected when TD-61 permits. pending_review/approved/suspended return 409 INVALID_STATE_TRANSITION. Post-approval edits remain BR-04/TD-44/DEC-25 profile-change work, read-only in this release. |
| TD-63 | Provider draft and response contract | Accepted implementation choice 2026-10-22 | Step-1 provider_type/name stay in auth; PUT changes only the listed step-2 fields. Omitted keys preserve values; explicit null clears nullable fields, categories array replaces selection (empty allowed in draft), omitted documents preserve them. Each document entry requires doc_type + a fresh unattached media hash; optional doc_number/expires_at, one entry per type/hash. Only this provider type's required document types accepted; image or PDF up to existing 5 MiB document limit. Recheck and lock media inside transaction, require uploader_id/type to match the provider user, and prevent stealing attached files. Establishments with an existing submitted status cannot clear CR to null (schema ck_providers_cr); return typed 422 instead of SQL failure. Replacement preserves old row/file and marks old same-role/type rows replaced, creates a pending row with fresh review metadata. Missing lists are stable machine-key strings, documents.<type> for documents; profile and all document numbers use existing last-four masking, VAT included. Nonnegative SMALLINT experience/count fields follow schema capacity, no invented business minimum. |
| TD-64 | Provider review timeline and bookability checklist | Accepted implementation choice 2026-10-22 | Timeline always received/verifying_documents/activated with localized labels and state completed/current/waiting. received completes after submitted_at; verifying is current for pending_review, completes when all required types have qualifying verified files; activated completes only for approved, otherwise waits. No dates or completion inferred for rejected/needs_changes/suspended activation. Checklist reports city, coverage (district or base coordinates + positive radius), photo (user image), portfolio (provider media), working_hours, payout_account (non-inactive provider primary payout); it is completion guidance, not booking eligibility certification. Add account_status/categories/services when approval, approved active/enabled category or active approved-category type-1 pricing is missing (BR-03/06/08); final checkout/availability remains its owning feature. |

| TD-65 | Driver SCR-19 submission boundary | Accepted working assumption 2026-10-22, owner supplied | DRIV-002/003: submission needs full name (base users.full_name, not a new name column), national_id, one active vehicle with car/motorbike type, make_model and plate_number, and national_id/driving_license/vehicle_registration documents under the shared TD-50/59 policy. model_year/color optional. City, districts, photo, birth_date, nationality and payout_account are missing_for_activation guidance before going online, never submit blockers. BR-01 consents remain the registration contract. |
| TD-66 | Driver edit and resubmission policy | Accepted working assumption 2026-10-22, owner supplied | Same state policy as provider: incomplete/needs_changes editable; rejected editable and resubmittable only if driver_resubmission_after_rejection is JSON true (absent defaults true). pending_review/approved/suspended give 409 INVALID_STATE_TRANSITION. Submit refreshes submitted_at, clears reason and records driver actor/from/to in the existing review-event transaction. |
| TD-67 | Shared onboarding actions and driver draft/status contract | Accepted implementation choice 2026-10-22 | Extract provider edit-state, submission completeness/document checks, upload checks, document replacement, submission event and timeline into shared Domain Onboarding rules/actions; both surface services reuse them. Driver PUT accepts optional full_name (nonempty max255), nullable national_id (max20), optional vehicle object and document replacements. A supplied vehicle object requires vehicle_type/make_model/plate_number; optional model_year (signed SMALLINT capacity) and color preserve omitted values and clear on null. Update the owned active vehicle in place under the profile lock; create only if none active, preserving inactive history and ux_vehicle_active. No vehicle IDs/ownership/status accepted. Shared TD-63 fresh upload/role/format/size/replacement/masking rules apply. Response keeps provider-style top-level fields and timeline, adds full_name and nullable vehicle (UUID, enum type, model/year/color/public plate/active), missing_for_activation includes the six TD-65 items plus account_status: saved city/districts/user image/birth date/nonblank nationality, primary non-inactive driver payout and approved status, following TD-64 completion semantics; no online/dispatch certification. Reads and mutations no-store/private. Operational route behavior tested with a test-only approved middleware probe until that feature is built. |

| TD-68 | Customer address default lifecycle | Accepted 2026-10-22, owner supplied | CUST-004: first live address becomes default; subsequent creates do not change it. Set-default clears the previous default and sets the owned target in one transaction. Deleting the default soft-deletes it and promotes the newest remaining live address; newest means created_at DESC then id DESC. Deleting the last address leaves no default. Lock the customers profile row (customer_addresses.customer_id references customers.user_id) before every mutation to serialize even concurrent first creates; the existing generated default_key unique index is the safety net. No maximum address count. |
| TD-69 | Customer address API and service-area guard | Accepted implementation choice 2026-10-22, owner contract | Supersede the address unregistration in TD-30 with new CustomerAddress endpoints on customer_addresses; leave TD-30 legacy classes/table reference-only. Create requires label (max60), city UUID and address_line (max255); label_type defaults to schema other. PUT preserves omitted keys; nullable district/details and paired lat/lng may be cleared. Both coordinate keys must be supplied together and both numeric in range or both null. Never accept ownership/default/deletion keys through writes; PATCH default is the explicit action. Domain checks on create/update/set-default reject inactive city/district with 422 SERVICE_AREA_UNAVAILABLE; district belonging to another city gives 422 BUSINESS_RULE_VIOLATED. Update checks the resulting city/district, including retained values. Read/delete remain available for saved inactive areas; automatic default promotion chooses newest even if its area has since been disabled, without certifying booking eligibility. Foreign, deleted, missing, malformed and integer address routes all return the same 404 ENTITY_NOT_FOUND; ownership checked before update validation and again under lock. List is unpaginated, default first then newest. Its empty list Form Request explicitly documents no query parameters; the generator honors that opt-out instead of inventing pagination keys. Every item exposes the supplied stable field contract, numeric/null coordinates and formatted nonempty city، district، address_line، details with localized geo fallback to Arabic; no geo-context header required. Existing active-account middleware applies; responses private/no-store. |


| TD-70 | Provider category and pricing API | Accepted 2026-10-22, owner supplied | PROV-009/010 approved operational group. Preserve onboarding GET /provider/categories; GET /provider/my-categories lists owned assignment UUIDs, category UUIDs, status and enabled state. POST /provider/categories requests an active extra category as requested, enabled by schema default, without reviewer metadata; existing assignment is 409 ENTITY_ALREADY_EXISTS (unique guard), no implicit re-request/review. ADMIN-007 owns later review. Category toggle accepts explicit is_enabled on owned approved assignments (foreign/missing routes 404). Services list requires category_id UUID and approved+enabled active owned category, returns only active roots/groups/choices with localized text, admin range/reference/duration and nullable own price/state/duration. PUT root requires price/is_active/choices array; optional nullable positive INT duration override; omitted duration preserves it. Choice array upserts only supplied active choices of that root, preserving omitted prices. All supplied choice rows become active; root visibility is independent. PATCH toggle accepts explicit is_active, including false before first pricing as a no-op. Summary counts priced roots and enabled complete roots in eligible categories. Lists unpaginated; all reads/writes private/no-store. |
| TD-71 | Atomic pricing and shared completeness | Accepted 2026-10-22, owner supplied | Lock provider then category then catalog nodes in ID order, matching admin category/catalog locks. Recheck provider approval/category eligibility in transaction. Domain validates type/root/choice membership and exact decimal ranges before SQL: 422 PRICE_OUT_OF_RANGE names the offending node with safe UUID/name/min/max; structural violations use BUSINESS_RULE_VIOLATED. Never price groups. Base and supplied choice updates commit together; enabling requires active base and every active choice in active groups priced, including explicit free zero. Incomplete pricing returns 422 BUSINESS_RULE_VIOLATED with missing node UUIDs/names and rolls back all writes. One Domain rule/query owns this predicate for enable, toggle, summary and OnboardingChecklist.services; category/account eligibility remain separate. Inactive catalog nodes are hidden, flags and prices retained. |
| TD-72 | Scoped direct provider profile and portfolio | Accepted 2026-10-22, owner supplied | PROV-008 direct PUT /provider/profile accepts only photo (fresh hash or null), bio, professional_title (max150) and schema default_transport_mode ask/joshen_driver/self; omitted keys preserve, null clears nullable values. Photo attaches to users.image; portfolio to existing providers.portfolio. All matching active provider tokens may complete these before approval (SCR-18); important name/phone/identity/documents/city/coverage/bank and review apply remain pending, DEC-25 open. Portfolio GET exposes hash/url/sort_order, POST accepts photos list of fresh hashes and appends, DELETE /{hash} removes an owned portfolio photo, PATCH /reorder accepts photos objects hash/sort_order containing every current owned hash exactly once with distinct nonnegative INT positions. Missing/foreign media routes 404; invalid sets/uploads 422 BUSINESS_RULE_VIOLATED. Reuse extracted TD-63 freshness/uploader/storage guard, enforce collection size and jpg/jpeg/png/webp MIME policy from media_keys under profile lock. Soft delete photo rows to keep physical cleanup outside rollback-sensitive writes. |
| TD-73 | Provider portfolio operating limit | Accepted working assumption 2026-10-22, owner supplied | platform_settings.provider_portfolio_max_photos is a JSON nonnegative integer; absent defaults to 12. Existing $media_keys retains 5 MiB and jpg/jpeg/png/webp; no count added there. Domain counts existing live portfolio + incoming hashes under provider lock, refuses over limit with 422 BUSINESS_RULE_VIOLATED naming limit. Lowering limit never removes existing photos; read/delete/reorder remain allowed, additions refused until they fit. GET portfolio returns portfolio_max_photos and can_add_photo. ADMIN-025 owns dashboard editing of platform_settings; current legacy app_configs /admin/settings is a different store. |


| TD-74 | Nested provider references and generated chaining | Accepted implementation choice 2026-10-22 | Complete ApiMasterRequest nested wildcard resolution for choices.*.service_id using concrete dot paths and batched UUID resolution, preserving existing scalar/list semantics. TD-57 generated variables distinguish provider_category_id (assignment data.id) from category_id (catalog data.category_id), and portfolio_hash (attached data.portfolio.*.hash) from media_hash (fresh upload). Explicit path parameter suffixes _id/_hash carry those meanings; structured save metadata belongs in PostmanVariableMap, never hand-written collection scripts. Captured route examples prune unavailable setters. |


| TD-75 | Read saved direct profile fields | Accepted implementation choice 2026-10-22 | GET /provider/profile uses the same scoped public resource as PUT and the same pre-approval provider-token boundary. Existing auth/me and onboarding do not expose bio/title/default transport; the edit screen must reload persisted values after restart. No additional writable fields, reviewed-change semantics or screens are introduced. |
| TD-76 | First-set completion fields vs reviewed changes | Accepted working assumption 2026-10-22, owner supplied; supersedes the "read-only" clause of TD-62 for these fields only | PROV-008/DRIV-006 + SCR-18/19: city, coverage, payout account and (driver) birth_date/nationality are completed from the account. Setting one for the FIRST time (unset → value) is a direct edit in every account status, because it completes the account and changes no approved data. CHANGING an already-set value while approved or suspended is BR-04 profile-change work (profile_change_requests, deferred by TD-44/DEC-25): 409 PROFILE_CHANGE_REVIEW_REQUIRED, nothing written, until that feature exists. Before first approval every change is direct, except that during pending_review changing an already-set city or payout account follows TD-62 (409 INVALID_STATE_TRANSITION); a first-time city or payout during pending_review is still direct. The profile photo is always direct (BRD DRIV-006, TD-72). Working hours stay with PROV-013. |
| TD-77 | Account completion API contract | Accepted implementation choice 2026-10-22 | Routes sit in the app account group (any status, matching app token): provider GET/PUT profile/work-area, PUT profile/photo (beside TD-72's PUT profile photo key, same rule), GET/POST profile/payout-account; delivery GET/PUT profile/work-area, PUT profile/photo, GET/PUT profile/personal, GET/POST profile/payout-account. GET routes return the PUT resource so the app can prefill after restart (TD-75 precedent). PUT preserves omitted keys. city_id is a required-if-sent active city UUID; district_ids is a list replacing the saved set (empty clears), each active and belonging to the resulting city — retained districts are rechecked when only the city changes (422 BUSINESS_RULE_VIOLATED); when city_id or district_ids is sent, an inactive resulting city or district is 422 SERVICE_AREA_UNAVAILABLE and districts without a city are 422 BUSINESS_RULE_VIOLATED (TD-69 rules reused). Provider base_lat/base_lng are sent together (both numbers or both null) and service_radius_km is an integer 1–32767 (SMALLINT, ck_providers_radius). service_radius_km is NOT NULL DEFAULT 10, so provider coverage is one group: it counts as set once districts or base coordinates are saved (TD-64 predicate); until then radius edits are first-set. Driver coverage = districts; birth_date (Y-m-d, before today) and nationality (max60) are separate fields, explicit null clears (a change). Re-sending the saved value is not a change. Payout POST takes holder_name (max150), bank_name (max100), iban ^SA[0-9A-Z]{22}$ (schema ck_pa_iban, no checksum or normalisation) and creates a pending primary row; replacing an account (where allowed) marks the old primary inactive in the same transaction, keeping history. GET payout returns the primary non-inactive account or null, IBAN always ****+last four (TD-45), plus can_edit. Work-area and personal resources carry locked_fields: the saved request keys whose change would get 409 now. Photo PUT takes photo (fresh owned hash or null) under the TD-63/72 upload guard and returns photo URL + has_photo. Each mutation locks the profile row; responses no-store/private. |
| TD-78 | Customer discovery eligibility and service location | Accepted working assumption 2026-10-22, owner supplied (eligibility list, accepting-bookings fixture, SCR-18 scope) | CUST-005/006/007/008 + BR-08/09: one Domain query (`App\Domain\Discovery\EligibleProviders`) serves every discovery endpoint. Eligible provider = account_status approved (so never suspended, TD-36), is_accepting_bookings, user active and not banned, coverage reaching the location, and at least one bookable offer: active type-1 service in an active category she is approved+enabled for, priced complete by the TD-71 predicate (`CompleteServicePricing::anyProvider()`, same rule as pricing/onboarding). Featured ranking (v_provider_featured priority, highest first) is an ORDER BY after filtering; it never adds a provider. Coverage (`ProviderCoverage`, shared with OnboardingChecklist, TD-64): district coverage matches the location's district, or — without a district — any of her districts in its city; radius coverage matches when the location's map point is within service_radius_km of her base point (ST_Distance_Sphere), or — without a map point — when her city is the location's city. Location precedence: `address_id` (owned address, TD-69) → `city_id`/`district_id` → the signed-in customer's default address → none. With no location, coverage is skipped (account and pricing gates still apply). Explicitly chosen disabled areas are 422 SERVICE_AREA_UNAVAILABLE; a default address whose area was disabled later still loads the screen with no eligible provider. `is_accepting_bookings` has no write path until PROV-006 (SCR-21 toggle): tests set it on the model; real providers stay ineligible until that toggle ships. SCR-18 completeness (photo, portfolio, working hours, payout) is not a browsing gate; checkout/availability owns it. |
| TD-79 | Customer discovery API contract | Accepted implementation choice 2026-10-22 | Public routes in guest.php under `app.optional:customer`: no bearer token = guest (BR-02); a sent token is checked like auth:api + app:customer + active (invalid/expired 401, other app 403, blocked account refused). Endpoints: GET onboarding-slides, home, categories, categories/{category}, services/{service}, search. Catalog = active categories/type-1 services regardless of location; `services_count` counts active type-1 services. `from_price` = lowest eligible base price at the location (never the order price, SCR-10), null when no eligible provider; choice `from_price` = lowest active choice price among providers who can book the root. Service details list only active option groups with active choices, each with is_required/selection (TD-42); type 2/3, inactive service or inactive category = 404 ENTITY_NOT_FOUND. Search tabs services (default) / packages / providers return only offerings with an eligible provider; q matches service names in any locale, package names, provider display names (LIKE, wildcards escaped); paginated `{data, links, meta}` with the query string kept. Home: banners = sliders target_app customer, placement home, active, starts_at null/≤ now, ends_at null/> now (BR-30); featured_packages = up to 10 live packages (approved, visible, not hidden, active category) of eligible providers, featured providers first, newest next — no "Featured" label (DEC-23 open); user = null for guests, else name, wallet_balance, the location's saved address (null when browsing by city), is_service_area_available; active_order = null until orders. Responses for a signed-in customer are no-store/private. |
| TD-80 | Base sliders and search routes retired | Accepted 2026-10-22 | `GET /client/sliders` is unregistered: home.banners replaces it with BR-30 targeting and dates. `GET /client/search/{suggestions,trending,popular}` and `/client/search/recent` are unregistered: they read the base search_histories table, which nothing writes (always empty), trending/popular would show other customers' raw queries, and the schema logs only Joshen-finder searches (finder_searches, BR-10). GET /client/search replaces them. Classes stay as reference (TD-30 precedent). |
| TD-82 | Delivery fee rules: one active rule per city plus one default | Accepted 2026-10-25 (reviewed: it is the schema's own model — `delivery_fee_rules.city_id NULL = default rule`, with base, per-km, min and max — and BR-11 names no other input) | BR-11 gives no formula and no values. `delivery_fee_rules` holds `base_fee`, `per_km_fee`, `min_fee`, `max_fee` per city (`city_id` null = default). The fee is `base_fee + per_km_fee × provider→customer km`, raised to `min_fee`, capped at `max_fee`, rounded half-up to 2 decimals (`DeliveryFeeCalculator`). A city rule beats the default; no active rule ⇒ 422 `DELIVERY_FEE_UNAVAILABLE`. A second active rule for one city is 409 `DELIVERY_FEE_RULE_CONFLICT`. Values are admin data, never constants. |
| TD-83 | Provider and driver coverage: the account owner and the admin both edit it | Accepted 2026-10-25 (reviewed against ADMIN-021) | ADMIN-021 says admins "link providers/drivers to their coverage areas", while SCR-18/19 and TD-76/77 let the account owner pick her own city, districts and radius. Both are built: the owner from her app (`profile/work-area`, direct for the first set, then BR-04 review), and the admin with `PUT /admin/providers/{id}/work-area` and `PUT /admin/drivers/{id}/work-area` in **any status** — the admin is the reviewer, so the owner's "changing a saved value after approval" block does not apply to her. Both use the same Domain action and rules (enabled city/districts, districts belong to the city). Audited with field names only. Limitation: the owner gets no in-app notice of an admin change. |
| TD-84 | Unrouted base wallet/address code removed; geo defaults are SAR | Accepted 2026-10-25 | `WalletController`, `WalletQueryService`, `WalletCommandService`, `WithdrawRequest`, `AddressController`, `AddressQueryService`, `AddressCommandService`, `AddressRequest` (all unrouted since TD-30, contradicting DEC-10 or replaced by CUST-004) are deleted. The base `transactions`, `withdraw_requests`, `addresses` tables and models stay until a schema cleanup migration is approved. `config/geo.php` defaults to `SA`/`SAR` and the seeded `app.default_currency` is `SAR` (DEC-17). |
| TD-85 | ADMIN-007 is a queue over the existing review | Accepted implementation choice 2026-10-25 | `GET /admin/registration-requests` unions submitted providers and drivers (drafts, admins and deleted users excluded); details and the approve / reject / request-changes decisions delegate to `ProviderService` / `DriverService` and so to `AccountReviewService` — no second review logic. Own permission section `registration_review`. Suspend / reactivate stay on the provider and driver pages. |
| TD-86 | Provider category requests are decided by admin; no stored reason | Accepted 2026-10-25 (reviewed: the BRD requires admin approval for a new main service (PROV-009, WF-02); rejection stays final as TD-70 already fixed (owner-supplied)) | AC-PROV-009 needs admin approval to add a main category. `GET /admin/category-requests` lists `provider_categories.status = requested` of **approved** providers (registration categories are approved with the account, TD-60); approve / reject stamp `reviewed_by` / `reviewed_at`, audit it and notify the provider. Rejection is final (TD-70). The schema has no reason column, so the optional rejection reason lives in the notice and audit trail. Same `registration_review` section. **Screen gap:** `provider--services-pricing--01-list` shows only the approved category chips and no "add a category" entry; the BRD requirement (PROV-009, WF-02) stands, so the provider app design must add one — the provider endpoint `POST /provider/categories` already exists (TD-70). |
| TD-87 | Package review lifecycle | Accepted 2026-10-25 (reviewed: D1 requires Joshen's review before packages go live, so changed content cannot stay live unreviewed; buyers keep their own snapshot) | D1: a package is `draft` → `pending_review` (provider submits) → `approved` / `rejected` (Joshen decides, reason required to reject). A `rejected` package can be fixed and resubmitted. **Editing an approved package returns it to `pending_review`** and it stops being shown until re-approved; customers who already bought keep their snapshot. The provider's own switch (`is_visible`) works on approved packages only; Joshen's hide (`hidden_by_admin`) is separate and survives edits. `archived` is final. D1 overrides the BRD line "no per-package approval according to settings": no setting. |
| TD-88 | Package content rules | Accepted 2026-10-25 (reviewed: the union of the screen (two services) and D1 (one service, several sessions) — any package with at least two sessions) | At least **two sessions in total** (the create screen says two services; D1's example is four sessions of one service, so one service with several sessions also qualifies); 1–20 sessions per service, ≤ 20 services; services must be active bookable services in her approved, enabled categories and fully priced (BR-06/07); `compare_at_price` = Σ her price × sessions at save time and the package price must be > 0 and ≤ it (schema check); `duration_min` is snapshotted from her duration. A package sells only while every included service stays active and enabled (BR-07). Net/commission shown to the provider is a preview from the active package commission rule, `null` without one. |
| TD-89 | Customer profile edit and mobile change | Accepted implementation choice 2026-10-25 | CUST-023: name, optional email, city, optional birth year and photo are edited directly (no review, BR-04 is for providers/drivers; email is unverified and never a login, DEC-15). The BRD's "change password" does not apply (no passwords). The mobile number changes in two steps — an OTP to the **new** number, then verification — and is saved only afterwards (AC-CUST-023). The phone is the person's identity in every app (TD-01), so the change also moves her provider/driver sign-in number; a number that belongs to another account is refused (409 `PHONE_IN_USE`). One pending change per customer, one send per 60 s plus the `otp-send` limiter. The OTP itself stays TD-24 (fixed `1234`) until the SMS gateway (DEC-15). |
| TD-90 | Add-category entry missing from the provider design (SCR-35) | Accepted 2026-10-25 | The BRD requires a service-addition request with admin approval (PROV-009, AC-PROV-009, WF-02) but the prototype screen `provider--services-pricing--01-list` has no entry point. The requirement stands (screens never override the BRD, guide §17). Backend: `POST /provider/categories` (TD-70) plus `GET /provider/my-categories/available` (categories she can still request) and the admin review (TD-86). The provider app screen is specified in [provider/services-pricing.md §2.1](provider/services-pricing.md) until design draws it. BRD §17 is read-only input, so the screen addition is recorded here as SCR-35. |
| TD-91 | Provider mobile change by OTP; driver has no screen | Owner instruction 2026-10-25 | SCR-26 (open DEC-25) shows the provider editing her phone directly (`provider--account--04-edit-profile-p1`). The owner asked to build what the screens show: the provider changes her mobile number with an OTP to the new number, in any account status, through the same shared implementation as the customer (TD-89): `POST /provider/profile/phone`, `…/resend-code`, `…/verify`. Challenges are bound to the app and account that asked. Name, documents and bank stay under BR-04 / DEC-25 (reviewed changes, deferred TD-44). The **driver** app has no profile screens captured (SCREENS_INDEX), so no driver endpoint is built; the shared actions can serve it when its screen exists. OTP is still the fixed `1234` (TD-24). |
| TD-92 | Phase 2 booking windows and overlap | Working assumption 2026-10-25 | `booking_overlap_statuses` = pending_provider, awaiting_payment, confirmed block the provider for `[start, start + estimated_duration_min)`; `booking_slot_step_minutes` = 15; `booking_minimum_lead_minutes` = 60; `instant_eta_minutes` = 45 (SCR-13); `provider_scheduled_response_minutes` = 30; `customer_payment_window_minutes` = 15. Every value is in `app_configs`. A settings update is rejected if scheduled provider response plus payment window exceeds minimum lead. **values proposed by the project owner on 2026-10-25; DEC-05 stays Partly resolved until the client confirms the numbers**. DEC-04 capacity/slot holds remain open. |
| TD-93 | Provider service commission at checkout | Owner instruction 2026-10-25 | Quote and order creation require the latest active `commission_rules` provider/service rule effective now and `calc_type=percent`; no rule or a fixed rule returns generic `COMMISSION_RULE_UNAVAILABLE`, logs the configuration detail, and writes no order. Snapshot the exact percent in `orders.provider_commission_rate`; never default to zero. **Fixed-amount provider commission is not supported at checkout until the earnings feature adds a snapshot column; checkout rejects it.** No commission schema or admin CRUD change in Phase 2. |
| TD-94 | Simulated payment gateway for Phase 2 | Working implementation 2026-10-25; **launch blocker** | `SimulatedPaymentGateway` implements `PaymentGatewayInterface` for card/mada only, gated to local/testing plus config. Production checkout must integrate a real DEC-06 vendor with verified callbacks, retries and reconciliation before launch. D3 paid rejection/timeout credits the customer wallet via `wallet_transactions` and its balance trigger. No wallet/payment screen in this slice. |
| TD-95 | Stored idempotency and human order codes | Owner instruction 2026-10-25 | Schema addition: internal table `idempotency_keys` (`user_id`, `scope`, `idempotency_key`, `request_hash`, `order_id`; unique per user + scope + key; no uuid, TD-22). `POST /client/orders` stores the key with the sha256 of the canonical body in the customer-locked transaction: same key + same body replays the same order, same key + different body is 409 `IDEMPOTENCY_KEY_REUSED`. Keys are pruned after 7 days (`model:prune`, daily). Order `code` is now a random `JSH-` + six digits (unique by `ux_orders_code`) instead of a hash of the key. | CUST-012/013, api-surfaces human codes |
| TD-96 | Realtime relay for order events | Owner instruction 2026-10-25 | Laravel publishes `{event, payload, recipient_id, app}` to the Redis channel `{REALTIME_APP_KEY}_laravel_to_node` through `RealtimePublisherInterface` (Redis driver on a prefix-less `realtime` connection; null driver while `REALTIME_ENABLED=false`); the `socket-io/` service emits to the private room `user:{id}:{app}` (joined from the JWT `app` claim) so one phone running two apps stays isolated. Events `order.requested` (provider) and `order.updated` (both) carry a uuid-only snapshot and are published after commit. Best effort: apps refetch over HTTP on every event and reconnect. Each order deadline dispatches a delayed `ExpireOrderJob` (the minute scheduler stays as a safety net). PM2 `ecosystem.yml` gains `Joshen Scheduler` and `Joshen Socket`. FCM push is **not** wired (launch blocker with TD-94). | CUST-017, PROV-014/015, D3, TD-16 (chat stays deferred) |
| TD-97 | Order endpoint throttles | Working assumption 2026-10-25 | Each pending order holds a provider slot, so `POST /client/orders` allows 6 and `POST /client/orders/{order}/pay` 10 requests per minute per user (`config/order_limits.php`, env `ORDER_CREATE_PER_MINUTE` / `ORDER_PAY_PER_MINUTE`), answering 429 `RATE_LIMITED`. Values are proposals; the client has not confirmed them. | CUST-012/013, BR-08 |
| TD-98 | Featured subscription working rules | Working assumption 2026-10-25; owner request (PROV-007 + ADMIN-005, screens `provider--featured--*`); DEC-12 stays Partly resolved | Several admin-managed plans (SCR-34); fee `price` excludes VAT, `vat_amount = round_half_up(price × vat_rate / 100)` in halalas, `total = price + vat`, all snapshotted on `provider_subscriptions`. Subscribing needs an approved account (BR-03) and is refused with 409 `FEATURED_SUBSCRIPTION_ACTIVE` while one is running (one active per provider); disabled plans 422 `FEATURED_PLAN_UNAVAILABLE`. A payment attempt is a `payments` row (`purpose=featured_subscription`); success sets `active`, `starts_at` = now (whole seconds), `ends_at` = start + `duration_days`; failure sets `payment_failed`, changes nothing else, and the retry reuses that subscription row with a new payment attempt. Methods: card, mada, apple_pay (simulator, TD-94) and `earnings_deduction`, which needs no gateway, marks the payment paid with 0 wallet/gateway amount and writes a **negative `subscription_deduction` earning** (`available`) for the payout feature to net; no balance check. Wallet and Tabby are not provider methods. Expiry: ranking stops at `ends_at` through `v_provider_featured`; `featured:expire` (every minute) and any read of her status set `expired` and notify. **`auto_renew` is stored and switchable but no automatic charge runs** (needs a real gateway token, DEC-06); at `ends_at` the plan simply expires. A plan with subscription history cannot be deleted (409 `FEATURED_PLAN_IN_USE`), only disabled; price edits affect new purchases only. No cancel, refund or admin suspend action (DEC-12 open). | PROV-007, ADMIN-005, BR-09/14/27 |
| TD-99 | Banner form rules | Working assumption 2026-10-25; ADMIN-023, BR-30 (the BRD does not define link targets) | `POST /admin/sliders` takes `{ar, en}` text (Arabic required, TD-12) and requires `target_app`, `placement` and `image` (schema §20: image is required, columns have no default for the first two); `link_type` defaults to `none`, `sort_order` to 0. `link` follows `link_type`: `none` → must be empty (and is cleared when an edit switches to `none`), `category`/`service`/`package`/`provider` → the record's **uuid** (TD-19) that must exist, `screen` → app route text, `url` → full URL. `ends_at` must be after `starts_at`, checked against the stored counterpart on partial updates (mirrors `ck_sliders_dates`). Sending a new image hash replaces the old one. `created_by` is the creating admin. | ADMIN-023, BR-30, TD-12, TD-14, TD-19 |
| TD-100 | Customer order tracking: tabs and execution stages | Working assumption 2026-10-26; CUST-017, BR-19/22 (the BRD names the groups but not their statuses) | `GET /client/orders?tab=` groups orders: `current` = pending_provider, awaiting_payment, preparing, on_the_way, arrived, in_progress and confirmed instant; `upcoming` = confirmed scheduled; `completed`; `cancelled` = cancelled + rejected + expired. A confirmed order advances `confirmed → (preparing →) on_the_way → arrived → in_progress → completed` only through `AdvanceOrderAction`, which stamps `departed_at` / `arrived_at` / `started_at` / `completed_at`, relies on the history trigger for the actor, and sends the customer one `order.updated` (`status_changed`) plus an inbox row — whoever (provider or driver) contributed it, so the customer sees one status (BR-22). `preparing` may be skipped; a repeat of the current stage is a no-op. The customer timeline hides the actor. Cancellation transitions stay with CUST-020. |
| TD-101 | Provider order execution endpoints | Working assumption 2026-10-26 (owner-approved plan); PROV-019/020, BR-03/19/22/23 | `POST /provider/orders/{order}/status` with body `status` = `preparing\|on_the_way\|arrived\|in_progress\|completed` (one endpoint; the value is validated against the execution stages) moves **her own** order (another provider's, unknown or integer id = 404; approved accounts only, BR-03) through `ExecuteOrderStageAction` → the shared `AdvanceOrderAction` (TD-100), so the customer sees one status. A repeat of the current stage is checked **before** any rule and returns 200 unchanged (no second history row or notification). `OrderExecutionGate` evaluates, in this order: `ORDER_NOT_EXECUTABLE` (422, `order_kind = package_purchase` has no visit) → `INVALID_STATE_TRANSITION` (422, wrong or skipped stage; `preparing` may be skipped) → `ORDER_STAGE_MANAGED_BY_DRIVER` (422, `on_the_way` when `transport_mode = joshen_driver`: it derives from the driver trip) → `ORDER_EXECUTION_TOO_EARLY` (TD-102) → the cycle F hook `DriverTripHasArrived`. With a Joshen driver she keeps `preparing`, `arrived`, `start` and `complete` (PROV-019 "after both arrive, the provider confirms arrival and begins service"; PROV-020 applies in both modes); `transport_mode` null is treated as self travel because nothing sets it before cycle F. **DEC-08 (who confirms driver arrival) stays Open:** `DriverTripHasArrived` passes today and does not decide it. The provider resource carries `can_prepare`, `can_start_travel`, `can_mark_arrived`, `can_start_service`, `can_complete` from the same gate. `GET /provider/appointments?tab=completed` lists her `completed` orders, newest `completed_at` first, in the lean tile (unpaginated, like the other tabs); `today` / `upcoming` keep scheduled orders through `preparing … in_progress` so an order under way does not vanish. Completion writes **no earning** (Sprint 3). |
| TD-102 | Scheduled start window | Working assumption 2026-10-26; **awaiting client confirmation**; BR-19 | A confirmed **scheduled** order may leave `confirmed` (`preparing` or `on_the_way`) no earlier than `execution_early_start_minutes` before `scheduled_at`; earlier is 422 `ORDER_EXECUTION_TOO_EARLY`. Default **120** in `app_configs` group `booking` (above the 45-minute instant ETA and 60-minute minimum lead). Reason: leaving closes the customer's free cancellation (BR-19), so a provider must not end it days early. Instant orders have no scheduled start and are never held back; there is no late limit (delay alerts are cycle C). Later stages ignore the window. Run `php artisan db:seed --class=AppConfigSeeder` on an existing database to add the key; the value is editable through `PUT /admin/settings` (BookingSettings validates it as a positive integer). | PROV-019, BR-19 |
| TD-103 | Suspended or unapproved provider mid-order | Known gap 2026-10-26; TD-36 | Execution endpoints sit in the `approved` group (BR-03), so a suspended provider cannot advance or complete an order that was already confirmed, and the customer sees it stall. How to hand those orders over is part of the deferred already-confirmed-order handling (TD-36) and cancellation (cycle B); nothing is invented here. | PROV-019/020, BR-03 |
| TD-104 | Rating availability flags | Owner instruction 2026-10-26; BR-28, DEC-19 (resolved by schema) | `can_rate` is on the customer order details (`status = completed` and no `customer_to_provider` review) and on the provider order tile and details (`status = completed` and no `provider_to_customer` review), so the two directions are independent. `provider_to_driver` is out of scope. DEC-19's customer submission limits stay open, so only the schema's one-per-direction rule (`ux_reviews_order_dir`) is used. The flag tells the app the order is **eligible**; the rating endpoints (CUST-021, PROV-024) are separate Phase 4 features, so the apps must not show the Rate action yet. | CUST-021, PROV-024, BR-28 |
| TD-105 | Customer cancellation default policy, rule stage and deployment | Owner-approved 2026-10-26; CUST-020, BR-19/20/21, DEC-24 | A forward data migration inserts the three customer/`any` DEC-24 rows (allowed `before_window` 0%/full wallet refund; denied `after_departure` and `after_arrival`), with Arabic and English translations, **only if no active customer rule exists**. The local demo seeder mirrors these customer rows and seeds no provider policy; tests create their own rows except one migration-default test. No active rule for the resolved stage fails closed with 422 `ORDER_CANCELLATION_NOT_ALLOWED`. Pre-execution statuses are `pending_provider`, `awaiting_payment`, `confirmed`, `preparing`; `on_the_way` starts the 422 `ORDER_CANCELLATION_LOCKED` execution lock even after delay. A timed `within_window` rule resolves scheduled `before_window` while `now <= scheduled_at - window_minutes`, otherwise `within_window`; instant `before_window` while `confirmed_at` is null or `now <= confirmed_at + window_minutes`, otherwise `within_window`. With no timed rule, all pre-execution orders resolve `before_window`. Exact booking type beats `any`, then `sort_order`/id. A same-customer repeat cancellation is 200 unchanged; another party's cancelled order is `INVALID_STATE_TRANSITION`. | CUST-020, BR-19/20/21, DEC-24 |
| TD-106 | Customer cancellation fee and refund boundary | Owner-approved 2026-10-26; CUST-020, BR-20, DEC-10 | Unpaid orders always have zero fee and no refund. For a paid order, fee = half-up 2-decimal percentage of `services_amount` plus `delivery_fee` only if `charge_delivery_fee`, clamped between zero and the paid amount; refund = paid amount minus fee, credited once to the wallet. A positive partial refund sets order and payment `partially_refunded`; a full refund sets `refunded`. If the fee consumes the whole payment, `refunds.amount > 0` forbids a zero row: no refund or wallet transaction is written and payment remains `paid`. No provider/driver earning or fee share is written in Sprint 2; earnings belong to Sprint 3. | CUST-020, BR-20, DEC-10 |
| TD-107 | Correlation id on validation 422 | Customer app team question 2026-10-26; TD-02 | The validation 422 body carries `data.errors` only. The trace id is the `X-Correlation-ID` header (present on every response); domain and 5xx errors also repeat it in `data.correlation_id`. Kept as is so the TD-02 body stays a pure field map; documented in `API_CONVENTIONS` 2.10.1 §3.1. |
| TD-108 | Generic 5xx messages outside local development | Customer app team request 2026-10-26; security | API 5xx responses always use a generic translated message in staging and production, even if debug mode is enabled. Exception details remain in logs and `data.correlation_id` identifies the request. Local developers may see details when debug is enabled. |
| TD-109 | Delay clock: monitored stages and deadlines | Owner-approved 2026-10-26 (Sprint 2 cycle C); **awaiting client confirmation of the numbers**; CUST-019, PROV-019, BR-21 | The BRD gives one admin-configured period but no per-stage expectation, so each watched stage gets a deadline: `confirmed`/`preparing` instant = `confirmed_at` + period; scheduled = later of (stage entry, `scheduled_at` − lead) + period (a late payment or an early `preparing` never alerts at once); `on_the_way` = expected arrival + period, never before `departed_at` + period (expected arrival = `confirmed_at` + `eta_minutes` for instant, `scheduled_at` for scheduled); `arrived` = `arrived_at` + period; `in_progress` = `started_at` + `estimated_duration_min` + period (not watched when the duration is null). Not watched: `pending_provider` and `awaiting_payment` (expiry owns them), terminal statuses, `package_purchase` orders (no visit) and `joshen_driver` orders until the trip flow (cycle F, where the departure derives from the trip; `OrderDelayClock::isMonitored()` is the hook). Stage entry comes from the `orders` columns (`confirmed_at`, `departed_at`, `arrived_at`, `started_at`), stamped by the application with the same clock as `delay_alerted_at`; only `preparing` has no column and reads its `order_status_history` row (written by the DB trigger). A stage whose entry is unknown is not judged. |
| TD-110 | Delay settings, defaults and missing rows | Owner-approved 2026-10-26; **awaiting client confirmation**; BR-21, DEC-05 | `order_status_delay_minutes` = `15` (missing-update period) and `scheduled_delay_clock_lead_minutes` = `60` (how long before the service time the scheduled clock starts) in `app_configs` group `booking`, validated by `BookingSettings` as positive integers. Invariant: the lead may not exceed `execution_early_start_minutes` (the clock must not start before the provider may move; 422 `INVALID_BOOKING_SETTINGS`). A forward migration inserts `execution_early_start_minutes` (closing the cycle A gap: without the seed every first stage update of a scheduled order failed), `order_status_delay_minutes` and `scheduled_delay_clock_lead_minutes` when missing and never overwrites an edit; `AppConfigSeeder` seeds them too. For the two new keys a missing row falls back to the code defaults; a **present but invalid** value makes `orders:alert-delays` log an error, exit with failure and send no alert (never alert on a guess). No new `ErrorCode`: the settings refusals reuse `INVALID_BOOKING_SETTINGS`. |
| TD-111 | Alert cadence and the meaning of "delayed" | Owner-approved 2026-10-26; BR-21, CUST-019 | One alert **per stage**, using the single `orders.delay_alerted_at` column and no schema change: an alert counts for the current stage when `delay_alerted_at >=` the moment the stage began (TD-109), so each new stage can alert once and the column always holds the latest alert (the inbox rows keep the history). `is_delayed` (admin) and `delay_alert` (customer) mean "alerted for the stage the order is in now"; they clear when the provider moves on. The `delayed` filter is the model scope `Order::delayed()`, built with the same stage-to-column mapping as `OrderDelayClock` (CASE) and tested to agree with it. `AlertOrderDelaysAction` picks candidates, then re-reads each order under `lockForUpdate`, re-judges its current stage and stamps `delay_alerted_at` once, so a status update that wins the race leaves no alert. The alert never changes status, provider or cancellation: `can_cancel` keeps coming from `OrderCancellationGate` (BR-19/21). |
| TD-112 | Admin order monitor: read model, PII and filters | Owner-approved 2026-10-26; ADMIN-013, BR-29 | `GET /admin/orders` and `/admin/orders/{order}` (section `orders`, action view) are **read-only**: the BRD only monitors, so no admin action changes an order (ADMIN-018 is Sprint 3). Customer and provider show name and phone as the existing admin screens do; national id, commercial register, VAT number, IBAN, authorized person, documents and payout account are never returned; notification history carries event, recipient app and time only (no title, body or phone); payments omit the gateway reference and failure text. Status history includes the actor (customers never see it, BR-22). Filters: `status`, `booking_type`, `payment_status`, `order_kind`, `provider_id`, `customer_id`, `city_id` (uuids), `created_from`/`created_to` (compare `created_at`), `scheduled_from`/`scheduled_to` (compare `scheduled_at`), `delayed`; `search` on the code; sorts `created_at` (default desc), `scheduled_at`, `total_amount`; `paginate=false` capped at 500 rows. The driver-request / driver-acceptance stages, `driver` and `dispatch` wait for `trips` (cycle F, null now); chat is deferred (`has_chat: false`, `chat_status: null`, TD-16). |
| TD-113 | Customer `delay_alert`, inbox scenario and realtime reason | Owner-approved 2026-10-26; CUST-019, BR-21/22 | The customer order details always carry `delay_alert` (`null` or `{alerted_at}`). New `NotificationScenario::ORDER_DELAYED` (`order_delayed`, en/ar texts) writes one customer inbox row and a live `order.updated` with `reason: delayed` whose `status` is the unchanged status (API_CONVENTIONS 2.11.0 §9.1). The provider is not notified and nothing is pushed through FCM (out of scope). |
| TD-114 | Joshen configuration storage and validation | Owner-approved 2026-10-26; CUST-009, ADMIN-012, SCR-11, TD-12 | `app_configs` rows in group `joshan`, inserted only when missing by a forward migration and `AppConfigSeeder`; both the generic settings writer and Joshen config endpoint validate through `JoshenSettings::assertUpdate`. Missing/invalid configuration fails closed with 422 `JOSHEN_CONFIG_UNAVAILABLE`; disabled search uses 422 `JOSHEN_DISABLED`. Seed neutral Arabic product copy marked `product_copy_pending`; admin replaces it. `input_fields.address` must remain true; a submitted hidden field is 422 `JOSHEN_INPUT_DISABLED`. |
| TD-115 | Finder occasion management and service tags | Owner-approved 2026-10-26; ADMIN-012, BR-05/06, TD-22/34/42 | Occasions have admin CRUD under RBAC section `services`; Arabic required, English optional, names unique per locale. Type-1 bookable services may carry `occasion_ids`; type-2/3 nodes may not. `service_occasions` is a link table with no public UUID; use parent and occasion UUIDs (TD-22). Deletion checks service/package/search links through `CatalogRecordIsUnused`; in-use occasions return 409 `CATALOG_IN_USE`. |
| TD-116 | Finder type list and exact budget brackets | Owner-approved 2026-10-26; CUST-009, ADMIN-012, BR-05/06, SCR-11 | Service types derive from active categories plus the optional `package` value controlled by `joshan_package_enabled`; this keeps the list aligned to catalog activation and the TD-42 tree. Admin-owned budget brackets are exact two-decimal strings: first minimum `0.00`, ascending contiguous bounds, all but last have a maximum, last is open-ended; each upper bound is inclusive and the next lower bound exclusive. First bracket includes `0.00`. Service price is the eligible provider base-service price at the selected location; package price is `packages.price`. Optional-choice prices are excluded. |
| TD-117 | Finder availability behavior | Owner-approved 2026-10-26; CUST-009, BR-08, DEC-02 | No date and no time means eligibility-only results, with no availability filter; this is not an instant booking claim. Date with optional time means scheduled availability using the existing booking slot engine; a date without time accepts any eligible slot that day. Time without date is 422 validation. Packages use ordinary package eligibility and occasion/budget/location filters only; there is no package slot check. |
| TD-118 | Finder pagination and search log identity | Owner-approved 2026-10-26; CUST-009, BR-02 | The first page creates one `finder_searches` row and returns `search_id` plus total `results_count`, including zero. Later pages send the search UUID and page number, check customer or guest-device ownership, return the same search UUID and total, and never create another log row. |
| TD-119 | Guest finder device identity and throttling | Owner-approved 2026-10-26; BR-02, CUST-009 | Guests may search without signing in but must provide opaque `X-Guest-Device-ID` (maximum 100 characters). The search limiter has configurable per-user/device and per-IP minute limits; 429 uses `RATE_LIMITED`. CORS allows the device header; the same value is used for continuation and the sign-in booking handoff (API_CONVENTIONS 2.12.0). |
| TD-120 | Finder-to-order linking and guest claim | Owner-approved 2026-10-26; CUST-009, ADMIN-012, TD-19 | `orders.source=joshen_finder` requires `finder_search_id`; browse/search forbid it. The Domain rule allows a customer-owned search or a guest search whose device header matches; a guest row is claimed (`customer_id` set and device id cleared) inside the order transaction. Other ownership mismatches return 422 `FINDER_SEARCH_NOT_LINKABLE`. A search may link to multiple orders; monitoring counts distinct converted searches and total orders. |
| TD-121 | `auth/my-permissions` answer for a super admin (supersedes TD-53) | Owner-implemented 2026-10-26, recorded 2026-10-27; ADMIN-024, BR-29 | `GET /admin/auth/my-permissions` returns `is_super_admin: true` **plus the full grant dictionary and every section** for a super admin, so the dashboard builds menus from one list for every admin. `permissions` is the `{resource: [actions]}` dictionary shared with `profile.permissions` (admin auth 2.0.0). The flag is still the authority; the lists are display data. | `AdminAuthService::myPermissions()`, `MyPermissionsResource`, [admin/auth.md](admin/auth.md) 3.0.0 |
| TD-122 | Finder results are paginated in memory | Accepted technical debt 2026-10-27; CUST-009 | `JoshanFinderQueryService` loads every eligible offer (or package) of a search, then slices the page in PHP, because the availability check (`BookableTime`) runs per offer and cannot be expressed in SQL. Acceptable at the launch catalogue size. **Revisit** when one search can return more than a few hundred offers or latency is measured above target: push coverage, price and occasion filters into SQL (already done) and only run the availability check on a SQL-paginated window, or cache per-provider day availability. | `JoshanFinderQueryService::results()` |


| TD-123 | Package purchase and acceptance | Owner-accepted E1/E2 2026-10-27; **awaiting client confirmation** of the FR-03 reading; D1, CUST-008, DEC-01 | A purchase begins `awaiting_payment` without provider acceptance. Successful payment activates the purchased package and completes the purchase order; `TransitionOrderAction` permits `awaiting_payment → completed` only for a paid package purchase. Sessions follow the scheduled provider acceptance flow. |
| TD-124 | Package allocation and Sprint 3 earnings | Owner-accepted E1/E2 2026-10-27; **awaiting client confirmation** of equal allocation; D1, BR-24/26, DEC-09 | Allocate price equally across sessions in halalas, grouping shares by item in stable item order and distributing remaining halalas in that order. Each item snapshots `allocated_value` and rounded-down `session_value`. Sprint 3 earnings must skip `order_kind = package_purchase`; each completed session earns its item session value, except the final completed session of an item carries `allocated_value - session_value × (sessions_total - 1)`. No earnings are created in this stage. |
| TD-125 | Session hold, release and expiry | Owner-accepted E1/E2 2026-10-27; **awaiting client confirmation** of the expiry boundary; D1 | Booking holds one session; `ReleasePackageSessionHold` releases it once with the terminal transition on cancellation, rejection or expiry. A scheduled start at or before `expires_at` is allowed. `SettleCustomerPackage` and `packages:expire` settle active packages as consumed when all sessions complete, or expired after expiry when no session remains open. An open session postpones settlement. |
| TD-126 | Zero-charge session and commission | Owner-accepted E1/E2 2026-10-27; **awaiting client confirmation** of zero-charge paid and interim VAT `0.00` (DEC-09) | A zero-total accepted session is `payment_status = paid` without a `payments` row. The effective provider package percentage commission is snapshotted on purchase and copied to its sessions; a missing or non-percent rule blocks purchase. Order VAT remains `0.00` pending DEC-09. |
| TD-127 | Package purchase snapshots and visit location | Owner-accepted E1/E2 2026-10-27; D1, TD-08 | Five schema changes: nullable purchase visit location with a check requiring city and address for other kinds; `customer_packages.validity_days`; `customer_package_items.name_snapshot`, `duration_min`, and `allocated_value`. Historic snapshots remain nullable. |
| TD-128 | Package wallet and refund gaps | Owner-accepted E1/E2 2026-10-27; **awaiting client confirmation** of purchase refunds; DEC-06/09/10 | The existing checkout accepts simulated card/mada only; wallet payment is not implemented. Unpaid purchases can be cancelled without a refund; paid purchase cancellation/refund is deferred and returns `PACKAGE_PURCHASE_CANCELLATION_UNAVAILABLE`. Session delivery charges follow the existing cancellation policy. |

---

| TD-129 | Staging payment simulation | Owner instruction 2026-10-27; supersedes staging restriction in TD-94 | Allow the shared simulator on `APP_ENV=staging` only when `PAYMENT_STAGING_SIMULATOR_ENABLED=true` and the simulator is enabled. Production remains blocked. This affects customer payments and provider featured subscriptions; no money is charged. | CUST-008/012/013/014, PROV-007, BR-14 |

| TD-130 | Non-production payment simulation | Owner instruction 2026-10-27; supersedes TD-129 | Allow the shared simulator in any environment except `prod` and `production`, subject to `PAYMENT_SIMULATOR_ENABLED`. Both production names remain blocked regardless of configuration. | CUST-008/012/013/014, PROV-007, BR-14 |

| TD-131 | Coupon pricing and administration | Owner instruction 2026-10-27; partially specified, implementation pending | Admins configure coupon usage limits. Coupon discounts apply to the order amount before VAT; delivery is excluded and added separately. Joshen funds the discount, without reducing provider commission. Preserve used coupons through deactivation. Scope selection, redemption/refund timing and percentage rounding require clarification before implementation. Existing interim order VAT policy remains in force pending DEC-09. | CUST-011/014, ADMIN-015/016, SCR-34 |
| TD-132 | Driver freed on trip arrival and trip list tab boundaries | Owner instruction 2026-10-27 | A trip in `arrived` is not live for driver dispatch matching or offer acceptance. Live driver statuses are strictly `accepted`, `to_provider`, `at_provider`, `picked_up`, `to_customer`. Drivers who have arrived at the customer are freed to receive and accept new trip offers, avoiding indefinite blocking pending settlement. Driver trips tabs are: `current` (accepted..to_customer), `completed` (arrived + completed), `cancelled` (cancelled). Settlement (arrival confirmation, driver amounts due) is deferred to DEC-08 / Sprint 3. | DRIV-007..014, DRIV-016, BR-16..19, BR-22..23, DEC-08 |

| TD-133 | Admin-editable driver dispatch limits | Owner request 2026-10-27; **awaiting client confirmation** | `app_configs` group `dispatch_limits` stores availability, offer response and trip status rates; defaults remain 60/30/30 requests per minute, with safe defaults used for missing or invalid data; no environment override. Dashboard updates accept 1–600 and apply after bootstrap cache invalidation. Insert missing rows without overwriting edits. | ADMIN-025, DRIV-007–014, BR-16–19, BR-22–23 |

| TD-134 | Provider transport choice | Owner instruction 2026-10-28; PROV-017/018 | Instant orders may choose after confirmation; scheduled orders inside the execution_early_start window. Driver choice requires ack_driver_terms; repeating it reuses the live trip. Self may replace a search, but not an accepted or underway trip. Null mode is stamped self on departure. | BR-16/18/19/25, DEC-07 |
| TD-135 | Trip snapshot | Owner instruction 2026-10-28 | Snapshot pickup and dropoff addresses and coordinates; reuse delivery distance calculation; instant duration is eta_minutes, scheduled duration is null; delivery fee copies the order fee and code uses TRP. | PROV-018, BR-11 |
| TD-136 | Driver dispatch coverage | Owner instruction 2026-10-28; awaiting client confirmation | Match eligible drivers covering pickup OR dropoff district, falling back to city when appropriate; zero eligible drivers immediately gives no_driver. | DRIV-008/009, BR-16/18 |
| TD-137 | Dispatch offers and expiry | Owner instruction 2026-10-28 | One offer per driver per trip; dispatch tick, delayed jobs and lazy expiry maintain offers and search deadlines. First acceptance wins: lock trip then offer, withdraw competing pending offers. | DRIV-008/009, BR-17 |
| TD-138 | Driver availability and privacy | Owner instruction 2026-10-28; DEC-07 remains open | Going offline withdraws pending offers but leaves accepted trips assigned. Driver trip responses expose provider display name, no phones and no customer name. | DRIV-007/010, DEC-07 |
| TD-139 | Driver trip execution boundary | Owner instruction 2026-10-28; DEC-08 remains open | Driver status endpoint advances execution to arrival; arrival confirmation and settlement in row 46 remain unbuilt. Driver orders use the delay clock after arrival for provider-managed stages. | DRIV-010..016, BR-21/22/23, DEC-08 |
| TD-140 | Admin trip monitor locations and completed mapping | Owner instruction 2026-10-28; DEC-08 remains open | The list shows pickup_city and dropoff_city/dropoff_district: no pickup district is stored in the trip snapshot and provider coverage is not the pickup location. Full addresses and coordinates appear in details. A persisted completed trip maps to arrived_at_customer until DEC-08 is resolved. | ADMIN-014, DEC-08 |
| TD-141 | Single push notification dispatch path | Owner instruction 2026-10-28; CUST-022, PROV-025, DRIV-018 | All schema-persisted domain notifications (orders, trips, reviews, packages, subscriptions) trigger push notifications through `NotificationObserver` -> `SendPushNotificationJob`. `FCMNotificationChannel` is reserved strictly for Laravel Notification classes (such as `GeneralNotification` via `NotificationChannelResolver`). Domain events never route through a secondary push channel, guaranteeing exactly one push dispatch per domain event. | CUST-022, PROV-025, DRIV-018, DEC-13 |
| TD-142 | FIREBASE_ENABLED flag and credentials resolution | Owner instruction 2026-10-28 | Push notification delivery is strictly gated by `FIREBASE_ENABLED` (default false, false in `phpunit.xml` and `.env.example`). When false, `isConfigured()` returns false and all external calls are skipped. Credential path resolution strictly uses `config('firebase.projects.app.credentials')` (absolute path, base_path-relative, or raw JSON) with per-instance memoization, no directory globs and no hard-coded fallbacks. When enabled with invalid credentials, an error is logged once without throwing or guessing. | CUST-022, PROV-025, DRIV-018 |
| TD-143 | Push delivery retry policy and idempotent logging | Owner instruction 2026-10-28 | `sendToUser` lets transient errors (`ServerUnavailable`, `QuotaExceeded`, `ServerError`, `ApiConnectionFailed`) propagate for queue retries, while permanent errors are caught, logged, and marked `failed`. `SendPushNotificationJob` runs with `$tries = 3` and backoff `[10, 60, 300]`, recording a `failed` delivery via `failed()` on exhaustion. `recordDelivery` is idempotent per `(notification_id, channel)` via `updateOrCreate` to prevent duplicate rows across retries. Accepted limitation: only whole-request exceptions are retried. Per-token transient failures (such as Unavailable or quota) appear in `MulticastSendReport`; if at least one token succeeds, the delivery is recorded as `sent` and failed tokens are not retried. | CUST-022, PROV-025, DRIV-018 |
| TD-144 | Order chat initial policy supersedes TD-16 | Owner instruction 2026-10-29; CUST-018, PROV-021, BR-15, AC-CUST-018, AC-PROV-021 | Enable order-linked participant chat after paid confirmation. Text only; no attachments, voice, edit or delete. Sending stops immediately on completion or cancellation; existing history remains visible. Explicit mark-read through the latest displayed message clears unread only for that recipient, across devices; no peer-visible receipts. Recipient gets generic in-app and preference-aware push notification without message content. No retention deletion schedule until DEC-14 is resolved. DEC-27 phone calls remain deferred. |
| TD-145 | Persisted private chat transport | Owner instruction 2026-10-29; CUST-018, PROV-021, TD-96 | HTTP owns authorization, validation, persistence and read state. Publish after commit to each participant's `user:{id}:{app}` room; events carry UUIDs, `chat.message` for conversation detail and `chat.conversation.updated` for recipient-specific list/unread summary. Socket inbound chat relays and integer conversation presence are superseded rather than restored. Clients deduplicate by message UUID and refetch history/summary on reconnect. |
| TD-146 | Role-specific unread, bounded chat list and reconciliation | Implementation correction 2026-10-29; CUST-018, PROV-021, TD-144/145 | Incoming messages are selected by the opposite `sender_role` for the authenticated token app, even when both order sides share one account. `GET /conversations` uses page/per_page (15 default, 100 maximum) and orders by newest message then conversation ID, with empty conversations last. Summary events are invalidation hints: clients may display them optimistically but refetch authoritative summaries, and discard/refetch an HTTP response if an event arrived during its request. This avoids a new persisted revision field or message retention rule. Telescope redacts chat request diagnostics and message-bearing model/query/Redis entries while retaining unrelated diagnostics. |
| TD-147 | Preview-only order tracking: grant, rooms, location visibility and limits | Implementation decisions 2026-10-29; CUST-017, CUST-019, SCR-16, BR-21, BR-22, TD-96, TD-144/145 | (1) The customer joins `order:{uuid}:tracking` with an HMAC-SHA256 grant signed by `REALTIME_GRANT_SECRET`, a secret **separate from the JWT secret** and equal in Laravel and Node; a missing secret logs an error and answers 503 `INFRASTRUCTURE_FAILURE`, never an untyped 500. (2) The grant lives `realtime.grant_ttl_seconds` (env `REALTIME_GRANT_TTL_SECONDS`, default 120). (3) Expiry only stops new joins: a joined membership is **not** revoked when the grant expires, because the room carries the same projection `GET /orders/{order}` returns; the client unsubscribes on preview close. (4) A socket may hold at most 3 tracking rooms. (5) Tracking and chat share one definition of the execution window, `OrderStatus::isExecutionWindow()` (confirmed, preparing, on_the_way, arrived, in_progress). (6) The driver's position is visible from `trips.accepted_at`, including the `to_provider` leg, and only while the trip is live for the driver; both `POST /delivery/trips/{trip}/location` and a position sent on `PATCH /delivery/availability` publish the same `order.tracking.location` event. (7) Location writes share the `dispatch-availability` rate budget (an admin setting). (8) Every order transition into confirmed, preparing, on_the_way, arrived, in_progress, completed or cancelled publishes `order.tracking.stage`; a trip action that also moves the order publishes the same projection twice, so clients treat stage events as idempotent. (9) Chat sends are limited to `order_limits.chat.per_minute` (env `ORDER_CHAT_PER_MINUTE`, default 30) per user; chat `status`, `sender_role` and `type` are `{value,label}` objects (api-surfaces.md); `GET …/conversation` never creates a row (`conversation.id` is null until the first send). **Open, not decided here:** stale-location threshold (no age limit is applied), ETA and distance source (both stay null), live-map content and whose position is shown during self-travel (DEC-26/SCR-16 follow-up), DEC-07, DEC-08, DEC-14, DEC-27. | CUST-017, CUST-018, CUST-019, PROV-021 |
| TD-148 | Coupon scope, usage counting, rounding and free-order rule | Working assumption 2026-11-01, awaiting owner confirmation; completes TD-131; CUST-011/014, ADMIN-015/016 | (1) Scope `applies_to` = `all` \| `category` \| `service` \| `provider`; `package` exists in the schema enum but is not offered, and coupons apply to service orders only — package purchases and package sessions ignore them. (2) Only `funded_by = platform` coupons are usable; the admin form never sends it. (3) A use counts at order creation (one `coupon_redemptions` row, taken under the coupon row lock) and is released when the order becomes `rejected`, `expired` or `cancelled`: the redemption row is deleted, `orders.coupon_id` and `discount_amount` stay as history. Completion keeps the use. (4) A percent discount is `services_amount × value / 100` rounded HALF_UP to 2 dp, then capped by `max_discount`, then by `services_amount`. (5) `min_order_amount` is compared with `services_amount` (before delivery). (6) A coupon may not bring the order total (services + delivery − discount) to 0 or below: `COUPON_FREE_ORDER`. (7) `per_customer_limit` is required, default 1 (schema NOT NULL); `usage_limit` null = unlimited. (8) Codes are stored upper-case and matched case-insensitively. | CUST-011/014, ADMIN-015/016 |
| TD-149 | Driver arrival confirmation and trip earning | Owner decision 2026-11-01; resolves DEC-08; DRIV-014/015, AC-DRIV-015, BR-24/25/26 | The driver's `arrived` stage (DRIV-014) stays as is. `POST /delivery/trips/{trip}/confirm-arrival`, `POST /provider/orders/{order}/confirm-arrival` and `POST /client/orders/{order}/confirm-arrival` confirm an `arrived` trip: the first one moves it to `completed`, sets `arrival_confirmed_by` and `completed_at`, writes the history row and one driver `earnings` row (source `trip`, gross = trip delivery fee, commission from the newest active `commission_rules` driver/delivery_fee rule — percent of the fee or fixed, capped at the fee (a percent above 100 is stored as 100.00), 0 when no rule — net = gross − commission, status `pending`, no payout). Customer and provider calls are throttled per user (`order-arrival`, `ORDER_ARRIVAL_PER_MINUTE`, default 10); the driver call shares `dispatch-status-transitions`. Later confirmations are 200 no-ops keeping the first confirmer; before arrival = 422 `INVALID_STATE_TRANSITION`. The provider moving a driver order to `in_progress` confirms an unconfirmed arrival as `provider`. The order status does not change. `pending → available` and payouts belong to the settlement feature. Admin monitor stage `completed` → `arrival_confirmed` (supersedes TD-140's mapping). | DRIV-015, DEC-08, TD-132, TD-139, TD-140 |
| TD-150 | Admin order-chat monitoring | Owner decision 2026-11-01; partly resolves DEC-14; ADMIN-013, AC-ADMIN-013, BR-15 | `GET /admin/conversations` (metadata only, no message text) and `GET /admin/conversations/{conversation}` (50 messages per page, `before` cursor) under the `orders` section's view permission. Read-only: no post, edit, delete or mark-read; participants' unread state is untouched. Each history read writes an `admin_action` audit row `orders.conversation.viewed` with conversation, order and message uuids, never the text; the response is `Cache-Control: no-store, private`. Admin order list `has_chat` and details `chat_status` now reflect the conversation. Messages are never deleted; the retention period stays open under DEC-14. | DEC-14, TD-144–147 |
| TD-151 | Currency and payment gateway status | Owner instruction 2026-11-01; DEC-06 | The platform has one currency, SAR; the BRD defines no currency control, so no multi-currency logs or conversion are built and every money field stays `DECIMAL(12,2)` SAR. The real gateway integration (DEC-06) is deferred: the simulated payment flow (TD-94/130) stands in for it and the sprint tracker counts the payment rows as done; the launch blocker stays. | DEC-06, TD-94, TD-130 |
| TD-152 | Refund destination is the wallet only | Owner decision 2026-11-01; DEC-10 (partly), ADMIN-018, CUST-020, PROV-016 | Every refund — automatic (rejection, timeout, customer or provider cancellation) and admin-initiated — is credited to the customer's wallet through a `wallet_transactions` `refund_credit` row. A wallet refund **never calls the payment gateway** (no `SimulatedPaymentGateway::refund()`, no real-gateway call later): the refund row, its `completed` status and `processed_at`, the `refund_credit` wallet transaction and the payment/order `refunded` / `partially_refunded` states are all written in one transaction by `CreditWalletRefundAction`, so a "wallet" refund can never send money back to the card. The `refunds.destination` column and the `original_method` enum value exist as in the schema but nothing writes or accepts it: the admin refund form has no destination field. When the client decides when `original_method` applies, adding it is a MINOR change. | ADMIN-018, DEC-10 |
| TD-153 | Provider cancellation (PROV-016) | Implementation decision 2026-11-01; BR-19, BR-20, DEC-24 | `POST /provider/orders/{order}/cancel` (optional `reason`). Cancellable: `awaiting_payment` (scheduled, accepted but unpaid), `confirmed`, `preparing`, kinds `service` and `package_session`; a `pending_provider` request is rejected, not cancelled; from `on_the_way` the lock is 422 `ORDER_CANCELLATION_LOCKED`. The active `provider` `cancellation_rules` row for the stage must allow it (none = 422 `ORDER_CANCELLATION_NOT_ALLOWED`, fail closed). A forward migration seeds the DEC-24 provider policy (allowed `before_window`, denied `after_departure` / `after_arrival`, fee 0, no ranking penalty) only when no active provider rule exists. A paid order is refunded the **whole** paid amount to the wallet (reason `provider_cancel`) whatever the rule's fee fields say; `orders.cancellation_fee` is `0.00`. The customer and provider get the `order_cancelled` inbox row and socket event. Her own replay is a 200 no-op; the provider tile and details add `can_cancel`. | PROV-016, BR-19, BR-20, DEC-24 |
| TD-154 | Cancellation rule administration | Implementation decision 2026-11-01; ADMIN-018, BR-20 | `/admin/cancellation-rules` CRUD in section `cancellations_refunds`. Rules may be edited in place: orders keep `cancellation_rule_id` and the fee they were charged, so history is never rewritten. A rule applied to an order is deactivated, never deleted (409 `CANCELLATION_RULE_IN_USE`). Form rules: percents 0–100; a window in minutes only on `before_window` / `within_window`; a rule that forbids cancelling carries no fee; a `provider` rule carries no fee and keeps no delivery fee (the provider path refunds in full, so a fee would be silently ignored). Descriptions are optional per locale (`ar`, `en`). | ADMIN-018, BR-20 |
| TD-155 | Settling the trip and amounts due of a cancelled order | Owner instruction 2026-11-01; BR-20, BR-24/25/26, TD-149 | Every cancellation path runs `SettleCancelledOrderAction` in the cancelling transaction: all non-terminal trips are cancelled (not only searching/accepted); a trip that already `completed` (arrival confirmed) keeps the driver's `pending` earning **only if** the active rule kept the delivery fee from the refund (`charge_delivery_fee` and a fee above 0), otherwise the earning is `reversed`; any provider earning (`order` / `package_session`) of the order is `reversed`. Only `pending` / `available` rows are reversed: `in_payout` / `paid` rows are left alone and reported (`earnings_locked`) for a manual adjustment. A fee kept from the customer is not shared out as a `cancellation_fee` earning (`provider_share_percent` is recorded, paying it belongs to the payouts feature). Cancelling after execution stays blocked for customers and providers (BR-19); the settlement exists so any future path that allows it, and the rare completed-trip case, behave the same. | BR-20, BR-24/25/26, TD-149 |
| TD-156 | Admin-initiated refund and idempotency | Implementation decision 2026-11-01, amended 2026-11-02; ADMIN-018, DEC-10, TD-95 | `POST /admin/refunds` `{order_id, amount}` (`admin.refunds.store`, action create) refunds part or all of an order's newest `paid` / `partially_refunded` payment that still has money, in any order status, to the wallet (reason `admin`). Refundable = payment amount minus its `completed` and `pending` refunds. Under the order and payment row locks the refund is created and completed at once (TD-152): there is no `failed` outcome, so the retry route (`admin.refunds.retry`), `RetryRefundAction`, `can_retry` and `REFUND_NOT_RETRYABLE` were removed (2026-11-02); the `failed` enum value stays in the schema for a future `original_method` refund. The route needs an `Idempotency-Key` header (1–100 characters), stored in `idempotency_keys` (scope `refunds.store`, per admin, new nullable `refund_id` column, 7-day window, TD-95): the key is read under the order lock, so a double click makes one refund and the second call replays it (same 201 and refund id); the same key with another `order_id` / `amount` is 409 `IDEMPOTENCY_KEY_REUSED`; a missing key is 422 `VALIDATION_FAILED`. Amounts are strings / BigDecimal on the whole refund path, never `float`. A **full** refund reverses the provider's pending or available earning (the driver's trip earning stays: the trip happened). | ADMIN-018, DEC-10 |
| TD-157 | Rule for any future gateway (`original_method`) refund | Owner instruction 2026-11-02; DEC-10, TD-152 | Not built. When `original_method` is added, the gateway call is made **outside any DB transaction and never while a row lock is held**: create the refund `pending` and commit; call the gateway; then settle in a new transaction (lock the order and refund, mark `completed` or `failed`). A `failed` refund then needs a retry path that follows the same three steps. The wallet path (TD-152) stays a single transaction because it has no external call. | ADMIN-018, DEC-10 |
| TD-158 | Manual payouts: requests, accounts, netting | Owner answers 2026-11-02 (questions a–d); DEC-11, DEC-25, ADMIN-017, PROV-023, DRIV-017, BR-24/25/26, TD-98, TD-149, TD-155 | **(a)** Provider earning (`order` / `package_session`) is written `available` when the order becomes `completed` (the single `TransitionOrderAction` path; a `package_purchase` earns nothing — the provider earns per session; gross = `services_amount`, or the package share `session_value`, the last session taking `allocated_value − (n−1)×session_value`); commission = snapshotted `provider_commission_rate` on gross; `delivery_fee_amount` = `delivery_fee` only when `transport_mode = self` (BR-25). The driver trip earning (TD-149) is `available` at arrival confirmation too; existing `pending` earnings of completed trips were migrated. **(b)** `POST /provider|delivery/payouts` locks her primary payout account then her `available` earnings, refuses an open (`requested` / `processing`) request, and claims **all** available rows including negative `subscription_deduction` (TD-98): `payouts.amount` is their net; ≤ 0 or below `payout_min_amount` (app_configs, default `0.00`) is `PAYOUT_AMOUNT_NOT_PAYABLE` and links nothing. **(c)** No verification screen: a `pending` or `verified` primary account is usable, `rejected` / `inactive` / none is `PAYOUT_ACCOUNT_UNUSABLE`. Marking a request `paid` on a `pending` account stamps it `verified` (`verified_by`, `verified_at`); cancelling with `reject_account: true` sets it `rejected`. **A `rejected` account may be replaced by `POST /profile/payout-account` in any account status** (it was refused by an admin, so it is not effective data; the reviewed-change flow of TD-44 is not built and would otherwise strand her). **(d)** No transfer without a request. Admin: `processing` (optional), `paid` (`bank_reference` required), `cancelled` (`note` required, earnings → `available`, `payout_id` null), `failed` (`note` required, from `processing` or `paid`, earnings → `available`, `payout_id` null); any other move is 409 `INVALID_STATE_TRANSITION` decided under the payout row lock; every decision is audited with the moved earning uuids and notifies her (`payout_paid`, `payout_cancelled`, `payout_failed`). Full IBAN is the separate audited route `GET /admin/payouts/{payout}/bank-account` under `payouts.approve`. Lock order: payout → payout account → earnings; request: account → earnings. TD-155 now reads: a completed trip keeps the driver's earning only if the rule kept the delivery fee, and that earning is `available`, not `pending`. A request failed after `paid` undoes the account verification that its own `paid` made (account back to `pending`, `verified_by`/`verified_at` cleared); one verified earlier stays verified. | DEC-11, ADMIN-017, PROV-023, DRIV-017 |
| TD-159 | Commission rule administration | Owner decision 2026-11-02 ("new rule with a start date"); ADMIN-016, BR-24, TD-93 | `/admin/commission-rules` CRUD + `PATCH …/toggle-active` (section `payouts`). The rule in force for (party, applies_to) = newest active rule with `effective_from` reached (latest start, then id) — one definition (`CommissionRuleTimeline`) used by checkout, package pricing and trip earnings; `effective_from` is compared at millisecond precision on the app clock. A rule that has taken effect is never edited or deleted (409 `COMMISSION_RULE_LOCKED`); a change is a new rule, optionally scheduled. Provider ↔ `service`/`package`, driver ↔ `delivery_fee`; percent ≤ 100; **provider rules are percent only** because orders snapshot a rate (`orders.provider_commission_rate`) — a fixed provider amount (BRD "percentage or amount") would need an order column. The provider rule in force cannot be switched off unless another provider rule of that type is in force (409 `COMMISSION_RULE_REQUIRED`; "no commission" = a 0% rule); a driver rule may be switched off (trips then pay no commission). Responses carry a derived `state` (`scheduled`, `current`, `superseded`, `inactive`) and `can_edit`. | ADMIN-016, DEC-09, TD-93, TD-149 |
| TD-160 | Ratings moderation | Owner decision 2026-11-02 ("hide/unhide, hidden excluded, no delete"); ADMIN-019, BR-28 | `GET /admin/reviews[/{review}]`, `POST …/publish`, `POST …/hide` (section `ratings`). Publish = visible + moderated; hide = invisible + moderated; both stamp `moderated_by`/`moderated_at` and write an audit row with the optional note (no schema column). "Review before publication" = the `filters[moderated]=0` queue; new ratings stay visible by default. Hidden ratings leave `rating_avg`/`rating_count` on `providers` / `customers` / `drivers` (recomputed from visible, not-deleted reviews on each visibility change). No delete from the dashboard, deviating from the BRD's "delete when necessary" by owner decision. Rating submission (CUST-021, PROV-024) is separate and not built. | ADMIN-019, BR-28 |
| TD-161 | Dashboard and reports figures | Owner decision 2026-11-02 ("standard set"); partly resolves DEC-20; ADMIN-002, ADMIN-022 | One definition (`PlatformMetrics`) serves `GET /admin/dashboard` (all time) and `GET /admin/reports/summary?from&to`; `GET /admin/reports/rankings` gives top services, packages, providers by completed orders and by rating, drivers by completed trips. Flows count inside the window on their own time (orders/trips `created_at`, payments `paid_at` for captured statuses, refunds `processed_at`, earnings `created_at` excluding reversed, wallet movements, payouts `paid_at`); standing figures are as of now (live orders/trips, amounts due, wallet balances, active/expired subscriptions, approved accounts). Provider and driver money always separate. Not cached. The dashboard's former `users` block is removed (dashboard doc 1.0.0, MAJOR). No PDF/Excel export. | DEC-20, ADMIN-002, ADMIN-022 |
| TD-162 | Rating cycle and support tickets for the customer and provider apps | Owner request 2026-11-02 (sprint sheet rows 65–66: "full cycle", "provider and client"); CUST-021, PROV-024, CUST-024, PROV-026, BR-28, DEC-19, DEC-21 | Ratings: `POST /{client,provider}/orders/{order}/rating` (direction from the token app; completed non-package-purchase order, one per direction, 409 after; active tags of the same direction; private note provider-only and never returned; saved visible at once and left in the admin queue, `moderated_at` null — pre-moderation stays open), `GET /{client,provider}/review-tags`, `GET /provider/ratings` (visible ratings + summary equal to the profile average). Support: `/{client,provider}/support/{reasons,tickets}` on the schema's tickets and messages; ticket code `SUP-` + 1000 + id; urgent reason ⇒ urgent priority; a user's follow-up returns a ticket to `under_review`, a closed ticket refuses (409); the provider desk is open in every account status; no push/notification on either feature. A migration seeds default reasons (customer, provider) and tags (customer→provider, provider→customer) only where none exist. Rate limits `review-submit` / `support-ticket` 10 per minute per user (fixed, not a setting). | CUST-021, PROV-024, CUST-024, PROV-026 |
| TD-163 | Driver support and the admin support desk, additive only | Owner answers 2026-11-03 (sprint sheet row 68) and correction the same day; DRIV-019, ADMIN-026, BR-29, DEC-21, TD-162 | Driver app: the same five routes as the other apps in the authenticated account group (open in every account status), optional `trip_id` (own trip only, 404 otherwise; `order_id` refused), default driver reasons by migration. Admin desk `/admin/support-tickets` (section `support`): list with filters (app, status, priority, reason, assignee, dates) and search (code, name, phone), details with thread, `POST …/messages` (sets `replied`), `PUT …/status` (`under_review` \| `closed` only; `replied` only by replying; closed is final for the desk), `POST …/close` (optional last message, one notice), `PUT …/assignee` (active super admin or supervisor with `support.view`; `null` clears; else 422 `SUPPORT_ASSIGNEE_INVALID`). Two notification events, `support_ticket_replied` and `support_ticket_status_changed` (`data.status` = `under_review` \| `closed`), written as inbox rows in the requester's app and locale. A user's follow-up on a closed ticket stays refused (TD-162). **The base contact form is not retired:** `/client/contact` and `/admin/contact-messages` (routes, names, controller, service, requests, responses, `contact-messages` rbac group) are unchanged because both apps already use them; they are documented as deprecated in admin/support.md. **Standing rule (owner, 2026-11-03):** changes are additive only — never remove or rename an existing route path, route name, request field, response key or enum value; if something cannot be done additively, stop and ask. Side effect: the `support` cells now also cover the desk routes, so existing roles with `support.*` gain them. | DRIV-019, ADMIN-026 |
| TD-164 | Customer wallet payments | Owner answers 2026-11-03; CUST-015/016, DEC-10 | `wallet_payments_enabled` defaults true and is admin-editable. Wallet pays the whole service order or package purchase, never split or featured subscriptions; insufficient balance and disabled setting return typed 422 errors. No admin adjustment. The order is locked before the wallet; the ledger trigger updates balance; wallet reads do not create a row. Admin wallet history defaults to refund credits, optional `include_debits=1` also shows order debits; customer detail counts stay refund-only. | CUST-015/016, ADMIN-020 |
| TD-165 | Tax invoices & receipts, ZATCA Phase-1 QR, backfill | Owner answers 2026-11-03 (sheet row 58); CUST-015, PROV-007, ADMIN-013, ADMIN-025, DEC-09, TD-19, TD-151, TD-164 | Electronic tax invoices are issued on every successful payment attempt (`payments.status = paid`): gateway order payment, customer wallet payment (`DebitWalletAction`), package purchase, and provider featured subscription (including `earnings_deduction`). Unique `payment_id` is the idempotency backstop; zero-total package sessions have no payment row, so no invoice. Sequential invoice code `INV-100000+`. Amounts are snapshotted: order subtotal = `services_amount + package_amount`, `delivery_fee`, `discount`, `vat_amount`, `total = payment->amount` (never recomputed, DEC-09); subscription subtotal = `price`, `vat_amount`, `total = payment->amount`. ZATCA Phase-1 QR is computed in pure PHP as Base64-encoded TLV (tags 1–5: Seller Name, VAT Number, Timestamp ISO 8601 with offset, Total, VAT amount; byte lengths using `strlen`, at most 255 bytes per tag, 2-decimal amounts). Two admin settings `vat_seller_name` and `vat_registration_number` (group `vat`, default `""` empty; non-empty registration number validated against 15-digit Saudi format `^3[0-9]{13}3$`); while empty, `qr_payload` is issued `null` without blocking payment. Setting seller name and VAT number before commercial launch is a launch blocker. `zatca_uuid` stays `null` (Phase 2). JSON only, no PDF library. Client routes: `GET orders/{order}/invoice` (`client.orders.invoice`, unpaid order returns 404 `INVOICE_NOT_FOUND`) and `GET invoices` (`client.invoices.index`); additive `invoice_id` (uuid or null) on `GET /orders/{order}` details. Provider routes: `GET featured/invoices` (`provider.featured-invoices.index`) and `GET featured/invoices/{invoice}` (`provider.featured-invoices.show`). Admin order details (`GET /orders/{order}`, ADMIN-013) add an additive `invoice` summary object (`id, invoice_no, subtotal, delivery_fee, discount, vat_amount, total, qr_payload, issued_at`). Backfill command: `php artisan invoices:backfill {--apply} {--chunk=100}` (dry-run by default, idempotent). Refunds do not issue credit notes (not built). All API changes are strictly additive. | CUST-015, PROV-007, ADMIN-013, ADMIN-025 |
| TD-166 | Notification preferences by category | Working assumption 2026-11-04 (Flutter handoff B02; owner confirmation pending for DEC-28); CUST-022, PROV-025, DRIV-018, DEC-13, TD-141 | Additive: `GET`/`PUT /{client,provider,delivery}/notifications/preferences/categories` over `notification_preferences (user, app, category)`. A missing row means push and SMS on; a read never creates rows; a write is a partial upsert of `categories.{orders\|payments\|chat\|offers\|account}.{push_enabled,sms_enabled}`; scope is per `(user, app)`. `users.allow_notifications` (existing `PUT …/preferences`, unchanged) stays the **account-wide master switch**: off ⇒ no push in any app whatever the categories say; on ⇒ the category decides. Enforcement is in the single push path (`SendPushNotificationJob` → `NotificationPreferenceService::pushAllowed`), never at write time: the inbox row is always written and realtime events are unaffected. `NotificationCategoryMap` maps every event key to a category (orders/trips/package purchase → `orders`; featured and payout events → `payments`; `chat_message` → `chat`; reviews, account review, support → `account`; `offers` has no event yet); a test fails when a `NotificationScenario` has no category. `sms_enabled` is stored but nothing sends SMS (DEC-15), and the response says `sms_delivery_available: false`. OS notification permission is a device setting, not a server preference. | CUST-022, PROV-025, DRIV-018 |
| TD-167 | Notification template engine, delivery audit logs, log SMS gateway, automated reminders, and complete BRD trigger suite | Owner confirmation 2026-11-04 (sheet row 60, WP-4); ADMIN-020, ADMIN-025, CUST-022, PROV-025, DRIV-018, DEC-13, DEC-15 | Full notification center & template engine. 60 seeded templates across scenarios and audiences (customer, provider, driver) with translations (ar, en) and validated {{placeholders}}. Nine missing BRD scenarios added: `order_payment_succeeded`, `order_payment_failed`, `order_rating_requested`, `order_status_update_reminder`, `wallet_refund_credited`, `trip_completed`, `earning_recorded`, `account_document_expiring`, `driver_rated`. Registration alerts dispatched on profile creation (Customer, Provider, Driver). Trip acceptance alerts driver as well as provider. Order chat routed through template engine. Platform switches in settings: `notification_push_enabled`, `notification_sms_enabled`, `notification_whatsapp_enabled` (disabled, WhatsApp returns 422 `CHANNEL_UNAVAILABLE`). Automated reminders with row locking and idempotency: `orders:remind-bookings` (rescheduled order reminded for new time only), `featured:alert-expiring`, `orders:remind-status-update` (`status_update_reminder_lead_minutes` default 5 < `order_status_delay_minutes`), `documents:alert-expiring` (`document_expiry_reminder_days` default 30). SMS job sends to E.164, retries on gateway false, logs single FAILED delivery row. Admin notification monitor `GET /admin/notifications` with filters and detailed delivery attempts. Inactive templates and `send_in_app = false` suppressed from inbox. | ADMIN-020, ADMIN-025, CUST-022, PROV-025, DRIV-018 |
| TD-168 | Payments monitor and payment-method switches | BRD gap sweep 2026-11-04; ADMIN-015, ADMIN-025, CUST-015, AC-ADMIN-015, AC-ADMIN-025; DEC-06, TD-151, TD-164 | Admin `GET /payments` and `GET /payments/{payment}` (section `payments`) are read-only over `payments`: customer order / package payments and provider plan payments, filters (status, method, purpose, order, customer, provider, dates), search (order code, transaction number, payer name), details with invoice, failure reason, refund history and plan period. Settings `payment_method_{card,mada,apple_pay}_enabled` (default on, `0`/`1` only; card and mada cannot both be off) are enforced in `PayOrderAction` and `SubscribeToFeaturedPlanAction` with 422 `PAYMENT_METHOD_DISABLED`; wallet keeps `wallet_payments_enabled`. Customer `GET /payment-methods` lists enabled methods and the wallet balance. Apple Pay, Tabby and saved cards for customers wait for the real gateway (DEC-06). Inbox fix: a template switched off no longer hides past notifications; only `send_in_app = 0` hides rows at read time. | ADMIN-015, ADMIN-025 |
| TD-169 | Provider and driver home pages | Owner request 2026-11-05; PROV-006, DRIV-005, BR-03/21/30, DEC-12/23 | Approved operational home endpoints aggregate existing summaries, with Riyadh calendar days and private no-store responses. A shared active-banner query serves all three homes without changing the customer shape. Verification uses required current verified documents. Provider featured status uses the existing subscription selection and expiry behavior. Driver `can_go_online` reflects the existing availability endpoint: true for an approved account even without an active vehicle; `has_active_vehicle` is separate. Going online does not imply dispatch or offer-acceptance eligibility; their existing checks remain, and broader activation-checklist enforcement is separate work. |
| TD-170 | Platform logo and typed settings | Owner request 2026-11-05; ADMIN-025 | Insert the `app_logo` media setting only if missing; store a validated shared upload hash and attach the image to prevent temporary-media cleanup. Admin settings exposes its value and URL; bootstrap adds an absolute `app.logo_url` with a placeholder. Validate each submitted value against its stored type before the atomic update; existing domain validators remain active. |
| TD-171 | Provider home appointment, rating and work-area projection | Owner review 2026-11-05; PROV-006, SCR-21, TD-77/109/158/160/169 | Home counts scheduled appointments with the same provider appointment query and Asia/Riyadh day boundaries; confirmed-today keeps its existing meaning. Rating uses the profile visible-review aggregate. Coverage uses the existing work-area resource city, districts and radius. Delay-clock evaluation uses bounded chunks and batched preparing history without changing its deadline rules. Today net earnings sum persisted non-reversed provider order or package-session earnings linked to completed non-purchase orders in the Riyadh calendar day, regardless of payout status or ledger creation time; empty totals are 0.00. |
| TD-172 | WP-5 review follow-up: settings error codes, driver home rating and earnings | Review 2026-11-08; ADMIN-025, DRIV-005, TD-169/170/171; owner rule: never break integrated endpoints | Settings: a key owned by a domain validator (`BookingSettings`, `JoshenSettings`, `TripSettings`, `DispatchLimitSettings`, `PayoutSettings`, `WalletSettings`, `PaymentMethodSettings`, `InvoiceSettings`, `NotificationSettings`) is skipped by the form-request type check, so its validator answers first with the code the dashboard already handles; `SettingValueTypes::assertDomainOwned()` then enforces the stored type (422 `VALIDATION_FAILED`). Other keys keep the TD-170 per-field check. The settings 422 keeps `data.error_code` and uses the standard first-error message. Driver home: additive `rating {average, count}` from `drivers.rating_avg` / `rating_count` (the provider home's aggregate); `today.rating` keeps its today-only meaning. `today.earnings_net` sums non-reversed `trip` earnings of trips completed in the Riyadh day with exact decimals (`EarningTotals::driverCompletedBetween`). | ADMIN-025, DRIV-005 |
| TD-173 | Admin dashboard alerts, web push, and admin general notifications | Owner request 2026-11-08; ADMIN-020, ADMIN-026, BR-03, BR-26, D1, TD-19/22/23/25/141; owner rule: never break integrated endpoints | **Alerts:** every active super admin, and every active supervisor allowed to call the route of the alert's page (BR-29, owner decision after review; not deleted, banned, disabled or on an inactive role), gets an inbox row (`notifications.app = admin`) and a Firebase web push for 7 events: account submitted for review, category requested, package sent to review, support ticket opened, requester replied, contact message, payout requested. Each is an editable `audience = admin` template (`AdminNotificationEvent`, kept out of `NotificationScenario` because admins have no preference categories); the templates list shows them with category `dashboard`. Rows are written after the caller's commit (`AdminAlerts`), so a rolled-back request alerts nobody. `data` carries `event`, `front_route_name` and the record uuid. Not possible yet: profile change requests (nothing creates one) and refund failures (refunds never fail; admins record them). **Inbox:** the shared `/api/v1/notifications` with the admin token (claim `app = admin`); `GET /admin/notifications` stays the system-wide monitor. **Web push:** `POST /admin/auth/device` (`device_type = web`, `admin.auth.*` so no section permission); logout with `device_token` unbinds it; FCM adds `webpush.headers.Urgency = high`. Same Firebase project and service account as the apps (owner, 2026-11-08). **General notifications:** `/admin/notifications/general` (index, show, store; section `notifications`). Target `all` / `cities` (profile `city_id`) / `users` (`customer_ids` = user uuid, `provider_ids`, `driver_ids` = profile uuid, ≤ 500). Reachable = can sign in (active, not admin-disabled, not banned); providers and drivers only when `approved` (owner). A picked unreachable person refuses the send (422 `NOTIFICATION_RECIPIENTS_UNREACHABLE`); a send reaching nobody is 422 `NOTIFICATION_NO_RECIPIENTS`. In-app + push only, sent now (owner). Recipients fixed in the request; `SendNotificationBroadcastJob` writes `event_key = general` rows (`data.broadcast_id`) in each recipient's locale, stamping each under a row lock so a retry never repeats. `general` maps to the `offers` preference category: muting offers mutes its push, never the inbox row. The legacy base `admin_notifications` table (Laravel-notifications shape, columns that no longer exist) is not reused. | ADMIN-020, ADMIN-026 |
| TD-174 | Driver nearby-requests alert and retry of failed general notifications | Owner request 2026-11-08 (review of TD-173 open points); driver inbox design `driver--notifications--01-inbox.png` (not in the BRD or §17); DRIV-018, BR-16, BR-17, TD-136, TD-137 | **Nearby alert:** a scheduled command (`trips:alert-nearby-drivers`, every minute) writes `driver_nearby_demand` to every **offline** driver who would be offered at least `driver_nearby_alert_min_trips` (setting, default 1, 1–50) searching, unexpired trips if she were online, using DriverEligibility with only the online rule inverted (approved, active vehicle, not on a live trip, district coverage per TD-136). At most one per driver per `driver_nearby_alert_cooldown_minutes` (setting, default 60, 1–1440), judged from her inbox rows. Text names the first waiting trip's district (or "your area"). It reserves and offers nothing, so dispatch (BR-16/17) is unchanged; the admin turns it off with the template. Category `orders`. **Retry:** `POST /admin/notifications/general/{id}/retry` (section `notifications`, action `create`) moves a `failed` send back to `queued` under its row lock and re-queues the job, which skips stamped recipients; any other status is 409 `INVALID_STATE_TRANSITION`. **Fix found on the way:** `NotificationSettings` passed `422` as the exception context, so four invalid notification settings answered 500; they now answer 422 `INVALID_NOTIFICATION_SETTINGS`. | DRIV-018, ADMIN-020, ADMIN-025 |
| TD-175 | Public static pages and FAQs on the three apps (supersedes the pages/FAQ part of TD-30) | Cycle audit 2026-11-08; BR-01, DEC-18, CUST-002, CUST-024, PROV-001, PROV-026, DRIV-001, DRIV-019; schema `static_pages`, `faqs` | `GET /{client,provider,delivery}/pages/{slug}` and `GET /{client,provider,delivery}/faqs`, public (no token: consent is shown before login). `slug` must be one of the schema enum (`terms`, `privacy`, `cancellation_policy`, `about`, `female_declaration`); anything else is 404 `ENTITY_NOT_FOUND`. Page audience is the app's (`client`→`customer`, `provider`→`provider`, `delivery`→`driver`) and falls back to the `all` page; no page is 404. Response keys always present: `id`, `slug {value,label}`, `title`, `content`, `version`, `updated_at` (ISO 8601); text in the request locale with Arabic fallback. FAQs: `is_active = 1`, the app's audience, ordered by `sort_order` then id, **unpaginated** (a short list; `data` is an array), item `id`, `question`, `answer`, `sort_order`. One shared `App\Domain\Content\PublicContent`, thin controllers per surface, no dedicated limiter (the default `api` limiter applies). `/admin/faqs` and `/admin/pages` are unchanged. | CUST-002, CUST-024, PROV-001, PROV-026, DRIV-001, DRIV-019 |
| TD-176 | Admin management of onboarding slides | Cycle audit 2026-11-08; SCR-08 (intro slides), schema `onboarding_slides`; ADMIN-023 pattern | `/api/v1/admin/onboarding-slides` (index, show, store, update, destroy) as a config-only `AdminBaseController` CRUD in section `banners_content`: `app` (customer/provider/driver), `sort_order`, `is_active`, `ar.title` (required, 200), `ar.body` (required, 500), optional `en.*` (both keys when sent), image by media hash (required on create, same `media_keys` as banners). Filters `app`, `is_active`; sort `sort_order`. No `toggle-active` (the sibling banners CRUD has none; `is_active` is a field). `GET /client/onboarding-slides` is unchanged and reads what the admin saves. | SCR-08, ADMIN-023 |
| TD-177 | Coherent demo seed owned by one scenario seeder | Cycle audit 2026-11-08; business-logic rules 7–9 (explicit states, exact money, snapshots) | `CycleScenarioSeeder` (local/dev/testing only, idempotent by order code) writes orders in every `orders.status` for scheduled and instant booking, a group order (D2), package purchases and a redeemed session (D1), ten driver trips (one per `trips.status`, one live trip per driver so five extra approved demo drivers exist), refunds to the wallet (TD-152) with ledger credits, provider and driver earnings from the same calculators the app uses, payouts in `paid`/`requested`/`processing`/`failed`, active and expired Featured subscriptions, chat, and support tickets in every status per app. Order status moves go through the `trg_orders_status_*` triggers so history ends at the status. The factory-only order-cycle seeders are removed from the `JoshenDemoSeeder` chain (they invent orphan orders and users) but kept for isolated runs. Order VAT is `0.00` as checkout writes it until DEC-09 is decided, and the customer wallet starts at 0 so its ledger always equals its balance. | — |
| TD-178 | Notification recipient filter on existing admin lists | Owner request 2026-11-08; ADMIN-003/004/006/020, BR-03/29, TD-173 | Optional boolean `filters[notification_reachable]` on `GET /admin/users`, `/admin/providers`, `/admin/drivers`: `1` = eligible for a general notification; `0` = ineligible within the existing list scope; omitted = unchanged. Reuse `BroadcastAudience::reachable()` as a database subquery so picker and send validation share one rule. Users must be active, not admin-disabled, not banned (false or null), not deleted; providers/drivers approved. No device or notification-preference requirement (inbox recipients do not require push). Existing search, city/status filters, pagination, UUID ownership and route permissions apply; no new endpoint or grant. Send-time 422 remains necessary if eligibility changes after selection. | ADMIN-020 |
| TD-179 | Admin dashboard closeout: lookups, admin push `recipient_app`, assignment eligibility | Owner request 2026-11-08; ADMIN-015, ADMIN-020, ADMIN-026, BR-29, TD-163/173/178; owner rule: never break integrated endpoints | **Lookups** (additive, section view cells only): `GET /admin/support-tickets/reasons` (all reasons, active and retired unless `filters[is_active]`; `filters[audience]`; label search in any locale), `GET /admin/support-tickets/assignees`, `GET /admin/payments/customers`; paginated under `reasons` / `assignees` / `customers`, or a direct array capped at 500 with `paginate=false`. Static paths are declared before the `{ticket}` / `{payment}` bindings and mapped in `config/rbac.php` `routes`. **Assignee rule:** one definition, `SupportDeskStaff`, over `User::scopeAdminsWhoCanSignIn()` (the `AccountCanAuthenticate` rules: not deleted, banned, admin-disabled, inactive, or on an inactive role) plus the desk's view permission; `AssignSupportTicketAction` and the lookup both use it, and the alert recipients use the same scope. Previously assignment accepted any `is_active` admin with the cell, including deleted, banned, disabled and inactive-role ones (a tightening, same 422 code). **Payment customers:** `id` is the customer profile uuid (`customers.uuid`), selected by an `EXISTS` over `payments → orders.customer_id`, exactly what `filters[customer_id]` resolves; accounts stay listed whatever their state; the filter itself is unchanged. **Push:** admin alerts carry `data.recipient_app = admin` (set by `AdminAlerts` and by the FCM sender for every push to the `admin` app); `data.app` keeps its meaning. No new migration, setting or permission row beyond the three route mappings. | ADMIN-015, ADMIN-020, ADMIN-026 |
| TD-180 | Group booking (D2) contract | Owner request 2026-11-09 with the prototype screens `customer--group-booking--04…06`; CUST-012/013/014, PROV-014, SCR-31 | Additive: `people` on quote, create and slots. One provider, one place and time; the customer plus 1+ companions (names only, no accounts), each with one or more services and options; one delivery fee; duration = sum; no coupons on a group; size cap setting `group_booking_max_people` (default 6, awaiting client confirmation). |
| TD-181 | Deferred scopes from the mobile closeout: hotline, driver reviews, reviewed profile changes | Owner request 2026-11-09 after the Flutter closeout; BRD DRIV-006, DRIV-019, DRIV-005; SCR-24, SCR-26; DEC-25; TD-44 | **Built:** `data.contact` (hotline, e-mail, WhatsApp) on bootstrap; urgent support reasons already cover "report an incident". **Not built, no BRD scope:** an emergency dispatch and a driver's review list. **Not built, needs DEC-25:** app-side profile, vehicle and document change requests and their admin apply (TD-44 prerequisites unmet). |
| TD-182 | Disposition of the deferred mobile capabilities R01–R08 | **Superseded by TD-183 (2026-11-09).** Backend working position 2026-11-09; TD-181, TD-44, TD-147, DEC-07, DEC-25 | Nothing new is decided or built. R04 (driver review list) and emergency dispatch stay outside the BRD (TD-181). R01–R03 (reviewed profile / vehicle / document changes) wait on DEC-25 and the TD-44 prerequisites. R06–R08 (live ETA, location freshness, self-travel tracking) wait on the TD-147 open items and DEC-07; ETA/distance stay `null`. Direct-edit locks and `locked_fields` stay authoritative; the app must not simulate approval, ETA or location. Reply: `docs/integration/2026-11-09-mobile-deferred-capabilities-reply.md`. |
| TD-183 | Owner approvals and working assumptions for R01–R08 | Owner instruction 2026-11-09; supersedes TD-182 and the "not approved" part of TD-181 for the driver review list and the open items of TD-147; R05, R06, R07, R08, R04 built; R01–R03 approved, not built | **Built:** (R05) support attachments accept `mp4`/`mov` and every ticket / message returns `has_media`; urgent tickets are answered by admins and supervisors. (R04) `GET /delivery/ratings` lists visible `provider_to_driver` ratings with a summary. (R06) the owner chose a stored speed per city, kept on the delivery fee rule: `delivery_fee_rules.avg_speed_kmh` (nullable) — the city's active rule, then the default (no city) rule, then 30 km/h; distance = straight line × `tracking_route_detour_factor`, ETA = distance / speed, calculated on the server on each location update; before pickup the leg is driver → provider → customer, after pickup driver → customer; `null` when unknown, never 0. (R07) `tracking.freshness` = `fresh` / `stale` / `unavailable` from the age of the last position (`tracking_stale_after_seconds`); the last pin stays visible while tracking is available. (R08) the provider sends her position with `POST /provider/orders/{order}/location` while `transport_mode = self` and the order is `on_the_way`; it is stored on the order (`orders.provider_lat/lng/provider_location_updated_at`) and published as the same `order.tracking.location` event. **Confirmed by the owner on 2026-11-10:** fallback speed 30 km/h, detour 1.3, stale after 60 s, foreground-only publishing. **Still not stated:** the operating hours of the support team. **Not built:** R01–R03 (reviewed profile / vehicle / document changes) — approved by the owner; the field matrix, stale-value checks and atomic apply are now proposed in `integration/2026-11-10-reviewed-changes-r01-r03-proposal.md` and await owner confirmation (TD-44 stays unmet until then). |
| TD-184 | Reviewed profile, vehicle and document changes (R01–R03): field matrix and rules | Owner answers 2026-11-10; BR-04, DEC-25, TD-44, TD-183 | DEC-25 is answered as a structured in-app request with admin review. Change types `work_area`, `bank_account`, `identity`, `vehicle`, `document`. The national id number never changes. One pending request per (user, change type), per `doc_type` for documents. The admin approves or rejects the proposal as submitted (no editing) with an optional note, or a required reason on rejection. A vehicle may be edited or replaced but not while she has an accepted or live trip. The old document stays valid during review. A new request notifies admins holding the `approve` permission of `providers` or `drivers`. Stale `old` values give `409 CHANGE_REQUEST_STALE`; apply is one transaction with a fixed lock order. Full detail: `integration/2026-11-10-reviewed-changes-r01-r03-proposal.md`. Contracts published ahead of build: `provider/profile-change-requests.md`, `delivery/profile-change-requests.md` (idempotency uses the `Idempotency-Key` header, TD-95, not a body `request_id`; new codes `CHANGE_REQUEST_ALREADY_PENDING`, `ACTIVE_TRIP_BLOCKS_VEHICLE_CHANGE`). **Built and tested 2026-11-10** (app, admin, notifications, migrations `2026_11_10_100000` and `2026_11_10_100100`); not deployed. Payment-gateway and SMS vendors stay open (DEC-06, DEC-15); FCM is in scope for this release. |
| TD-185 | QA hardening 2026-11-10 (QA-03, QA-07, QA-08, QA-09, QA-10, QA-NEW-01) | **Decided (technical, additive).** (1) Admin payloads naming a customer add `customer.user_id`; `customer.id` is unchanged (integrated apps). (2) A declined payment keeps HTTP 200 and the order body; only `message` changes. (3) `phone_code` is not reshaped: the two formats are documented (API_CONVENTIONS §4). (4) CORS origins and the preflight cache come from env. (5) Arabic validation labels are filled in `lang/ar.json` and `lang/ar/validation.php`. **Open for the owner (QA-09):** which label a package purchase (`order_kind = package_purchase`) shows; today it is stored as `booking_type = scheduled` with `scheduled_at = null`. Working assumption: clients read `order_kind` and treat a null `scheduled_at` as "no appointment yet"; the stored value is not changed until the owner decides. | Owner request 2026-11-10 to close the QA findings; QA-09 awaits the owner | QA-03, QA-07, QA-08, QA-09, QA-10, QA-NEW-01 |
| TD-186 | QA round 2026-11-11 (full API sweep, dashboard crawl, static translation audit) | **Decided (technical, additive).** (1) A non-string `change_type` is a 422, not a 500. (2) `EnumLabel` looks up `enums.<Enum>.<value>` in the request locale and falls back to the title-cased case name; `lang/{ar,en}/enums.php` added. (3) Arabic labels added for every request field found without one (300 attribute labels); the missing keys `booking.order_not_found`, `booking.payment_methods.*`, `booking.payment_failed_reason`, `joshan.labels.service`, `The file must be of type: :type`, `Deleted`, `Unsupported verification type.` are translated. **Not fixed here (other owners):** the hosted dashboard serves stale lazy-route chunks after a deploy (index.html without `Cache-Control`, missing assets answered 200 text/html), the Admins form shows the raw key `LABELS.role_id`, and the seed factory writes `phone_code` as `966` instead of `+966`. | Owner request 2026-11-11 to test every request, button, form and message | QA-NEW-04 to QA-NEW-09 |

## Resolution log

### TD-184 — Reviewed profile, vehicle and document changes (2026-11-10)

- **Source:** owner answers in the thread, 2026-11-10 (national id fixed; one pending request per type; approve or reject without editing; vehicle edit and replace, refused during an accepted or live trip; old document valid during review; notification to the provider and driver reviewers).
- **Decision:** as in the TD-184 row above. DEC-25 is resolved as the BRD reading (structured request, admin review). TD-44's prerequisites are met and the feature is built.
- **Notifications:** user notices `profile_change_approved` / `profile_change_rejected`; admin alerts `admin_provider_change_requested` / `admin_driver_change_requested`, sent to admins holding the `approve` action of `providers` / `drivers`.
- **Also from the same answers:** FCM is in this release; the payment gateway and the SMS gateway are integrated later (DEC-06, DEC-15 stay open and remain launch blockers).
- **Still open:** support operating hours; the review time target; owners of the OTP/SMS, payment and device-push gates.

### TD-183 — Owner approvals for R01–R08 and what was built (2026-11-09)

- **Source:** owner answers in the Flutter closeout thread (2026-11-09): implement reviewed changes (R01–R03) and the driver review list (R04); tickets are answered by admins and supervisors with image and video attachments; ETA from a stored speed per region; the same socket carries R07 and R08.
- **Built and tested:** R04, R05, R06, R07, R08, and the relay validation in `socket-io` (it rejected the estimate keys and, separately, `can_confirm_arrival`; both are now accepted).
- **Confirmed by the owner (2026-11-10):** fallback speed 30 km/h, detour factor 1.3, stale after 60 s, foreground-only provider publishing.
- **Not built:** R01–R03. Needed first: allowed fields per change type and role, per-field validation, stale-value checks and the atomic apply (TD-44), then submit / list / cancel in both apps and approve / reject in the dashboard.
- **Still open:** DEC-07; the live-map design (SCR-16); the support team's operating hours.

### TD-182 — Deferred mobile capabilities R01–R08 (2026-11-09)

- **Source:** Flutter request "deferred mobile capabilities and release scope" (baseline `c59a572`).
- **Decision:** none new. The backend position restates TD-181 and the open items of TD-44, TD-147, DEC-07 and DEC-25. No capability is ready; the release can close with the locked or unavailable UI.
- **Still open (owner):** DEC-25 and the per-field matrix; whether a driver review list is wanted; the emergency dispatch boundary and its operations owner; ETA/distance source; stale-location threshold and live-map content; provider self-travel tracking. When the owner answers, supersede this entry with a new one.

### TD-181 — Deferred scopes from the mobile closeout (2026-11-09)

- **Source:** Flutter closeout follow-up (2026-11-09) asked whether four scopes are ready; owner asked to settle them from the BRD and screens.
- **Emergency contact / dispatch:** the BRD has no dispatch. SCR-24 adopts, beside support tickets, a phone hotline and a "report an incident or violation (urgent)" entry (DRIV-019, PROV-026, CUST-024). Urgent support reasons already exist; the hotline number (`support_phone`) was stored but no app could read it, so bootstrap now returns the `contact` group (API conventions 4.9.0). No emergency dispatch is built or implied.
- **Driver review history:** DRIV-005 shows the driver's rating on Home and no BRD feature lists her reviews; `reviews.direction = provider_to_driver` exists in the schema. A review list is **not approved scope**; it needs a BRD line or an owner decision first. Home keeps its aggregates.
- **Vehicle, document and profile changes of an approved account:** DRIV-006 / PROV-008 require admin review and keep the old data active until approval (BR-04). The screens (SCR-26) route documents and bank changes "through support" and allow direct name/phone edits, which conflicts, so DEC-25 stays Open. TD-44 (owner-authorized) forbids applying a change request until the allowed fields per type and role, validation, stale-value checks and the transactional apply are defined. **Not built.** Until DEC-25 is answered the direct-edit locks stay authoritative and Flutter must not simulate approval.
- **Needs from the owner:** an answer to DEC-25, and for each change type (`vehicle`, `document`, `identity`, `work_area`, `bank_account`) the fields a driver or provider may change and whether the admin can edit before approving.

### TD-180 — Group booking (D2) (2026-11-09)

- **Source:** Owner request after the Flutter closeout review; prototype screens `customer--group-booking--04…06` (people, time and address, review). D2: one order, one provider, one place and time, several people each with their own services, one delivery fee.
- **Decision:** `POST /client/orders/quote`, `POST /client/orders` and `GET /client/providers/{provider}/slots` accept an optional `people` list in place of `service_id` / `option_ids`; the single-service request is unchanged. Exactly one person is the signed-in customer (`is_customer`, named from her profile); every other person is a free-text name, with no account and no id of its own beyond the order's `order_people` uuid. Each person has at least one service (a service cannot repeat inside one person; two people may pick the same service or option). The whole group is served by the one chosen provider, who must offer every service. `services_amount` is the sum of every line, `delivery_fee` is charged once (BR-11), `estimated_duration_min` is the sum of every service so the provider's calendar is blocked for the whole visit, and the snapshot rules of checkout apply unchanged (`orders.is_group = true`, one `order_items` row per person and service). Payment, rejection, expiry, cancellation, refund, earnings and commission are order-level and work as for a single order.
- **Working assumptions (awaiting client confirmation):** a group has 2 to `group_booking_max_people` people (admin setting, default 6, customer included); coupons are not offered on a group order (the coupon rules take one service); there is no per-person cancellation or partial refund — the order is the unit.
- **Still open:** a per-person split of the bill, and editing the people of a placed order (admin order editing treats the lines as it does today).
- **Schema:** none; `orders.is_group`, `order_people` and `order_items.order_person_id` were already in `db-schema.sql`.

### TD-179 — Admin dashboard closeout (2026-11-08)

- **Source:** Owner request after the dashboard integration review; frontend reply of the same day.
- **Decision:** As in the TD-179 row above. No business scope was added: the lookups read existing data and the assignment rule applies the dashboard's own sign-in rules.
- **Still open:** Deployed Firebase configuration, running notifications worker, real delivery and live-browser acceptance (runbook in `admin/notifications.md` §11.6) are operational gates, not verified by the backend repository.

### TD-178 — Notification recipient filter on existing admin lists (2026-11-08)

- **Source:** Owner approved the reachable filter for the frontend general-notification picker.
- **Decision:** As in the TD-178 row above; no new eligibility policy or schema change.
- **Still open:** Deployed Firebase/queue verification and live browser push acceptance are separate operational gates.

### TD-177 — Coherent demo seed owned by one scenario seeder (2026-11-08)

- **Source:** Cycle audit: the demo seed held eleven orphan `awaiting_payment` orders with trips in `searching` (impossible before a paid, confirmed order) and only one order per cycle state.
- **Decision:** As in the TD-177 row above.
- **Still open:** DEC-09 (order VAT) and DEC-06 (payment gateway) affect the seeded amounts; revisit when decided.

### TD-176 — Admin management of onboarding slides (2026-11-08)

- **Source:** Cycle audit: the customer intro slides could only change by re-seeding.
- **Decision:** As in the TD-176 row above.
- **Still open:** whether the provider and driver apps get an intro-slide read endpoint (not requested).

### TD-175 — Public static pages and FAQs on the three apps (2026-11-08)

- **Source:** Cycle audit: no app could read the terms, privacy policy or FAQs that registration consent and the support screens link to; `/client/faqs` and `/client/pages/{type}` were unregistered by TD-30 and never returned.
- **Decision:** As in the TD-175 row above.
- **Still open:** nothing in this change; DEC-15 (SMS) is unrelated.

### TD-174 — Driver nearby-requests alert, retry of failed general notifications (2026-11-08)

- **Source:** Owner request to handle the open points left by TD-173.
- **Decision:** As in the TD-174 row above. Owner answers: automatic, settings-driven alert (threshold default 1, cooldown default 60 minutes); add a retry endpoint; leave profile change request alerts until BR-04 / DEC-25 is built.
- **Still open:** profile change request alerts (BR-04, DEC-25).

### TD-173 — Admin dashboard alerts, web push, and general notifications (2026-11-08)

- **Source:** Owner request to document Firebase web integration for the admin dashboard; the backend could not receive admin tokens or send admin alerts, so the owner chose to build it.
- **Decision:** As in the TD-173 row above.
- **Owner answers (2026-11-08):** events = registration to review, change/approval requests, support tickets and replies, money (payout) events, plus admin-composed general notifications to the apps; recipients = every active admin; `/api/v1/notifications` is the admin's own inbox and `/admin/notifications/general` holds what the dashboard sent; targets = all users of an app, chosen users, by city; channels = in-app + push; send now only; providers/drivers reached only when approved; same Firebase project.
- **Review against BRD and screens (2026-11-08):** BR-29 applies, so alerts follow section permissions (owner chose this over "every admin"); admin templates show category `dashboard` instead of `null` so the integrated templates list keeps an object (owner). `general` → `offers` matches DEC-13's `offers` category and the promo card on the customer inbox design. The driver inbox design's "delivery requests near you, go online" item is not in the BRD or §17 and is not built; a general notification can reach drivers by city, not by district.
- **Still open:** profile change request alerts wait for a feature that creates profile change requests; scheduled sends and SMS for general notifications are not built; the driver "requests near you" alert (screen only).

### TD-171 — Provider home review corrections (2026-11-05)

- **Source:** Owner review of PROV-006 and SCR-21.
- **Decision:** Reuse appointment and work-area projections and the provider visible-rating aggregate; evaluate BR-21 deadlines in bounded batches through the existing clock.
- **Owner clarification:** Sum stored net amounts for non-reversed provider `order` and `package_session` earnings whose non-purchase order is completed in the Riyadh day, including pending, available, in-payout and paid rows. Exclude other source types, and use order completion time rather than earning creation time. The range includes the start and excludes the next day’s start; return `0.00` when empty.
- **Still open:** N/A for this definition.

### TD-167 — Notification template engine, delivery audit logs, log SMS gateway, automated reminders, and complete BRD trigger suite (2026-11-04)

- **Source:** Sprint sheet row 60 (WP-4: notification templates, delivery log, SMS dispatcher, admin notification management, reminders, and missing event triggers).
- **Decision:** As in the TD-167 row above.
  1. **Missing BRD events:** 9 new scenarios added to `NotificationScenario`, mapped in `NotificationCategoryMap`, seeded in `notification_templates` with Arabic/English copy and placeholders, and triggered after DB commits from core domain actions.
  2. **Profile registration:** `AppProfileObserver` dispatches `user_registered` upon creation of `Customer`, `Provider`, and `Driver` app profiles.
  3. **Driver linkage & Chat:** `TripNotifications::driverAccepted()` alerts both provider and accepting driver; `OrderChat` routes recipient alerts through `NotificationTemplateSender`.
  4. **Platform channel switches & validation:** `notification_push_enabled`, `notification_sms_enabled`, `notification_whatsapp_enabled` (forced 0; enabling or requesting WhatsApp on templates fails with 422 `CHANNEL_UNAVAILABLE`). `status_update_reminder_lead_minutes` (default 5) must be strictly less than `order_status_delay_minutes` (else 422 `INVALID_NOTIFICATION_SETTINGS`).
  5. **SMS resilience:** Formats phone number to E.164 standard; throws `RuntimeException` on gateway failure so queue retries (3 attempts with backoff); writes a single `FAILED` row in `notification_deliveries` on permanent failure.
  6. **Automated reminders:** Scheduled via cron with DB transaction and row-level locking. `orders:remind-bookings` (checks `scheduled_at` idempotency so rescheduled orders receive reminders for new times only), `featured:alert-expiring`, `orders:remind-status-update` (lead window before delay alerts), and `documents:alert-expiring` (approved documents nearing expiry).
  7. **Admin monitor:** `GET /admin/notifications` provides comprehensive filtering (`audience`, `event_key`, `channel`, `status`, `from`, `to`, `search`) and detailed delivery history with latency metrics and error notes.
  8. **Inbox filtering:** Inactive templates and templates with `send_in_app = false` are filtered out of customer, provider, and driver inboxes. *(Corrected by TD-168 the same day: an inactive template stops new notifications but no longer hides past ones; only `send_in_app = false` rows are hidden.)*
- **Still open:** Real SMS provider integration (DEC-15; blocked until commercial vendor and API credentials provided), DEC-28 (unmutable notification categories).

### TD-166 — Notification preferences by category (2026-11-04)

- **Source:** Flutter handoff 2026-11-04, item B02 (DEC-13 resolved by schema, so the model is fixed; the open points are logged as DEC-28 and DEC-29).
- **Decision:** As in the TD-166 row above. The existing `PUT /client/notifications/preferences` (`allow_notifications`) is untouched.
- **Still open:** DEC-28 (essential messages), DEC-29 (token detach on logout / shared device), SMS delivery (DEC-15), an `offers` event (no promotion feature exists), `allow_notifications` for provider and driver apps has no endpoint of its own (account-wide flag; only the client route writes it).

### TD-165 — Tax invoices & receipts and ZATCA Phase-1 QR (2026-11-03)

- **Source:** Owner answers on 2026-11-03 (sheet row 58): issue invoice on every paid path (order gateway, wallet, package purchase, featured subscription); sequential `INV-100000+` numbers; snapshot amounts from order/subscription; ZATCA Phase-1 Base64 TLV (tags 1–5) in pure PHP; empty settings default to `qr_payload = null` without blocking payment; non-empty registration number validated as 15-digit Saudi format; JSON only (no PDF); provider routes under `featured/invoices`; client routes `orders/{order}/invoice` and `invoices`; additive `invoice_id` in client order details and `invoice` summary in admin order details; idempotent backfill command; refunds do not issue credit notes.
- **Decision:** As in the TD-165 row above.
- **Still open:** ZATCA Phase-2 integration (`zatca_uuid`, cryptographic stamp, clearance/reporting API); credit notes for refunds (not built); PDF generation (not built).

### TD-164 — Customer wallet payments (2026-11-03)

- **Source:** Owner WP-2 instructions and approval of the optional admin debit filter.
- **Decision:** As in TD-164 above; existing routes and response shapes remain additive.
- **Still open:** Real gateway vendor (DEC-06) and notification management in WP-4.

### TD-163 — Driver support and the admin support desk (2026-11-03)

- **Source:** Owner answers on 2026-11-03: keep the base contact form working, additive only (first answer "retire" was corrected the same day); two separate notification events; `replied` only by replying; optional closing message; assignee must be an admin who can see the desk; drivers link a trip, not an order; driver support open in every account status.
- **Decision:** as in the TD-163 row above.
- **Still open:** editable notification templates (sheet row 60), an unassign/assignment notice, hotline / FAQ / live-chat parts of SCR-24.

### TD-162 — Rating cycle and support tickets (2026-11-02)

- **Source:** Owner request on 2026-11-02 for sprint sheet rows 65 and 66; scope answered as the full cycle on both the customer and provider apps.
- **Decision:** as in the TD-162 row above.
- **Still open:** admin support desk on tickets (ADMIN-026, still base contact messages), notifications for ratings and replies, DEC-19 customer submission limits, provider → driver rating, reason/tag admin CRUD.

### TD-159…161 — Commission engine, ratings moderation, dashboard and reports (2026-11-02)

- **Source:** Owner answers on 2026-11-02: build here; commission change = new rule with a start date; ratings hide/unhide with hidden excluded and no delete; KPIs = the standard (BRD) set.
- **Decision:** as in the TD-159, TD-160 and TD-161 rows above.
- **Still open:** fixed provider commission (needs an order column); pre-moderation of new ratings; DEC-20 performance targets; report export.

### TD-158 — Provider and driver amounts due and manual payouts (2026-11-02)

- **Source:** Owner instruction 2026-11-02 (request → admin review → manual bank transfer → record the bank reference) and owner answers to the four questions on DEC-11 (available at once; minimum from a setting and one open request; no verification screen, a transfer proves the account; no transfer without a request) plus "net the featured-plan deduction".
- **Decision:** as in the TD-158 row above. BRD basis: PROV-023 and DRIV-017 (totals, amounts awaiting transfer, transferred amounts, orders or trips behind each, transfer date and status), ADMIN-017 (separate provider and driver amounts, linked orders / trips, execute or record transfers, set "Transferred", totals before and after commission), BR-24/25/26.
- **Status:** DEC-11 Partly resolved → Resolved.
- **Acceptance impact:** AC-PROV-023, AC-DRIV-017, AC-ADMIN-017 covered by `Provider/EarningPayoutTest`, `Delivery/EarningPayoutTest`, `Admin/PayoutManagementTest` and the real-process `Payout/ConcurrentPayoutRequestTest`.
- **Still open:** earnings of orders completed before this release are not back-filled; payout export to PDF / Excel; reviewed bank-detail changes (TD-44 / DEC-25) still deferred.

### TD-152…157 — Cancellation and refund management (2026-11-01, amended 2026-11-02)

- **Source:** Owner instruction to build ADMIN-018 with PROV-016, owner answer "wallet only" to the DEC-10 question, and owner instruction to settle the trip and driver earning of a cancelled order from the active rule (BR-20).
- **Amendment 2026-11-02:** owner review — a wallet refund must not call the payment gateway; with no external call nothing can refuse it, so the `failed` branch and the retry route are gone; the admin refund is idempotent (TD-156); the rule for a future gateway refund is TD-157.
- **Decision:** as in the TD-152…157 rows above. BRD basis: BR-20 (full wallet refund before execution, no provider or driver earning), PROV-016 (provider cancels before execution, customer refunded), AC-ADMIN-018 (canceling party, refund, wallet credit, no party earnings).
- **Still open:** DEC-10 — when `original_method` applies; DEC-07 — driver cancel or no-show after accepting; DEC-11 — payout cycle and when `pending` earnings become `available`; ranking penalty for provider cancellation (DEC-24: none).

### TD-149…151 — Arrival confirmation, admin chat monitor, SAR only (2026-11-01)

- **Source:** Owner answers on 2026-11-01 to DEC-08 ("any one of three, first wins"; "create the driver earning now") and DEC-14 ("read-only monitor"; "audit reads, no deletion"); owner instruction that the currency is SAR and the payment rows pass while the gateway is deferred.
- **Decision:** as in the TD-149, TD-150 and TD-151 rows above. BRD basis: DRIV-015 lists the three confirming parties; the source BRD's provider flow says "after both arrive, the provider confirms arrival and begins service", which is why starting the service confirms implicitly.
- **Still open:** DEC-14 chat retention period; DEC-11 payout cycle and when a pending driver earning becomes available; DEC-06 gateway vendor.

### TD-148 — Coupon scope, usage counting, rounding and free-order rule (2026-11-01)

- **Status:** Working assumption, **awaiting owner confirmation**. Completes the items TD-131 left pending.
- **Source:** Implementation of CUST-011/014 and ADMIN-015/016 on `feat/coupons`; no conflict with DEC-09 (total formula and "coupons funded by platform or provider" — provider funding stays unused) or TD-131.
- **Decision:** scope, usage counting and release, rounding, minimum, free-order refusal, per-customer default and code matching exactly as in the TD-148 row above. Error codes: `COUPON_INVALID`, `COUPON_EXPIRED`, `COUPON_NOT_APPLICABLE`, `COUPON_FREE_ORDER`, `COUPON_MIN_ORDER_NOT_MET`, `COUPON_USAGE_LIMIT_REACHED` (422) and `COUPON_IN_USE` (409, delete of a used coupon).
- **Still open:** provider-funded coupons (DEC-09), VAT interplay (interim 0.00), refund wording when a coupon order is partly refunded.

### TD-147 — Preview-only order tracking contract (2026-10-29)

- **Source:** Review of the realtime tracking and order chat work on `feat/real-time`; CUST-017, CUST-019, SCR-16, BR-21, BR-22.
- **Decision:** Signed short-lived grant with its own secret and configurable lifetime; membership kept after expiry; three rooms per socket; driver position shown from acceptance; shared `dispatch-availability` budget for both location writers; every order transition into a tracked stage publishes the projection; chat send throttle and `{value,label}` chat enums; reads never write.
- **Still open:** stale-location threshold, ETA/distance source, live map and self-travel position (SCR-16), DEC-07, DEC-08, DEC-14, DEC-26, DEC-27. No default is applied for any of them.

### TD-146 — Role-specific unread and bounded conversation recovery (2026-10-29)

- **Source:** Owner-requested correction to CUST-018/PROV-021 and TD-144/145; the same account may hold both app profiles (TD-25).
- **Decision:** Count and mark unread by opposite sender role, paginate summaries using existing Laravel response metadata, and treat delayed summary events as refetch signals. Keep message IDs as UUIDs and mark-read bounded by a conversation-owned displayed message. Scope Telescope redaction to participant chat diagnostics and message-bearing entries, without disabling unrelated recording.
- **Still open:** DEC-14 retention and admin monitoring; DEC-27 calls and other deferred features remain unchanged.

### TD-144/145 — Paid order text chat and private delivery (2026-10-29)

- **Source:** Owner instruction on 2026-10-29; CUST-018, PROV-021, BR-15, AC-CUST-018, AC-PROV-021.
- **Decision:** Supersede TD-16 for participant order chat with the text-only, read-only terminal lifecycle, explicit private unread state and generic recipient notification above. Socket transport uses the existing per-app private rooms; HTTP remains authoritative.
- **Still open:** DEC-14 general retention and admin monitoring; DEC-27 phone calls. No deletion schedule, attachments, voice, editing or deletion is approved.

### TD-141…143 — FCM push delivery pipeline, credentials gating and retry policy (2026-10-28)

- **Source:** Engineering review and hardening of FCM push notification work on branch `feat/fcm-notifications`.
- **Decision:** Establish single push dispatch path via `NotificationObserver` -> `SendPushNotificationJob` (TD-141); gate all FCM execution behind `FIREBASE_ENABLED` with explicit memoized credential resolution (TD-142); implement transient error propagation, 3-attempt exponential backoff, and idempotent delivery logging in `notification_deliveries` (TD-143).

### TD-140 — Admin trip monitor locations and completed mapping (2026-10-28)

- **Source:** Owner clarification for ADMIN-014 and DEC-08.
- **Decision:** Use pickup and dropoff cities and the dropoff district on list rows, with full addresses and coordinates in details. Map completed trips to arrived_at_customer until DEC-08 is resolved.

### TD-134…139 — Transport and driver dispatch (2026-10-28)

- **Source:** Owner instructions for cycle F1/F2 and execution boundaries.
- **Decision:** The transport choice, snapshot, dispatch, availability, privacy and execution rules are recorded in the corresponding rows above; coverage remains awaiting client confirmation and DEC-07/08 remain open.


### TD-132 — Driver freed on trip arrival and trip list tab boundaries (2026-10-27)

- **Source:** Cycle F STAGE F3 owner instruction, DRIV-010..014, BR-17, BR-23, DEC-08.
- **Decision:** A trip in `arrived` is not live for driver dispatch. Live driver statuses for dispatch matching and offer acceptance are strictly `[accepted, to_provider, at_provider, picked_up, to_customer]`. A driver who has arrived at the customer is freed to receive and accept new trip offers immediately, preventing drivers from being permanently blocked pending settlement. On the driver delivery surface (`GET /delivery/trips`), tabs are categorized as:
  - `current`: `accepted`, `to_provider`, `at_provider`, `picked_up`, `to_customer`
  - `completed`: `arrived`, `completed`
  - `cancelled`: `cancelled`
- Settlement (arrival confirmation by dual parties, driver earnings credits, amounts due) is deferred to DEC-08 / Sprint 3.

### TD-131 — Coupon pricing and administration (2026-10-27)

- **Source:** Owner instructions for dashboard and client coupons.
- **Decision:** Admin-configurable limits; discount before VAT and excluding delivery, funded by Joshen without changing provider commission; preserve used coupons by deactivation.
- **Pending:** Scope options, usage and refund timing, percentage rounding — assumed in [TD-148](#td-148--coupon-scope-usage-counting-rounding-and-free-order-rule-2026-11-01) (working assumption, awaiting owner confirmation).

### TD-130 — Non-production payment simulation (2026-10-27)

- **Source:** Owner instruction to support `APP_ENV=dev` and all environments except `prod` and `production`.
- **Decision:** The existing simulator switch controls non-production use; both production names are denied.


### TD-129 — Staging payment simulation (2026-10-27)

- **Source:** Owner request to unblock Flutter QA.
- **Decision:** Permit the shared simulated gateway in staging by explicit configuration; production remains unavailable until DEC-06 integration.


### 2026-10-27 — TD-123…128: Prepaid package cycle

- **Status:** E1/E2 accepted by the owner; the FR-03 interpretation, equal allocation, expiry boundary, interim VAT 0.00 (DEC-09), purchase refunds and zero-charge paid remain **awaiting client confirmation**.
- **Decision:** Record the implemented purchase, allocation, hold, expiry, commission, schema and payment boundaries above; Sprint 3 must use the stated earnings rules.
- **Source:** owner stage E3 instruction, D1, CUST-008, CUST-013/015/020, BR-07/08/24/26 and DEC-01/09.

### 2026-10-27 — TD-121…122: Super admin permissions answer and finder pagination debt

- **Status:** TD-121 is the owner's change to `auth/my-permissions` (supersedes TD-53, whose entry below stays as history); TD-122 is accepted technical debt noted during the cycle D review.
- **Decision:** A super admin receives the full grants (TD-121). Finder pagination stays in memory for now and is revisited by a measured trigger, not a date (TD-122).
- **Source:** project owner, 2026-10-27; ADMIN-024, BR-29, CUST-009.

### 2026-10-26 — TD-114…120: Joshen guided finder (CUST-009, ADMIN-012)

- **Status:** Owner-approved implementation plan with amendments; setting and throttle defaults remain explicitly configurable.
- **Decision:** `JoshenSettings` owns settings validation for both admin write paths. Occasion CRUD and type-1 service tagging feed the existing catalog. Category and package types derive from active catalog data. The first search logs; pagination reuses that row. Eligibility, coverage, ranking and scheduled availability reuse existing domain code. Guest identity uses `X-Guest-Device-ID`; order linking and one-time guest claim run in the order transaction. A search may convert into multiple orders. No admin-handled special request is created (DEC-02).
- **Source:** Project owner approval and amendments on 2026-10-26; CUST-009, AC-CUST-009, ADMIN-012, AC-ADMIN-012, BR-02/05/06/08/09/10, SCR-11, DEC-02, TD-12/19/22/27/42, schema `finder_searches` and `orders`.

### 2026-10-26 — TD-109…113: Order delay alerts and admin order monitor (ADMIN-013, CUST-019)

- **Status:** Owner-approved Sprint 2 cycle C plan with three amendments: stage entry from the `orders` columns rather than the history table (two clocks), `execution_early_start_minutes` added to the insert-if-missing migration, and tests for the preparing clock and the delayed filter. TD-109/110 values are working assumptions **awaiting client confirmation** (like TD-102); DEC-05 stays Partly resolved.
- **Decision:** As in TD-109…113. The delay alert only informs the customer; it never changes status, cancellation or the provider (BR-21). Admin order management is read-only.
- **Source:** project owner request and approval on 2026-10-26; ADMIN-013, AC-ADMIN-013, CUST-019, AC-CUST-019, PROV-019, BR-21/22/29, DEC-05.

### 2026-10-26 — TD-108: Generic staging and production 5xx responses

- **Decision:** API 5xx responses do not expose exception text outside local development. The correlation id remains in the response body and details stay in server logs.

### 2026-10-26 — TD-105/106: Customer cancellation (CUST-020)

- **Status:** Owner-approved Sprint 2 cycle B scope and amendments.
- **Decision:** DEC-24 defaults come from a production migration only when there is no active customer policy, with matching local demo rows and translations. Stage windows, typed lock/policy refusals, replay ownership, exact fee cap, zero-refund boundary and no Sprint 2 earnings are fixed as in TD-105/106.
- **Source:** project owner request and approval on 2026-10-26; CUST-020, AC-CUST-020, BR-19/20/21/22, DEC-24, DEC-10, D3.
- **Affected features:** customer cancellation, customer order details, provider cancelled appointments, shared order realtime.
- **Acceptance impact:** AC-CUST-020 tests cover both sides of departure, the default full wallet refund and refusal after execution. PROV-016 and ADMIN-018 remain later work.

### 2026-10-26 — TD-101…104: Provider order execution (PROV-019/020)

- **Status:** Owner-approved plan (2026-10-26). TD-101/TD-103 working assumptions; TD-102 working assumption awaiting client confirmation; TD-104 owner instruction. DEC-08 stays Open (not decided here); DEC-19 customer submission limits stay Open.
- **Decision:** As in TD-101…104. The owner replaced the first plan in three ways: the transport gate applies only to a Joshen-driver order and blocks only the provider's own departure; each refusal has its own `ErrorCode` (`ORDER_STAGE_MANAGED_BY_DRIVER`, `ORDER_EXECUTION_TOO_EARLY`, `ORDER_NOT_EXECUTABLE`, all 422 like `INVALID_STATE_TRANSITION`); and `can_rate` ships on both order resources now.
- **Source:** project owner request and approval on 2026-10-26; PROV-019/020, CUST-019, BR-03/19/22/23/28, TD-92/96/100, DEC-08, DEC-19.
- **Affected features:** PROV-019, PROV-020, PROV-022 (completed tab), CUST-019 (customer result), CUST-017.
- **Acceptance impact:** AC-PROV-019 and AC-PROV-020 covered by `Provider/OrderExecutionTest`. Still open: the delay alert half of AC-CUST-019 (cycle C), driver-trip departure (cycle F), earnings on completion (Sprint 3).


### 2026-10-25 — TD-98: Featured subscription (PROV-007, ADMIN-005)

- **Status:** TD-98 working assumption; DEC-12 remains Partly resolved (refunds on cancellation, suspension effects, renewal overlap), DEC-06 open (vendor), DEC-23 open (customer "Featured" label).
- **Decision:** Several plans with VAT (SCR-34), paid in the app, activating dated ranking priority that the existing `v_provider_featured` view and `EligibleProviders::rankFeatured` already apply after eligibility (BR-09/27). Failed payments never activate and are retryable. Paying from earnings is a negative earnings row. Automatic renewal is stored but not charged until a real gateway exists.
- **Source:** project owner request on 2026-10-25 (BRD PROV-007/ADMIN-005, `provider--featured--01…03` screens); DEC-12, DEC-23, BR-14, TD-55, TD-94.
- **Affected features:** PROV-007, ADMIN-005; read by CUST-010/011 and the Joshen finder (ranking); ADMIN-004 (`featured_status`).
- **Acceptance impact:** AC-PROV-007 and AC-ADMIN-005 covered by `FeaturedSubscriptionTest` and `FeaturedSubscriptionManagementTest`. Launch blockers: real payment gateway, automatic renewal charge, payout netting of the deduction.

### 2026-10-25 — TD-95…97: Order hardening and realtime

- **Status:** TD-95/96 owner instruction ("fix them and handle the risks, and make the real time using socket"); TD-97 working assumption.
- **Decision:** Idempotency is stored and body-checked; order codes are short and random; instant orders block the provider from placement through arrival and service; the overlap check runs in SQL; order creation and payment are throttled per user; a delayed job enforces each deadline on time; order events reach the apps live over Socket.IO in per-app private rooms.
- **Source:** project owner request on 2026-10-25; D3, BR-08/12/13, TD-92/94.
- **Affected features:** CUST-010/011/012/013/014/017, PROV-012/013/014/015/022.
- **Acceptance impact:** a reused key with another body is a 409; a third `POST /orders` in a minute from one user is a 429 beyond the limit; an unanswered instant request expires and refunds at its deadline and both apps are notified live; a rejected or expired order no longer exposes the address to the provider.

### 2026-10-25 — TD-92…94: Phase 2 order-cycle working rules

- **Status:** TD-92 working assumption; TD-93 owner instruction; TD-94 temporary implementation and launch blocker.
- **Decision:** Owner approved configurable overlap, slot, lead, ETA and deadlines as working assumptions, with a sum guard; requires current percent provider commission at both quote and checkout; Phase 2 uses only a local/testing simulated payment driver. DEC-04/05/06 retain their open client/vendor questions.
- **Source:** project owner responses on 2026-10-25; D3, BR-05/06/08/11/12/13/14/24, SCR-13.
- **Affected features:** CUST-010/011/012/013/014/017, PROV-012/013/014/015/022.
- **Acceptance impact:** quote/create reject missing or fixed commission; deadline changes cannot exceed minimum lead; paid instant rejection/timeout refunds to wallet.

### 2026-10-22 — TD-78…80: Customer home, catalog and search

- **Status:** Accepted (TD-78 working assumption).
- **Decision:** as in TD-78…80. Owner answered 2026-10-22: keep is_accepting_bookings in eligibility and set it in test fixtures until PROV-006 ships its toggle; browsing eligibility is the supplied list only, SCR-18 completeness stays with checkout.
- **Source:** owner task brief; CUST-001/005/006/007/008, SCR-08/09/10, BR-02/05/06/08/09/30, TD-42/64/69/71, DEC-23 (open).
- **Impact:** client/home.md and client/catalog.md 1.0.0; OnboardingChecklist coverage now reads ProviderCoverage (no contract change); CompleteServicePricing gains an all-provider form (same predicate). No table changes, no new error codes.

### 2026-10-22 — TD-76/77: Account completion fields (provider + driver)

- **Source:** owner instructions; PROV-008, DRIV-006, AC-PROV-008/AC-DRIV-006, SCR-18/19/26, BR-04/08, TD-44/62/64/65, DEC-25 (still open).
- **Decision:** first-time set of city/coverage/payout/driver personal data is direct in any status; changing it after approval is 409 PROFILE_CHANGE_REVIEW_REQUIRED until profile change requests exist; pending_review blocks city/payout changes (TD-62). Owner answered 2026-10-22: add the dedicated photo route beside TD-72's photo key; first-time city/payout allowed during pending_review; driver birth_date/nationality follow the same rule; add GET work-area/personal for prefill.
- **Impact:** provider/profile.md 1.1.0 (new endpoints), delivery/profile.md first scoped release. New error code PROFILE_CHANGE_REVIEW_REQUIRED. No table changes.

### 2026-10-22 — TD-70…75: Provider pricing and direct profile scope

- **Status:** Accepted owner contract; TD-73 default is an editable working assumption.
- **Decision:** As in TD-70…75 below. Preserve the onboarding catalog route and expose own assignments separately. Pricing stays approved-only; account photo/bio/title/transport and portfolio are direct edits before approval. DEC-25 remains open for the remaining reviewed fields.
- **Impact:** provider/services-pricing.md and scoped provider/profile.md first release; shared complete-pricing rule fixes onboarding guidance; admin/settings.md documents the portfolio operating key. No table changes.
- **TD-74 implementation note:** generated service_choice_id comes from nested active choice UUIDs; the choice payload example cannot reuse the root service_id. Pricing response examples remain captured from actual atomic forms.

Newest first. One entry per decision change.

### 2026-10-22 — TD-68/69: Customer service addresses

- **Source:** owner instructions; CUST-004/AC-CUST-004, BR-02/08, schema customer_addresses and the single customer--account--04-addresses.png screen.
- **Decision:** explicit default lifecycle and newest-live promotion after soft deletion; transactional profile locking, UUID ownership, paired coordinates and typed service-area validation; stable localized resource contract.
- **Impact:** client/addresses.md initial release and six customer-app endpoints; existing schema reused, legacy address classes/table remain reference-only. Booking eligibility must still be rechecked by its owning feature.

### 2026-10-22 — TD-65…67: Driver onboarding

- **Source:** owner instructions; DRIV-002/003, SCR-19, TD-35/43/48/50/51/52 and provider onboarding.
- **Decision:** registration submission separated from activation completion; driver rejection setting/edit states; shared provider/driver Domain rules/actions and timeline, full_name base mapping, one in-place active vehicle and shared document policy.
- **Impact:** delivery/onboarding.md initial release; provider API contract unchanged through shared extraction; no schema additions, operational endpoints or post-approval change-request writes.


### 2026-10-22 — TD-58…64: Provider onboarding

- **Source:** owner instructions A/B/B2/C/D; PROV-002/003/004, SCR-18, D4.
- **Decision:** step-2 submission separate from bookability, shared approval-document policy, transactional category approval, configurable rejection resubmission, locked editable-state enforcement, replacement document rows and derived status checklist/timeline.
- **Impact:** provider/onboarding.md first release, admin/providers.md review behavior update; no schema additions. DEC-18 remains declaration-only; DEC-25/TD-44 remain deferred for post-approval changes.

### 2026-10-25 — TD-02, TD-82…91 (assumptions reviewed): validation errors, service areas, cleanup, registration review, packages, customer profile

- **Owner request:** fix the issues and business gaps found in the project review.
- **Decision:** TD-02 shipped (`data.errors`); TD-82/83 are accepted for ADMIN-021 (fee rules; coverage editable by the owner and, in any status, by the admin); TD-84 removes dead base code and fixes the geo defaults; TD-85 builds ADMIN-007 as one queue over the existing review; TD-86 adds admin approval of extra main categories (AC-PROV-009); TD-87/88 build packages (PROV-011, ADMIN-011, CUST-008 details), accepted after review; TD-89 builds CUST-023 (profile edit, mobile change by OTP); TD-90 records the missing add-category screen (SCR-35) and adds `GET /provider/my-categories/available`; TD-91 adds the provider mobile change by OTP (SCR-26, phone only).
- **Source:** ADMIN-007, ADMIN-021, PROV-009, BR-03, BR-08, BR-11, DEC-10, DEC-17, TD-30, TD-60, TD-70, TD-76/77.
- **Impact:** admin/service-areas.md 1.0.0 (MAJOR for the delete flow only: 500 → 409 `SERVICE_AREA_IN_USE`), admin/registration-review.md 1.0.0, API_CONVENTIONS 2.7.0, admin/settings.md 0.2.1, provider/services-pricing.md 1.0.1, provider/packages.md 1.0.0, admin/packages.md 1.0.0, client/catalog.md 1.1.0, client/profile.md 1.0.0. New error codes `SERVICE_AREA_IN_USE`, `DELIVERY_FEE_RULE_CONFLICT`, `DELIVERY_FEE_UNAVAILABLE`, `PACKAGE_SERVICES_UNAVAILABLE`, `PHONE_IN_USE`; new notification events `provider_category_reviewed`, `package_reviewed`. No table changes.

### 2026-10-22 — TD-81: Examples and payloads live only in the Postman collection

- **Owner request:** the Postman examples and payloads must be updated in `docs/postman-collection.json` itself, not kept as single files in `docs/postman/examples`; update skills, rules and steering.
- **Decision:** TD-81. The collection is the single store; tests merge recorded examples into it; the generator reads them back and preserves them; the folder is deleted.
- **Impact:** 562 example files merged into the collection (956 captured responses, none lost), 61 doc links in provider/delivery/admin/client docs rewritten to `Postman: route › "code name"`, steering `postman-collection.md`, `architecture.md`, `feature-documentation.md`, `joshen-postman` and `joshen-feature-docs` skills, Codex rule and Kiro hook updated. No API contract change.

### 2026-10-22 — TD-57: Request chaining through saved variables

- **Owner request:** requests run from `docs/postman-collection.json` (Postman and the `/docs` API console) must save response values and reuse them in related requests; update the docs for MCP client-side agents.
- **Decision:** TD-57. Per-app token variables, saved uuids/hashes/OTP challenge, generated from routes and verified against captured examples; same data exposed to the console and the MCP server.
- **Impact:** [API_CONVENTIONS.md](API_CONVENTIONS.md) 2.6.0 §8.1, [MCP_AGENT_GUIDE.md](MCP_AGENT_GUIDE.md) 1.1.0, MCP server 1.2.0. No API contract change.

### 2026-10-22 — TD-56: Postman collection contract

- **Owner request:** make every task by any model update the Postman collection with each payload key's type and what it refers to, plus examples of every response the user can get.
- **Decision:** TD-56. Field docs live on the Form Request next to the rules; response examples are captured from feature tests, never typed; a coverage check with a shrinking baseline enforces it. Added to steering (`postman-collection.md`), AGENTS.md, CLAUDE.md, the `joshen-feature`, `joshen-feature-docs` and new `joshen-postman` skills, the Codex command rules and the Kiro regenerate hook.
- **Impact:** 215 real examples recorded from the existing API tests; 37 of 146 routes complete; 454 gaps left in the baseline. No API contract change.

### 2026-10-22 — TD-54/55: Provider/driver review follow-ups

- **Owner request (review of ADMIN-004/006):** stop masked identity numbers leaking through list search; show the BRD's featured subscription status; emit ISO dates on the new endpoints; fix the customers version bump; keep the brain header current.
- **Decision:** TD-54 (no identity-number search) and TD-55 (`featured_status` from subscriptions, DEC-12 working assumption). The new provider/driver endpoints emit ISO 8601 with offset (TD-03 target); TD-38's offsetless format stays only on the legacy catalog/customer contracts. The 1.0.1 customers city-filter 404 → 422 change is recorded as MAJOR (changed error contract).
- **Not decided here:** approving the categories a provider picks at registration (PROV-002/003, BR-06). The owner will design it with provider onboarding, where the provider completes her data, documents and categories from her profile. Listed in providers.md §13.
- **Impact:** [admin/providers.md](admin/providers.md) 2.0.0, [admin/drivers.md](admin/drivers.md) 2.0.0, [admin/customers.md](admin/customers.md) 2.0.0.

### 2026-10-22 — TD-53: Super admin answer of `auth/my-permissions`

- **Owner request:** new admin endpoint for the signed-in admin's permissions; for a super admin the list must be empty because she has access to every admin action.
- **Decision:** `is_super_admin: true` + empty `permissions` and `sections`. The dashboard checks the flag first. `profile.permissions` in `auth/me` and the other account payloads is unchanged (whole catalog, TD-28).
- **Impact:** [admin/auth.md](admin/auth.md) 1.2.0, [admin/admins-permissions.md](admin/admins-permissions.md) 1.5.1.

### 2026-10-21 — TD-50…52: Live approval and identity review follow-ups

- **Owner request:** localized missing-document labels; automatic verification during approval; full document numbers in audited identity endpoints; public plates; BRD-backed vehicle prerequisite and a manual fixture.
- **BRD finding:** DRIV-002/SCR-19 include vehicle type/model/plate and three documents in registration; DRIV-003 submits completed registration for review. Require one driver-owned active vehicle on initial approve; reactivate remains TD-35 restoration.
- **Decision:** reuse the locked qualifying document collection, verify pending rows and attribute reviewer/time within the same approval transaction. Preserve prior verifications and non-qualifying rows. Existing review-event note stores counts; linked profile/document audits provide UUID traceability without schema changes or identity values. No rewrite of earlier approvals or manual review history.
- **Affected:** providers.md and drivers.md 1.1.0; existing routes/approve grants unchanged. Expanded role-scoped identity documents and public vehicle plates supersede the relevant TD-45/48 masking scope; IBAN remains masked everywhere. New opt-in fixture +966 500009204 has valid attached documents but no vehicle.

### 2026-10-21 — TD-49: Same validation fixes for customers/users

- **Owner request:** continue the fixes in users too, after provider review and driver implementation.
- **Decision:** use normal city-filter validation with 422 and concrete sort attributes in customer/refund-history requests. Preserve all TD-39 refund-only history behavior and exact existing user route grants.


### 2026-10-21 — TD-48: Driver management

- **Scope:** ADMIN-006 reuses the provider account-review service and request validation; no new schema. Full identity requires its audited approve-route grant; other values/IBAN stay masked.
- **Behavior:** active vehicle summary on list, all vehicles in details, coordinates only online; use actual schema make_model and profile counters. Important changes remain metadata only (TD-44), financial/trip/confirmed-trip effects deferred.
- **Manual fixtures:** add complete car/motorbike and missing-driving-licence submissions to the local/dev-only seeder. ADMIN-007 remains planned.


### 2026-10-21 — TD-47: Live-review fixes and reproducible manual review

- **Owner request:** named 422 city filters, localized transition labels, concrete sort keys, trace unreviewed approved seed accounts and provide local review scenarios.
- **Finding:** UserSeeder directly inserted approved provider/driver profiles with approved_at but no submission/approver/documents. AppAuthService explicitly creates incomplete profiles. The original provider now has live suspend/reactivate events; do not rewrite that history.
- **Decision:** normal shape validation, safe incomplete seed defaults, and a separate opt-in dev-only seeder with real synthetic attachments and submitted events. Existing fixture/test factories can still explicitly create approved states for isolated test setup; they are not app registration flows.


### 2026-10-21 — TD-43…46: Provider account management

- **Owner authorization:** additive migration for nullable from/to account statuses; keep important-change requests read-only; full identity only through a dedicated approve-permission GET with a read audit; IBAN never revealed. Wallet remains TD-39 refunds only.
- **Decision:** as TD-43…46. ADMIN-004 important-change review remains partially covered until allowed fields, validation and atomic apply semantics are defined. No driver endpoints shipped in this release. Existing create migration is unchanged.
- **Affected:** ADMIN-004, later ADMIN-006/007; schema guide and provider integration contract.


### 2026-10-21 — TD-42: Screen-aligned catalog and provider-owned pricing

- **Owner approval:** confirmed category → bookable service → independent groups (eyelashes, hairstyle) → choices, including free choices; admin creates the catalog/ranges and provider selects/prices it. Owner explicitly pointed to the existing provider_services migration, which is reused unchanged structurally.
- **Breaking:** type1/2 meanings change; services.md 2.0.0. Type3 now supports zero-range choices. No new pricing table or admin provider-price mutation route.
- **Enforcement:** matching domain rules, SQL CHECKs, provider-price triggers, visibility views and factories; each group's selection requirements remain input to future checkout.
- **Upgrade boundary:** current local catalog has no services/prices. Guard populated legacy trees before DDL; they require a reviewed data mapping. Do not rewrite financial/order history or silently delete old grouping records.

### 2026-10-21 — TD-41: Category slug approved

- **Owner authorization:** after seeing a category payload with slug=makeup, the owner confirmed adding a unique category slug and the corresponding schema/migration change.
- **Decision:** one globally unique, non-translated slug, accepted as any string. UUID routing and the flat catalog stay unchanged.
- **Working compatibility assumptions:** optional/nullable, max255, no auto-generation or ASCII restriction, standard trim/empty normalization, existing database collation governs uniqueness. Omission preserves; null clears. Existing data is not backfilled or deleted.
- **Owner form clarification:** category UI is title (ar/en.name), description, icon image (upload hash) and active; optional slug/sort_order/icon_key metadata need not appear. Services own price ranges. Regenerated request examples follow this form and omit prohibited/fake-media values.
- **Contract:** services.md 1.2.0 (MINOR), schema SQL + baseline and upgrade migrations; no new route or permission.

### 2026-10-21 — TD-40: Align database-generated timestamps with UTC

- **Evidence:** live session reported SYSTEM; NOW() was three hours ahead of UTC_TIMESTAMP(). PHP application timezone is UTC.
- **Decision:** pin application database connection sessions to +00:00, without table changes or data rewrites; add a MySQL wallet-trigger timestamp regression check. TD-38's offsetless timestamps remain UTC.
- **Historical limit:** prior naive DATETIME values cannot be safely normalized without writer/timezone evidence. Deployment must reload cached configuration/workers; new connections apply the setting automatically.

```
### YYYY-MM-DD — DEC-xx / TD-xx: <short title>
- **Status:** Open → Working assumption | Resolved | Accepted | Superseded
- **Decision:** <the rule, stated so it can be implemented and tested>
- **Decided by:** <role/name>          **Source:** <meeting, message, BRD version>
- **Affected features:** <IDs>         **Affected docs:** <docs/... files to bump>
- **Acceptance impact:** <AC-xx to update>
```

### 2026-10-21 — TD-39: Admin customers and refunds-only history
- **Status:** Accepted.
- **Decision:** As in TD-39; no migrations or schema changes. Refund history means refund-credit entries only, as explicitly confirmed by the owner. Full balance remains authoritative.
- **Decided by:** Project owner (scope/refund-credit confirmation), Engineering (query contract/field naming). **Source:** original Feature B task and clarification, 2026-10-21.
- **Affected features:** ADMIN-003. **Affected docs:** admin/customers.md 1.0.0, admins-permissions.md 1.3.0.
- **Acceptance impact:** AC-ADMIN-003, customer scoping, real-address ownership, refund-only history, UTC filters and audited account blocking.

### 2026-10-21 — TD-38: Legacy timestamps are UTC
- **Status:** Accepted technical decision, requested live-review correction.
- **Decision:** Keep current UTC Y-m-d H:i:s catalog/customer values; correct local-time parsing guidance and provide explicit UTC parsing. ISO endpoints are unchanged.
- **Decided by:** Engineering, within the owner's choice of documentation correction or offset addition. **Source:** Feature A live review, 2026-10-21.
- **Affected features:** ADMIN-003/008/009/010. **Affected docs:** services.md 1.1.1, customers.md 0.1.1 then 1.0.0, API_CONVENTIONS.md 2.5.1.
- **Acceptance impact:** Catalog UTC timestamp assertion; customer resource/filter tests use UTC dates.

### 2026-10-21 — TD-37: Category and service images use upload hashes
- **Status:** Accepted, supersedes the service-image omission in TD-34.
- **Decision:** Both catalog models use HasMedia with an image collection. React uploads first, then submits the hash in create/update forms; URL accessors provide the image for display.
- **Decided by:** Project owner. **Source:** media clarification, 2026-10-21.
- **Affected features:** ADMIN-008/009. **Affected docs:** admin/services.md 1.1.0, database/README.md 1.9.5.
- **Acceptance impact:** HTTP tests exercise real POST /api/v1/media → create/edit → list/show, replacement/removal and refusal to reattach another record's file.

### 2026-10-21 — TD-33…36: Admin catalog and review rules
- **Status:** Accepted; TD-34 is an owner-approved working assumption.
- **Decision:** As in TD-33…36. Owner explicitly approved ancestor/addon visibility, rejecting conflicting range edits with the affected count, the shared transition map and approval prerequisites, and deferral of confirmed-order suspension handling.
- **Decided by:** Project owner. **Source:** admin CRUD task follow-up, 2026-10-21.
- **Affected features:** ADMIN-003/004/006/007/008/009/010. **Affected docs:** admin/services.md now; admin/providers.md and admin/drivers.md when implemented.
- **Acceptance impact:** AC-ADMIN-008/009/010 now; AC-ADMIN-004/006/007 in subsequent features.

### 2026-10-21 — TD-32: Base admin endpoints documented at 0.x
- **Status:** Accepted
- **Decision:** As in TD-32. Chosen over waiting for each BRD feature, because the React team needs the working endpoints (service areas, customers, banners, settings) now and needs to know which ones fail.
- **Found while capturing responses (no code changed):** `GET /faqs` and `GET /pages` return 500 without an explicit `sort[...]` (default sort uses `created_at`, which those tables lack); `POST /pages` always 500 and page text (`content` → `body`) cannot be edited; FAQ `audience` / `sort_order` and slider `target_app` / `placement` / dates cannot be set (BR-30); slider text is saved in the request locale only (TD-12); every `/categories` call except `DELETE` is 500 (TD-18: categories are flat); `PUT /settings` with an unknown key is 500 after saving the earlier keys; over-length FAQ/slider text is 500 (request max > column size); `/dashboard` exposes server versions and counts all accounts.
- **Decided by:** Project owner (request), Engineering (doc placement and status)          **Source:** documentation request 2026-10-21
- **Affected features:** ADMIN-002, ADMIN-003, ADMIN-008…010, ADMIN-021, ADMIN-023, ADMIN-025, ADMIN-026, CUST-002 (consent pages)         **Affected docs:** the eight `docs/admin/*.md` at 0.1.0, PROJECT_BRAIN §4.4 and §5, feature-documentation.md
- **Acceptance impact:** none — each doc's §12 lists the AC rows these endpoints do not cover yet

### 2026-10-21 — TD-31: Docs viewer secured and exposed over MCP
- **Status:** Accepted
- **Decision:** As in TD-31. Before this, `/api/v1/docs/*` was public in every environment, the proxy fetched any URL (SSRF, TLS checks off), and quick-token minted super-admin JWTs without login outside `production` (so on any `dev`/`staging` server).
- **Decided by:** Project owner (request), Engineering (gate design)          **Source:** docs viewer review 2026-10-21
- **Affected features:** none (developer tooling)         **Affected docs:** API_CONVENTIONS 2.3.0 §8
- **Acceptance impact:** `DocsViewerTest`, `DocsMcpServerTest`

### 2026-10-21 — TD-29, DEC-15: Session policy
- **Status:** Accepted working assumption (auth review 2026-10-21)
- **Decision:** As in TD-29. A 60-day access token made refresh pointless and left a leaked admin token valid for two months.
- **Decided by:** Engineering, from the review's proposed defaults          **Source:** auth + RBAC review 2026-10-21 §1
- **Affected features:** CUST-001…003, PROV-001/005, DRIV-001/004, ADMIN-001         **Affected docs:** client/provider/delivery auth 2.1.0, admin/auth 1.1.0, API_CONVENTIONS 2.2.0
- **Acceptance impact:** `test_td_29_*` (TTL per app, refresh after expiry, 401 outside the window)
- **Open for product owner:** final TTL values.

### 2026-10-21 — DEC-18, TD-17: Consent controls on the screens (open for product/design)
- **Status:** Open — product/design
- **Decision:** The API stays strict: `guarantees_female_gender`, `accept_policies`, `accept_terms` must each be accepted. The prototype screens lack controls: customer `customer--auth--04` has the female declaration only (no terms/privacy); provider `provider--registration--0[1-2]` and driver `driver--registration--01` have none. Each screen must collect explicit consent with unchecked checkboxes, and the app must never send `true` without a user action, or the stored consent timestamps lose their meaning. Per-screen list: §2 "Screen changes required" of each app's auth doc.
- **Affected features:** CUST-002, PROV-001, DRIV-001         **Affected docs:** client/provider/delivery auth 2.1.0
- **Related:** the terms and privacy pages the checkboxes link to need `admin/content.md` (the base static-page route is unregistered, TD-30).

### 2026-10-21 — TD-26: OTP send limit per phone and app
- **Status:** Amended (auth review 2026-10-21 §10)
- **Decision:** otp-send identity bucket = surface + normalized phone, 5 per 10 minutes (`AUTH_OTP_SEND_PER_PHONE`), so one person registering in all three apps is not locked out. New abuse ceiling: 12 per phone across apps per 10 minutes (`AUTH_OTP_SEND_PER_PHONE_ALL_APPS`). Per-IP limits unchanged.
- **Affected docs:** client/provider/delivery auth 2.1.0         **Acceptance impact:** `test_td_26_otp_send_*`

### 2026-10-21 — TD-23: Provider step-1 name (amendment)
- **Status:** Amended (auth review 2026-10-21 §4)
- **Decision:** `provider--registration--01-basic-individual` and `--02-basic-establishment` have one name field. `display_name` stays required; `full_name` becomes optional. A **new** identity without `full_name` takes `display_name` as `users.full_name`; an existing identity's name is never overwritten (TD-23 cross-app rule). Contract relaxation, non-breaking.
- **Affected:** PROV-001         **Affected docs:** provider/auth 2.1.0         **Acceptance impact:** `test_ac_prov_001_*`

### 2026-10-21 — TD-30, TD-25: Legacy client and notification routes
- **Status:** Accepted (auth review 2026-10-21 §6, §7)
- **Decision:** As in TD-30. Chosen over rewriting each route on base tables the schema replaces: the owning features rebuild them on the schema tables.
- **Amended (project owner, same day):** the shared inbox `/api/v1/notifications` is kept for every app; its service now reads the schema table scoped by user + `app` claim instead of Laravel's notifications columns. `{id}` is constrained to a uuid so `clear-all` / `mark-all-read` never match it. Acceptance: `NotificationInboxTest`.
- **Affected docs:** client/auth 2.1.0, admin/admins-permissions 1.1.0, PROJECT_BRAIN §5         **Acceptance impact:** `AccountRoutesTest`

### 2026-10-21 — DEC-17: Localized error messages
- **Status:** Implementation note (DEC-17 unchanged)
- **Decision:** `lang/ar/validation.php` (all rules + attribute names of the auth, roles, supervisors and permission requests), `lang/ar.json` (generic handler and service messages). Validation, 401, 403, 404, 405, 429 and 500 messages follow `Accept-Language`, Arabic by default. A 422 keeps Laravel's combined message ("(and N more errors)", translated); `data.errors` remains TD-02. A 500 returns `INTERNAL_ERROR` + `correlation_id`, never SQL or exception text with `APP_DEBUG=false`; debug headers only with `APP_DEBUG=true` outside production; `X-Powered-By` removed.
- **Acceptance impact:** `LocalizedErrorsTest`, `ProductionErrorOutputTest`

### 2026-10-20 — TD-27, DEC-14: Permission catalog and section list
- **Status:** DEC-14 Partly resolved (sections fixed); TD-27 Accepted working assumption, authorized by the ADMIN-024 task
- **Decision:** 21 sections merged from the BRD ADMIN-024 list and the admin registry: Joshen finder → `services`; coupons → `payments`; commissions → `payouts`; FAQs, static pages, onboarding slides → `banners_content`; feature flags → `settings`; audit logs and the permission matrix → `administrators`; geography → `service_areas`; the base `/users` list → `customers`. Mapping and sync command as in TD-27.
- **Decided by:** Engineering working assumption (ADMIN-024 task)          **Source:** DEC-14, BRD 1.3 ADMIN-024
- **Affected features:** ADMIN-024, every admin feature         **Affected docs:** admin/admins-permissions.md, admin/README.md, crud-conventions.md
- **Acceptance impact:** AC-ADMIN-024; new catalog test blocks unmapped admin routes

### 2026-10-20 — TD-28: Hierarchy direction and security rules
- **Status:** Accepted working assumption, authorized by the ADMIN-024 task
- **Decision:** As in TD-28. ARCHITECTURE.md said a child "inherits Super Admin permissions", which would hand every child role everything; the implemented direction is senior-inherits-junior (NIST RBAC style). Also decided: deleting a role with child roles is `ROLE_IN_USE`; supervisor delete is a soft delete (`deleted_at`) and the email stays reserved (DB unique); a toggle `reason` is kept in the audit entry (no column); the supervisor list sorts only by `created_at`/`last_login_at`; `/admin/users` lists app users only. The auth-time "role must be active" check is kept, so the LAST_SUPER_ADMIN rule guards role deactivation.
- **Decided by:** Engineering working assumption (ADMIN-024 task)          **Source:** ADMIN-024, BR-29, task security rules 1–7
- **Affected features:** ADMIN-024, ADMIN-001         **Affected docs:** admin/admins-permissions.md 1.0.0, admin/auth.md 1.0.1, ARCHITECTURE.md
- **Acceptance impact:** AC-ADMIN-024 tests run with RBAC enforced

### 2026-10-20 — TD-23: Admin compatibility (amended by TD-28)
- **Status:** Amended
- **Decision:** The "preserve local/testing bypass" clause of the TD-23 admin-compatibility entry is withdrawn: RBAC is enforced in every environment; grants come from `PermissionResolver`.
- **Affected:** ADMIN-001, BR-29         **Affected docs:** admin/auth.md 1.0.1

### 2026-10-20 — TD-26: Per-IP auth limits for carrier CGNAT
- **Status:** Accepted, project-owner request after the auth review
- **Decision:** otp-send 300 per IP per 10 minutes (was 30), otp-verify 300 per IP per minute (was 60); per-phone, per-token, per-email limits and admin per-IP limit unchanged; every value env-configurable.
- **Supersedes:** the per-IP values in TD-23 "Abuse defaults".
- **Affected docs:** client/provider/delivery auth 2.0.1, `config/auth_limits.php`, `.env.example`

### 2026-10-20 — TD-25: One user_type for app users
- **Status:** Accepted, project-owner request after the auth review
- **Decision:** New app identities get `user_type = client` ("app user") whichever app they join first; access comes from profile rows + the `app` claim. Previously the first app's type was stamped, which misdescribed people using several apps.
- **Known gap:** `GET /api/v1/notifications` (base notification module) already returns 500 because the schema replaced Laravel's notifications table; the notifications feature must rebuild it per app (`notifications.app`).
- **Affected:** TD-01, TD-13, `AppAuthService::activate()`

### 2026-10-20 — TD-24: Fixed four-digit OTP until gateway integration
- **Status:** Accepted, explicit project-owner request.
- **Decision:** The first shared OTP generator returns the literal string 1234. Verification configuration and all three app requests use four digits. Send/resend both use this generator; the SMS channel remains logging-only.
- **Supersedes:** DEC-15's six-digit/random generation policy while the gateway is pending. No environment restriction was requested for this temporary implementation.
- **Unchanged:** Ten-minute expiry, five incorrect attempts, resend/request limits, one-time challenge consumption, app/profile boundaries and token handling.
- **Affected:** client/provider/delivery auth guides 2.0.0, Verification module, auth acceptance tests and generated Postman validation.

### 2026-10-20 — TD-23: Unknown account UX
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** Login without this app profile returns 404 ACCOUNT_NOT_REGISTERED, including deleted identities. This deliberately exposes per-app registration for the requested sign-up UX; enumeration trade-off accepted. Duplicate registration returns 409 ENTITY_ALREADY_EXISTS.
- **Affected:** CUST-001/002/003, PROV-001/005, DRIV-001/004
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Cross-app identity data
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** Reuse an existing phone identity when joining another app. Preserve full_name and locale; provider display_name is independent. New/pending app-only identities have password NULL. Existing admin credential hashes remain administrative data; an app OTP cannot confer an admin JWT claim.
- **Affected:** TD-01, DEC-15, TD-17
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Driver step-1 name
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** Require full_name at driver registration although driver--registration--01-phone-otp.png shows phone only. users.full_name is NOT NULL; add a name control before OTP.
- **Affected:** DRIV-001, SCR-19
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Saudi phone normalization
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** Central PhoneNumber accepts string calling code +966/966/00966 and national 5XXXXXXXX/05XXXXXXXX, stores +966 and 5XXXXXXXX. Lookup includes historical base representations to avoid duplicate identities. No other calling code accepted for this Saudi contract. The old PhoneValidationRule checks user_type and is not used for identity uniqueness.
- **Affected:** TD-01, DEC-17
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Pending state and atomicity
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** Pending registration data is encrypted in configured cache, keyed by opaque challenge UUID and bound to its app. No users/profile written before verify. Context lasts 1440 minutes for resend after OTP expiry; each code expires per Verification config (10 minutes). Re-registration after cooldown replaces challenge/payload. Successful verification consumes OTP with user/profile/consents/wallet/device writes in a MySQL transaction. Incorrect-attempt counters commit; failed profile creation rolls back verification. All apps serialize a phone through a shared-cache atomic lock (120-second lease, 5-second wait) and row locks on existing records. All API nodes must share cache; lost context restarts the phone flow. The challenge retains the existing account UUID and rejects deletion/replacement before verify/resend. Verified historical phone formats are canonicalized. Detached device rows can be reused on a later login. No schema additions.
- **Affected:** TD-01, TD-13, TD-19
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Device schema mapping
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** device_apps.device_id is unique: a devices row represents one account/app/push registration. A push token shared by apps gets separate devices rows. Verify upserts this app registration; logout deletes only its device_apps row, preserving other app associations. No client-supplied device_id is used.
- **Affected:** TD-13, devices/device_apps
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Abuse defaults
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** Working config defaults: otp-send 5 per normalized phone and 30 per IP per 10 minutes across apps; otp-verify 10 per token and 60 per IP per minute; admin-login 5 per lowercased email and 30 per IP per minute. Send/resend cooldown 60 seconds per app/phone. Values are in config/auth_limits.php. Named limiter failures include RATE_LIMITED and Retry-After; cooldown uses challenge timer. Resend rotates code, resets attempts, retains token. OTP length/expiry/attempts come solely from Verification config.
- **Affected:** DEC-15, TD-01
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Admin compatibility
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** Keep login as email-valued request field with password. Restrict admin types before token issuance; non-admin/unknown/wrong-password share generic 422 UNAUTHENTICATED. JWT app=admin. GET auth/me and GET profile use dedicated user/profile resource; flat route grants combine role ancestry and direct grants. Exempt admin.auth.* and admin.profile.* from section RBAC after auth/app/active. Preserve broader legacy RBAC behavior, including local/testing bypass; resource resolver/seed reconciliation remains outside auth. Admin forgot-password is unspecified/unimplemented.
- **Affected:** ADMIN-001, BR-29
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-23: Guest route ownership
- **Status:** Accepted working assumption, authorized by the authentication task
- **Decision:** guest.php holds public content, geography and non-personal search only. Upload/contact and personal recent searches require customer auth. Public auth entry points belong in client.php. Existing business-resource behavior remains with its owning feature.
- **Affected:** BR-02, CUST-001
- **Evidence:** Four-surface auth guides and API auth acceptance tests.

### 2026-10-20 — TD-22: Translation/link rows use foreign keys
- **Status:** Accepted; amends the UUID scope of TD-20
- **Decision:** Remove UUID columns from the 17 translations and seven pure link tables in the original create-table migrations. Their models do not use `HasUuid` and their factories do not generate UUID values. Parent FKs, locale keys and existing indexes identify these internal rows. Restore the SQL file exactly to the version supplied before the full-schema implementation.
- **Decided by:** Project owner **Source:** follow-up request to remove translation/pivot UUIDs and revert the SQL
- **Affected docs:** `docs/database/README.md` 1.9.1, `docs/database/db-schema.sql`, `docs/PROJECT_BRAIN.md`, `docs/CHANGELOG.md`
- **Acceptance impact:** No translation/pivot CRUD is introduced. Parent record UUIDs and profile `user_id` FK behavior remain as defined. Existing databases are not rebuilt; original migrations remain the baseline.

### 2026-10-20 — TD-21: Full-schema migration baseline
- **Status:** Accepted
- **Decision:** Implement the full 95-table SQL as a fresh-install baseline, changing original create-table migrations and removing superseded column corrections. Build every model, factory and seeder; keep package models in their module namespaces. Create chat storage now while chat functionality remains deferred. UUID references may specify `table:user_id` for the unique profile/wallet FK target, while ordinary references retain primary `id` resolution.
- **Decided by:** Project owner (scope and original-migration rewrite); engineering (mapping and fixture implementation) **Source:** request to create all schema migrations/models/factories/seeders without add-column migrations
- **Affected docs:** `docs/database/README.md` 1.9.0, `docs/database/db-schema.sql`, `docs/PROJECT_BRAIN.md`, `docs/CHANGELOG.md`
- **Acceptance impact:** Database/factory/seed verification only; all feature implementation and deployed acceptance statuses stay unchanged. No existing development database is rebuilt by this task.

### 2026-10-20 — TD-20: Every table has id + uuid
- **Status:** Accepted (supersedes the exclusions in TD-19)
- **Decision:** No table is left without `id` and `uuid`. Key-less tables gain an `id` primary key and keep their former key as UNIQUE; translations and pivots gain a `uuid` with a `DEFAULT (UUID())` fallback. Foreign keys that targeted `user_id` of the profile tables are **not** repointed to `id` (many columns named `provider_id` / `driver_id` hold the user id and are joined to `users`), so the profile tables' `user_id` stays a UNIQUE FK target — revisit if profiles ever need to move off the user id.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`, `joshen-migrations` / `joshen-feature` skills
- **Acceptance impact:** None yet (tables not built); profile endpoints expose the profile `uuid`, not the integer user id

### 2026-10-20 — TD-19: Public uuid on every addressable table
- **Status:** Accepted
- **Decision:** Integer `id` for joins and foreign keys, `uuid` for everything a client sees or sends. Models use `App\Core\Traits\HasUuid` (route key = `uuid`, serialized `id` = uuid); form requests declare `uuidReferences()` so related-record fields arrive as uuids and are stored as integer ids. An integer where a uuid is expected is a 404 (route) or 422 (body). JWT `sub` and audit/notification payloads still hold integer ids internally.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`, `docs/API_CONVENTIONS.md`, `.kiro/steering/api-surfaces.md`, `.kiro/steering/crud-conventions.md`, `.kiro/steering/business-logic.md`
- **Acceptance impact:** Every endpoint contract changes (ids are strings); docs of future features show uuids

### 2026-10-20 — TD-18: Service tree replaces option tables
- **Status:** Accepted
- **Decision:** Options are addon nodes of the service tree (`services.parent_id` + `type`). Required/single-choice rules sit on the parent (`children_required`, `children_selection`). Providers price every priced node in `provider_services`, so the order total comes from that one table. A subservice is bookable only when she has priced all its active addons (`v_bookable_provider_services`). The tree shape is enforced by a trigger (parent one type up, same category); depth is capped at 3.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected features:** ADMIN catalog, PROV service pricing, CUST booking  **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`, `.kiro/steering/business-logic.md`
- **Acceptance impact:** BR-05 (required options) and BR-06 (priced options) are checked against addon children; the BRD gains an extra "service" grouping level above the subservice

### 2026-10-20 — TD-17: Registration consent flags on users
- **Status:** Accepted
- **Decision:** `users.gender` is removed and replaced by boolean columns `guarantees_female_gender`, `accept_policies`, `accept_terms` (default false). This alters a base table, an explicit exception to TD-13. The profile-table timestamps (`female_declared_at`, `terms_accepted_at`, `privacy_accepted_at`) are kept for the per-app record.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`
- **Acceptance impact:** BR-01 — registration must send all three flags as true; the `gender` field is gone from profile responses and update requests

### 2026-10-20 — D3, DEC-26: Response countdowns are dashboard settings
- **Status:** DEC-26 Open → Partly resolved (countdown); D3 default made a named setting
- **Decision:** Two `int` settings in `app_configs` (group `response_timers`, seeded by `AppConfigSeeder`, edited via the admin settings endpoint): `provider_instant_response_seconds` = 20 (provider accept window for paid instant orders, D3) and `driver_offer_response_seconds` = 12 (driver offer countdown). Code reads them when it sets `orders.provider_response_due_at` and the offer expiry; never hard-coded.
- **Decided by:** Project owner  **Source:** follow-up to the screen review
- **Affected features:** CUST-012, PROV-014/015, DRIV-008/009, ADMIN-025
- **Affected docs:** `admin/settings.md`, `provider/orders.md`, `delivery/trip-*.md` (when written); guide §13 / §17 (SCR-27, SCR-30)
- **Acceptance impact:** AC-PROV-014 / AC-DRIV-009 use the setting values, not constants

### 2026-10-20 — DEC-23…DEC-27: Screen review against the BRD
- **Status:** DEC-23 Partly resolved · DEC-24 Resolved · DEC-25, DEC-26, DEC-27 Open · DEC-11 note added · screen additions adopted
- **Decision:** 125 prototype screens compared with BRD v1.3; register in Project Guide §17 (`SCR-01`…`SCR-34`). Screens that contradict D1 (package booked directly with a people counter; 30/60/90 validity), D4 (staff under establishments), DEC-15 (4-digit OTP) and DEC-10 (wallet top-up notification) are prototype errors — **the decisions stay**. Cancellation keeps the BRD policy (DEC-24). "Verified" follows verification status in customer/driver apps; the provider app shows her plan via `is_subscribed` + plan mini resource (DEC-23). UI-only screen additions are adopted; rule-changing ones are open (DEC-11, DEC-25…27) and the BRD applies until decided.
- **Decided by:** Project owner  **Source:** screen review session, `docs/Joshen_screens_by_feature/`
- **Affected features:** CUST-002/003/005–010/012–024, PROV-001/002/006–008/011–017/019/021–026, DRIV-001/002/005/006/008–010/017–019, ADMIN-005/011/018
- **Affected docs:** `brd/BRD-Khadami-Project-Guide-en.md` + `-ar.md` (guide 1.1, §13, §17, per-feature notes); feature docs for the IDs above when written
- **Acceptance impact:** AC-CUST-020 / AC-PROV-016 unchanged (BRD policy kept); provider auth resource gains `is_subscribed` + plan (AC-PROV-007)

### 2026-10-20 — TD-01, DEC-15, DEC-17, TD-16: Auth, languages, chat
- **Status:** TD-01 Proposed → Accepted · DEC-15 Partly resolved → Resolved · DEC-17 Partly resolved → Resolved · TD-16 Accepted
- **Decision:** One phone can hold customer, provider, and driver profiles; tokens are per app. App users have no password and log in with phone + SMS OTP; only admins use a password. All apps ship in Arabic and English. Order chat is deferred to production.
- **Decided by:** Project owner  **Source:** setup session answers
- **Affected features:** CUST-002/003, PROV-001/005, DRIV-001/004, ADMIN-001, CUST-018, PROV-021
- **Affected docs:** `client/auth.md`, `provider/auth.md`, `delivery/auth.md` (not written yet), surface READMEs, `PROJECT_BRAIN.md`
- **Acceptance impact:** AC-CUST-003 (forgotten-password flow) no longer applies to app users — login is OTP-only; AC-CUST-018 / AC-PROV-021 deferred

### 2026-10-20 — TD-15: Banners = Slider; shipping zones removed
- **Status:** Accepted
- **Decision:** BRD banners are the base `Slider` / `sliders`. Shipping zones (not in BRD or screens) are removed from code, database, and schema.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected features:** ADMIN-023 (banners), service areas
- **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`, `PROJECT_BRAIN.md`, `.kiro/steering/business-logic.md`, `.kiro/steering/architecture.md`, `docs/postman-collection.json`
- **Acceptance impact:** None (admin `shipping-zones` endpoints were never part of the BRD)

### 2026-10-20 — TD-14: Files through the Media module
- **Status:** Accepted
- **Decision:** Schema tables keep no file URLs; every file is a `media` row in a named collection declared in the model's `$media_keys`.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`, `PROJECT_BRAIN.md`
- **Acceptance impact:** None yet (no feature built on these tables)

### 2026-10-20 — TD-13: Base tables used without changes
- **Status:** Accepted
- **Decision:** The schema copies the base identity, RBAC, and geography tables exactly and points every domain FK at them; base migrations are not changed. Consent timestamps move to the profile tables; push-token app info moves to `device_apps`.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`, `PROJECT_BRAIN.md`
- **Acceptance impact:** BR-01 consent is stored per profile (customer / provider / driver registration)

### 2026-10-20 — TD-12: Translation tables
- **Status:** Accepted
- **Decision:** Every translatable schema table gets an `{entity}_translations` table instead of `name_ar` / `name_en` columns; Arabic row required, English optional. Banners, category/service descriptions, and featured-plan descriptions/features are included as admin-managed display text. Order snapshots keep the Arabic name.
- **Decided by:** Project owner  **Source:** schema review session
- **Affected docs:** `docs/database/db-schema.sql`, `docs/database/README.md`, `docs/API_CONVENTIONS.md`, `docs/admin/README.md`, `.kiro/steering/api-surfaces.md`, `PROJECT_BRAIN.md`
- **Acceptance impact:** None yet (no feature built on these tables)

### 2026-10-20 — TD-08…TD-11: Schema adoption, test DB, product name
- **Status:** Accepted
- **Decision:** Mixed adoption of `db-schema.sql` (TD-08); MySQL test database (TD-09); product name Joshen (TD-10); triggers as safety net (TD-11). TD-01 proposal updated for the schema's multi-profile users; TD-05 money precision changed to `DECIMAL(12,2)`.
- **Decided by:** Project owner  **Source:** setup session after adding `docs/database/db-schema.sql`
- **Affected docs:** `.kiro/steering/*`, `docs/database/README.md`, `PROJECT_BRAIN.md`, templates

### 2026-10-20 — D1–D4 and schema answers to DEC-01, 02, 04–15, 17, 19, 21, 22
- **Status:** see table
- **Decision:** Client-confirmed decisions D1–D4 and design additions adopted from the `db-schema.sql` header; DEC rows updated with what the schema fixes.
- **Decided by:** Client (D1–D4)  **Source:** `docs/database/db-schema.sql` header
- **Affected features:** booking, packages, payments, wallet, ratings, support, notifications
- **Acceptance impact:** AC-CUST-012, AC-PROV-012, AC-PROV-014, AC-PROV-011, AC-ADMIN-011, AC-CUST-020 must follow D1/D3 and the cancellation rules

### 2026-10-20 — DEC-16: Platforms
- **Status:** Open → Resolved
- **Decision:** Flutter for the customer, provider, and driver apps; React JS for the admin dashboard; Laravel API backend.
- **Decided by:** Project owner  **Source:** project setup instruction

### 2026-10-20 — TD-04…TD-07: Project documentation and API conventions
- **Status:** Accepted
- **Decided by:** Engineering (project setup)  **Source:** steering setup
