Skip to content

ADR-0025: Faults are graph citizens — alarms and tickets are entities, RCA verdicts are epistemic links

ADR-0025: Faults are graph citizens — alarms and tickets are entities, RCA verdicts are epistemic links

Status

Accepted

Date

2026-07-03

Context

The ticketing module and root-cause-analysis engine are the first product workload built on the ontology layer (PR #13: OntologyPort, EntityGraphPort with cycle-safe BFS traversal, the fiber+PON reality with its network.pon_downstream link family). A fault pipeline needs three representations decided: what an alarm is, what a ticket is, and what a root-cause conclusion is.

The candidates for alarms: a trait on the affected device (lightest, but links are entity↔entity so tickets could never link to alarms, and concurrent alarm types on one device collide in a single slot); a series trait per ADR-0015 (the long-run home for real NMS volume — the P4 store now exists via vinxi-port-assert and the assert_series syscall from the series thin-slice (PR #14), but v1 series are numeric points off the decision spine, the streaming alert evaluator is explicitly deferred, and injector alarms are discrete typed events, not cadenced measurements); or an entity in the graph.

For root-cause conclusions: a JSON trait on the ticket (a string column in disguise — no per-conclusion settledness, not traversable), or a link carrying the world plane’s existing epistemics (LinkCurrent already has settledness and derived_from).

Decision

Alarms are entities. Kind fault.Alarm, with traits {alarm_type, severity, status, first_raised, last_seen, count}. The alarm entity ID is derived deterministically from (affected entity, alarm_type), so re-raises are idempotent upserts that bump count/last_seen — ADR-0013 compliance by construction. Raise and clear events are truth statements; the projected alarm entity cites them via derived_from.

Tickets are entities, and the ticket kind is domain-neutral. Kind ticketing.Ticket (traits {status, severity, title, opened_at, assignee}) lives in its own schema document with zero network vocabulary. Fault semantics arrive in a separate bridging document (fault-management) declaring the alarm kind and the link types: fault.raised_on (alarm→device), fault.grouped_into (alarm→ticket), fault.symptom_of (alarm→alarm), fault.suspected_root_cause (ticket→entity), fault.affects (ticket→devices). Future ticket classes — customer complaints, field work orders — are additional bridging documents over the same core kind.

RCA verdicts are epistemic links. The engine writes a Prediction statement (with confidence) to the truth plane and projects a fault.suspected_root_cause link with settledness: PROVISIONAL and derived_from citing that statement. A human ConfirmRootCause Act records a Decision and settles the link. A wrong verdict is repaired with a Correction statement and a re-pointed link — the audit trail survives. Symptom suppression uses the same pattern: non-root alarms link fault.symptom_of the root alarm, so un-suppression is an ordinary epistemic repair. The engine proposes; it never declares.

Alternatives Considered

  • Alarm as a device trait: rejected — tickets cannot link to non-entities, concurrent alarm types collide, grouping degenerates into JSON bookkeeping.
  • Alarm as a series trait (ADR-0015): deferred, not rejected — correct for real NMS volume, but P4’s v1 stores numeric points only, the streaming alert evaluator remains deferred, and an alarm must hold typed state and participate in links — which a series point cannot. Precedent agrees: Trafficure’s series slice independently modeled its alert as an entity (trafficure.CongestionAlertalertOnRoadRoad) with the numeric telemetry in P4 underneath. When the alert evaluator lands, derivation-fired alerts become a second alarm ingress into this same fault pipeline.
  • Root cause as a ticket trait: rejected — loses per-conclusion settledness, traversability (“all tickets rooted in this splice closure”), and the evidence chain.

Consequences

  • Correlation and RCA become graph traversals: the fault domain inherits the network.pon_downstream link family, direction-inverted, as its traversal alphabet. The ontology already encodes the domain.
  • The bridging-document split hits a known gap, not a hypothetical: SchemaDocument::validate() is single-document, which is why the Trafficure slice co-located CongestionAlert with Road in one document (TODO.md, series-slice items). The ticketing build must add snapshot-level cross-document link validation — or co-locate fault-management with the network document as an explicitly interim measure.
  • Alarm volume lives in the world plane and statement log until the alert path over P4 matures (streaming evaluator + §26.3 window-digest copy-out). Revisit trigger: the first real NMS integration, where alarm cadence turns series-shaped and this ADR’s alarm representation gains an ADR-0015 migration path.