Audience: Schema owners, integrators. Use: Define and govern the catalog of facts exchanged in a BSFG deployment.
Overview
BSFG transports facts rather than RPC commands or request/response messages. A fact is an immutable statement about the world, with semantic meaning shared across zones.
Systems integrating with BSFG must establish a fact vocabulary — an agreed-upon set of predicates, subject types, and object schemas that describe the domain and maintain semantic consistency across producers and consumers.
The Message Catalog Framework provides a standardized format to document and register all facts used in a deployment. It becomes the semantic contract between systems and serves as the definitive reference for what facts are exchanged, who produces them, and what they mean.
Why Facts, Not Messages?
Traditional integrations exchange messages or events, which are often imperative or instructional:
"Process this order now"
"Run batch #42"
"Update temperature reading"
BSFG instead exchanges facts, which are declarative statements about what has happened or what is true:
order:ENT-12345 order_created {...}
batch:PlantA/B1042 batch_started {...}
sensor:T-023 temperature_recorded {...}
This shift from imperative to declarative enables:
- Asynchronous autonomy: Consumers decide when and how to react; they are not commanded
- Replay safety: Facts are idempotent; replaying them does not create side effects
- Immutability: Facts are appended once and never modified; history is authoritative
- Decoupling: Producers and consumers need not know each other; the fact speaks for itself
Fact Structure
Every fact transported by BSFG has a canonical structure:
message
├── envelope (system-level metadata)
│ ├── message_id
│ ├── from_zone
│ ├── to_zone
│ ├── produced_at_unix_ms
│ ├── correlation_id
│ ├── causation_id
│ ├── labels
│ ├── object_media_type
│ └── object_schema
└── fact (domain-level semantics)
├── subject
├── predicate
└── object_json
The envelope is handled by BSFG infrastructure and remains consistent across all facts. The fact is where domain semantics live.
Subject
The subject identifies the entity the fact is about:
subject := kind:id | kind:scope/id
Examples:
- order:ENT-12345 — a specific order
- batch:PlantA/B1042 — a batch at a specific plant
- recipe:ENT/R-STANDARD — a recipe scoped to the enterprise
- machine:PlantB/MX-07 — equipment at a plant
Predicate
The predicate expresses the relationship or state transition. It defines semantic meaning:
predicate := stable lower_snake_case relation
Examples:
- order_created — an order came into being
- order_modified — order details changed
- batch_started — a batch began execution
- batch_completed — a batch finished
- step_completed — a manufacturing step ended
- temperature_recorded — a sensor reading was logged
Predicates are stable: once deployed, they do not change. New predicates are added; old ones are never removed or renamed (maintaining replay compatibility).
Object JSON
The object carries the payload — domain-specific data in canonical JSON format.
{
"order_id": "ENT-12345",
"customer_id": "CUST-999",
"total_amount": 15000.0,
"currency": "USD",
"ship_to": "Plant A",
"created_at": "2026-03-06T14:30:00Z"
}
The structure and semantics of the object are registered in the message catalog (see Catalog Table section below).
Message Catalog Table
Every fact type used in an integration must be registered in a message catalog table. This table serves as the authoritative reference for fact vocabulary.
Use this template to document facts:
| Predicate | Subject Type | Producer(s) | Consumer(s) | Schema ID | Description |
|---|---|---|---|---|---|
| [predicate_name] | [kind] | [system name] | [system names] | [schema_id_v1] | [semantic meaning in one sentence] |
Example Entries
Here are realistic catalog entries for a typical manufacturing integration:
| Predicate | Subject Type | Producer(s) | Consumer(s) | Schema ID | Description |
|---|---|---|---|---|---|
| order_created | order | ERP | MES, Data Lake | order_created_v1 | New sales order received from customer or internal demand |
| order_modified | order | ERP | MES, Data Lake | order_modified_v1 | Order quantity, destination, or deadline changed in ERP |
| order_shipped | order | ERP | Data Lake, Analytics | order_shipped_v1 | Order fulfilled and shipped to customer or plant |
| batch_started | batch | MES | ERP, Data Lake, Historian | batch_started_v1 | Production batch began execution on the plant floor |
| batch_completed | batch | MES | ERP, Data Lake, Quality System | batch_completed_v1 | Production batch finished and passed all quality gates |
| step_completed | batch_step | MES / Equipment Controller | ERP, Data Lake | step_completed_v1 | A manufacturing step (e.g., mixing, heating, cooling) finished within a batch |
| temperature_recorded | sensor_reading | Data Logger / IoT | Historian, Data Lake | temperature_recorded_v1 | Temperature measurement from equipment sensor at a point in time |
| quality_result_recorded | quality_test | Quality System / Lab | ERP, Data Lake, Batch System | quality_result_recorded_v1 | Lab result or quality inspection outcome for a batch or material lot |
| maintenance_completed | equipment | Maintenance System | ERP, Data Lake, MES | maintenance_completed_v1 | Preventive or corrective maintenance work finished on equipment |
Schema Governance
Every fact type has an associated object schema that defines the structure, field names, types, and required vs. optional fields.
Schema Ownership
The producer owns the schema. The producer defines what fields the object contains. Consumers must accept the schema as-is and adapt their processing accordingly.
Examples:
- order_created is produced by ERP → ERP owns and defines order_created_v1
- batch_started is produced by MES → MES owns and defines batch_started_v1
- temperature_recorded is produced by equipment → equipment producer owns the schema
Schema Immutability
Once a schema is deployed in production, it becomes immutable. The object structure must not change. If a new field is needed:
- Backwards compatible: Add an optional field → consumers ignore unknown fields
- Breaking change: Rename field, change type, or remove field → create new schema version
Examples:
order_created_v1 (original)
order_created_v2 (added optional "internal_notes" field)
order_created_v3 (changed "total_amount" type, breaking change)
Schema Registration
For each fact type, register the schema JSON Schema (or equivalent description). Example:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "order_created_v1",
"title": "Order Created",
"description": "A new sales order was created",
"type": "object",
"properties": {
"order_id": { "type": "string" },
"customer_id": { "type": "string" },
"total_amount": { "type": "number" },
"currency": {
"type": "string",
"enum": ["USD", "EUR"]
},
"ship_to": { "type": "string" },
"created_at": {
"type": "string",
"format": "date-time"
}
},
"required": ["order_id", "customer_id", "total_amount"]
}
Store these schemas in a central registry (e.g., Git, schema registry, or documentation wiki) so all systems can reference them.
Versioning Rules
BSFG fact predicates and schemas follow strict versioning rules to maintain compatibility and prevent silent failures.
Predicate Versioning
Predicates are never versioned. Once order_created is defined, it remains stable forever. New predicates are created for genuinely new semantic meanings; old predicates are never renamed or removed.
If a different order creation flow emerges, create a new predicate:
order_created (original flow)
order_created_manual (new: manually entered order)
order_created_import (new: imported from legacy system)
Schema Versioning
Schemas are versioned using semantic versioning tied to the predicate:
[predicate]_v[major]
order_created_v1
order_created_v2
batch_started_v1
Versioning policy:
- v1: Original schema deployed to production
- v2: Backwards-compatible change (new optional fields, no deletions, no type changes)
- v3: Breaking change (field renamed, type changed, field required → optional or vice versa)
Backwards compatibility rule: Consumers written for order_created_v1 must continue to work when they receive order_created_v2 facts.
Breaking Changes
When a breaking change is necessary, increment the schema version and both producer and all consumers must be updated simultaneously:
- Producer releases new code that emits both old and new schema versions (dual-emit)
- All consumers upgrade to handle the new schema
- Producer switches off the old schema version
Message Catalog as Semantic Contract
The message catalog is the definitive contract between all systems. It serves as:
- API specification: What facts exist? What do they contain? Who produces them?
- Semantic reference: What does each predicate mean in the business domain?
- Verification checklist: Are all required facts documented? Are schemas registered?
- Change log: When and why did a fact or schema change?
Treat the message catalog as you would treat an API contract: update it when facts change, review it before deployment, and maintain it as the authoritative source of truth.
Catalog Checklist
Before deployment, verify:
- ☐ All fact predicates used in the integration are listed in the catalog
- ☐ For each predicate: subject type, producers, and consumers are identified
- ☐ For each predicate: schema ID is registered and JSON Schema is available
- ☐ Schemas are reviewed by all producers and key consumers
- ☐ All schema versions are stored in a central registry (Git, docs, or schema service)
- ☐ Producers understand they own schema definitions
- ☐ Consumers understand backward compatibility expectations
- ☐ Versioning rules are understood by all teams
- ☐ Catalog is accessible to all integrating systems (wiki, Git, or portal)
Cross-Links to Related Documentation
- Message Model — Envelope structure and fact semantics
- Integration Contract — Producer and consumer obligations
- Producer Guide — How to emit facts correctly
- Consumer Guide — How to consume and process facts
- Architecture Map — Message model and system layers