Build a Laravel permit-review workflow for reviewer 47 and reviewer 81. Reviewer 47 may inspect and approve case 62; reviewer 81 may not discover its private title by guessing a route ID. A detail route uses model binding plus a policy and returns a deliberate resource projection. The queue contains at most 47 cases, with district labels and attachment counts, ordered by creation time and a unique ID for cursor continuation. A browser approval form carries CSRF protection, a stable operation ID, a reason, and an expected revision. Its service rechecks permission in a transaction, locks current case state, and writes one approval result and one outbox event. A lost HTTP response can be replayed under the same ID. A post-commit job wakes delivery, while a sweep recovers events if that dispatch is lost.
Project: Laravel permit review workflow
Build contract
- Authenticate the reviewer, bind the case, and authorize view and approval as separate actions.
- Scope the queue in SQL, eager load the displayed district, count attachments, and cap each cursor page at 47 cases.
- Validate browser input and current assignment; store one unique command outcome and outbox row per approval.
- Delay the queue wake-up until commit, then recover any pending outbox row through an independent sweep.
- Map missing, forbidden, stale, duplicate, and internal outcomes to controlled responses with no private notes.
Implementation checkpoint
<?php
function mayClaimPermitEvent(string $eventId, array $deliveredIds, array $leasedIds): bool
{
return !in_array($eventId, $deliveredIds, true)
&& !in_array($eventId, $leasedIds, true);
}
$deliveredIds = ['permit-62-approved-47'];
$leasedIds = ['permit-81-reviewed-29'];
var_export(mayClaimPermitEvent('permit-62-approved-47', $deliveredIds, $leasedIds));
// Output: falseCost and boundaries
A private detail route pays for binding and policy work before it may return data. A list policy implemented as a per-row relationship lookup can make a 47-case page issue many queries; the queue must scope by reviewer in SQL. Eager loading and attachment counts remove repeated reads but still add database work, so inspect actual rows and plans. Cursor pagination reduces deep-offset cost when order columns are indexed, at the price of numbered-page navigation. An approval lock serializes conflicting writes for one case. Operation and outbox rows use storage proportional to the replay and delivery windows. A queue worker and sweep add capacity needs, yet they make promised notifications inspectable and recoverable after a process restart.
Failure drill
Guess case 62 as reviewer 81 through detail and export routes. Render 47 cases and measure query count with district eager loading removed, then restore it. Seed equal creation timestamps and follow cursor pages without duplicate IDs. Submit approval without a CSRF token, with a stale revision, and with reviewer 81's valid session. Race two requests using one operation ID. Commit an approval, lose its response, and replay; inspect case, operation, and outbox rows for one logical transition. Roll back before commit and confirm the wake-up job does not run. Kill the process after commit but before dispatch and let the sweep recover the pending event. Force a provider timeout after possible acceptance and reconcile by event identity.
Acceptance checks
- A valid login never grants access to a case outside its current policy scope.
- The reviewer queue stays bounded and deterministic without an N+1 relation pattern.
- A replay preserves one status change, operation row, and outbox event.
- A lost queue wake-up does not erase a committed notification intent.
Common Mistakes
- Assuming model binding includes authorization.
- Eager loading children for an unbounded list.
- Sending provider messages inside a retried database transaction.
- Equating an after-commit queue hint with durable intent.
Related lessons
Laravel Policy, Query, and Queue Boundaries; Laravel Route Binding, Policy, and Private Resource Scope; Laravel Eloquent Loading and Cursor Page Cost; Laravel Form Requests, Transactions, and Approval Replay; Laravel After-Commit Jobs and Outbox Recovery.
