# Bug register — 2026-11-10

Severity: P0 blocks all use, P1 release blocker, P2 must fix soon, P3 minor. Layers: BACKEND, REACT (the dashboard is actually **Vue 3**, see report), INTEGRATION, DATABASE, INFRASTRUCTURE, REALTIME, UNKNOWN. Nothing here was fixed (test-only run).

| ID | Sev | Layer | Title | Status |
|---|---|---|---|---|
| QA-01 | P1 | INFRASTRUCTURE | Hosted Laravel Telescope reachable without login | OPEN: needs deploy |
| QA-02 | P1 | INFRASTRUCTURE | API served over plain HTTP, no HTTPS redirect, no HSTS | OPEN: Ops |
| QA-03 | P2 | INTEGRATION | Admin payloads name the customer by profile id; /admin/users/{id} only accepts the user id | FIXED in code (not committed) |
| QA-05 | P3 | DATABASE | Seeded provider Sara cannot take instant bookings (terms never acknowledged) | FIXED in code (not committed) |
| QA-07 | P3 | BACKEND | Failed payment answers 200 with the generic message 'Data updated successfully' | FIXED in code (not committed) |
| QA-08 | P3 | INTEGRATION | Two formats for phone_code | DOCUMENTED (by design, not reshaped) |
| QA-09 | P3 | BACKEND | Package purchases stored as booking_type=scheduled with scheduled_at NULL | OPEN: owner decision |
| QA-10 | P3 | INFRASTRUCTURE | CORS allows every origin | FIXED in code (not committed), needs config on each host |
| QA-NEW-01 | P3 | BACKEND | Arabic validation messages leak English field names and one hard-coded English sentence | FIXED in code (not committed) |
| QA-NEW-03 | P3 | REACT | Delete confirmation says "delete this file" for an FAQ | OPEN: dashboard wording |
| QA-NEW-04 | P2 | INFRASTRUCTURE | Hosted dashboard shows blank pages after a redeploy (stale cached build, missing chunks answered 200 text/html) | OPEN: Ops / dashboard build |
| QA-NEW-05 | P3 | REACT | Admins add form shows the raw key "LABELS.role_id مطلوب" | OPEN: dashboard |
| QA-NEW-06 | P3 | DATABASE | Seed/factory users get `phone_code` `966` (no plus); real registrations get `+966` | OPEN: seed |
| QA-NEW-07 | P2 | BACKEND | HTTP 500 on `POST /{provider,delivery}/profile/change-requests` when `change_type` is an array/object | **FIXED in code** |
| QA-NEW-08 | P2 | BACKEND | Raw keys / English text shown in Arabic: 6 missing translation keys, 8 admin enum labels, 2 hard-coded messages | **FIXED in code** |
| QA-NEW-09 | P3 | BACKEND | 31 more request fields without an Arabic label (messages like "حقل is visible مطلوب") | **FIXED in code** |
| QA-NEW-02 | P2 | INFRASTRUCTURE | Hosted dashboard is very slow: list pages take 7-31 s to settle; first render after login stayed blank for more than 10 s | OPEN: Ops / not a code defect |

---

## QA-01 — P1 — INFRASTRUCTURE — Hosted Laravel Telescope reachable without login

- **Where:** GET https://joshen-back.elkady.aait-d.com/telescope
- **Reproduce:** `curl -s -o /dev/null -w '%{http_code}' https://joshen-back.elkady.aait-d.com/telescope`
- **Expected:** 401/403/404 for the public
- **Actual:** HTTP 200 (re-observed 2026-11-10 ~17:20; 3.5 s, earlier call 20-30 s)
- **Root cause / hypothesis:** Commit bd2727f closed it (Telescope gated outside `local`), but the later commit 822ad43 ("removing the telescope") reverted that: `OPEN_ENVIRONMENTS` is `['local', 'dev']` again and `config/telescope.php` defaults `enabled` to `true`. The hosted server runs as `dev`, so it is open by code, not by a missed deploy. Re-checked after the 2026-11-11 deploy: still 200.
- **Fix and regression test:** Deploy the branch; re-test; add a feature test that /telescope is 404/403 for a guest when APP_ENV=dev.
- **Status:** **OPEN: regressed in code by 822ad43, still 200 on the hosted server after the 2026-11-11 deploy.** To close: restore the gate from bd2727f (guests only on `local`; `TELESCOPE_ENABLED` default off outside local) or set `TELESCOPE_ENABLED=false` in the server `.env` and `php artisan optimize:clear`; or put the path behind an IP allow-list. Owner choice, since 822ad43 looks deliberate.

