Data Contracts
This article assumes you've completed the Getting Started guide and understand the data modeling concepts.
Data contracts are the enforcement mechanism that makes data models mandatory. Without a contract, models are just documentation - contracts make validation happen.
Overview
In the component chain, contracts sit between models and execution:
Payload Shapes → Data Models → Data Contracts → Data Flows
↑
Enforcement happens hereWhen you specify a data contract in a bridge, the UNS output plugin validates every message against the associated model.
UI Capabilities
The Management Console provides read-only access to contracts:
View contract list
✅
Shows all contracts with their models
Filter/search
✅
Filter by name, instance, or model
View associations
✅
See which model version each contract enforces
View usage
✅
Shows count of stream processors using contract
Create contracts
❌
Auto-created when creating models in UI
Edit contracts
❌
Immutable once created
Delete contracts
❌
Must be done via config.yaml

What you see in the UI:
Name: Contract identifier (e.g.,
_cnc_v1,_pump_v2)Instance: Which UMH Core instance owns the contract
Model: The model and version being enforced (e.g.,
cnc (v1))Stream Processors: Count of processors using this contract
Auto-Creation via UI
When you create a model in the UI, it automatically generates a matching contract:
Create model
pumpversionv1in UISystem auto-creates contract
_pump_v1Contract immediately available for use in bridges
Configuration
Access configuration via: Instances → Select instance → ... → Config File
Basic Structure
datacontracts:
- name: _pump_v1 # Contract name (used in bridges)
model:
name: pump # References a data model
version: v1 # Specific version to enforceNaming Convention
Contracts follow the pattern _modelname_version:
Always start with underscore
Include model name
End with version number
Examples:
_pump_v1,_temperature_sensor_v2,_workorder_v1
Enforcement Mechanism
Where Validation Happens
Bridge → UNS Output Plugin → [Contract Check] → Kafka/Redpanda
↑
Validation happens hereThe UNS output plugin (output: uns: {}) performs validation:
Reads metadata: Extracts
data_contractfrom messageLooks up contract: Finds the associated model
Validates structure: Checks topic path matches model
Validates payload: Ensures data types match shapes
Result:
✅ Valid → Message published to topic
❌ Invalid → Message rejected, bridge degraded
Validation Failures
When validation fails:
ERROR: schema validation failed for message with topic 'umh.v1.enterprise.site._pump_v1.invalid.path':
Valid virtual_paths are: [pressure, temperature, motor.rpm].
Your virtual_path is: invalid.pathResult:
Message rejected (not published)
Bridge enters degraded state
Error logged with details
Bridge retries with backoff
Contract Types
The Special _raw Contract
# No explicit definition needed - always available
msg.meta.data_contract = "_raw";Accepts any structure
No validation performed
Use for exploration and development
Bridge never goes degraded from data issues
Model-Based Contracts
datacontracts:
- name: _pump_v1
model:
name: pump
version: v1Enforces exact model structure
Validates data types via payload shapes
Rejects non-conforming messages
Use for production systems
Relationship to Data Types
Raw/Exploration
_raw
No validation
Device Models
_pump_v1, _sensor_v1, _cnc_v1
Model-based validation
Business Models
_workorder_v1, _maintenance_v1
Always strict
Examples
Simple Device Contract
# Model definition
datamodels:
- name: temperature-sensor
version:
v1:
structure:
celsius:
_payloadshape: timeseries-number
# Contract (auto-created or manual)
datacontracts:
- name: _temperature-sensor_v1
model:
name: temperature-sensor
version: v1
# Usage in bridge
msg.meta.data_contract = "_temperature-sensor_v1";
msg.meta.tag_name = "celsius";
msg.payload = 23.5;Relationship to Stream Processors
Stream processors don't use contracts directly - they reference models:
templates:
streamProcessors:
pump_aggregator:
model: # Direct model reference
name: pump
version: v1However, if a matching contract exists (_pump_v1), the stream processor's output will be validated against it automatically.
Last updated

