Auto-approved evidence was hidden because download checked the approval tier. Viewers of the work order can open it, and a request can store more than one scanned document.
The route contract keeps the poc endpoint from main and the uplift evidence routes from this branch. Create still passes the scanned evidence document through the locked mutation.
The legacy ingest batch holds the per-work-order lock, taken in id order,
for every work order it may cancel until the batch commits. A vendor
uplift request now runs under the same lock. Uplift creation on both
routes refuses a work order cancelled by either lifecycle or status text,
so a request can neither slip past a cancel nor land after one.
The webhook and reconciliation saves now stage the same pending-uplift
cancellation, with its own sync audit row, as the board cancel. The rule
and the write live in one data-layer helper so the paths cannot drift.
An admin revoke overturns a human decision, so admins may revoke only
admin-approved uplifts. The work-order revoke path let an admin revoke an
auto-approved uplift they had requested themselves. Both revoke endpoints
now refuse it; dispatchers keep revoking their own auto-approved uplifts.
Board create left the new primary dispatch with no WorkOrderId, so uplifts
created on those work orders were written to a dispatch the work order's
uplift reads never resolve: not listed, allowance never consumed, queue WO
number blank. The same orphan made ApptDate/vendor patches fail with
"A primary dispatch is required".
- Board create backfills Dispatch.WorkOrderId after the first save.
- Uplift create resolves its dispatch through the read-side scope
(non-deleted, owned or linked); otherwise the stable
"no primary dispatch" error.
- Data-only migration assigns existing orphaned primaries to the single
work order naming them primary; idempotent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the operation work order when staging uplift audit logs so a dispatch linked through DispatchWorkOrders cannot write the event to its primary work order. Add coverage for the cross-work-order fallback case.
Three contract gaps found reviewing the frontend consumer:
- Revoking an auto-approved uplift never restored the dispatch NTE. Create
raises NTE for both auto-approved and approved requests, but revoke restored
it only for Approved, so the allowance was freed while the NTE stayed raised
and every create -> auto-approve -> revoke cycle compounded the inflation.
Revoke now compensates for NoApprovalRequired symmetrically.
- Revoke and cancel had no work-order lifecycle check, so a direct API call
could still mutate uplifts on a Completed or Canceled work order; the board
dialog's read-only state is UX only. Both now reject terminal work orders in
the service.
- WorkOrderBoardCancelService read the pending-uplift list outside any gate, so
an in-flight create could commit after that read and leave a pending uplift on
a Canceled work order. The cancel flow now runs inside the same per-work-order
gate as create, so the pending read, withdrawal and status audit serialize
against it.
Auto-approval now uses the WO-scoped $500/$5,000 Emergency cap instead of dispatch NTE, rejects a second open request across dispatches, and cancelling a WO withdraws pending uplifts with audit.
Expose workorders/{id}/uplifts list/create/cancel/revoke for the SH-196 dialog, aggregate upliftSummary on board rows, and add service/controller regression tests.