1. Why Single-Owner Dispatch Matters
In buildings and hotels, broadcast group-chat messages fail because everyone assumes someone else is handling the problem. UnitVO enforces Single-Owner Dispatch: every work order has one clear owner, one response timer, and an explicit escalation path.
2. Deterministic Worker Eligibility (DispatchEngine)
The C# DispatchEngine evaluates every worker deterministically:
| Rule | Condition | Failure Reason Code |
|---|---|---|
| Shift Status | worker.OnShift == true | off_shift |
| Safety Pause | worker.SafetyHold == false | safety_hold |
| Active Load | worker.ActiveAssigned < worker.MaxActive (unless emergency override) | at_capacity |
| Skill Match | worker.Skills contains required skill | missing_skill |
| Certification | If RequiresCert == true, worker holds valid certification | missing_certification |
| Language | If RequiredLanguage set, worker speaks language | missing_language |
| Property Access | worker.Properties includes target property | wrong_property |
Eligible workers are ranked by lowest active load, highest ZoneScore, lowest FatigueScore, and deterministic lexical tie-breaking on WorkerId.
3. Atomic Offer Book & 120-Second TTL
- One Active Offer Per Item: Creating an offer reserves the work item so two dispatchers cannot simultaneously offer the same ticket to different workers.
- 120-Second Expiration: Unaccepted offers expire after 120 seconds (
UVO-OFFER-004), releasing the item for re-dispatch. - Eligibility Re-Verification on Accept: When a worker taps Accept,
OfferBook.Acceptre-runsDispatchEngine.Eligibilityagainst the worker's live state. If the worker went off-shift or hit capacity while the offer was pending, acceptance is rejected withworker_no_longer_eligible.
4. Tenant-Scoped Context-Bound Idempotency (ScopedRequests)
Every state-mutating request is hashed over (OrganizationId, PropertyId, ActorId, ToolName, CanonicalPayload). Retrying with the same key and identical payload replays the original result; reusing the same key with a mutated payload returns a conflict error.