Workflow: Posting Lifecycle

Technical reference for the canonical posting-workflow module. Posting workflow owns parent posting creation, tutor applications, coordinator push, parent acceptance, parent withdrawal before scheduling, and coordinator reopen. It hands off to trial-workflow once a trial match exists.

Lowercase status contract

Persisted posting statuses are lowercase only: draft, open, filled, closed, parent_withdrawn.

Address source of truth

Parent posting create/update does not accept freeform address fields anymore. Worker derives postal_code and location_area from the selected child’s effective address (child_profiles.address_data, fallback users.address_data).

Module Contract

ConcernImplementationWhat it does
Workflow moduleworkers_nivaa/src/lib/workflows/posting-workflow/Owns posting aggregate loading, policy guards, commands, projections, and workflow effects.
Commandscommands.tsImplements createPosting, updatePosting, applyToPosting, reapplyToPosting, withdrawPostingApplication, pushPostingApplications, acceptPostingApplicationAndCreateTrialMatch, parentWithdrawPosting, closePosting, and reopenPosting.
Family gatefamily-onboarding integrationBlocks posting creation for children who are not ready for posting.
Tutor gatetutor-onboarding integrationBlocks non-approved tutors from applying/reapplying/withdrawing.
Address derivationresolveEffectiveChildPostingLocation()Derives service area/postal code from child or inherited parent address server-side.

Tables And Fields Touched

EntityWorkflow usage
postingsCreated, updated, filled, closed, withdrawn, or reopened. Canonical states: draft, open, filled, closed, parent_withdrawn.
posting_applicationsTracks tutor application status (Pending, Pushed, Shortlisted, Rejected, Withdrawn).
student_profilesCreated on demand when acceptance requires a student profile for the new trial match.
matchesCreated at parent acceptance with match_type = 'Trial', status = 'parent_accepted', and trial_phase = 'Date_Pending'.
trial_date_proposalsExpired/closed when a parent withdraws after a trial match exists but before any trial appointment has been created.

Detailed Lifecycle

StepTriggerWorkflow writeNotifications / fanoutNext state
1. Parent creates draft or live postingPOST /postingscreatePosting() inserts a postings row with lowercase status and worker-derived address metadata.Coordinator-facing notification fanout can fire after creation.Posting enters draft or open.
2. Parent updates postingPATCH /postings/:idupdatePosting() mutates editable fields and re-derives address from the linked child.No special lifecycle fanout by default.Posting stays editable while policy allows.
3. Tutor applies / re-applies / withdrawsPOST /postings/:id/apply, /reapply, /withdrawApplication rows are inserted or status-transitioned on posting_applications.Coordinator-facing notification/event fanout may fire.Posting candidate pool changes without altering posting status.
4. Coordinator pushes applicationsPOST /admin/postings/:id/pushSelected applications become Pushed and coordinator_pushed = 1.Parent is notified that curated educator choices are ready.Parent can review blind educator cards.
5. Parent accepts one educatorPOST /postings/:id/acceptacceptPostingApplicationAndCreateTrialMatch() creates a trial matches row, marks the selected application Shortlisted, rejects competing non-withdrawn applications, and sets postings.status = 'filled'.Parent, tutor, and coordinator fanout continues through shared notifications. If initial trial slots are bundled, the route hands off immediately into trial-workflow proposal creation.Control moves to trial-workflow.
6. Parent withdraws before trial schedulingPOST /postings/:id/withdrawparentWithdrawPosting() sets postings.status = 'parent_withdrawn'. If a pre-scheduled trial match exists with no trial appointments yet, the match is closed out and pending date proposals are expired.Tutor/coordinator can see explicit withdrawn state instead of silent disappearance.Posting is withdrawn but still coordinator-reopenable.
7. Coordinator reopens parent-withdrawn postingPOST /admin/postings/:id/reopenreopenPosting() sets postings.status = 'open' only when no trial appointment exists.Normal posting visibility resumes.Posting re-enters the marketplace.
8. Coordinator closes postingPOST /admin/postings/:id/closeclosePosting() sets postings.status = 'closed'.No acceptance handoff occurs.Posting leaves the marketplace without creating a new match.

Withdrawal Boundary

Parent withdraw window
posting=open
  -> parent may withdraw

posting=filled + trial match exists + trial_appointment_count=0
  -> parent may still withdraw

posting=filled + trial_appointment_count>=1
  -> parent withdraw is blocked
  -> coordinator reopen is also blocked

Trial Handoff

  • Posting workflow owns acceptance only up to trial-match creation.
  • Once a trial match exists, trial-workflow owns date negotiation, invoice/payment, feedback, and conversion.
  • Bundled initial trial slots on the accept route are treated as a handoff shortcut into trial-workflow, not as posting-workflow owning trial negotiation.