Incident Ops is split into four bounded contexts. Each one is a module in the repository, has its own model and its own vocabulary, and talks to the others only through the published contract: the HTTP API and the CloudEvents defined in contracts.md.
flowchart LR
subgraph IM ["Incident Management · services/api"]
im["Incident aggregate<br/>Open host service:<br/>HTTP API + CloudEvents"]
end
subgraph ES ["Escalation · services/functions"]
esacl["Anti-corruption layer<br/>Infrastructure/AntiCorruption"]
es["AcknowledgementWatch"]
esacl --> es
end
subgraph OA ["Operational Analytics · services/insights"]
oaacl["Anti-corruption layer<br/>infrastructure/incidents/acl.py"]
oa["IncidentRecord, IncidentHistory"]
oaacl --> oa
end
subgraph OC ["Operations Console · apps/web"]
oc["features/*/domain<br/>conforms to the contract"]
end
im -- "U · CloudEvents incident.*<br/>D · customer/supplier" --> esacl
im -- "U · GET /api/incidents/{id}, /api/oncall/current<br/>D" --> esacl
es -- "POST /escalate (command into the supplier)" --> im
im -- "U · GET /api/incidents/export<br/>D · customer/supplier" --> oaacl
im -- "U · REST + SignalR<br/>D · conformist" --> oc
oa -- "U · KPIs, clusters, RCA drafts<br/>D · conformist" --> oc
U marks the upstream side of a relationship and D the downstream side.
customer/supplier over a published language; anti-corruption layer on the downstream side
Upstream
Incident Management publishes incident.triggered and incident.escalated (CloudEvents 1.0) on incident-events and serves GET /api/incidents/{id} and GET /api/oncall/current
Downstream
Escalation translates the envelope and payload in Infrastructure/AntiCorruption into IncidentProfile, IncidentState, EscalationLevel and AcknowledgementDeadline; upstream DTOs never reach the domain
Back channel
Escalation asks the supplier to act with POST /api/incidents/{id}/escalate; the decision to change the incident stays in Incident Management
Failure handling
a payload that breaks the contract or an Escalation invariant becomes MalformedMessageException and is dead-lettered
customer/supplier over a published language; anti-corruption layer on the downstream side
Upstream
GET /api/incidents/export?from=&to= (flat incidents, no timeline) and GET /api/incidents/{id} for RCA drafts
Downstream
infrastructure/incidents/acl.py is the only code that knows the API field names (serviceId, ackDueAt, ...); it builds IncidentRecord value types, and the CSV source goes through the same translation
Shared rule
SLA compliance follows the contract definition; Analytics recomputes it on IncidentRecord instead of importing code from the API
REST endpoints and the /hubs/incidents SignalR hub (IncidentChanged, TimelineAppended)
Downstream
the console adopts the contract shapes as its types (features/incidents/domain), and mirrors the transition rules and SLA thresholds only to decide what to show and which actions to offer; the API stays the authority and answers 409 to an invalid action
Only Incident Management changes an incident. Other contexts ask through the API.
A change to the published language starts in contracts.md, reviewed through CODEOWNERS; pull requests that touch contracts/ run the workflows of all four contexts.
Anti-corruption layers are checked by architecture tests: the Escalation domain does not see upstream DTOs, and import-linter keeps the Analytics domain free of HTTP and serialization frameworks (code structure).