Skip to main content

webscheduler-operations

WebScheduler operations — spark commands, testing rules, known debt, audit/hardening progress board, and the next hardening queue. Use this skill when running CLI commands, dispatching the notification queue, running audits, executing migrations, running the test suite, looking up known debt items, or checking the recent behavior-hardening log. Covers the core runtime commands (`php spark serve`, `php spark migrate -n App`, `notifications:dispatch-queue`, `notifications:test-reminder`, `notifications:backfill-templates`, `notifications:purge-delivery-logs`, `notifications:export-delivery-logs`, `notifications:repair-business-id`, `audit:provider-assignments`, `audit:reminder-pipeline`, `audit:purge-logs`, `npm run dev`, `npm run build`), PHPUnit testing rules (test DB only, never live schema), known active debt themes (`ProviderWorkingHoursTrait`, migration sequence gap near `2025-10-22-174832`, `@material/web` package), the verified-in-current-pass audit board, the remaining debt list, the next-hardening que

소스 정보

저장소
niloc95/xscheduler_ci4
최근 소스 활동
2026년 7월 24일 12:05
감지된 SKILL.md 언어
영어
스타
1
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
webscheduler-operations
description
WebScheduler operations — spark commands, testing rules, known debt, audit/hardening progress board, and the next hardening queue. Use this skill when running CLI commands, dispatching the notification queue, running audits, executing migrations, running the test suite, looking up known debt items, or checking the recent behavior-hardening log. Covers the core runtime commands (`php spark serve`, `php spark migrate -n App`, `notifications:dispatch-queue`, `notifications:test-reminder`, `notifications:backfill-templates`, `notifications:purge-delivery-logs`, `notifications:export-delivery-logs`, `notifications:repair-business-id`, `audit:provider-assignments`, `audit:reminder-pipeline`, `audit:purge-logs`, `npm run dev`, `npm run build`), PHPUnit testing rules (test DB only, never live schema), known active debt themes (`ProviderWorkingHoursTrait`, migration sequence gap near `2025-10-22-174832`, `@material/web` package), the verified-in-current-pass audit board, the remaining debt list, the next-hardening queue (split `scheduler-today-view.js`, reduce inline scripts, add CI coverage, fix mock expectations, business selector UI, npm uninstall `@material/web`, fix `ProviderWorkingHoursTrait::getBusinessHours()`), and the verification rule (each queue item ends with `npm run test:frontend:lifecycle`, `npm run test:unit:calendar`, `npm run build`). Also includes execution log items 14-19 (frontend decomposition tranches) and the recent behavior-hardening log items 20-36 with their status and notes. Trigger on any mention of "spark", "command", "audit", "test", "PHPUnit", "Vitest", "debt", "hardening", "queue dispatch", "migration", "npm run", or planning/tracking work.
# WebScheduler — Operations ## Core Runtime Commands ```bash php spark serve php spark migrate -n App php spark notifications:dispatch-queue # enqueue due reminders + dispatch queue php spark notifications:test-reminder <id> [businessId] [minutesUntilStart] php spark notifications:backfill-templates # backfill missing notification templates php spark notifications:purge-delivery-logs # purge old delivery log rows php spark notifications:export-delivery-logs # export delivery logs php spark notifications:repair-business-id # repair business_id on queue rows php spark api:key create --user <id> --name "<label>" # issue an external API key (token shown once) php spark api:key list # list issued keys (secrets never shown) php spark api:key revoke --id <keyId> # revoke a key php spark api:spec:validate # fail if docs/technical/openapi.yml drifts from the routes (CI gate) php spark audit:provider-assignments # audit provider-service-location mappings php spark audit:reminder-pipeline # audit reminder scheduling pipeline php spark audit:purge-logs # purge old audit log rows php spark api:key create --user <id> --name "<label>" [--scopes a,b] [--expires "+90 days"] php spark api:key list # all issued API keys + status php spark api:key revoke --id <keyId> # soft-revoke; effective next request npm run dev npm run build # Vite asset compilation only (no ZIP, no version bump) npm run docs:build # Assemble the standalone developer API docs site into dist/developer/ (upload to /developer) npm run package:local # Build deployment ZIP from current source (no Vite rebuild) npm run package # npm run build + npm run package:local npm run release:patch # Bump patch → build → ZIP → commit/tag → push → CI publishes GitHub Release npm run release:minor # Same, minor version bump npm run release:major # Same, major version bump ``` See `docs/deployment/UPDATER.md` for full command semantics and the in-app updater architecture. ## Testing Rules - Use **PHPUnit**. - Integration tests MUST run against test DB config only. Locally, run the DB suite with `vendor/bin/phpunit -c phpunit.mysql.xml.dist` (the default `phpunit.xml.dist` has no DB connection, so any service that touches `$model->db` at construction — e.g. `BookingMetricsService` — errors). - **Never run tests against default/live schema.** - Calendar selector unit tests are runner-aligned via `npm run test:unit:calendar` (Vitest). - **`.env.example` MUST end with a trailing newline.** CI builds the test/setup env with `cp .env.example .env` then `echo "KEY = val" >> .env`; without the trailing newline the first appended key concatenates onto the last line (`...false CI_ENVIRONMENT = testing`), and CI4's `DotEnv` rejects unquoted values containing spaces — `php spark` fatals and the PHPUnit + Setup-Wizard jobs never run. The two `ci-cd.yml` env steps now `printf '\n' >> .env` defensively, but keep the trailing newline in `.env.example`. --- ## Known Debt and Guardrails ### Active Debt Themes - Scheduler loading-path discipline must remain strict. - Frontend large-module decomposition is active and tracked in Execution Log items 14–19 (below). - Schema-compatibility in mixed local DB states must remain guarded. - ~~`ProviderWorkingHoursTrait::getBusinessHours()` calls `$settings->getValue()`~~ — **RESOLVED:** already uses `getByKeys()` correctly. - Composer packages added: `google/apiclient:^2.0` (Google Calendar), `stripe/stripe-php:^12.0` (Stripe). Keep these updated alongside any API version changes. - Integration test DB has a migration sequence gap near `2025-10-22-174832`. `BusinessHoursServiceIntegrationTest` and some journey tests cannot run against the test DB until the gap is repaired. - `@material/web` and related `@material/*` packages (9 total) still in `package.json` — unused. Remove with `npm uninstall @material/button @material/card @material/drawer @material/icon-button @material/list @material/textfield @material/top-app-bar @material/web @material-tailwind/html`. #### Localization debt (deferred from the 2026-07-23 currency/timezone audit) See `architecture` skill §8A for the owning contract. Resolved in that pass: the `setting('Localization.currency')` casing bug that pinned all money rendering to ZAR; `stored_timezone` being written as `'UTC'` for admin bookings (shifted every admin notification by the UTC offset); the Stripe currency dropdown that `PaymentService` ignored; PayFast being offered to non-ZAR businesses. Still outstanding: - **No locale-aware number formatting.** `format_currency()` hardcodes symbol-prefix and `.`/`,` separators. Wrong grouping for EUR/CHF, wrong precision for zero-decimal JPY. Needs `ext-intl` + `NumberFormatter`; `ext-intl` is not currently a composer requirement. - **No relocation migration for business hours.** `xs_business_hours.start_time` is naive wall-clock. Changing `localization.timezone` re-interprets existing rows in the new zone. A business genuinely relocating needs a guided migration (and a settings-screen warning), not a silent dropdown change. - **No `stored_timezone` backfill.** Appointments created by admins before the fix still hold `'UTC'`. Not backfilled because the booking channel of historical rows can't be reliably inferred. Their notifications remain offset until rebooked. - **`TimezoneService::businessTimezone()` process-static cache.** Long-running spark workers serve a stale timezone after a settings change until restarted. #### API surface debt (deferred from the 2026-07-23 token-auth pass) Resolved in that pass: the hardcoded `dev-local-token-123` / `dev:dev` shared secrets; the missing `xs_api_keys` table; token requests having no identity for RBAC or business scoping; the session short-circuit that let a browser cookie mask an invalid token; wildcard CORS; appointments/calendar/customers being unreachable by token; the `/api` vs `/api/v1` split. Still outstanding: - **Resource coverage is partial.** There is still no API CRUD for customers, services, providers, categories, business hours, or unavailabilities/blocked times. The token surface covers appointments, calendar, customer-appointment reads, locations writes, settings, integrations and users only. Adding a resource means a new controller under `app/Controllers/Api/` plus an entry in `$xsApiAuthenticatedRoutes` (`app/Config/Routes.php`) — both registrations come from that one array. - **Scopes are declared but not enforced per-route.** `xs_api_keys.scopes` is stored and readable via `ApiIdentity::hasScope()`, but no route asserts a scope; keys effectively inherit their bound user's role permissions. Wire scope checks in when the resource surface grows. - **No key-management UI.** Keys are CLI-only (`php spark api:key`). A Settings → Integrations panel would follow the existing `Api\V1\Integrations` + `integration-hub.js` pattern. - **Rate limiting is per-key, in-cache.** 120 req/min via the shared cache; it resets on cache flush and is not coordinated across app servers. - **Non-API surfaces still read the session directly.** `DatabaseBackup`, `DashboardPageService` and most of `UserManagement` use `session()->get('user_id')`. That is correct today because those routes are session-only, but any future move to `api_auth` must switch them to `current_user_id()` first. ### SaaS Multi-Tenancy Gap Register The following core tables lack `business_id` — system is intentionally single-tenant; these must be addressed before any multi-tenant deployment: | Table | Gap | |---|---| | `xs_appointments` | No `business_id` — all appointments implicitly belong to business 1 | | `xs_customers` | No `business_id` — customer records are not tenant-scoped | | `xs_services` | No `business_id` — services shared across all tenants | | `xs_locations` | No `business_id` — locations not tenant-scoped | | `xs_users` | No `business_id` — providers/staff not explicitly tenant-scoped | **Estimated effort:** Medium — adding `business_id` to all 5 tables + scoping every service query method + migration. Do not attempt incrementally; this requires a coordinated multi-table migration and query audit. --- ## Audit Progress Board ### Verified in Current Pass - Mandatory quality gates (§14.4) were rerun after tranche updates. - Targeted decomposition items 14–19 are now implemented and linked in §17.3. - `/profile` renders authoritative account data and audit activity through `ProfilePageService`, with SPA-safe tab/image behavior and integration coverage recorded in §17.4 item 20. - Appointment create customer search now respects the shared parsed-payload contract from `apiRequest()`, with hardening recorded in §17.4 item 21. - Frontend test + build validation is required after each decomposition tranche. - `current_business_id()` helper added to `permissions_helper.php`; `NotificationCenterService` and `NotificationSettingsService` switched off hardcoded constant — business context resolver documented in `architecture` skill (§17.4 item 22). - Reminder queue metadata (`reminder_offset_minutes`, `schedule_fingerprint`) added to `xs_notification_queue`; stale-reminder cancellation logic in `NotificationQueueDispatcher` — schema updated in `database` skill, queue fields in `notifications` skill (§17.4 item 23). - Two new resolver-focused unit tests passing: `testResolveBusinessIdUsesSessionBusinessContext` (`NotificationCenterServiceTest`) and `testNotificationSettingsServiceResolvesBusinessIdFromSessionContext` (`SettingsBoundaryServicesTest`). - Unified avatar/initials system implemented across all PHP views and JS modules — see `frontend` skill (§17.4 item 24). - Appointments toolbar mobile two-rail layout implemented — see `frontend` skill (§17.4 item 35). - Services section fully migrated from `layouts/dashboard` to `layouts/app` design system — see `frontend` skill (§17.4 item 36). ### Remaining Debt Outside Items 14–19 - ~~`ProviderWorkingHoursTrait` getValue bug~~ — **RESOLVED:** already uses `getByKeys()` correctly. - Test DB migration-sequence gap near `2025-10-22-174832` still blocks part of integration suite. - `testSettingsPageServiceBuildsDefaultAdminContextWhenSessionUserMissing` (`SettingsBoundaryServicesTest`) fails because the mock expectation `expects($this->once())` on `getByKeys` is violated when `LocalizationSettingsService` makes a second internal call. **Fix:** change to `expects($this->atLeastOnce())` or mock `LocalizationSettingsService` separately. Pre-existing. - `DashboardBoundaryServicesTest::testDashboardPageServiceBuildLandingViewData{IncludesAppointmentScope,PassesArrayProviderScope}` error at construction: they build `DashboardPageService` with a **mocked** `AppointmentModel` (whose `->db` is `null`), and `BookingMetricsService::__construct()` assigns `$this->appointments->db` to a non-nullable `BaseConnection $db` → `TypeError`. Fails before any tested logic runs. **Fix:** either inject a `BookingMetricsService` mock into the `DashboardPageService` constructor in those tests, or make `BookingMetricsService` resolve `db_connect()` when `$model->db` is null. Pre-existing; non-gating (the PHPUnit job is `continue-on-error`). - `@material/web` and 8 other `@material/*` packages still in `package.json`; confirmed unused. Run `npm uninstall @material/button @material/card @material/drawer @material/icon-button @material/list @material/textfield @material/top-app-bar @material/web @material-tailwind/html` - `public-booking.js` is 2154 lines — needs splitting into modules under `resources/js/modules/public-booking/` (booking-flow, manage-flow, render, api). Phase 6 of engineering review. - CSRF/fetch logic duplicated in 7 modules outside `core/api.js` — centralization needed (Phase 6). --- ## Next Hardening Queue ### Immediate Follow-up Work 1. Split `scheduler-today-view.js` into render/data helpers while preserving existing event contracts. 2. Continue reducing inline script usage in non-target legacy views (module-bootstrapped parity). 3. Add CI coverage for `npm run test:unit:calendar` in frontend validation workflow. 4. Fix pre-existing mock expectation count failure in `SettingsBoundaryServicesTest::testSettingsPageServiceBuildsDefaultAdminContextWhenSessionUserMissing`. 5. Add an admin-visible business selector / `?business_id=` query-param handoff to the Notifications and Settings pages — `current_business_id()` already reads `?business_id=` from GET params, so no PHP changes are needed; only a UI affordance is required. 6. Run `npm uninstall @material/web` to remove unused 485 kB package from `package.json` and `package-lock.json`. 7. Fix `ProviderWorkingHoursTrait::getBusinessHours()` — replace `$settings->getValue()` with `SettingModel::getByKeys(['booking.day_start','booking.day_end'])`. 8. Adopt `MutationResult` in existing mutation services (`AppointmentFormMutationService`, `UserManagementMutationService`) as a follow-on refactor — any new mutation service must return `MutationResult` from creation. 9. Fully green the `PHPUnit Tests` CI job (currently `continue-on-error`, non-gating; item 62 fixed its bootstrap fatals). Remaining, in order: (a) add `database.default.*` to the phpunit "Create test database config" step (migrate/seed use the `default` group, not `tests`); (b) repair the `2025-10-22-174832` migration-sequence gap so `migrate:refresh` completes; (c) fix the `DashboardBoundaryServicesTest` null-`db` mock construction errors; (d) restore coverage ≥40% or adjust the threshold. Only once all pass should `continue-on-error` be reconsidered. ### Verification Rule Each queue item MUST end with: ```bash npm run test:frontend:lifecycle npm run test:unit:calendar npm run build ``` --- ## Execution Log — Items 14–19 (Frontend Decomposition) Status legend: `done`, `in_progress`, `queued`. | Item | Scope | Status | Notes | |---|---|---|---| | 14 | `public-booking.js` decomposition | done | State/constants extracted to `resources/js/modules/public-booking/state.js`. | | 15 | `scheduler-core.js` decomposition | done | Calendar model URL building extracted to `resources/js/modules/scheduler/calendar-model-url.js`. | | 16 | Selector test runner alignment | done | Added `test:unit:calendar` script and Vitest dependency in `package.json`. | | 17 | `scheduler-day-view.js` decomposition | done | View constants/status metadata extracted to `scheduler-day-view-config.js`. | | 18 | `scheduler-week-view.js` decomposition | done | Day grid rendering extracted to `week-view-day-grid.js`. | | 19 | `app.js` decomposition | done | Shared UI helpers extracted to `resources/js/modules/app/shared-ui.js`. | --- ## Recent Behavior Hardening Log — Items 20–36 | Item | Scope | Status | Notes | |---|---|---|---| | 20 | `/profile` live view hardening | done | Added `ProfilePageService`, replaced placeholder profile cards/activity with authoritative data, moved tab/avatar behavior to `resources/js/modules/profile/profile-page.js`, and covered the flow with `tests/integration/ProfileJourneyTest.php`. | | 21 | Appointment customer-search payload contract | done | Appointment-form search accepts parsed JSON payloads from `resources/js/core/api.js`; shared `extractJSON()` accepts object payloads so sibling search surfaces do not fail on `.match()` calls. | | 22 | Business context resolver for notification services | done | Added `current_business_id()` to `permissions_helper.php`. `NotificationCenterService` and `NotificationSettingsService` resolve scope via `resolveBusinessId()` instead of `NotificationCatalog::BUSINESS_ID_DEFAULT`. Full contract in `architecture` skill. | | 23 | Reminder queue metadata + stale-reminder cancellation | done | Added `reminder_offset_minutes` and `schedule_fingerprint` columns to `xs_notification_queue` (migration `2026-04-21-150000`). Fingerprint cancellation logic in dispatcher. | | 24 | Unified avatar / initials system | done | Single PHP source of truth in `app/Helpers/app_helper.php`. Single JS source of truth in `resources/js/utils/avatar.js`. Globals `window.xsGetAvatarInitials` / `window.xsGetDisplayName` from `app.js` for inline view scripts. All avatar surfaces updated. Parity tests added for both PHP and JS. | | 25 | Appointment inline notification dispatch regression fix | done | Restored immediate queue dispatch in `AppointmentBookingService::queueNotifications()` via `NotificationQueueDispatcher::dispatch(NotificationCatalog::BUSINESS_ID_DEFAULT)`. Added queue idempotency pre-check hardening in `NotificationQueueService::enqueueRaw()`. | | 26 | Setup hosting compatibility mode + resilience hardening | done | Added shared-hosting-safe DB probe path (`app/Helpers/SetupCompatibilityHelper.php`) and compatibility UI module (`resources/js/setup-compat.js`) with smart suggestion banner. Setup controller accepts JSON `testConnection`, forwards `compatibility_mode`, returns sanitized payloads. Fixed setup-page CSRF header value. | | 27 | Reminder enqueue idempotency — re-enqueue after cancellation/failure | done | `NotificationQueueService::enqueueRaw()` previously blocked re-enqueue regardless of status. Rows with status `cancelled` or `failed` are now deleted before fresh insert; reminders that previously cancelled or failed retry on next cron run. In-flight (`queued`, `processing`) and delivered (`sent`) rows still dedupe normally. | | 28 | Reminder scan window — 48 h lookback for missed cron runs | done | `enqueueDueReminders()` filtered `start_at > now` which silently excluded all appointments with a reminder window that opened while cron was stopped. Scan now starts at `now - 48 hours` (`start_at >=`), allowing catch-up delivery. Idempotency keys prevent double-sending. Added per-channel `log_message('info',...)` diagnostics for skip reasons and `log_message('warning',...)` when enabled-channel list is empty. |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기