Skip to content

Build Status & Roadmap

Snapshot: 17 September 2026, except §7 which carries later changes · production in sync with main

The figures on this page were checked against production on 17 September. Anything shipped since then is listed in §7 under its own date; the rest has not been re-checked.

This page is the state of the build: what is deployed, what has been verified against production, what is genuinely broken or missing, and what has to happen before launch. It is deliberately separate from Scope & Roadmap, which is the authoritative contract for what belongs in MVP / Scale / Vision. That page decides scope; this one reports reality.

Open work: the owners' first revision round is tracked in Revision Round 1 — Todo. 47 of 48 items are closed; the one open is the regression gate, open for iOS only.

Everything below was checked against production or the repository on the date above, not recalled from memory. Where a claim rests on evidence, the evidence is named.


At a glance

Built, live and demo-ready — not yet launchable. Four things stand between this build and real customers, and three of them need the owners rather than code:

Blocker Needs
B1 No real payments — sandbox keys, online payment switched off Owners: a live merchant account and keys
B3 No imagery — 3 media rows platform-wide Owners: vendor photography and brand artwork
B4 Zero reviews, while "Top rated" is the default sort Owners: a default sort until reviews exist
B5 The regression flows have never run on an iPhone A current TestFlight build, and one pass on a phone

1. Live systems

Surface Where State
API api.yam3at.com Live. 323 routes; the 91 admin routes all sit behind admin authentication and a named permission.
Web dashboards app.yam3at.com Live. 64 pages — 30 admin, 17 vendor.
Mobile TestFlight only Version 1.0.0; the build number is set per upload. 49 screens. Android not on Play, by decision.
Docs docs.yam3at.com Live; this page and the revision round.
Infra Hetzner <production-host> 8 containers up: api, web, nginx, mysql, redis, meilisearch, scheduler, worker. Disk 20% (42G/226G).

Which commit TestFlight carries is not recorded here. The mobile fixes from 17 September — the sign-in keyboard wait and the booking screen's opening day — reach testers with the next run of the ios-release workflow.

Shared box. Five other projects run on this host. Never restart anything outside the yam3at compose project.

Backups. Daily at 02:00 to /var/backups/yam3at/, kept 14 days, plus the manual backups taken before each production cleanup. 22 files present; the newest is 17 September 02:00. Restore procedure: operations/runbook.md.

Scheduler. 10 recurring jobs: quote expiry (hourly), calendar sync (hourly), unpaid-checkout expiry (every 5 minutes), ticket reservation expiry (every 15), booking auto-complete, subscription processing, vendor licence checks, FX refresh, expired-token pruning and idempotency-key pruning (daily).

Security headers on the API, verified live: HSTS (2 years, includeSubDomains), X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin. APP_DEBUG=false, APP_ENV=production.

Timezone. The platform runs on Asia/Kuwait (UTC+3, no DST), with the MySQL connection pinned to +03:00 to match. Both halves are required: TIMESTAMP columns convert on read using the session timezone, so an app sending Kuwait wall-clock to a UTC session would store every new instant three hours wrong. Verified live on 17 September — PHP and MySQL report the same time, with +03:00. The decision and its trade-off are recorded in ADR-0001.


2. Integrations: wired, test, or fake

This table is the honest answer to "is it done?".

Integration Configured Actually usable
Payments MyFatoorah token + webhook secret; Tap key No — sandbox only. PAYMENT_DRIVER=myfatoorah, still pointed at apitest.myfatoorah.com. The Tap key is sk_test_… and TAP_WEBHOOK_SECRET is empty.
Email Resend Yes
SMS / OTP Twilio (+ Verify service) Yes
Push FCM (project, client email, private key) Yes — last seen delivering on 16 Sep, including a tap on a cold-started app opening the order
AI Anthropic, claude-sonnet-5, with daily + monthly budget caps Yes
Search Meilisearch via Scout Yes
Maps Google Maps key (mobile-restricted) Yes, gated behind MAPS_ENABLED at build time
Social login Google OAuth; Apple on iOS Yes — the Apple button is confirmed on iOS in the R1-46 screenshots
WhatsApp — No. No credentials exist; the sender is a no-op.

Checked on 17 September: every key above is present (values not read). Email, SMS and AI were not re-exercised that day.

Online payment is switched off on the server, not only hidden in the app. The online_payments_enabled setting is false, and while it is off the server refuses every online charge — order, plan, deposit, milestone, ticket, boost and paid subscription. The app and the web booking page read the same switch, so both show KNET and card as "قريبًا". Cash is unaffected. Turning it on is audited (R1-10).


