Integration

BSFG Message Catalog Framework

Registry format for facts exchanged between zones

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:

flowchart TD
  M["message"]
  E["envelope (system-level metadata)"]
  F["fact (domain-level semantics)"]

  M --> E
  M --> F

  E --> E1["message_id"]
  E --> E2["from_zone"]
  E --> E3["to_zone"]
  E --> E4["produced_at_unix_ms"]
  E --> E5["correlation_id"]
  E --> E6["causation_id"]
  E --> E7["labels"]
  E --> E8["object_media_type"]
  E --> E9["object_schema"]

  F --> F1["subject"]
  F --> F2["predicate"]
  F --> F3["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:

  1. Producer releases new code that emits both old and new schema versions (dual-emit)
  2. All consumers upgrade to handle the new schema
  3. 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)