# Ticket routing workflow blueprint

```yaml
name: ticket-routing
version: 1.0.0
trigger:
  events: [triage.completed, route.rejected, route.lease_expired, route.reroute_requested]
input:
  required: [ticket_id, triage_version, category, language, region, priority, policy_version]
output:
  required: [route_id, owner_or_queue, lease_state, candidate_set_hash, reasons]
side_effects:
  allowed: [assign_ticket, create_lease, notify_owner, emit_route_event]
  forbidden: [change_triage, relax_required_permission, send_customer_reply]
```

## States

`received -> eligible_set_built -> capacity_read -> ranked -> lease_offered -> acknowledged`

Branches: `no_eligible_candidate`, `capacity_stale`, `rejected`, `lease_expired`, `superseded`, `write_recovery`.

## Ranking contract

1. Hard-filter permission, region, required language, required skills and availability.
2. Reject capacity snapshots older than `capacity_max_age`.
3. Rank by continuity flag, skill score, ascending weighted load, oldest last assignment, stable agent ID.
4. Keep the complete rule version and candidate-set hash.

## Concurrency

- Assignment uses compare-and-set against current ticket assignment version.
- Only one active lease token exists per ticket.
- A timeout handler must present the active token before invalidation.
- Acknowledgement after invalidation is rejected and recorded.

## Events

`route.requested`, `route.no_candidate`, `route.lease_created`, `route.notified`, `route.acknowledged`, `route.rejected`, `route.lease_expired`, `route.fallback_applied`, `route.failed`.

## Acceptance tests

1. Ineligible candidates never enter ranking.
2. Required skill is never silently relaxed.
3. Optional skill relaxes only after configured timeout and records why.
4. Stale capacity produces queue ownership.
5. Identical inputs produce deterministic tie-breaking.
6. Concurrent workers create one assignment.
7. Duplicate notification uses the same idempotency key.
8. Expired lease cannot be acknowledged.
9. Rejected lease records a bounded reason.
10. Material ticket change returns to triage.
11. No candidate sends to the named duty destination.
12. Reroute preserves prior route attempts.
13. Ticket closure cancels pending lease actions.
14. Audit replay explains eligibility and rank without private roster details.