3. Production data

The test data is gone. On 16 September the load-test set, the audit accounts, the QA leftovers and the two old service-app demo vendors were removed — 418 rows across 30 tables, after a backup and a dry run — and replaced with eight curated events providers. Details in Revision Round 1, items R1-37 to R1-42.

Table Rows (17 Sep) Was 27 Aug Note
users 17 55 4 real people, 1 admin, 2 deletion tombstones, 8 demo providers, 1 demo customer, 1 CI account for the iPhone screenshot run
vendors 8 15 all 8 are curated demo providers, all published
services / packages 8 / 16 14 / 29 one service each, two packages each
events 5 31 created by real users, plus one demo
RFQs / quotations 0 / 0 28 / 77
bookings 0 6
orders 0 3
reviews 0 0 still nothing reviewed — see B4
notifications 2 30 the "hello / world" test broadcast is gone
categories 31 31 8 carry stock. Empty ones are left out of category lists (R1-24), but the home screen still shows the Home, Cars, Beauty and Business areas — kept visible by decision on 17 Sep
media 3 3 still effectively no imagery — see B3

Platform settings in force: commission 5%, deposit 20%, payout hold 7 days, referral reward 2 KWD, loyalty 1 point/KWD redeemed at 100, free vendor RFQ limit 5, checkout hold 15 minutes, provider accept window 24 hours, super_app_enabled true, maintenance_mode false, customer and vendor registrations open, online_payments_enabled false.


4. Verified working, end to end

These were walked on a device against production, not asserted from code.

16–17 September — iPhone screen sizes, on simulators (R1-46). Codemagic ran the app on an iPhone SE (3rd gen), an iPhone 16 and an iPhone 16 Pro Max — 11 screens each, full-device captures with the status bar, keyboard and Dynamic Island drawn in. Safe areas, the floating navigation, the sticky booking and pay bars, the menu beside the Dynamic Island and the keyboard on sign-in and the address sheet pass on all three. The run found one defect, fixed: the booking screen opened on a fully booked day late in the evening. No Mac or iPhone was needed. This checks layout only; the flows are B5.

16 September — the owners' regression sheet, on Android. A release APK on a fresh install worked the 28-row sheet; 27 rows pass, and the full sheet with evidence is in Revision Round 1 §3.

  • Direct booking, end to end on the live catalogue, including a triple-tap on Confirm producing exactly one order, and an offline Confirm that correctly refused to show success and then succeeded on retry — one order, not two.
  • Provider declines / nobody accepts / abandoned checkout: all three resolve correctly, the customer is told in Arabic, and the held time slot goes back on sale. The 15-minute checkout sweep was caught being done by production's own scheduler, not by hand.
  • Conversations are linked to a booking and its provider (order_id and vendor_id on the row) — the feature the owners asked for.
  • The app lock (R1-34), with a simulated fingerprint: turning it on needs a successful scan, a cold launch locks, returning after a minute away re-locks over the private screen, a short switch away doesn't, and "sign out instead" really signs out. The Face ID prompt itself still needs an iPhone.
  • Every sort returns what it claims (R1-25), checked against temporary seeded data on production, then removed: price, rating, most booked and newest each gave their own, correct order.
  • Six defects were found by this run and fixed, none of which a static check could have caught: a notification claiming an order was confirmed when it was not, the keyboard covering the sign-in button, a header scrolling under the status bar on four screens, two screens drawing their own image fallback, provider chat unreachable at most statuses, and the account showing "Bookings: 0" beside a live order.

16 September — TestFlight launch. A TestFlight build opened to a permanent white screen: the app waited on a push-notification call before drawing anything, and on iOS that call never answered. Fixed so the app draws first; the owners confirmed the next build opens.

27 August.

  • Direct booking: browse → service → package → date → slot → address → payment → order confirmed (YM-2026-000003), then cancelled cleanly.
  • Events / RFQ: create event with date and guest count → RFQ to a vendor → vendor quotes → customer accepts → booking created with the deposit computed correctly at 20% (290.000 of 1450.000).
  • Planner: generated a checklist whose due dates are back-calculated from the event date — budget 120 days out, venue 90, guest count 60, invitations 30, final headcount 3.
  • Auth: registration, login, session surviving app restart from the Keychain, logout, role-based navigation (customer vs vendor).
  • Vendor panel: pending-state banner, RFQ inbox, quoting, analytics, pricing intelligence, gallery, calendar, blocked dates.
  • Localisation: Arabic renders correctly throughout, including notification titles and pricing advice, in both ar and en.

