Versus Desk

Incident Management

How Versus Desk records, routes, and resolves incidents — the INC ticket model, a declarative state machine, business-hours SLAs, CI links into live inventory, and automatic incident intake from Observe alerts, all on one hash-chained audit trail.

An incident in Versus Desk is a record of something broken — a failing virtual server, a fired alert, a user report — that an operator drives to resolution. Incidents live in the same service desk as changes, problems, and requests, share one ticket model and one audit trail, and link directly to the devices, certificates, and virtual servers already in your Versus inventory rather than to a separate CMDB.

This page covers what an incident is, the lifecycle it moves through, how SLAs are timed, how incidents open themselves from monitoring alerts, and how an operator works one from the API.


The incident record

Every ticket type — incident, problem, change, request — is a row on a shared Ticket table, with type-specific fields on a one-to-one extension. Incidents carry an IncidentExt.

Core fields on the ticket:

FieldMeaning
numberHuman ID, INC-000001, allocated monotonically per prefix inside the create transaction (gaps from rolled-back writes are acceptable, not backfilled).
short_description / descriptionThe one-line summary and full detail.
stateWhere the incident is in its lifecycle. Validated by the state machine, never a database enum.
priority1 (highest) to 4, derived from impact (1–3) and urgency (1–3).
assignment_group / assigneeWho owns the work — a queue and, once picked up, a person.
requester_user / requester_portalWho reported it — a full Versus user or a lightweight portal identity.
originHow it was created: manual, observe_alert, workflow, catalog, email, api, or sync.
opened_at / resolved_at / closed_atLifecycle timestamps.

Incident-specific fields on IncidentExt:

  • major_incident — flags a high-severity incident for special handling.
  • category / subcategory — tenant-editable classification (not a fixed enum).
  • problem — a link to the Problem ticket this incident rolls up into.
  • caused_by_change — a link to a Change that introduced the fault.
  • resolution_code / resolution_notes — required to resolve (see below).
  • reopen_count — incremented every time a resolved incident is reopened.
  • pending_reason — why the incident is on hold: awaiting_user, awaiting_vendor, awaiting_change, or scheduled.

Lifecycle

Incidents move through a declarative state machine — one engine drives all four ticket types, and transitions are data, not hardcoded buttons. The incident path is:

New ──assign──▶ Assigned ──start──▶ In Progress ──resolve──▶ Resolved ──close──▶ Closed
                                        │  ▲                     │
                                     pend│  │unpend        reopen│
                                        ▼  │                     ▼
                                     Pending                 In Progress

Each transition fires an event and may enforce a guard:

  • assign (New → Assigned) requires an assignee or an assignment group.
  • start (Assigned → In Progress) picks the work up.
  • pend (In Progress → Pending) requires a valid pending_reason.
  • unpend (Pending → In Progress) resumes work.
  • resolve (In Progress or Pending → Resolved) requires both resolution_code and resolution_notes — you cannot close out an incident without recording how it was fixed. The resolution is written and resolved_at stamped as part of the transition.
  • close (Resolved → Closed) finalizes it and stamps closed_at.
  • reopen (Resolved → In Progress) clears resolved_at and increments reopen_count.

Transitions are concurrency-safe: the service layer takes a row lock on the ticket (select_for_update), re-reads the current state, re-checks the guard, applies the change, and commits — all in one transaction. An illegal transition (wrong state for the event, or a failed guard) is rejected with 409 Conflict and changes nothing.

The UI does not hardcode which buttons to show. It reads the ticket's available_events — the set of events legal from the current state — and renders exactly those actions.

Every transition writes two records: a TicketTransition row (the human-readable "what happened" log) and an entry on the append-only, hash-chained audit trail (ItsmAuditEvent), so the full history of an incident is tamper-evident.


Priority, impact, and urgency

Priority is a 1–4 value (1 is highest). It is set from impact (how much is affected, 1–3) and urgency (how time-sensitive, 1–3), both defaulting to 2. Priority is what SLA policies and queue views sort and filter on, and it is what the alert-intake path maps incoming severities onto.


SLAs

An incident's clock is an SLA instance attached from a matching SLA definition. Two metrics are tracked:

  • Response — time to first agent action.
  • Resolution — time to resolve.

An SlaDefinition declares a target_minutes, an optional business-hours calendar, a precedence, a warning_threshold_pct (default 75%), and an applies_to matcher on ticket type, priority, and assignment group. When an incident is created — and again whenever a relevant field changes — Versus re-evaluates the definitions and attaches the highest-precedence match per metric, without disturbing an instance already running.

Business-hours due dates

