Skip to content

Swap workflow verification

Verified locally on 15 September 2026.

Implemented workflow and policy

  1. A tutor selects their ACTIVE course allocation and another tutor's ACTIVE allocation for a different course. This exchanges whole allocations, including every course session and weekly hours; it does not exchange individual dated sessions.
  2. The API derives the other tutor from the target allocation, verifies ownership and tutor roles, rejects duplicate pending pairs (including reversed pairs), and rejects destination-course allocation collisions.
  3. Both incoming assignments are checked for marks, availability, timetable clashes and weekly hours. Outgoing allocations are excluded to avoid false clashes and double-counted hours.
  4. Requests remain PENDING while awaiting the other tutor's acceptance and then organiser approval. Only the requested tutor can accept or decline via /swaps/:id/accept and /swaps/:id/decline. Acceptance records requesteeAcceptedAt and rechecks constraints without moving allocations. The requested tutor may decline even after accepting, until the organiser approves. The requester may cancel while pending.
  5. Organiser approval requires recorded recipient acceptance, checks ownership, status and constraints again, reassigns the two existing allocation rows, records the reviewer and resolution time, and notifies both tutors within one serializable transaction. Existing allocation IDs are preserved.
  6. Rejection and cancellation leave assignments unchanged and notify the other tutor. Terminal requests cannot be resolved again.

Existing allocation policy is retained: missing marks and excess weekly hours are advisory warnings. Unverified marks, marks below the threshold, unavailability and timetable clashes block the swap. The UI displays persisted warnings for organiser review. This is not a guarantee that every approved swap is within the tutor's hours budget.

Fixes from this review

  • Moved creation's role and allocation reads inside the transaction so every serialization retry checks fresh ownership/status/role data.
  • Added recipient notifications for rejected and cancelled requests, in the same transaction as the resolution.
  • Reset the request dialog on reopening and prevent dismissal during submission.
  • Close a successfully saved request before refreshing the list, so a refresh failure does not leave a submitted form available for resubmission.
  • Preserve the list and show errors when refreshing fails; disable competing row actions while a resolution is pending.
  • Display field validation errors and saved rejection reasons, enforce the reason length in the form, and explain empty allocation choices.
  • Clarified that the feature exchanges whole course allocations.

Automated evidence

  • API: 382 tests passed, 11 skipped across the full suite. Includes 64 swap route/service tests using mocked authentication and an in-memory database adapter.
  • Frontend: 64 tests passed across the full suite, including 13 swap page tests and 12 allocation board tests using mocked API responses.
  • Both repository lint commands passed.
  • Frontend production build passed, with a bundle-size advisory.
  • Git diff whitespace checks passed.

New regression coverage includes changes to ownership/status/role during a creation retry, recipient resolution notifications, form reset, field error display, and successful submission followed by refresh failure. Existing coverage exercises permissions, duplicate requests, constraint failures, approval revalidation, terminal states and rollback on notification/status-transition failures.

Verification limits and live acceptance

Recipient consent follow-up: migration 20260915090000_swap_recipient_consent was applied to the configured database with approval. It adds a nullable acceptance timestamp; old pending requests require acceptance and existing terminal requests are unchanged. Prisma client was regenerated after briefly stopping the local API child; its existing nodemon supervisor restarted it and /health returned OK. Hosted API/frontend code deployments have not been performed.

The allocation board reloads on mount, on tab focus/visibility return, and every 30 seconds while visible. Background reloads pause during dragging or while the assignment dialog is open. A regression test verifies that refreshed swap ownership changes course membership, tutor allocation counts and unequal weekly hour totals. Partner acceptance, decline and cancellation leave board assignments unchanged; final organiser approval moves them.

Follow-up from browser error reports: /notifications was missing from the API. Added authenticated list, unread-count, single-read and read-all routes, each scoped to the caller's account. Responses provide the bell's message field and a /swaps link for swap notifications. Seven route tests cover authentication, account isolation (including organisers), counts and read actions. This resolves the missing route in local code; authenticated browser delivery still needs acceptance testing.

The swap form also retained allocation choices loaded when the page mounted, even if the allocation board subsequently deleted an assignment. It now reloads choices before opening and clears selections and refreshes after a 404/409 response. Missing-allocation API errors identify the originating or target allocation. This fixes a reproducible stale-selection path; the console logs alone do not establish which allocation was removed in the user's database.

The notification/stale-choice follow-up passed 370 API tests and 11 swap page tests before the subsequent consent changes. Development configuration points to http://localhost:3000/api/v1; the local API has since been restarted with the consent changes.

The Profile page also incorrectly described availability as busy time. Corrected it to describe free time, matching backend validation, and added requesting/other tutor labels to swap constraint errors. Existing saved availability may need to be corrected by tutors who followed the old wording. The availability validator's 20 tests and the swap page's 8 tests passed after this correction.

The opt-in PostgreSQL suites were skipped; no running disposable local PostgreSQL database was available (Docker daemon was unavailable). In-memory rollback tests do not prove database isolation or foreign-key behavior. The existing PostgreSQL swap suite covers competing decisions, duplicate proposals and preserved historical links when run against its guarded local b5_test database.

Authenticated browser behavior and hosted transaction latency were not tested. The only database change performed was the approved additive consent migration; no tutor accepted a request or transferred a real allocation through these checks.

Before production acceptance, use two tutor accounts and one organiser account to request and approve a valid swap; verify each tutor's allocations, hours, notifications and historical timesheet/excusal ownership. Repeat rejection and cancellation, and test a constraint change between request and approval. Run the PostgreSQL concurrency suite against the disposable database. Overlapping requests involving an allocation remain pending after another swap transfers it; their approval is blocked by ownership revalidation, and they can be rejected or cancelled.