Tests, 25 October: 630 API tests pass (2 408 assertions), and 47 mobile tests. flutter analyze and the web tsc check are both clean, and analyze is a blocking CI step.


5. Launch blockers

Ranked. Nothing below is a nice-to-have.

B1 — No real payments

PAYMENT_DRIVER=myfatoorah, still on the test sandbox (apitest.myfatoorah.com). The Tap key is a test key. TAP_WEBHOOK_SECRET is empty, so Tap webhook signatures cannot be verified even once a live key lands. Online payment is switched off on the server until this is done, so nothing can be charged by accident.

To close: obtain the merchant account, swap to live keys and base URLs, populate the webhook secret, then turn on Online payments (KNET / card) in Admin → Settings. The app follows that switch; no new mobile build is needed for it.

~~B2 — Test data is live and public~~ — CLOSED 16 September 2026

418 rows across 30 tables removed after a database backup and a dry run: the 37 load-test users and their 12 [LT] vendors, both audit accounts, seven QA leftovers the audit had never named, the two old service-app demo vendors, and the "hello / world" test broadcast. Eight curated events providers replaced them. UserPurge derives the whole graph from a set of account ids — about 45 tables, children first, in one transaction — and both yam3at:load-test --wipe and yam3at:purge-users run through it, with a per-table count and --dry-run.

Deliberately kept: the four accounts on personal addresses. Removing a real person's account is not a cleanup decision to take unasked.

B3 — No imagery

Still 3 media rows platform-wide. Vendor cards, service heroes and galleries all fall back to the brand mark. The app no longer looks broken; it still looks empty — visible on every screen of the iPhone screenshots.

To close: real vendor photography, plus the mascot/brand artwork. Note the launcher icon problem in G4.

B4 — Zero reviews, and rating is the default sort

reviews is empty, so every listing shows "جديد / New" and "Top rated" — the default ordering on the browse screen — has nothing to rank by. The sorts themselves are correct: all four were verified on a device against temporary seeded data (R1-25). The demo content deliberately does not invent reviews; fabricated scores for providers nobody has booked would be fake social proof shown to real customers.

To close: the owners choose a default sort until reviews accumulate. Worth deciding at the same time: "Top rated" ranks one 5★ review above fifty 4.9★ ones — literally what it says, but a weighted rating would be fairer once reviews arrive.

B5 — The regression flows have never run on an iPhone

The sheet passes 27 of 28 rows on Android, and iOS layout now passes on three simulator sizes. But the rows are flows — booking, paying, push, Face ID — and none has run on an iPhone. This is what keeps the R1-47 gate open.

To close: run ios-release in Codemagic so TestFlight carries the current code, then one pass of the sheet on a phone.


6. Known gaps, not blocking

# Gap Impact
G2 WhatsApp channel is a no-op. No Business API credentials exist; Notifier skips the channel. Medium — WhatsApp is the dominant channel in Kuwait
G4 The Android launcher foreground is the full square icon — checked 17 Sep: 432×432, not one transparent pixel — so Android 12+ paints a yellow circle with a square inside it. The adaptive-icon XML is correct; only the asset is wrong. Low, but it is the first thing every Android user sees
G5 Android release signs with debug keys (buildTypes.release uses the debug signing config). Harmless while Android stays off Play, blocking the moment it doesn't. Deferred by decision
G8 Order numbers are sequential (YM-2026-000001, …002) and customer-visible, so they reveal order volume and invite enumeration — against the house rule. Medium; next round
G9 About 30 display paths still format money through a float — reports, dashboards, affiliate and AI-quote totals. None feeds a charge; the booking, order, coupon and wallet paths were fixed (R1-08). Low; next round
G10 Delete account and export data have not been run. Both screens exist; the account under test was the owners' demo account. Needs one pass on a throwaway account

Waiting on the owners

  • The partners' categories draft (مقترح فئات وخدمات منصة يمعات, 17 Sep): 17 event categories and 255 service types, 12 suggested for launch. Decided: nothing changes until the partners pick; service types will become filters inside each category; Home, Cars, Beauty and Business stay visible meanwhile. Compared category by category with the build — 9 of the 17 exist, 3 in part, 5 not at all; the platform has no service-type layer yet.