The due time is computed against a BusinessCalendar — an IANA time zone, per-weekday open hours, and a holiday list. A four-hour resolution target opened at 4pm on a Friday lands the following business morning, not at 8pm, correctly skipping closed hours, weekends, and holidays. A definition with no calendar is treated as 24×7.

Warnings, breaches, and escalation

A single scheduler tick (run on a short cadence by the check_itsm_gates job) scans running instances by their materialized due_at:

  • At the warning threshold it fires an sla_warn.
  • Past due_at it records a breach (sla_breach).

Both are routed through Observe's alerting layer as alert events — escalation reuses the platform's existing notification routing rather than being a separate system. See Observe Alerting.

Stop, pause, and resume

  • The response SLA stops on the first agent action — the first public comment, or the incident reaching In Progress.
  • The resolution SLA stops on resolve, recording met or breached.
  • pend pauses running instances; unpend resumes them and pushes due_at forward by exactly the paused duration, so a hold awaiting a vendor doesn't burn the clock.

You can list an incident's instances at GET /api/itsm/tickets/{number}/sla/, and filter the queue to everything currently breached with ?sla=breached.


Incident intake from alerts

Incidents don't only come from people. When an Observe alert fires, it can open an incident automatically. The intake path is fingerprint-deduplicated:

  • If an open incident (any state except Resolved or Closed) already exists for the same alert fingerprint, the re-fire appends an internal system comment instead of opening a duplicate.
  • Otherwise a new incident is created with origin = observe_alert and origin_ref carrying the alert rule UID and fingerprint. The alert severity maps to priority: critical → 1, high → 2, medium → 3, low → 4.

When the alert later resolves, Versus adds an internal comment to the incident rather than auto-closing it — a resolved alert doesn't mean the operator is finished, so closing stays a deliberate agent action. The intake runs in-process (Observe and the desk share one application core), so there is no webhook to configure between them.


CI links: incidents against real inventory

An incident links to the things it affects through CI links, which point directly into existing Versus inventory — there is no separate CMDB to keep in sync. A CI link references a device, vip (virtual server), certificate, rule, or service (application) by type and id.

Because the link is indexed by (ci_type, ci_id), Versus can answer "every ticket touching this device" instantly — the same index the change-management risk engine reads to weigh recent incidents on a device a change is about to touch. See Change Management.

Incidents also link to other tickets: problem_incident (rolls an incident up to a problem), caused_by, duplicate_of, related, and fixes, alongside the problem and caused_by_change foreign keys on IncidentExt.


Working an incident from the API

Incidents are the incident ticket type on the ticket endpoint, addressed by number. Reading requires the itsm.view permission; creating and editing require itsm.agent.

Create an incident:

POST /api/itsm/tickets/
Authorization: Bearer …
Content-Type: application/json

{
  "ticket_type": "incident",
  "short_description": "vs_app01 pool members all down",
  "priority": 2,
  "impact": 1,
  "urgency": 2,
  "assignment_group_id": "…",
  "ci_links": [ { "ci_type": "vip", "ci_id": "…", "ci_label": "vs_app01" } ]
}

Drive it through its lifecycle with the transition action — the event must be legal from the current state, and guarded events carry their required data in fields:

POST /api/itsm/tickets/INC-000001/transition/
{ "event": "assign", "fields": { "assignee_id": "…" } }

POST /api/itsm/tickets/INC-000001/transition/
{ "event": "start" }

POST /api/itsm/tickets/INC-000001/transition/
{
  "event": "resolve",
  "fields": {
    "resolution_code": "config_error",
    "resolution_notes": "Re-enabled the disabled pool members; monitor now green."
  }
}

Add a comment (public to the requester, or internal to agents only), which also stops the response SLA when public:

POST /api/itsm/tickets/INC-000001/comments/
{ "body": "Failover confirmed healthy.", "visibility": "public" }

List and filter the queue with standard query parameters — ticket_type, state, assignment_group, assignee, priority — plus desk shortcuts: q (text search), ci_type+ci_id (tickets touching one CI), sla=breached, and saved views view=mine|submitted|group|open|closed.

On close, incidents can trigger a CSAT survey to the requester, closing the feedback loop on the resolution.


Where incidents fit

Incidents are one corner of Versus Desk, and they connect outward:

  • Alerts open and update them automatically (Observe Alerting).
  • Problems aggregate recurring incidents for root-cause analysis; changes that caused a fault are linked back via caused_by_change.
  • Workflows can create incidents as an origin, and change risk scoring reads incident history on shared CIs (Change Management).
  • Existing ServiceNow incident data can be brought across, and the desk can run alongside ServiceNow (ServiceNow Integration).

Every incident action — transition, comment, assignment, SLA warning, SLA breach — lands on the same tamper-evident audit chain, so an incident's whole life is reconstructable after the fact.


Related