The asynchronous contract of the PropFlow platform: every domain event,
staged in a transactional outbox and relayed to RabbitMQ (transient
fan-out) and Kafka (replayable history). Delivery is at-least-once —
consumers dedupe on eventId. Narrative companion: docs/events.md;
typed source of truth: libs/contracts.
Work distribution — topic exchange propflow.events.
Audit stream — topic propflow.work-orders, keyed by workOrderId.
Every domain event, retained as a replayable log. Messages are keyed by
workOrderId, so one aggregate's events stay ordered within a
partition; a fresh consumer group at offset 0 rebuilds any projection.
Work Orders service appends every domain event to the audit stream.
Events are staged in the outbox_events table inside the same
transaction as the state change (ADR-0007) and published by the outbox
relay — the service never talks to a broker on the write path. The same
relay also publishes each event to its RabbitMQ routing key.
Available only on servers:
Accepts one of the following messages:
A maintenance request was opened.
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.created",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
A manager assigned (or reassigned) a technician.
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.assigned",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.started",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.completed",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
Cancelled from any non-terminal state.
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.cancelled",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
The AI classification was applied (asynchronously, after created).
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.triaged",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
Notifications service alerts the property manager.
Queue notifications.work-order-created. Failures retry via the TTL
pattern (<queue>.retry, 5s, max 3 attempts) before parking in
<queue>.dlq. Deduped by eventId in the processed-events store.
Available only on servers:
Accepts the following message:
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.created",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
Work Orders service classifies the request with an LLM.
Queue work-orders.triage. Best-effort — classification failures are
skipped, never retried (ADR-0006). Applying a result emits
work-order.triaged through the outbox. Idempotent via triaged_at.
Available only on servers:
Accepts the following message:
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.created",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
Notifications service alerts the tenant.
Queue notifications.work-order-completed, same retry/DLQ and
dedup semantics as the created consumer.
Available only on servers:
Accepts the following message:
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.completed",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
Every domain event, retained as a replayable log. Messages are keyed by
workOrderId, so one aggregate's events stay ordered within a
partition; a fresh consumer group at offset 0 rebuilds any projection.
Audit service projects the stream into the activity feed.
Consumer group audit-service, reading from the beginning. Ingestion
is idempotent by construction — INSERT … ON CONFLICT (event_id) DO NOTHING — so redeliveries and full replays are safe.
Available only on servers:
Accepts one of the following messages:
A maintenance request was opened.
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.created",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
A manager assigned (or reassigned) a technician.
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.assigned",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.started",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.completed",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
Cancelled from any non-terminal state.
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.cancelled",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
The AI classification was applied (asynchronously, after created).
{
"eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
"type": "work-order.triaged",
"occurredAt": "2019-08-24T14:15:22Z",
"correlationId": "string",
"actorId": "string",
"data": {
"workOrderId": "50150040-5a5c-4c26-9d75-30728d69a42e",
"propertyId": "8e8b8450-dc1b-4360-8109-7067337b42cb",
"title": "string",
"description": "string",
"priority": "low",
"status": "open",
"assigneeId": "665a9750-71bd-4b96-bacd-9efa4ae022dd",
"triage": {
"category": "plumbing",
"urgency": "emergency",
"reasoning": "string"
}
}
}
A maintenance request was opened.
A manager assigned (or reassigned) a technician.
Cancelled from any non-terminal state.
The AI classification was applied (asynchronously, after created).
Shared envelope of every domain event. eventId is the idempotency key consumers dedupe on.
Full work-order snapshot at the time of the event.
AI classification with closed vocabularies — the model is constrained to these enums via a JSON schema, so consumers can rely on them like any other contract enum.