Architecture Decision Record

BSFG ADR-0035

Status: Accepted · Date: 2026-03-06

Status: Accepted

Date: 2026-03-06

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_id groups related messages across a larger exchange
  • causation_id links a message to its immediate predecessor when one exists
  • message_id remains 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