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

ConcernImplementationWhat it does
Workflow moduleworkers_nivaa/src/lib/workflows/session-workflow/Owns session mutation rules and read projections.
Aggregaterepository.tsLoads appointment context, parties, latest/open reschedule request, and discrepancy state.
Guardsguards.tsEnforces access checks, freeze windows, check-in/out windows, conflict policy, and confirmation windows.
Commandscommands.tsImplements proposeReschedule, decideReschedule, cancelSession, checkInSession, checkOutSession, parent-only confirmAppointment, and admin/parent verifyAppointmentAction.
Conflict resolutionconflicts.tsChecks tutor and child overlap when a new reschedule slot is proposed or approved.
Schedulerscheduler.tsRuns expireDueRescheduleRequests() and pending reminder sweeps without leaking writes into reads.
Effectseffects.tsCentralizes audit logging and notification fanout for all session workflow mutations.

Canonical Appointment States

StateMeaningPrimary owner
scheduledFuture or upcoming appointment that has not begun and is eligible for self-service session-change rules.Session workflow
checked_inTutor has checked in within the allowed time window.Session workflow
awaiting_approval_parentTutor checked out; parent must acknowledge or dispute.Session workflow
approvedParent acknowledged a completed checked-in/checked-out session.Session workflow + package consumers
disputedParent disputed the completed session and discrepancy handling is active.Session workflow + discrepancy/ticket consumers
cancelled_by_parent / cancelled_by_tutorSession was cancelled before completion by the named actor.Session workflow / admin tools
not_completedOriginal session start passed with an unresolved open reschedule request or another terminal incomplete path.Scheduler + session workflow

Reschedule Request States

StateMeaning
proposedCurrent open proposal awaiting the counterparty.
approvedProposal accepted and appointment slot updated.
rejectedProposal rejected; original appointment slot remains unchanged.
supersededAn open proposal was replaced by a counter-proposal in the same thread.
cancelledA session cancel command closed the open reschedule thread.
expiredScheduler expired the open proposal at the original appointment start time.

Detailed Lifecycle

StepTriggerWorkflow writeNotifications / fanoutWhat it impacts next
1. Appointment existsAuto-schedule, trial conversion, manual creation, or historical importappointments.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 reschedulePOST /reschedule or parent/tutor session UIproposeReschedule() 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 rejectsPOST /reschedule/:id/decisiondecideReschedule() 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 cancelledPOST /appointments/:id/cancelcancelSession() 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 inPOST /appointments/:id/checkincheckInSession() 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 outPOST /appointments/:id/checkoutcheckOutSession() 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 acknowledgesPOST /appointments/:id/confirm or verify path with approve actionconfirmAppointment() / 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 disputesPOST /appointments/:id/dispute or verify path with dispute actionverifyAppointmentAction() 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 expiryCron or explicit scheduler sweepexpireDueRescheduleRequests() 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

ActionRuleImplemented in
New self-service reschedule/cancelAllowed only while scheduled, future-dated, and more than 4 hours before original start.guards.ts via deriveSessionChangePolicy()
Approve/reject an existing open rescheduleAllowed inside the 4-hour freeze as long as the original session has not started and the actor is the counterparty.guards.ts + decideReschedule()
Check-inOpens 30 minutes before scheduled start.getCheckInGuardReason()
Check-outOpens 30 minutes after scheduled start and stays open until 24 hours after scheduled end.getCheckOutGuardReason()
Parent acknowledge/disputeCannot 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 expiryOpen request expires at the original scheduled start, not at end.scheduler.ts