Raised in the revision round so they are decided, not so they grow it:

  • Onboarding prices are in the wrong format — "900 KD" and "2,150 KD" on the third slide, where the rest of the app shows "450.000 د.ك".
  • The app still opens by selling the super-app. Onboarding, the home domain row and the AI's quick suggestions offer home and car services, and after the cleanup only events categories have stock, so all of those lead to an empty state: a well-designed one, but empty.
  • The AI plans thinly — asked for a party for 80, it suggested catering alone.
  • The default sort before reviews exist, and weighted rating — see B4.
  • Signing in from the onboarding carousel returns to the carousel instead of into the app; the session is started correctly.

Closed on 27 August

  • G1 — mixed datetime types. event_milestones.scheduled_at was the only DATETIME among 210 datetime columns. Converted; production reports zero datetime columns.
  • G3 — scheduling tools under Analytics. Vendor calendar and blocked dates moved into the vendor panel beside pricing and gallery.
  • G6 — weak recommendations. Candidates were drawn from every visible vendor; now scoped to the events domain, ranked by the categories that suit the event type.
  • G7 — unaudited settings. Settings changes are now audited with before/after values.

7. Recently fixed

25 October — a full review of money, access, personal data and operations

Four reviews were run against the whole build. Everything below is fixed, tested and deployed; each fix has a test that fails on the code it replaced.

A disclosure, closed. This documentation site was publishing three real customers' personal email addresses, a street address and the orders they belonged to, with no login. Redacted at source, rebuilt, republished, and the superseded copies removed from the server. It remains in git history — removing it there is a separate decision. The production host's IP is no longer printed beside the root SSH commands here either.

Money. None of these could bite today, because online payment is off at the server; all of them become real the day it is switched on.

  • A cancelled order could never be refunded. Cancelling returns the wallet portion and stamps refunded_at, and the admin refund refused on that stamp alone — so a customer who paid online and whose order was then declined by the vendor had no path to their money. It now refunds what is still outstanding.
  • A cash order part-paid from wallet credit settled as though the provider had taken the whole amount at the door. One paid entirely from the wallet left them unpaid and billed commission on top. The settlement follows what they collect.
  • A replayed success webhook flipped a refunded payment back to paid, counting the money as collected twice. A refund is final.
  • payout_hold_days was a setting nothing read: a settlement was payable the moment the order completed, before the customer could dispute it.
  • A usage-limited coupon was checked before the transaction and incremented without re-checking. The use is now claimed in one conditional write.
  • Paying for a plan grouped its orders with nothing holding them still, so two taps built two groups and two charges. The orders are claimed under a lock.
  • A ticket reservation that expired gave its seat back but was still issued on a late callback — two valid QR codes for one seat, with no refund route.
  • The slot went back on sale while the customer was still on the payment gateway, so a success callback could confirm them into a time already taken.

Access. Signing in with Google or Apple matched an account by email alone, so someone who registered your address first kept your account when you later signed in with the provider. Suspension stopped only password login — the token already issued stayed valid and OTP or social login minted a fresh one. TOTP was enforced on the password path only. Vendor review notes (free text an admin writes on approval or rejection) and licence numbers were returned to anonymous callers.

Personal data. Deleting an account revoked the tokens and renamed the users row; everything else stayed, including the address snapshot on every order, which the vendor's inbox kept serving. Erasure now removes what existed only to serve that person and scrubs the personal data out of what the platform must keep. The export, which omitted orders and addresses, now answers the question it claims to.

Operations. Production could not write a single line to its error log for five weeks — and Laravel throws when it cannot, so a request that logged anything failed outright and the failure went unrecorded. Database dumps were world-readable on a box shared with five other projects. The health check tested only the database, so Redis dying left every signed-in request failing while the endpoint said ok; it now checks both, the deploy polls it and fails loudly, and the docs stop pointing monitoring at /up. The scheduler drifted past roughly eight minutes of each day. Docker logs are capped. Both deploy scripts were committed non-executable, which is why a pre-deploy backup silently failed on 17 September.

Still open, and why. These need a decision or something only the owners have:

Item Needs
Plan orders don't check slot availability A product decision: enforce slots at booking, or stop plan orders occupying slots. They currently occupy a slot they never reserved.
Civil IDs, IBANs and Google refresh tokens are plaintext Encrypting them needs a migration over existing rows
No consent record at signup; no unsubscribe on campaigns A product and legal decision
Terms, Privacy and Refund pages are live, headed "Draft" A lawyer
No off-site backup Somewhere to put it
No uptime alerting An external monitor on /api/v1/health
Android release builds sign with the debug key Generating a keystore is a one-way identity decision
Order numbers are sequential and customer-visible Next round (G8)
Event guests have no consent, erasure or retention A product decision