## QA-02 — P1 — INFRASTRUCTURE — API served over plain HTTP, no HTTPS redirect, no HSTS

- **Where:** GET http://joshen-back.elkady.aait-d.com/api/v1/health
- **Reproduce:** `curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://joshen-back.elkady.aait-d.com/api/v1/health`
- **Expected:** 301 to https + Strict-Transport-Security
- **Actual:** HTTP 200, no redirect, no HSTS header on https responses (response headers carried only Access-Control-*)
- **Root cause / hypothesis:** Commit bd2727f added `EnforceHttps` (`FORCE_HTTPS`) but 822ad43 deleted the middleware, the config key and the `.env.example` note, so the application no longer redirects or sends HSTS. Re-checked after the 2026-11-11 deploy: HTTP still 200, no HSTS.
- **Fix and regression test:** Deploy, then assert redirect and HSTS in a smoke check.
- **Status:** **OPEN: regressed in code by 822ad43; still plain HTTP after the 2026-11-11 deploy.** To close: restore `EnforceHttps` and set `FORCE_HTTPS=true` (mind the proxy-loop warning in the old config comment), or add the 301 + HSTS in the Apache vhost. Owner/Ops choice.

## QA-03 — P2 — INTEGRATION — Admin payloads name the customer by profile id; /admin/users/{id} only accepts the user id

- **Where:** GET /admin/orders (customer.{id,full_name}); dashboard link /users/{customer.id}
- **Reproduce:** `GET admin/orders?per_page=1 -> customer.id; GET admin/users/{that id}`
- **Expected:** 200 with the customer
- **Actual:** API: 404 (local joshen_qa). Hosted dashboard Orders page renders the link as /users/01a1c104-e6d5-... (same profile-id pattern as previous QA); the 404 itself was verified through the API, not by clicking it on the hosted dashboard
- **Root cause / hypothesis:** Admin order resource emits customer profile uuid; admin/users resolves user uuid.
- **Fix and regression test:** Add additive customer.user_id (do not change id: integrated clients); dashboard uses it. Regression: order payload customer id resolves at admin/users.
- **Status:** **FIXED in code (not committed).** `customer.user_id` added to the order list and details, payments (+ customer filter list), conversations, cancellations, refunds and reviews. Verified live on a fresh local seed: every one resolves at `GET /admin/users/{user_id}` (200); `customer.id` unchanged. Tests: `test_ac_admin_013_customer_block_carries_a_user_id_that_resolves` + 3 updated shape assertions. Docs: admin/orders 2.5.0, payments 1.2.0, cancellations-refunds 2.1.0, ratings 1.1.0. **Remaining:** deploy, and the dashboard must link `/users/{customer.user_id}`.

## QA-05 — P3 — DATABASE — Seeded provider Sara cannot take instant bookings (terms never acknowledged)

- **Where:** DB joshen_qa
- **Reproduce:** `select p.instant_terms_ack_at from providers p join users u on u.id=p.user_id where u.phone='500001102'`
- **Expected:** non-null so the D3 flow runs on seeded data
- **Actual:** NULL
- **Root cause / hypothesis:** JoshenDemoSeeder does not set instant_terms_ack_at (not read in this session; behaviour matches previous QA).
- **Fix and regression test:** Seed the acknowledgement; note in qa-demo-accounts.md that windows are relative to seeding time.
- **Status:** **FIXED in code (not committed).** `CycleScenarioSeeder` now acknowledges Sara's instant terms; live: `can_receive_instant: true`. Test assertion added to `CycleScenarioSeedTest`. A server seeded earlier needs a re-seed. The 6-hour window behaviour and finder default stay as documented in qa-demo-accounts.md.

## QA-07 — P3 — BACKEND — Failed payment answers 200 with the generic message 'Data updated successfully'

