Milestone 3 — cancellation, rescheduling & notifications
- Milestone prompt: spec/slotbook/milestones/M3.md
- Tech constraints (all milestones): spec/slotbook/BRIEF.md
Process
CC-Opus4.8-i1 20/20 CC-Opus4.8-i2 20/20 18/20 before one fix-it CX-5.6-i1 20/20 18/20 before one fix-it CX-5.6-i2 20/20 16/20 before one fix-it
CC-Opus4.8-i1 10.6 CC-Opus4.8-i2 9.7 CX-5.6-i1 15.7 CX-5.6-i2 13.4
CC-Opus4.8-i1 106 CC-Opus4.8-i2 92 CX-5.6-i1 n/a CX-5.6-i2 n/a
CC-Opus4.8-i1 40.8K CC-Opus4.8-i2 37.4K CX-5.6-i1 55.7K CX-5.6-i2 54.4K
CC-Opus4.8-i1 9.25M CC-Opus4.8-i2 5.55M CX-5.6-i1 6.67M includes fix-it replay CX-5.6-i2 5.56M includes fix-it replay
CC-Opus4.8-i1 105 CC-Opus4.8-i2 90 CX-5.6-i1 52 CX-5.6-i2 41
CC-Opus4.8-i1 +1,129 CC-Opus4.8-i2 +1,070 CX-5.6-i1 +736 CX-5.6-i2 +882
CC-Opus4.8-i1 browser click-testing, API probes, typecheck, build CC-Opus4.8-i2 browser click-testing, typecheck, build CX-5.6-i1 API probes, ad-hoc self-test suite, typecheck, build CX-5.6-i2 API probes, browser checks (MCP), typecheck, build
CC-Opus4.8-i1 no (did at m1) CC-Opus4.8-i2 no (did at m1) CX-5.6-i1 no CX-5.6-i2 no
Task checks
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ CX-5.6-i1 ✓ CX-5.6-i2 ✓
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ CX-5.6-i1 ✓ CX-5.6-i2 ✓
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ CX-5.6-i1 ✓ CX-5.6-i2 ✓
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ CX-5.6-i1 ✓ CX-5.6-i2 ✓
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ 1 fix-it CX-5.6-i1 ✓ 1 fix-it CX-5.6-i2 ✓ 1 fix-it
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ CX-5.6-i1 ✓ CX-5.6-i2 ✓ 1 fix-it
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ CX-5.6-i1 ✓ CX-5.6-i2 ✓ 1 fix-it
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ 1 fix-it CX-5.6-i1 ✓ 1 fix-it CX-5.6-i2 ✓ 1 fix-it
CC-Opus4.8-i1 ✓ CC-Opus4.8-i2 ✓ CX-5.6-i1 ✓ CX-5.6-i2 ✓
Code metrics
CC-Opus4.8-i1 3,746 44 files CC-Opus4.8-i2 3,214 40 files CX-5.6-i1 2,102 34 files CX-5.6-i2 2,659 36 files
CC-Opus4.8-i1 0 CC-Opus4.8-i2 0 CX-5.6-i1 0 CX-5.6-i2 0
CC-Opus4.8-i1 44 CC-Opus4.8-i2 24 CX-5.6-i1 21 CX-5.6-i2 21
CC-Opus4.8-i1 1 CC-Opus4.8-i2 2 CX-5.6-i1 1 CX-5.6-i2 2
CC-Opus4.8-i1 26 CC-Opus4.8-i2 13 CX-5.6-i1 10 CX-5.6-i2 5
CC-Opus4.8-i1 5 CC-Opus4.8-i2 1 CX-5.6-i1 4 CX-5.6-i2 4
CC-Opus4.8-i1 0 CC-Opus4.8-i2 0 CX-5.6-i1 0 CX-5.6-i2 0
CC-Opus4.8-i1 0 CC-Opus4.8-i2 2 CX-5.6-i1 1 CX-5.6-i2 0
CC-Opus4.8-i1 1 eqeqeq CC-Opus4.8-i2 2 exhaustive-deps CX-5.6-i1 0 CX-5.6-i2 0
CC-Opus4.8-i1 1.27 CC-Opus4.8-i2 1.06 CX-5.6-i1 1.06 CX-5.6-i2 1.12
CC-Opus4.8-i1 99.5% CC-Opus4.8-i2 99.2% CX-5.6-i1 99.3% CX-5.6-i2 99.7%
CC-Opus4.8-i1 0.2% CC-Opus4.8-i2 3.4% CX-5.6-i1 2.9% CX-5.6-i2 2.9%
Review
One card per registered criterion; cells are descriptive, not scores. Full text with citations: light review · i2 addendum.
CC-Opus4.8-i1 ✓ atomicity settled at the database: the swap is a single
UPDATE, concurrent takers resolve through the partial unique index — no locks, no both-held/neither-held window; the new slot revalidated server-side with self-exclusion. CC-Opus4.8-i2 ✓ converges on i1's resolution: a single in-place UPDATE … WHERE status='confirmed' with the index arbitrating concurrent takers, plus excludeBookingId self-exclusion so a booking can't block its own move. CX-5.6-i1 ✓ reuses its m2 concurrency primitive wholesale: row FOR UPDATE + per-host advisory lock + availability re-check with self-exclusion inside one transaction, ending in a single UPDATE. CX-5.6-i2 ✓ replicates i1's design wholesale (src/lib/bookings.ts) — the client-submitted slot is never trusted. CC-Opus4.8-i1 ✓ exactly-once: cancel and reschedule gate on
status='confirmed', zero rows updated means no email, and sends are best-effort behind the committed write — a mail failure can't fail or double-fire a mutation. CC-Opus4.8-i2 ⚠ sends are awaited post-commit with no try/catch — a mail failure surfaces as a user-facing error after the mutation durably succeeded; the m1 email seam, now on three write paths. CX-5.6-i1 ✓ invitee-cancel notifies the host only, host-cancel notifies both parties; the optional host message is validated and appended only in the host-cancel branch. CX-5.6-i2 ✓ notifications fire only after commit and gate on .changed — repeated cancels can't re-fire mail. CC-Opus4.8-i1 ⚠ subjects embed raw UTC contract instants (
…moved to 2026-08-14T09:00:00.000Z) — machine format in human-facing copy. CC-Opus4.8-i2 ⚠ raw UTC instants in subjects, as i1's. CX-5.6-i1 ✓ best subject lines of the run: event, action, and both times in every notification (lib/booking-emails.ts). CX-5.6-i2 ⚠ regressed against its own i1: raw UTC instants interpolated directly, and all three notification tests failed before the fix-it. CC-Opus4.8-i1 ✓ 256-bit
randomBytes token, UNIQUE column, unknown token → plain 404; the manage page still works after the host archives the event type. CC-Opus4.8-i2 ✓ graceful degradation for orphaned bookings: a deleted event type leaves reschedule reporting no_event_type and the manage UI offering cancel-only. CX-5.6-i1 ✓ database-generated UUID with UNIQUE + backfill, format-gated by regex before any query; host cancel scoped in SQL (WHERE host_id) — cross-host cancels and double-cancels are no-ops. CX-5.6-i2 ✓ 256-bit base64url token, regex format gate, notFound() on miss, noindex metadata. CC-Opus4.8-i1 ✓ extracted the slot picker into a shared component (
src/ui/slot-picker.tsx) — booking and reschedule are literally the same picker; duplication held at 0.2%. CC-Opus4.8-i2 ⚠ the opposite call to i1: ManagePanel.tsx is a ~360-line copy of BookingFlow.tsx (duplication 0.4% → 3.4%), with copy drift already visible between the twins. CX-5.6-i1 ⚠ the manage flow copy-pastes the picker's timezone, formatting, grouping and grid code (0.3% → 2.9%, 7 clones) — the opposite call to claude-i1's extraction. CX-5.6-i2 ⚠ 2.9% duplication from timezone-helper and buffer-math copy-paste — i1's rate, from a less severe cause. CC-Opus4.8-i1 ⚠ the cross-duration overlap race is unchanged: the index arbitrates start equality only, and reschedule inherits the app-level-only overlap check. Milestone 4 targets exactly this. CC-Opus4.8-i2 — CX-5.6-i1 — CX-5.6-i2 —
CC-Opus4.8-i1 — CC-Opus4.8-i2 — CX-5.6-i1 ⚠ the catch-all 409 propagated: the new reschedule action maps any failure to
slot_unavailable — two of three write paths now report infrastructure failure as "slot taken"; cancel's catch returns a generic "failed" (the better pattern, applied inconsistently). CX-5.6-i2 — CC-Opus4.8-i1 ⚠ none. CC-Opus4.8-i2 ⚠ none. CX-5.6-i1 ⚠ none. CX-5.6-i2 ⚠ none.