16–17 September

  • Browsing a domain had no way to narrow it. Tapping Events listed every hall, caterer, photographer and florist at once, with only a search box and a sort order. The browse screen now carries a row of the domain's own categories, each with how many services sit behind it under the current search, and "All" to come back out. The counts come from the same query as the list, so a chip never opens an empty screen, and a category with nothing behind it is not offered at all. The API side is live; the app side reaches testers with the next mobile build.

16–17 September

  • Deleting a category in admin silently stripped it from its services, vendors, quote requests and orders — every link is nullOnDelete. Admin now refuses while it's in use.
  • Vendors couldn't sign up into a category with no providers — the web form read the customer list, which hides empty categories. Sign-up now lists every active event category.
  • TestFlight opened to a white screen — the app awaited a push call before drawing; now it draws first and every native call at startup has a deadline.
  • The app lock never re-locked, and a verified email showed as unverified (R1-34).
  • Sorts returned ties in an unstable order, and the filters button didn't reset (R1-25).
  • A slow keyboard still covered the Login button — the form stopped waiting after 1.3 seconds; an emulator keyboard took 2.1 to start opening. Found by the iPhone screen-size walk.
  • The booking screen opened on a fully booked day in the evening — a greyed-out day, selected, over "no slots available". Found in the iPhone Pro Max screenshot.
  • iPhone screen-size checks without a Mac — a Codemagic workflow that walks three simulators and returns device screenshots, hardened over four runs.

27 August

Would have blocked real users

  • Saved addresses never loaded. Address.fromJson cast latitude with as num?; MySQL decimals serialize as strings, so no direct booking could be completed by anyone — and every retry silently created a duplicate address.
  • Event date and guest count did not exist in the mobile app, though the API stored both and the planner derives every checklist deadline from the date.
  • Event bookings were unreachable with super_app_enabled on, as it is in production.
  • Availability slots were three hours off for Kuwait.
  • Service ratings and booking counts were never computed, while catalog discovery sorted on exactly those columns.

Security — the session token moved from a plaintext cache file to the Android Keystore / iOS Keychain with a one-time migration; allowBackup disabled; the catalog no longer lists direct-booking services with no package.

Localisation — all 25+ notification types resolve per reader; vendor pricing advice is bilingual with Arabic plural forms; the network-error fallback behind ~20 screens is localised.

UX — sign-up rebuilt with a real password confirmation and an invite-code field; pending and rejected vendors see their status; unrated listings say "جديد / New".


8. Roadmap

Now — close the launch blockers

  1. Live payment merchant account; live keys, base URLs and webhook secret; then turn on online payments in Admin → Settings (B1).
  2. Vendor photography and brand artwork, including a correct adaptive-icon foreground (B3, G4).
  3. Decide the default sort before reviews exist (B4).
  4. Run ios-release, then the regression sheet on an iPhone (B5).
  5. The owners' decisions listed under "Waiting on the owners".

Next — first real supply and demand

  1. Onboard and verify the first real vendors; the approval queue and status banner are ready.
  2. WhatsApp Business API (G2) — the highest-leverage channel in this market.
  3. Non-sequential order numbers and the remaining float money formatting (G8, G9); delete-account and export run on a throwaway account (G10).
  4. Re-check recommendation quality once the catalogue is real.

Then — scale

  1. Android to Play, which forces real release signing (G5).
  2. Vendor subscription billing, once payments are live.
  3. Escrow milestones — the code path exists and is tested, but is unexercised without real payments.

9. How to re-run this audit

# deployed version + container health
ssh root@<production-host> 'cd /opt/yam3at && git rev-parse --short HEAD && \
  docker compose -f docker-compose.prod.yml ps'

# data snapshot
ssh root@<production-host> 'cd /opt/yam3at && docker compose -f docker-compose.prod.yml \
  exec -T api php artisan tinker --execute="echo DB::table(\"users\")->count();"'

# test suites
cd apps/api && php artisan test
cd apps/mobile && flutter test && flutter analyze

# iPhone screen sizes: start "Yam3at iOS screen sizes (simulators)" in Codemagic

Deployment procedure and rollback: DEPLOYMENT.md in the repository root, and operations/runbook.md.

Caution when scripting against production from Windows. Passing Arabic through a Git Bash curl -d string transcodes it to the ANSI codepage and stores ????. Write the JSON to a file with explicit UTF-8 and use curl --data-binary @file. Two records were corrupted this way during the audit and had to be repaired.