- **Where:** POST /client/orders/{id}/pay {method:card, simulate_failure:true}
- **Reproduce:** `local joshen_qa, order JSH-20005`
- **Expected:** specific failure message
- **Actual:** HTTP 200, message generic, data.payment_status.value=failed
- **Root cause / hypothesis:** Generic default write message; the 200 itself is documented.
- **Fix and regression test:** Return a payment-specific message; apps branch on payment_status.
- **Status:** **FIXED in code (not committed).** A declined payment still answers 200 with the order, but `message` is now "فشلت عملية الدفع..." (live verified; the retry that succeeds keeps the normal message). Test added in `BookingQueryTest`; docs client/payment 1.1.1.

## QA-08 — P3 — INTEGRATION — Two formats for phone_code

- **Where:** GET /admin/users vs GET /admin/countries
- **Reproduce:** `compare phone_code`
- **Expected:** one format
- **Actual:** users '+966'; countries '20','966'
- **Root cause / hypothesis:** Inconsistent contract; dashboard prepends '+' (previous QA saw '++966' in the UI; not re-seen visually this session).
- **Fix and regression test:** Document one format; dashboard normalises.
- **Status:** **DOCUMENTED (by design, not reshaped).** Changing `20` to `+20` would break integrated clients (project rule: additive only). The two formats and the client rule are in API_CONVENTIONS §4 (4.11.0) and TD-185. Nothing left for the backend unless the owner approves a versioned change.

## QA-09 — P3 — BACKEND — Package purchases stored as booking_type=scheduled with scheduled_at NULL

- **Where:** DB joshen_qa order JSH-20201
- **Reproduce:** `select booking_type,scheduled_at from orders where code='JSH-20201'`
- **Expected:** neutral label / documented rule
- **Actual:** scheduled | NULL
- **Root cause / hypothesis:** PurchasePackageAction writes 'scheduled' for every purchase (per previous QA; code not re-read).
- **Fix and regression test:** Product owner decides the label; dashboard shows neutral label for order_kind=package_purchase.
- **Status:** **OPEN: owner decision.** The BRD defines no label for a package purchase. Working assumption recorded in TD-185 (clients read `order_kind`; null `scheduled_at` means no appointment). The stored value is unchanged.

## QA-10 — P3 — INFRASTRUCTURE — CORS allows every origin

- **Where:** OPTIONS https://joshen-back.elkady.aait-d.com/api/v1/health with Origin: https://evil.example
- **Reproduce:** `curl -I -X OPTIONS -H 'Origin: https://evil.example' ...`
- **Expected:** dashboard origin allow-list
- **Actual:** Access-Control-Allow-Origin: * (Max-Age not re-measured)
- **Root cause / hypothesis:** config/cors.php open origin.
- **Fix and regression test:** Allow-list dashboard origin; set max-age.
- **Status:** **FIXED in code (not committed), needs config on each host.** `config/cors.php` reads `CORS_ALLOWED_ORIGINS` (comma list) and `CORS_MAX_AGE` (default 3600). Live: with the dashboard origin set, only that origin is echoed and `Access-Control-Max-Age: 3600`. The default stays `*` so nothing breaks until the variable is set. **Set `CORS_ALLOWED_ORIGINS=https://joshen-dashboard.front.aait-d.com` on the hosted server.**

## QA-NEW-01 — P3 — BACKEND — Arabic validation messages leak English field names and one hard-coded English sentence

- **Where:** POST /admin/coupons with invalid bodies, Accept-Language ar (default)
- **Reproduce:** `empty body -> 'حقل Discount type مطلوب'; value=-5; ends_at before starts_at; value=150 percent`
- **Expected:** fully Arabic messages
- **Actual:** 'حقل Discount type مطلوب', 'قيمة Code مستخدمة من قبل', 'حقل ends at يجب أن يكون...'; 'The discount percentage cannot exceed 100.' in English
- **Root cause / hypothesis:** CouponRequest::attributes() uses __('Discount type'), __('Discount value'), __('Code') with no Arabic entries in lang/ar; ends_at/starts_at have no attribute labels; the percent>100 message is a literal English string. Other requests were not audited.
- **Fix and regression test:** Add Arabic lang entries and attributes for every request key; audit all Form Requests; add a localisation test.
- **Status:** **FIXED in code (not committed).** 269 Arabic field labels in `lang/ar/validation.php` (all four surfaces; the extraction script reports 0 unmapped keys), 76 labels/messages in `lang/ar.json`, label maps for the coupon, featured-plan, cancellation-rule and commission-rule forms. Live: coupon empty / 150% / negative / date-order bodies return Arabic only. Test: `test_ac_admin_015_arabic_validation_messages_contain_no_english_labels`.

