Repository Public
1# Plan and validation record23State on 2026-11-11, commit deployed on M4M64c. Each phase was closed only after its checks ran.45## Phases67| Phase | Content | Validation performed |8|---|---|---|9| 1. Audit and design | Environment audit (ARCHITECTURE.md), schema, decisions (DECISIONS.md) | DNS, manifests, sibling repos, runtimes inspected |10| 2. Foundations | Schema + migration runner, passkeys, sessions, CSRF, settings, bootstrap | Migration applied on 4 databases; passkey enrolment, login, logout, recovery code in e2e |11| 3. Availability | Engine, weekly hours, exceptions, private events, agenda | 25 engine tests (DST spring/autumn, midnight, buffers, limits, zones) |12| 4. Bookings | Public pages, types, requests, approve / decline / propose, double-booking protection | 32 service tests incl. concurrency; e2e acceptance scenarios |13| 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 |14| 6. Integrations | ICS subscription (read-only), outgoing feed, stale policy | 9 tests (recurrence across DST, all-day, 304, failures, SSRF); e2e failure reporting |15| 7. Finishing | Mobile, accessibility, security headers, backup / restore, docs, HTTPS deployment | axe on every screen (desktop + mobile), backup → restore compared, production walkthrough |1617## Results of the checks (2026-11-11)1819- `tsc --noEmit`: clean. `eslint`: clean.20- Vitest: **66 / 66** (real PostgreSQL 17).21- Playwright: **28 / 28** (14 scenarios × desktop Chrome and Pixel 7), production build.22- Backup → restore into an empty database: same row counts, exclusion constraint present; wrong passphrase refused.23- Production (https://calendar.spboucher.ai): health 200, TLS valid, security headers present, admin redirects to24 sign-in, admin API 401 without session; one real request submitted and withdrawn through the visitor link;25 web and worker restarted with PM2, data intact, health back in seconds; first encrypted backup written.2627## Acceptance tests of the brief2829| # | Test | Where it is proven |30|---|---|---|31| 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) |32| 2 | Confirmed appointment → slot disappears at once | `tests/booking.test.ts`, `e2e/admin.spec.ts` |33| 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) |34| 4 | Request is pending for the owner, never "confirmed" for the visitor | `e2e/public.spec.ts` |35| 5 | Approval: atomic re-check, real booking, notification, ICS | `tests/booking.test.ts`, `e2e/admin.spec.ts` |36| 6 | Decline / propose another time | `tests/booking.test.ts`, `e2e/admin.spec.ts` |37| 7 | DST, several zones, limits, midnight | `tests/engine.test.ts`, `tests/external.test.ts`, `e2e/public.spec.ts` (Paris visitor) |38| 8 | Cancel / reschedule, expired / revoked links | `tests/booking.test.ts`, `e2e/admin.spec.ts` |39| 9 | External calendar unavailable / stale, e-mail failure and retries | `tests/booking.test.ts`, `tests/external.test.ts`, `e2e/admin.spec.ts` |40| 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 |41| 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.** |42| 12 | Responsive and accessibility with Playwright | every public and admin screen: axe WCAG 2.1 A/AA, no horizontal overflow, desktop + mobile |4344## Status — functional / not configured / blocked / not implemented4546| Area | Status | Note |47|---|---|---|48| Public site: home, booking flow, time-zone conversion, FR/EN, light/dark | **Functional** | |49| Requests subject to approval; automatic confirmation per type | **Functional** | |50| Visitor link: follow, withdraw, cancel, reschedule, accept a proposal, download ICS | **Functional** | |51| Availability engine, weekly hours (default and per type), exceptions, vacations, recurring absences | **Functional** | |52| Double-booking protection (lock + re-check + exclusion constraint), idempotency | **Functional** | |53| Admin: dashboard, inbox, agenda (day / week / month / list), private events, drag-to-move (mouse) | **Functional** | Drag is disabled on touch screens (use "Déplacer"). |54| Admin: appointment types, contacts (history, export, erasure), settings, audit, sessions | **Functional** | |55| Passkeys (WebAuthn), recovery codes, shell recovery, revocable sessions, CSRF | **Functional** | Real-device biometrics to be confirmed by the owner. |56| ICS subscription of an external calendar (read-only free/busy), stale policy | **Functional** | No calendar connected yet. |57| Private ICS feed of confirmed appointments | **Functional** | Off until enabled in Settings → Integrations. |58| Encrypted daily backup, restore script | **Functional** | On the node only: no off-node copy yet. |59| Health endpoint, worker heartbeat, dashboard alerts | **Functional** | No external alerting (e-mail / push) when the site is down. |60| **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). |61| Video link | **Not configured** | Each type's "location detail" is empty: type your meeting link / phone number in Types. |62| Public profile (headline, bio, photo), Reply-to address | **Not configured** | Defaults: name only. |63| Google Calendar / Microsoft 365 (OAuth), CalDAV, writing events to an external calendar | **Not implemented** | No OAuth client credentials were available; schema ready (D-10). |64| Automatic creation of video meetings | **Not implemented** | Manual link only (D-12). |65| Embeddable booking widget | **Not implemented** | Planned; today `frame-ancestors 'none'` forbids embedding. |66| Offline mode / service worker, push notifications | **Not implemented** | Installable (web manifest, icons) but online only. |67| Nonce-based CSP, CAPTCHA, several owners | **Not implemented** | See SECURITY.md (known limits). |68| Reminder e-mails | **Functional, partly tested** | Scheduling, cancellation and obsolescence are tested; a reminder actually sent hours later was not observed end to end. |69| Nothing | **Blocked** | |7071## Next steps (not started)72731. Configure the e-mail transport; send the test e-mail; re-queue parked notifications.742. Owner: enrol the passkey on a real device, add a second one, fill the public profile and the meeting link.753. Off-node copy of the encrypted backups (rsync to another node).764. Google / Microsoft connectors once OAuth clients exist; then optional write-back of confirmed appointments.775. Embeddable widget (`frame-ancestors` allow-list + compact layout).78