Data Contracts
CitadelMesh uses Protocol Buffers (protobuf) for all data contracts, ensuring type-safe, versioned, and efficient serialization across polyglot services. This document details all protobuf schemas, evolution strategies, and best practices.
Protobuf Packages
proto/citadel/v1/
├── telemetry.proto # Canonical telemetry and metrics
├── commands.proto # Control commands and results
├── incidents.proto # Security and operational incidents
├── policy.proto # Policy evaluations and decisions
├── twin.proto # Digital twin state and mutations
└── events.proto # Event type definitions
Core Message Types
Telemetry (telemetry.proto)
Purpose: Canonical representation of all building sensor data
syntax = "proto3";
package citadel.v1;
import "google/protobuf/timestamp.proto";
// Single telemetry point
message Point {
// Digital twin entity ID (e.g., "hvac.zone1.temp")
string entity_id = 1;
// Metric name (e.g., "temp.zone", "access.door")
string metric = 2;
// Numeric value
double value = 3;
// SI unit preferred (°C, kW, lux, etc.)
string unit = 4;
// Event timestamp (from source system)
google.protobuf.Timestamp timestamp = 5;
// Data quality indicator
string quality = 6; // "good" | "bad" | "uncertain"
// Additional metadata
map<string, string> attributes = 7;
}
// Batch of telemetry points
message PointBatch {
repeated Point points = 1;
// Source system identifier
string source_system = 2;
// Collection timestamp
google.protobuf.Timestamp collected_at = 3;
}
Example Usage:
from citadel.v1 import telemetry_pb2
from google.protobuf.timestamp_pb2 import Timestamp
point = telemetry_pb2.Point(
entity_id="hvac.zone1.temp",
metric="temp.zone",
value=72.5,
unit="°F",
timestamp=Timestamp(seconds=int(time.time())),
quality="good",
attributes={
"floor": "2",
"building": "building_a",
"zone_type": "office"
}
)
Commands (commands.proto)
Purpose: Control commands for building systems
syntax = "proto3";
package citadel.v1;
import "google/protobuf/timestamp.proto";
// Control command
message Command {
// Unique command ID (ULID)
string id = 1;
// Target entity (door, HVAC zone, light, etc.)
string target_id = 2;
// Action to perform (e.g., "unlock", "set_temp", "dim")
string action = 3;
// Action parameters
map<string, string> params = 4;
// Time-to-live for command
int32 ttl_seconds = 5;
// OPA-issued safety token
string safety_token = 6;
// Issuing agent/user SPIFFE ID
string issued_by = 7;
// Issue timestamp
google.protobuf.Timestamp issued_at = 8;
// Command priority
Priority priority = 9;
}
// Command priority levels
enum Priority {
PRIORITY_UNSPECIFIED = 0;
PRIORITY_LOW = 1; // Optimization, nice-to-have
PRIORITY_NORMAL = 2; // Standard operations
PRIORITY_HIGH = 3; // Urgent but not emergency
PRIORITY_EMERGENCY = 4; // Life safety, bypasses some checks
}
// Command execution result
message CommandResult {
// Reference to original command
string command_id = 1;
// Execution success
bool success = 2;
// Result message (error details if failed)
string message = 3;
// Vendor response data
map<string, string> response_data = 4;
// Execution timestamp
google.protobuf.Timestamp executed_at = 5;
// Execution time in milliseconds
int32 execution_time_ms = 6;
}
// Safety policy violation
message PolicyViolation {
// Reference to blocked command
string command_id = 1;
// Violated policy name
string policy_name = 2;
// Human-readable reason
string reason = 3;
// Policy evaluation context (for replay)
map<string, string> context = 4;
// Violation timestamp
google.protobuf.Timestamp occurred_at = 5;
}
Example Usage:
from citadel.v1 import commands_pb2
command = commands_pb2.Command(
id=ulid(),
target_id="door.lobby.main",
action="unlock_door",
params={
"duration_seconds": "300",
"reason": "Security incident response"
},
ttl_seconds=600,
safety_token="<OPA token>",
issued_by="spiffe://citadel.mesh/agent/security",
priority=commands_pb2.PRIORITY_HIGH
)
Incidents (incidents.proto)
Purpose: Security and operational incidents
syntax = "proto3";
package citadel.v1;
import "google/protobuf/timestamp.proto";
// Security or operational incident
message Incident {
// Unique incident ID (ULID)
string id = 1;
// Incident type
IncidentType type = 2;
// Severity level
Severity severity = 3;
// Location/zone identifier
string location = 4;
// Related signals (camera IDs, sensor IDs, events)
repeated string signals = 5;
// AI-generated hypothesis or description
string hypothesis = 6;
// Actions planned or executed
repeated string actions = 7;
// Owner (agent or human)
string owner = 8;
// Current status
IncidentStatus status = 9;
// Creation timestamp
google.protobuf.Timestamp created_at = 10;
// Resolution timestamp (if resolved)
google.protobuf.Timestamp resolved_at = 11;
// Additional metadata
map<string, string> metadata = 12;
}
enum IncidentType {
INCIDENT_TYPE_UNSPECIFIED = 0;
UNAUTHORIZED_ACCESS = 1;
FORCED_ENTRY = 2;
LOITERING = 3;
AFTER_HOURS_ACCESS = 4;
TAILGATING = 5;
FIRE_ALARM = 6;
INTRUSION_ALARM = 7;
HVAC_FAILURE = 8;
POWER_OUTAGE = 9;
}
enum Severity {
SEVERITY_UNSPECIFIED = 0;
INFO = 1;
LOW = 2;
MEDIUM = 3;
HIGH = 4;
CRITICAL = 5;
}
enum IncidentStatus {
STATUS_UNSPECIFIED = 0;
OPEN = 1;
IN_PROGRESS = 2;
ESCALATED = 3;
RESOLVED = 4;
CLOSED = 5;
}
Example Usage:
from citadel.v1 import incidents_pb2
incident = incidents_pb2.Incident(
id=ulid(),
type=incidents_pb2.FORCED_ENTRY,
severity=incidents_pb2.CRITICAL,
location="door.server_room",
signals=[
"door.server_room.sensor",
"camera.corridor_02.analytics"
],
hypothesis="Door forced open, no valid access card presented",
actions=["lock_adjacent_doors", "alert_security", "capture_video"],
owner="spiffe://citadel.mesh/agent/security",
status=incidents_pb2.OPEN
)
Policy (policy.proto)
Purpose: Policy evaluation requests and results
syntax = "proto3";
package citadel.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/struct.proto";
// Policy evaluation request
message EvaluationRequest {
// Policy ID to evaluate
string policy_id = 1;
// Evaluation context (JSON-encoded)
google.protobuf.Struct context = 2;
// Request ID for correlation
string request_id = 3;
// Requesting entity SPIFFE ID
string requester = 4;
}
// Policy evaluation result
message EvaluationResult {
// Original request ID
string request_id = 1;
// Policy ID evaluated
string policy_id = 2;
// Allow or deny
bool allow = 3;
// Human-readable rationale
string rationale = 4;
// Applied constraints
repeated string constraints = 5;
// Full explain trace (for debugging)
google.protobuf.Struct explain_trace = 6;
// Evaluation timestamp
google.protobuf.Timestamp evaluated_at = 7;
// Safety token (if allowed)
string safety_token = 8;
}
Digital Twin (twin.proto)
Purpose: Digital twin state and mutations
syntax = "proto3";
package citadel.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/struct.proto";
// Digital twin entity
message Entity {
// Entity ID (e.g., "hvac.zone1")
string entity_id = 1;
// Entity type (HVAC zone, door, camera, etc.)
string entity_type = 2;
// Parent entity (for hierarchy)
string parent_id = 3;
// Site/building ID
string site_id = 4;
// Ontology model (brick, haystack, custom)
string ontology_model = 5;
// Entity attributes (current state)
map<string, string> attributes = 6;
// Relationships to other entities
repeated Relationship relationships = 7;
// Last updated timestamp
google.protobuf.Timestamp updated_at = 8;
}
// Entity relationship
message Relationship {
// Relationship type (feeds, controls, part_of, etc.)
string type = 1;
// Target entity ID
string target_id = 2;
// Relationship metadata
map<string, string> metadata = 3;
}
// Twin mutation (state change)
message TwinMutation {
// Entity ID to mutate
string entity_id = 1;
// Mutation operation
MutationOp op = 2;
// Ontology model
string ontology_model = 3;
// Mutation payload (JSON-LD or JSON)
string payload = 4;
// Mutation source
string source = 5;
// Mutation timestamp
google.protobuf.Timestamp timestamp = 6;
}
enum MutationOp {
MUTATION_OP_UNSPECIFIED = 0;
UPSERT = 1; // Create or update
PATCH = 2; // Partial update
DELETE = 3; // Remove entity
}
Schema Evolution
Backward Compatibility Rules
- Never change field numbers
message Point {
string entity_id = 1; // NEVER change this to 2
string metric = 2;
// ...
}
- Add optional fields only
message Point {
string entity_id = 1;
string metric = 2;
double value = 3;
string unit = 4;
google.protobuf.Timestamp timestamp = 5;
string quality = 6;
map<string, string> attributes = 7;
// New field (safe to add)
string source_device_id = 8; // ✅ OK
}
- Reserve removed fields
message Point {
reserved 9; // Previously used field
reserved "old_field_name";
string entity_id = 1;
// ...
}
- Use new message versions for breaking changes
// Old version
package citadel.v1;
message Command { ... }
// New version with breaking changes
package citadel.v2;
message Command { ... }
Evolution Example
// v1.0 - Initial version
message Point {
string entity_id = 1;
string metric = 2;
double value = 3;
}
// v1.1 - Add optional fields
message Point {
string entity_id = 1;
string metric = 2;
double value = 3;
string unit = 4; // ✅ Added
Timestamp timestamp = 5; // ✅ Added
}
// v1.2 - Add quality field
message Point {
string entity_id = 1;
string metric = 2;
double value = 3;
string unit = 4;
Timestamp timestamp = 5;
string quality = 6; // ✅ Added
}
// v2.0 - Breaking change (requires new package)
package citadel.v2;
message Point {
string id = 1; // ❌ Breaking: renamed entity_id
Metric metric = 2; // ❌ Breaking: changed type
// ...
}
Code Generation
Multi-Language Support
# buf.gen.yaml
version: v1
plugins:
# Python
- plugin: python
out: src/proto_gen/python
opt: pyi_out=src/proto_gen/python
# Python gRPC
- plugin: grpc-python
out: src/proto_gen/python
# C# / .NET
- plugin: csharp
out: src/proto_gen/csharp
opt:
- base_namespace=CitadelMesh
# C# gRPC
- plugin: grpc-csharp
out: src/proto_gen/csharp
# TypeScript
- plugin: es
out: src/proto_gen/typescript
opt:
- target=ts
# TypeScript gRPC-Web
- plugin: grpc-web
out: src/proto_gen/typescript
Generated Code Usage
Python:
from citadel.v1 import telemetry_pb2
point = telemetry_pb2.Point()
point.entity_id = "hvac.zone1.temp"
point.value = 72.5
# Serialize
data = point.SerializeToString()
# Deserialize
received = telemetry_pb2.Point()
received.ParseFromString(data)
C#:
using CitadelMesh.V1;
var point = new Point
{
EntityId = "hvac.zone1.temp",
Value = 72.5
};
// Serialize
byte[] data = point.ToByteArray();
// Deserialize
var received = Point.Parser.ParseFrom(data);
TypeScript:
import { Point } from './proto/citadel/v1/telemetry_pb';
const point = new Point();
point.setEntityId('hvac.zone1.temp');
point.setValue(72.5);
// Serialize
const data: Uint8Array = point.serializeBinary();
// Deserialize
const received = Point.deserializeBinary(data);
Validation
CI Pipeline Checks
# .github/workflows/proto-validation.yml
name: Protobuf Validation
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Buf lint
run: buf lint
- name: Buf breaking change detection
run: buf breaking --against '.git#branch=main'
- name: Generate code
run: buf generate
- name: Run tests
run: pytest tests/proto/
Related Documentation
- Protocol Strategy - Protobuf in protocol layer
- Integration Matrix - Data contract usage
- Agent Topology - Agents using protobuf