## QA-NEW-02 — P2 — INFRASTRUCTURE — Hosted dashboard is very slow: list pages take 7-31 s to settle; first render after login stayed blank for more than 10 s

- **Where:** 41 dashboard routes, Playwright, desktop
- **Reproduce:** `scripted sweep of all sidebar routes, domcontentloaded + content + networkidle`
- **Expected:** pages usable in a few seconds
- **Actual:** per page 6.7-31.4 s (median ~11 s); slowest /delivery-fee-rules 31 s, / 25 s; Chrome reported 'Slow network detected'; API /health answered in 0.5 s
- **Root cause / hypothesis:** Not isolated. Candidates: hosting/network between test machine and server (QA-06 in the previous report saw the same stalls). Not proven which.
- **Fix and regression test:** Measure from a second network and from the server; check Apache keep-alive/compression/HTTP2; profile slow admin list endpoints.
- **Status:** **OPEN: Ops / not a code defect.** Page load time of the hosted environment; needs measurement on the server and network. Not changed.

## Not bugs (checked)

- Login with a wrong password returns 422 (dashboard network log). Confirm it is the documented contract; not classified as a defect.
- Public client/provider/delivery routes (categories, faqs, pages, countries, regions, home, search) answer without a token. They are documented guest endpoints.
- `admin/registration-requests/{type}/{id}` answers 404 before 401 with a placeholder `type` because the route constrains `type`; with a real type it is guarded (not re-tested with a real type).
- Scheduled orders refuse execution statuses while far in the future (`ORDER_EXECUTION_TOO_EARLY`): intended.

## QA-NEW-03 — P3 — REACT — Delete confirmation says "file" for every record type

- **Where:** hosted dashboard, FAQs list -> row menu -> حذف
- **Reproduce:** open /faqs, search `QA-` after creating a QA record, open the row menu, press Delete.
- **Expected:** a message that names the record ("delete this question?").
- **Actual:** "هل أنت متأكد من حذف هذا الملف؟ لا يمكن التراجع عن هذا الإجراء." (this file).
- **Root cause / hypothesis:** a generic confirm component with a fixed text; dashboard repository not available here, so not verified in code.
- **Fix and regression test:** pass the record type into the dialog text; add a UI test per module.
- **Status:** OPEN (dashboard team).

## QA-NEW-04 — P2 — INFRASTRUCTURE — Blank pages after a redeploy

- **Where:** https://joshen-dashboard.front.aait-d.com, any lazy route (seen on /orders and /trips).
- **Reproduce:** keep a browser session that loaded the previous build, let the site be redeployed (index.html changed at 08:39 UTC on 2026-11-11), open /orders.
- **Expected:** the page loads (or the app reloads itself).
- **Actual:** blank page, no content, console: "Failed to fetch dynamically imported module .../assets/show-all-BQXeCOzG.js" and "Expected a JavaScript module but the server responded with a MIME type of text/html". Clearing the browser cache made the same page load normally (8 rows).
- **Evidence:** `curl -I` on `/` shows no `Cache-Control`; a missing file (`/assets/does-not-exist-123.js`, `/assets/nothing.png`) answers **200 text/html** instead of 404; the new `index.html` references `index-5PELEc0a.js` and a different set of `show-all-*` chunks.
- **Root cause:** old entry file still in browsers + old hashed chunks deleted at deploy + SPA fallback turning the 404 into HTML. Server config, not application code in this repository.
- **Fix:** `Cache-Control: no-cache` on `index.html`, long immutable cache on hashed assets, keep the previous release's `/assets` for a few days, return real 404 for missing `/assets/*`, and in the Vue router reload the page on a chunk-load error.
- **Status:** OPEN (Ops + dashboard team).

