Workflow: Session Lifecycle
Technical reference for the session workflow module. This is the canonical owner of lowercase appointment lifecycle statuses, threaded reschedule negotiation, cancellation, check-in/check-out, parent acknowledgement/dispute, admin override, and timed expiry/reminder sweeps.
Read path rule
Session projections must not mutate data. Expiry and reminders now run from workers_nivaa/src/lib/workflows/session-workflow/scheduler.ts; projection helpers are read-only. This replaced the older write-on-read behavior that caused hidden side effects during simple appointment reads.
Module Contract
| Concern | Implementation | What it does |
|---|---|---|
| Workflow module | workers_nivaa/src/lib/workflows/session-workflow/ | Owns session mutation rules and read projections. |
| Aggregate | repository.ts | Loads appointment context, parties, latest/open reschedule request, and discrepancy state. |
| Guards | guards.ts | Enforces access checks, freeze windows, check-in/out windows, conflict policy, and confirmation windows. |
| Commands | commands.ts | Implements proposeReschedule, decideReschedule, cancelSession, checkInSession, checkOutSession, parent-only confirmAppointment, and admin/parent verifyAppointmentAction. |
| Conflict resolution | conflicts.ts | Checks tutor and child overlap when a new reschedule slot is proposed or approved. |
| Scheduler | scheduler.ts | Runs expireDueRescheduleRequests() and pending reminder sweeps without leaking writes into reads. |
| Effects | effects.ts | Centralizes audit logging and notification fanout for all session workflow mutations. |
Canonical Appointment States
| State | Meaning | Primary owner |
|---|---|---|
scheduled | Future or upcoming appointment that has not begun and is eligible for self-service session-change rules. | Session workflow |
checked_in | Tutor has checked in within the allowed time window. | Session workflow |
awaiting_approval_parent | Tutor checked out; parent must acknowledge or dispute. | Session workflow |
approved | Parent acknowledged a completed checked-in/checked-out session. | Session workflow + package consumers |
disputed | Parent disputed the completed session and discrepancy handling is active. | Session workflow + discrepancy/ticket consumers |
cancelled_by_parent / cancelled_by_tutor | Session was cancelled before completion by the named actor. | Session workflow / admin tools |
not_completed | Original session start passed with an unresolved open reschedule request or another terminal incomplete path. | Scheduler + session workflow |
Reschedule Request States
| State | Meaning |
|---|---|
proposed | Current open proposal awaiting the counterparty. |
approved | Proposal accepted and appointment slot updated. |
rejected | Proposal rejected; original appointment slot remains unchanged. |
superseded | An open proposal was replaced by a counter-proposal in the same thread. |
cancelled | A session cancel command closed the open reschedule thread. |
expired | Scheduler expired the open proposal at the original appointment start time. |
Detailed Lifecycle
| Step | Trigger | Workflow write | Notifications / fanout | What it impacts next |
|---|---|---|---|---|
| 1. Appointment exists | Auto-schedule, trial conversion, manual creation, or historical import | appointments.status = 'scheduled' | Creation fanout depends on the source flow. | Self-service session-change rules become available if the appointment is still future-dated. |
| 2. Parent or tutor proposes a reschedule | POST /reschedule or parent/tutor session UI | proposeReschedule() inserts a reschedule_requests row, computes new_end_time from the original duration, and may supersede the prior open request. | Counterparty + admin scheduling notifications are emitted. | Open reschedule requests now block tutor check-in/check-out. |
| 3. Counterparty approves or rejects | POST /reschedule/:id/decision | decideReschedule() marks the request approved or rejected. On approval, it mutates appointments.date/start_time/end_time; on rejection, the original appointment slot remains. | Parent/tutor/admin notifications are emitted. | Approved requests move the session slot; rejected ones close the thread. |
| 4. Session cancelled | POST /appointments/:id/cancel | cancelSession() marks the appointment with the actor-specific cancelled state and closes any open reschedule request as cancelled. | Counterparty + admin notifications are emitted. | Cancelled sessions stop all remaining check-in/check-out/approval actions. |
| 5. Tutor checks in | POST /appointments/:id/checkin | checkInSession() validates the 30-minute-before-start window and writes status = 'checked_in' plus checked_in_at. | Coordinator/session visibility changes are propagated through notifications where configured. | The session becomes in progress. |
| 6. Tutor checks out | POST /appointments/:id/checkout | checkOutSession() validates the 30-minutes-after-start-through-24-hours-after-end window and writes status = 'awaiting_approval_parent', checked_out_at, and implicit tutor confirmation. | Parent review notifications are emitted. | Parent can now approve or dispute. |
| 7. Parent acknowledges | POST /appointments/:id/confirm or verify path with approve action | confirmAppointment() / verifyAppointmentAction() require checked_in_at and checked_out_at for native sessions, then write status = 'approved', confirmation timestamps, and package deduction side effects. | Tutor/admin notifications are emitted. | Package consumers can treat the session as final. |
| 8. Parent disputes | POST /appointments/:id/dispute or verify path with dispute action | verifyAppointmentAction() writes status = 'disputed' and discrepancy records. | Admin discrepancy/ticket flows are notified. | Billing/package flows must treat the session as disputed rather than final. |
| 9. Scheduler expiry | Cron or explicit scheduler sweep | expireDueRescheduleRequests() marks open requests expired and marks the appointment not_completed at the original session start if no agreement was reached. | Parent/tutor/admin notifications are emitted. | The original booked session is dead; any later teaching must be logged separately or handled by coordinator action. |
Time Windows
| Action | Rule | Implemented in |
|---|---|---|
| New self-service reschedule/cancel | Allowed only while scheduled, future-dated, and more than 4 hours before original start. | guards.ts via deriveSessionChangePolicy() |
| Approve/reject an existing open reschedule | Allowed inside the 4-hour freeze as long as the original session has not started and the actor is the counterparty. | guards.ts + decideReschedule() |
| Check-in | Opens 30 minutes before scheduled start. | getCheckInGuardReason() |
| Check-out | Opens 30 minutes after scheduled start and stays open until 24 hours after scheduled end. | getCheckOutGuardReason() |
| Parent acknowledge/dispute | Cannot happen before the session date and generally closes after the 2-day confirmation window. Native approval also requires tutor check-in and check-out. | getConfirmationWindowError() + approval/dispute commands |
| Reschedule expiry | Open request expires at the original scheduled start, not at end. | scheduler.ts |