Context
BSFG already separates envelope from fact. In messaging patterns, a correlation identifier is used to link a message to the conversation or request it belongs to, and message-history style metadata belongs in headers rather than the body because it is system-level control information, not application content. :contentReference[oaicite:0]{index=0}
BSFG needs a stable way to express lineage without turning the fact body into transport history. Two distinct questions must remain separate:
- what broader conversation or workflow is this message part of?
- what immediate prior message caused this one to exist?
Options Considered
| Option | Description | Benefits | Drawbacks |
|---|---|---|---|
| No lineage fields | Omit both correlation and causation; infer lineage from payloads or external systems. | smallest envelope no lineage governance |
poor replay traceability hard to relate retries, workflows, and derivations debugging becomes externalized |
| Correlation only | Carry a single workflow or conversation identifier in the envelope. | good grouping of related messages simple model |
immediate lineage is ambiguous fan-in and fan-out causality is harder to express |
| Causation only | Carry only the immediate predecessor message ID. | precise local derivation good for step-by-step tracing |
weak conversation grouping workflow-level analysis becomes cumbersome |
| Correlation and causation in the envelope (Selected) | Carry both a broader correlation identifier and an immediate causation identifier as first-class envelope fields. | supports both workflow grouping and immediate lineage keeps lineage in transport metadata rather than fact semantics improves replay and diagnostics |
producers must populate them consistently lineage policy needs governance |
Decision
BSFG uses both correlation_id and causation_id in the envelope.
correlation_id = broader conversation / workflow / batch interaction
causation_id = immediate prior message that caused this message
These are envelope fields, not fact fields.
correlation_idgroups related messages across a larger exchangecausation_idlinks a message to its immediate predecessor when one existsmessage_idremains the unique idempotency key for the current message
Example:
message_id = msg-9007
correlation_id = batch-B1042-release-flow
causation_id = msg-9006
This keeps lineage explicit while preserving the rule that the fact body carries domain meaning, not transport history. Header-level lineage is consistent with established messaging patterns. :contentReference[oaicite:1]{index=1}
Consequences
Benefits:
- workflow grouping and local derivation are both available
- operators can reconstruct message chains without parsing domain payloads
- fact semantics remain clean and replayable
- downstream projections can use lineage without treating it as business truth
Tradeoffs:
- lineage fields can be misused or left blank unless producer discipline exists
- complex many-to-one causality may still need richer downstream modeling
- teams must distinguish correlation from causation explicitly