Audience: Integrators, solution architects. Use: Apply the normative producer and consumer contract for BSFG integrations.
Purpose
The integration contract is a normative specification that defines exactly what producers and consumers must implement to interact correctly with BSFG. All systems integrating with BSFG via the four RPC operations must satisfy this contract.
Without explicit contract semantics, integrators often make incorrect assumptions about exactly-once delivery, synchronous semantics, or boundary-level business acceptance. This leads to integration defects: duplicate emissions, premature confirmation, or silent message loss.
Producer (Emitter) Responsibilities
A producer is any system that calls AppendFact to emit facts into BSFG.
Required Invariants
- MUST generate a stable
message_idderived deterministically from the business event — not from wall-clock time, random generation, or sequence allocation that resets on restart. Examples:- Hash of the business event ID
- UUID derived from a stable seed
- Deterministic function of (entity_kind, entity_id, event_type)
- MUST treat
AppendFactas retry-safe — on network error or timeout, retry with the samemessage_idand identical payload. Generating a new ID for a retry is not safe and violates idempotency. - MUST NOT mutate facts once emitted — facts are append-only. If a correction is needed, emit a new fact with a different
message_id. - MUST upload artifacts before referencing them — complete
PutObjectsuccessfully before issuingAppendFact. A missing artifact at retrieval time indicates a producer defect. - MUST tolerate asynchronous propagation —
AppendFactconfirmation means durably stored at ingress boundary only, not delivered to, processed by, or acknowledged by any consumer.
Required Call Sequence
IF artifact needed:
PutObject(blob) → {digest, size, bucket, key}
[wait for durability]
AppendFact({
envelope: {message_id, from_zone, to_zone, produced_at_unix_ms, ...},
fact: {subject, predicate, object_json}
})
[wait for durability at ISB/ESB]
IF timeout or error:
retry AppendFact(same message_id, same payload)
Consumer (Receiver) Responsibilities
A consumer is any system that calls FetchFacts and ConfirmReceipt to retrieve and process facts from BSFG.
Required Invariants
- MUST retrieve facts using
FetchFacts— consumers drive their own fetch progress via durable named consumers. Fetch is pull-driven; consumers must not assume push delivery. - MUST process facts idempotently — at-least-once transport (ADR-0033) means any fact may be delivered more than once. Idempotency is the consumer's responsibility, not the boundary's.
- MUST persist business outcome before confirmation — durably record the result before calling
ConfirmReceipt. Confirming before persisting loses the outcome on restart. - MUST confirm processing using
ConfirmReceipt— this advances the durable consumer cursor, allowing the producer's store buffer to truncate. - MUST tolerate delayed or replayed facts — facts may arrive after connectivity restoration or boundary replay. Process them as if they are new.
Required Call Sequence
FetchFacts(consumer_name, limit) → batch of facts
FOR each fact in batch:
apply business logic (idempotent)
persist outcome to durable storage
[if error, do NOT confirm]
IF all facts processed successfully:
ConfirmReceipt(consumer_name, highest_offset)
IF error during processing:
retry on next fetch from last cursor position
Delivery Semantics
BSFG guarantees the following about delivery:
The canonical guarantee ladder is: at-least-once transport → idempotent materialization at the boundary via putIfAbsent → application-owned idempotent consumption downstream.
At-Least-Once Transport
Every fact appended successfully will be delivered at least once to the receiving zone's forward buffer. Facts are never lost after successful append, as long as the store buffer is not truncated (default 7-day TTL).
Idempotent Materialization at the Boundary
The same message_id submitted multiple times is stored once. The boundary deduplicates automatically via putIfAbsent at the forward buffer. This is why retry with the same ID is safe.
Replay-Based Recovery
On boundary reconnection, facts are replayed from the last confirmed cursor position. No facts are skipped; no facts are lost or duplicated beyond what idempotent append ensures.
Application-Owned Idempotent Consumption
BSFG does not guarantee exactly-once delivery. Consumers must expect at-least-once and implement idempotent processing in their own application state and outcome handling.
Artifact Lifecycle
Producer Obligations
PutObject→ store artifact in zone-local bucket- Wait for durability acknowledgment
AppendFact→ reference artifact by bucket, key, digest- Do not modify or delete the artifact after fact-addressing
Consumer Retrieval
- Extract artifact reference from fact's
object_json - Call
GetObject(bucket, key)from zone-local object store - Verify digest if requiring high integrity assurance
- Retry if artifact is unavailable (may be in transit or temporarily unavailable)
- Do not treat transient unavailability as permanent failure
Common Misassumptions to Avoid
❌ Misassumption 1: "AppendFact confirms business acceptance"
WRONG. AppendFact confirms only that the fact was durably stored at the ingress boundary (ISB or ESB). It does not confirm delivery to or processing by any consumer. Business acceptance happens downstream, after the consumer processes and persists the outcome.
❌ Misassumption 2: "I should generate a new message_id on retry"
WRONG. Generating a new ID for a retry creates duplicate facts. The whole purpose of message_id is to make retries safe. Use the same ID and payload on every retry of the same business event.
❌ Misassumption 3: "Facts are delivered exactly once"
WRONG. BSFG guarantees at-least-once, not exactly-once. Consumer applications must implement idempotent processing. A fact may arrive multiple times due to producer retry, boundary replay, or consumer restart.
Integration Patterns
See the following guides for detailed implementation patterns:
- Producer Guide — emitter role, call sequences, examples
- Consumer Guide — receiver role, idempotency strategies, recovery