Updated by Claude Code at the end of every session.
| Phase | Status | Notes |
|---|---|---|
| 0. Architecture pack | DONE | CLAUDE.md + docs prepared before coding |
| 1. Inspect repo & hosting | IN PROGRESS | Skeleton, CI, hosting-check tooling done. cPanel Tools screenshot + domain received. Waiting for hostcheck page screenshot + Resource Usage / Domains / PHP screenshots. |
| 2–7 | DONE | protocol, Laravel + DB, auth/tenancy, Super Admin, client admin, sessions |
| 8. Tablet PWA | DONE | kiosk PWA + TABLET_SETUP.md (App pinning) |
| 9. Photos | DONE | server + tablet camera (face detection, one photo) |
| 10, 12, 13, 14 | DONE | devices/tablets server side, Telegram, notifications/subscriptions, monitoring/backups |
| 11. ESP32 firmware | DONE (code) | one ESP32 per branch, up to 8 lamps; builds in CI; on-hardware test pending (owner’s bench, HARDWARE.md §5) |
| 15. Security testing | DONE | see below, TESTING.md, SECURITY_REVIEW.md |
| 16. Deployment | TOOLING READY | package + scripts + guides done; installing on the hosting waits for the owner |
Done:
apps/{api,web-admin,tablet,android-kiosk}, devices/esp32, packages/protocol, infrastructure/ (README per folder), .editorconfig, root README layout table.infrastructure/hosting-check/hostcheck.php: one-time, token-protected, self-deleting (first view / 24 h) capability report. build.sh injects random name + token. Integration tests tests/run.sh (PHP built-in server).ci.yml (gitleaks full history + hosting-check tests), hosting-check.yml (manual, produces the upload artifact).docs/HOSTING_CHECK_UZ.md. DEPLOYMENT.md §1 results table expanded with what each item affects.Remaining for Phase 1:
packages/protocol: 17 schemas (device + tablet + common), examples (valid/invalid), generated src/generated/protocol.ts, Ajv validator, OpenAPI validation test. CI job protocol.docs/API.md (all endpoints, conventions, error codes) + docs/openapi.yaml (3.1 skeleton referencing the protocol schemas).apps/api: Laravel 13.34 skeleton trimmed to API; migrations for every table in DATABASE.md with composite tenant FKs, CHECK constraints, double-booking guard (generated column on MySQL/MariaDB, partial unique index on PostgreSQL).tablets.token_hash (no Sanctum PATs), 3-column FKs (tenant, branch, table), ISO weekdays.TenantContext + TenantScope (fails closed) + BelongsToTenant; TenantAgnosticUserProvider for auth only.api: MySQL 8 / MariaDB 10.6 / PostgreSQL 13 on PHP 8.3 + MySQL on PHP 8.4; pint + composer audit; migrate → rollback → migrate.POST /api/auth/login|logout, GET /api/me, PUT /api/me/password, GET /api/auth/csrf; LoginService lockout (5 → 15 min), per-IP+login throttle, audit auth.*.ResolveUserTenant (before route binding), EnsureTenantUser, EnsureSuperAdmin, RequirePermission (perm:), EnsureActiveSubscription (402), EnsureIdempotency, AuthenticateTablet, AssignRequestId, SecurityHeaders.config/permissions.php (pinned by a test), ApiErrorRenderer + ErrorCode + lang/uz/errors.php, Redactor + Monolog processor, AuditLogger, rate limiters for every principal type, idempotency:prune scheduled hourly./storage/{path} file serving disabled (would have exposed private files).GET /api/admin/branches[/{branch}] (used by the isolation tests)./api/super/*: dashboard, tenants list (derived-status filter, search, usage counts), create (owner + optional first payment), update, suspend/activate/deactivate, payments (base = max(now, expiry)), extend, set expiry (new event type via migration 2026_10_02_000001), limits (LIMIT_CHANGED old/new), subscription history, owner password reset (temporary password shown once), payments list with sum, audit log with filters, platform settings. /api/admin/subscription shows status + payment instructions even when inactive.apps/web-admin: PWA shell, login, Super Admin pages (dashboard, clients list/create/detail with all actions, payments, audit, settings), client home with subscription status. 12 vitest tests (format, API client, login flow, guards)./admin/* deep links (SpaController). CI job web-admin uploads the web-admin-dist artifact.LimitGuard (tenant row FOR UPDATE, only active rows count, re-activation re-checked) for branches, tables, users (devices in Phase 10).WorkingHoursCalendar (branch timezone, shift belongs to start day).PriceCalculator: integer maths, round up to step), staff (owner/manager rules, last-owner protection, no self-deactivation, per-branch restriction via BranchAccess), tenant settings (privacy notice, retention, photo required, warning text/minutes, operators-can-view-photos), tenant audit log.COUNT(*) … FOR UPDATE → lock rows then count.SessionStateMachine, SessionService (prepare/start/cancel/confirmStarted/failStart/stop/complete/markPayment), SessionFinalizer + sessions:finalize (every minute), TableStatusResolver, DeviceCommandBus (queue only; delivery = Phase 10), ReportService (branch-timezone day/month ranges, played minutes, per-table, utilization).packages/protocol schemas in tests (opis/json-schema).public_id in a narrowed select (strict mode), MySQL JSON key reordering in tests.POST /api/tablet/sessions/{id}/photo (multipart photo, throttled, Idempotency-Key), ImageSanitizer (finfo + getimagesize, 320–2560 px, ≤ 2 MB, GD re-encode), PhotoStorage/LocalPrivateDisk, PhotoService (store/replace, view, delete, retention), photos:prune daily 01:30.GET/DELETE /api/admin/photos/{photo}; web-admin session page shows the photo on demand and can delete it./device/v1: register (secret + rate limit), pairing-status (token once), poll (heartbeat + commands, retry 6 s × 3, expiry), ack (idempotent, START→ACTIVE, ERROR→FAILED+STOP, STOP→COMPLETED), state, firmware download (published only, sha256 header).AuthenticateDevice (hashed token, tenant from device), DeviceRegistry (register/pair/move/unpair with LimitGuard), DeviceGateway, TabletRegistry + /api/tablet/register|pairing-status, admin devices/tablets endpoints, Super Admin firmware upload/publish (ESP32 magic byte check).DeviceSimulator speaks the real protocol; spec §61 end-to-end (pair → 10-min session → START → warning → internet loss → local OFF → server completes → resync), §43.4, §43.10, retries/expiry, poll ≤ 6 queries, tablet pairing, OTA download rules. 160 API tests on 3 DBs.TelegramClient/HttpTelegramClient, TelegramService (configure → getMe + setWebhook with secret, disable, link code, webhook handling, daily reports), NotificationService (dedupe_key UNIQUE, in-app + per-chat Telegram logs, delivery), DeviceMonitor (+ migration devices.offline_since), session-failed alert in SessionService::failStart.telegram:daily-reports (5 min), notifications:deliver (1 min), devices:monitor (1 min).SubscriptionMonitor + subscriptions:check (hourly): calendar-day reminders in the tenant timezone at the platform reminder_days (default 5/3/1/0), deduplicated per expiry instant (an extension restarts the cycle), subscription_expired + EXPIRED event + audit exactly once, mirrored as Super Admin (platform) notifications.HealthService + /health[/db|storage|messaging|backups] (public = status only; details with HEALTH_TOKEN or Super Admin), scheduler heartbeat (cache), Super Admin “Tizim holati” page.BackupCrypto (AES-256-GCM chunked, tamper/truncation detection) + BackupService (run/verify/restore/prune/photos) + commands backup:run [--photos], backup:verify, backup:restore --force, platform:prune; daily/weekly schedule; docs/BACKUP.md./up (replaced by /health).PHOTO_REQUIRED on start for every tenant; the photo_required tenant setting and its admin checkbox were removed (stale stored values ignored); the kiosk bootstrap always says photoRequired: true.photo_required=false cannot bypass it); tablet 17 (no skip/manual buttons, no upload without a face, retry reloads the detector).infrastructure/release/build.sh → bilyart-<version>.zip (33 MB): API without tests, vendor --no-dev --classmap-authoritative (git histories stripped), admin + tablet builds, deploy scripts, VERSION/COMMIT; boots and resolves all routes before zipping.activate.sh (cPanel Terminal): PHP ≥ 8.3 + extension check, first run creates shared/.env with generated APP_KEY/BACKUP_ENCRYPTION_KEY/HEALTH_TOKEN (chmod 600) and stops; then shared storage/.env links, maintenance around migrate --force, config/route/event cache, atomic current switch, keep 3 releases, /health probe. Failure before the switch keeps the old release live. rollback.sh.optimize failed on the missing views dir (now config/route/event cache only); PlatformSeeder used env() (breaks with cached config) → config/platform.php; added admin:create-super (hidden password, nothing in .env).infrastructure/deploy/tests/run.sh on every PR. Workflows: release.yml (manual, version), deploy.yml (manual, typed DEPLOY, secrets-gated SSH: scp + activate + health retry, environment production).DEVICE_REGISTRATION_SECRET GitHub secret, lamp power; then the real install (DEPLOY_UZ.md) and the §61 hardware scenario.devices/esp32: lib/core (session timer with hard cap, warning flasher, command handling with 16-id idempotency log and expiry, /state apply, boot recovery with provisional clock, poll/ack builders) — 17 host tests, fed with the protocol examples. src/ glue: setup portal (captive, WPA2 random password printed on first boot), registration + pairing with the code on the portal, pinned root CAs (Let’s Encrypt + Sectigo), /state → /poll → /ack loop in a network task, relay loop on the other core (never blocks), NVS session + 30 s checkpoint, task watchdog 30 s, BOOT button (3 s portal / 10 s factory reset), status LED, OTA with streaming SHA-256 check + rollback if the new image cannot reach the server in 10 min..bin + merged factory image + SHA256SUMS as esp32-firmware artifact; workflow_dispatch with a firmware version input for OTA releases. First CI build: flash 51 %, RAM 15 %.POST /api/super/firmware/{id}/rollout (OTA to idle paired devices, idempotent), uploads must carry the BLYFWVER: marker matching the typed version; firmware download sends Content-Length. Web admin: Super Admin → Proshivka page (upload / publish / rollout); multipart support in the API client.HARDWARE.md (parts, contactor sizing, wiring, electrician note), ESP32_FLASHING.md (Uzbek, browser-based first flash, pairing, OTA), DEVICE_PROTOCOL §6b.scripts/native-test.sh; CI runs pio.DEVICE_REGISTRATION_SECRET (ESP32_FLASHING.md §0).apps/tablet: React 19 + Vite 8 PWA under /tablet/ (fullscreen, landscape). Pairing screen (register → big 6-digit code → poll → token in IndexedDB), tables grid with live countdowns, duration quotes, confirm (price, pay-at-desk note, privacy notice), camera with MediaPipe face detection → one JPEG → upload → start → waits for the ESP32 ACK → countdown. Server-time offset, IndexedDB display cache, offline banner (no starts offline), closed/suspended screens, 60 s idle reset (cancels reservation), revoked tablet → re-pair, 5-minute warning (chime + TTS, local timing), wake lock, heartbeat./tablet/{path?} shell with its own CSP ('wasm-unsafe-eval', media-src blob:) and camera=(self); everything else keeps camera=(); .htaccess sets the same for static files.tablet-dist artifact.TABLET_SETUP.md (Uzbek, click-by-click), ARCHITECTURE tablet notes, SECURITY §9, READMEs.RouteSweepTest: walks the real route table — every admin route with a model parameter (all methods, nested own-parent/foreign-child) answers 404 to another tenant and tenant B’s rows stay byte-identical; guests get 401 on every admin/super/account route; client users 403 on every Super Admin route; tablet/device routes reject missing, browser-session and swapped credentials; a tablet cannot touch another tenant’s session. New parameter names without a fixture fail the test.admin rate limiter was unused → applied to admin + super groups; session cookie not Secure by default when the env variable is missing; no UI to change one’s own password.config/security.php, SecurityHeaders, SpaController), .htaccess HTTPS redirect / dotfile block / static-file headers, admin build ships .htaccess with CSP + cache rules (artifact now includes hidden files). Admin UI verified in Chromium under the CSP: zero violations.user:2fa-reset console command) + “Hisobim” page (password change, 2FA with otpauth link + local QR) + login code field.npm audit (prod deps, high+) in CI for web-admin and protocol. docs/TESTING.md maps spec §43 items 1–15 to tests; docs/SECURITY_REVIEW.md manual checklist.2026_10_05_000001_multi_channel_devices, verified up/down/up on MySQL, MariaDB, PostgreSQL): devices.channel_count (1..8), device belongs to a branch (new FK (tenant, branch) → branches); billiard_tables.device_id + device_channel with UNIQUE(device, channel) and FK (tenant, branch, device) → devices; game_sessions.device_channel, device_commands.channel; old 1:1 pairings became channel 1; backup order devices → tables.register.channelCount, poll.channels[], START/STOP/WARNING payload.channel, state.sessions[], ack channel.devices.manage), running games block rewiring/moving/unpairing; superseding scoped per channel; rollout “busy” if any of the device’s tables plays; offline alerts name all tables of the device.BAD_CHANNEL for channels the board lacks, poll/ack per channel, RELAY_CHANNELS build flag (default 4), relay pins 26/27/25/33/32/23/22/21, per-channel NVS + one checkpoint blob, legacy NVS session → channel 1. 19 host tests.MultiChannelDeviceTest: independent channels end to end, wiring rules, cross-tenant wiring refused by API and DB, running-session conflicts, unwired table, BAD_CHANNEL fails the start); 25 web-admin tests.font-src 'self'), new cue-ball logo and app icons, theme colours.index.css; dark felt sidebar with icons (lucide) and user card on desktop/iPad, felt top bar + slide-in menu on phones; restyled buttons, inputs, custom select chevron, cards, KPI tiles with icon chips, status pills with dots, empty state; dashboard table tiles with status stripe; split login with SVG billiard balls.nbx.itcode.uz (AutoSSL), CloudLinux PHP Selector 8.3 with pdo_pgsql/pgsql/mbstring/gd/zip/fileinfo/intl, PostgreSQL 13.23 (db itcode_nbx), release 1.0.0 built by Actions and activated (migrations ran), document root → billiard/current/apps/api/public, cron added.activate.sh made ~/billiard 700 → Apache could not read .htaccess (403). Fixed to 711 (+ deploy test asserts that others can traverse to public/). Hot-fix on the server: chmod 711 ~/billiard./ in APP_URL, copy-paste commands).admin:create-super), 2FA on; System health all green (pgsql, storage, cron, backups). Platform live at https://nbx.itcode.uz.active branches × price_per_branch × months, transfers to the card in the payment instructions and uploads the receipt (re-encoded to JPEG, private storage). The Super Admin checks the bank account and taps Tasdiqlash (optional amount/method correction) → SubscriptionService::recordPayment (+30 days per month) or Rad etish with a reason. Both sides get notifications; everything is audited.subscription_payment_requests, permission billing.manage (owner only), setting pricePerBranch (0 = off), routes under /api/admin/payment-requests (allowed while expired) and /api/super/payment-requests, admin pages Obuna (client) and To’lov so’rovlari (Super Admin), included in backups.activate.sh extension check fixed (SIGPIPE under pipefail gave a false “json missing”); product shown as NBX. Price per branch and payment instructions set by the owner.apps/android-kiosk: Kotlin WebView shell (no dependencies) as Device Owner via QR provisioning — Lock Task without system UI, launcher/auto-start, screen stays on, camera only for our origin, hidden 7-tap + admin PIN exit, “remove kiosk mode” to return the tablet to normal.build-kiosk.sh, key in GitHub secrets, certificate checked with apksigner) and shipped at /kiosk/ with provisioning.json; admin panel → Qurilmalar → Kiosk ilova shows the setup QR (Wi-Fi optional, password never leaves the page).devices.kind = CASH, same pairing/token/OTA), branch setting payment_mode (CASHIER as before / BILL_ACCEPTOR).cash_notes unique per device), the server starts the lamp when paid; extra money = extra time, cancel/3-minute timeout = play what was paid, nothing paid = cancelled. Unassigned money and paid-but-not-started games notify staff; Admin → Kassa (box totals, bills, resolve, collections with counted vs expected).device.cash-*), tablet pay screen, admin Kassa page, tests (API 223 on 3 DBs, tablet 19, admin 32) and docs done. Demo video recorded with a simulated box.itcode.uz → recorded in DEPLOYMENT.md §1 (SSH, Terminal, Cron, Git VC, MySQL and PostgreSQL, Node/Python apps, CloudLinux LVE).hosting-check artifact (1-day retention) is downloadable by any signed-in GitHub user. Mitigated: domain not included in the artifact, file self-deletes on first view and after 24 h, report contains no secrets.