## QA-NEW-05 — P3 — REACT — Raw translation key in the Admins form

- **Where:** /admins/form, press حفظ with an empty form.
- **Actual:** validation line "LABELS.role_id مطلوب" (the field label was not resolved); the other fields show Arabic labels.
- **Fix:** add the missing `LABELS.role_id` translation (both languages). **Status:** OPEN (dashboard).

## QA-NEW-06 — P3 — DATABASE — `phone_code` stored in two formats

- **Evidence:** `select phone_code,count(*) from users group by 1` on a fresh seed: `+966` 23, `966` 9. The nine `966` rows are factory-generated seed users (`UserFactory` default `'966'`); users created through registration are normalised to `+966` (the request rule is `in:+966` after normalisation).
- **Impact:** explains part of QA-08 (two formats). Production data is not affected unless factories are run there.
- **Fix:** set the factory default to `+966` (check tests that assert `966`). **Status:** OPEN (not changed to avoid test churn without an owner decision).

## QA-NEW-07 — P2 — BACKEND — 500 on a non-string `change_type`

- **Found by:** the hostile-payload sweep (4 requests of 696).
- **Reproduce:** `POST /provider/profile/change-requests` with `{"change_type":["x"],"changes":{"a":1}}` (or an object).
- **Actual (before):** HTTP 500 `INTERNAL_ERROR`; log: "Array to string conversion at SubmitChangeRequestRequest.php:30". `null`, numbers and unknown strings already answered 422.
- **Fix:** `change_type` is only cast when it is a string. Test: `test_a_non_string_change_type_is_a_validation_error_not_a_server_error`. Docs: provider and delivery change-request guides 1.0.1. **Status:** FIXED in code (uncommitted); re-run of the sweep: 0 server errors.

## QA-NEW-08 — P2 — BACKEND — Raw keys and English text under the Arabic locale

- **Found by:** the Codex static audit, each item re-verified by me (live or in code). Fixed: `booking.order_not_found` (was shown as the raw key; used in 5 places), `booking.payment_methods.*` and `booking.payment_failed_reason` (order notifications), `joshen.labels.service`, `The file must be of type: :type`, `Deleted`, `Unsupported verification type.`, `Resource not found.`; the 8 admin enum labels (FAQ, banner target app / placement / link type, static page slug / audience, onboarding slide app, user type) now follow the locale (`lang/{ar,en}/enums.php`). English responses are unchanged.
- **Tests:** `EnumLabelLocaleTest` (2 tests). Live check after the fix: unknown order -> "الطلب غير موجود."; pages slug "إقرار الأنثى"; banner placement "الرئيسية". **Status:** FIXED in code (uncommitted).
- **Corrected claims from the Codex report:** "474 parity differences" was wrong. Measured: 0 keys missing in Arabic for any of the 42 English files; 264 keys exist only in Arabic (the field-label map). Three Laravel framework files (`auth`, `pagination`, `passwords`) have no Arabic version, but no code reads them, so no user can see them.

## QA-NEW-09 — P3 — BACKEND — Request fields without an Arabic label

- 31 further fields (e.g. `is_visible`, `is_enabled`, `assignee_id`, `through_message_id`, `customer_package_item_id`, `apps.*`, `items.*`) were found by the sweep and by a broader extraction; all 31 are now in `lang/ar/validation.php` (300 labels in total). Live re-sweep: English-in-Arabic validation messages 97 -> 6, and the remaining 6 are an enum value (`users`) and echoed input keys (`changes.0`). **Status:** FIXED in code (uncommitted).

## Informational notes (not defects)

- The admin text fields store input such as `<script>alert(1)</script>' OR 1=1 --` as plain text (categories, FAQs, roles, feature flags, occasions). The API is a JSON API, so safety depends on the dashboard and apps escaping output; Vue escapes by default. Rendering of such a value was **not** tested in the dashboard.
- "Please wait before requesting another code" (429) on an immediate resend after register is the documented OTP cooldown.
- Validation messages for an invalid session token contain the technical word "UUID".
