Plan and validation record
State on 2026-11-11, commit deployed on M4M64c. Each phase was closed only after its checks ran.
Phases
| Phase | Content | Validation performed |
|---|---|---|
| 1. Audit and design | Environment audit (ARCHITECTURE.md), schema, decisions (DECISIONS.md) | DNS, manifests, sibling repos, runtimes inspected |
| 2. Foundations | Schema + migration runner, passkeys, sessions, CSRF, settings, bootstrap | Migration applied on 4 databases; passkey enrolment, login, logout, recovery code in e2e |
| 3. Availability | Engine, weekly hours, exceptions, private events, agenda | 25 engine tests (DST spring/autumn, midnight, buffers, limits, zones) |
| 4. Bookings | Public pages, types, requests, approve / decline / propose, double-booking protection | 32 service tests incl. concurrency; e2e acceptance scenarios |
| 5. Communications | Outbox, Resend + SMTP transports, FR/EN templates, ICS, cancel / reschedule, reminders | Retries / dead / unconfigured tested; e2e delivers through real SMTP to a sink and checks ICS |
| 6. Integrations | ICS subscription (read-only), outgoing feed, stale policy | 9 tests (recurrence across DST, all-day, 304, failures, SSRF); e2e failure reporting |
| 7. Finishing | Mobile, accessibility, security headers, backup / restore, docs, HTTPS deployment | axe on every screen (desktop + mobile), backup → restore compared, production walkthrough |
Results of the checks (2026-11-11)
tsc --noEmit: clean.eslint: clean.- Vitest: 66 / 66 (real PostgreSQL 17).
- Playwright: 28 / 28 (14 scenarios × desktop Chrome and Pixel 7), production build.
- Backup → restore into an empty database: same row counts, exclusion constraint present; wrong passphrase refused.
- Production (https://calendar.spboucher.ai): health 200, TLS valid, security headers present, admin redirects to sign-in, admin API 401 without session; one real request submitted and withdrawn through the visitor link; web and worker restarted with PM2, data intact, health back in seconds; first encrypted backup written.
Acceptance tests of the brief
| # | Test | Where it is proven |
|---|---|---|
| 1 | Monday 9–12, block 10–11 → only valid slots | tests/engine.test.ts, tests/booking.test.ts, e2e/admin.spec.ts (through the API and the public slots) |
| 2 | Confirmed appointment → slot disappears at once | tests/booking.test.ts, e2e/admin.spec.ts |
| 3 | Two visitors, same slot → policy applied, one approval | tests/booking.test.ts (hold and no-hold, 6 concurrent approvals, 8 concurrent auto-confirmations, raw SQL), e2e/admin.spec.ts (two simultaneous approvals: 200 + 409) |
| 4 | Request is pending for the owner, never "confirmed" for the visitor | e2e/public.spec.ts |
| 5 | Approval: atomic re-check, real booking, notification, ICS | tests/booking.test.ts, e2e/admin.spec.ts |
| 6 | Decline / propose another time | tests/booking.test.ts, e2e/admin.spec.ts |
| 7 | DST, several zones, limits, midnight | tests/engine.test.ts, tests/external.test.ts, e2e/public.spec.ts (Paris visitor) |
| 8 | Cancel / reschedule, expired / revoked links | tests/booking.test.ts, e2e/admin.spec.ts |
| 9 | External calendar unavailable / stale, e-mail failure and retries | tests/booking.test.ts, tests/external.test.ts, e2e/admin.spec.ts |
| 10 | Double booking, IDOR, CSRF, spam, race, errors, restart | tests above + e2e/*.spec.ts (401 / 403 / 404, honeypot, forged token, cross-origin) + restart on production |
| 11 | Passkey flow and recovery | e2e/admin.spec.ts with Chromium's virtual authenticator (user verification on). Touch ID / Face ID on real hardware: not verified — to be done by the owner at first sign-in. |
| 12 | Responsive and accessibility with Playwright | every public and admin screen: axe WCAG 2.1 A/AA, no horizontal overflow, desktop + mobile |
Status — functional / not configured / blocked / not implemented
| Area | Status | Note |
|---|---|---|
| Public site: home, booking flow, time-zone conversion, FR/EN, light/dark | Functional | |
| Requests subject to approval; automatic confirmation per type | Functional | |
| Visitor link: follow, withdraw, cancel, reschedule, accept a proposal, download ICS | Functional | |
| Availability engine, weekly hours (default and per type), exceptions, vacations, recurring absences | Functional | |
| Double-booking protection (lock + re-check + exclusion constraint), idempotency | Functional | |
| Admin: dashboard, inbox, agenda (day / week / month / list), private events, drag-to-move (mouse) | Functional | Drag is disabled on touch screens (use "Déplacer"). |
| Admin: appointment types, contacts (history, export, erasure), settings, audit, sessions | Functional | |
| Passkeys (WebAuthn), recovery codes, shell recovery, revocable sessions, CSRF | Functional | Real-device biometrics to be confirmed by the owner. |
| ICS subscription of an external calendar (read-only free/busy), stale policy | Functional | No calendar connected yet. |
| Private ICS feed of confirmed appointments | Functional | Off until enabled in Settings → Integrations. |
| Encrypted daily backup, restore script | Functional | On the node only: no off-node copy yet. |
| Health endpoint, worker heartbeat, dashboard alerts | Functional | No external alerting (e-mail / push) when the site is down. |
| Transactional e-mail (acknowledgement, confirmation + ICS, decline, proposal, cancellation, reschedule, reminder, owner alerts) | Not configured | Code and both transports are tested; production has MAIL_TRANSPORT=none, so notifications are parked and shown as such. Needs a Resend key or SMTP credentials (OPERATIONS.md → E-mail). |
| Video link | Not configured | Each type's "location detail" is empty: type your meeting link / phone number in Types. |
| Public profile (headline, bio, photo), Reply-to address | Not configured | Defaults: name only. |
| Google Calendar / Microsoft 365 (OAuth), CalDAV, writing events to an external calendar | Not implemented | No OAuth client credentials were available; schema ready (D-10). |
| Automatic creation of video meetings | Not implemented | Manual link only (D-12). |
| Embeddable booking widget | Not implemented | Planned; today frame-ancestors 'none' forbids embedding. |
| Offline mode / service worker, push notifications | Not implemented | Installable (web manifest, icons) but online only. |
| Nonce-based CSP, CAPTCHA, several owners | Not implemented | See SECURITY.md (known limits). |
| Reminder e-mails | Functional, partly tested | Scheduling, cancellation and obsolescence are tested; a reminder actually sent hours later was not observed end to end. |
| Nothing | Blocked |
Next steps (not started)
- Configure the e-mail transport; send the test e-mail; re-queue parked notifications.
- Owner: enrol the passkey on a real device, add a second one, fill the public profile and the meeting link.
- Off-node copy of the encrypted backups (rsync to another node).
- Google / Microsoft connectors once OAuth clients exist; then optional write-back of confirmed appointments.
- Embeddable widget (
frame-ancestorsallow-list + compact layout).