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