Data model

This page synthesizes the entity/domain model across the three registry/telemetry services of the trustmint tier: ThingHub (the canonical thing registry), ThingIO (telemetry

  • scopes + pipelines), and ThingLab (device simulation). It is the cross-service map an integrator needs to reason about where a piece of state lives.

What’s live vs parked. ThingHub is the always-on registry. The device telemetry pipeline (EMQX MQTT ingress → thingio-consumer → TimescaleDB) is BUILT and wired but PARKED (DIAL_ONLY=true) — the ThingIO entities and query surface below are defined and stable, but live end-to-end telemetry consumption is gated. ThingLab runs api-only (THINGLAB_ROLE=api); its telemetry emitter role is parked. The live data plane is EMQX + TimescaleDB + Temporal + MSK (Kafka).

Service split at a glance

Service

Language

Owns

Live?

ThingHub

Go

Thing registry, Registrar, Assignment, Repository, Scope (identity), ThingModel

live

ThingIO

Go

Scope (identity + attribute), Pipeline, TelemetryRecord, ConsumerApp

telemetry pipeline parked (DIAL_ONLY)

ThingLab

Python (FastAPI, api-only)

Template, Fleet, Run

api live; emitter parked

All three delegate authorization to the DotID policy-engine (see ADR-0004) and authenticate via Keycloak (OIDC/JWT). Domain events flow over MSK (Kafka). Persistent state is PostgreSQL; telemetry time-series is TimescaleDB.


ThingHub — the thing registry

ThingHub is the universal thing registry and single source of truth for device identity, metadata, and lifecycle state. Other services reference ThingHub as the canonical device registry.

Thing

The core entity. A thing represents any device or asset — a robot, a sensor, a warehouse product, or any physical item that needs tracking.

Thing
├── thing_id            (nullable; null at enrollment, NanoID 16 chars assigned at provisioning)
├── serial_number       (optional, immutable once set; burned in factory)
├── device_key          (symmetric key registered by manufacturer; used for symmetric_key provisioning)
├── parent_thing_id     (nullable, for sub-device → gateway)
├── registrar_id        (registrar that created it — permanent, immutable)
├── access_code         (nullable; generated at provisioning; registrar can regenerate)
├── name                (display name)
├── status              (lifecycle state: ENROLLED → PROVISIONED → ACTIVE → ...)
└── attributes          (dynamic key-value pairs, JSONB)

Identity fields vs attributes. Identity fields (thing_id, serial_number, device_key, parent_thing_id, registrar_id, access_code) are structural — indexed, constrained, used for lookups. Attributes are descriptive key-value pairs (JSONB) — flexible, user-defined, and used for filtering and OTA target selection.

serial_number is optional because not every thing has one at enrollment: factory devices have SN burned in; cloud-first SKU registrations may not; warehouse inventory and virtual things never have one.

Device descriptor. Devices manufactured in-house are flashed with a read-only descriptor (analogous to USB VID/PID): vid (vendor), pid (product/model), hw_revision. The descriptor fields are attributes, not identity fields — they describe a class of device (many units share a vid:pid). At first boot the device reports them as attributes.

ThingModel

ThingModel
├── id
├── registrar_id        (registrar that owns this model)
├── name                (display name, e.g. "BROIT-X1")
├── properties          (JSONB — includes provisioning_method and provisioning_config)
├── created_at
└── updated_at

ThingAssignment

The account’s reference into a thing — the row that materializes the repository (see ADR-0001).

ThingAssignment
├── thing_id
├── account_id          (account with access)
├── org_id              (optional)
├── assigned_at
└── ended_at            (null = active)

Key design decisions:

  • account_id / org_id are attributes, not schema-level foreign keys — things are scoped and filtered by attributes.

  • Registrar is the account that created the thing: permanent, immutable, always has full access, can manage assignments and regenerate the access_code.

  • Assigned accounts are N accounts with concurrent access granted via access_code.

  • There is no owner / claim / transfer concept. Access is additive and managed through assignments. See thing access model.

Registrar & Repository

Registrar (PENDING → ACTIVE | REJECTED) and the per-account Repository (1:1 with account, materialized by the repositories table) are covered in Registrar/ThingMake/Arsenal and ADR-0003. Group and tag memberships key off the assignment row, not the thing PK — see ADR-0002.

Scope (ThingHub)

A scope is a per-account saved dynamic filter over the account’s assigned things, filtering identity fields only (type, model_id, status, registrar_id). Scopes are private (not shareable, even within an org) and re-evaluate live. Attribute-based filtering is ThingIO’s job (below).

Provisioning validator

When a device enrolls via DDI with a proof field, ThingHub/thingmake dispatches the proof to a provisioning validator selected by the thing model’s provisioning_method. Five methods:

Method

Proof

How it works

auto

none

Provisioned immediately on DDI enrollment; no verification. Low-security / dev.

symmetric_key

{ "device_key": "<key>" }

Validator SHA-256-hashes the key and compares against the stored device_key hash.

certificate

{ "certificate_pem": "<PEM>" }

X.509 chain validation against the manufacturer’s trusted CA from provisioning_config.

webhook

{ "payload": <any> }

Validator calls the manufacturer’s HTTPS webhook; expects { "verified": true/false }.

none

n/a

DDI provisioning disabled; provision only via the management API.

Provisioning method is per-model, stored in ThingModel.properties JSONB. On success: thing_id (NanoID) assigned, status → PROVISIONED, access_code generated, X.509 cert + license token issued. On failure: 403 Forbidden, thing stays ENROLLED.

Lifecycle: ENROLLED PROVISIONED ACTIVE SUSPENDED DECOMMISSIONED. (This is the ThingHub registry lifecycle; the device-facing wire lifecycle is the five-state state machine.)

Domain events (MSK / Kafka)

ThingHub publishes: thinghub.thing.enrolled, thinghub.thing.provisioned, thinghub.thing.state-changed, thinghub.thing.updated, thinghub.thing.decommissioned. ThingIO and ThingLab consume these to maintain local device references.


ThingIO — telemetry, scopes, pipelines

ThingIO provides tenant-scoped device grouping (Scopes), telemetry storage/query, configurable stream-processing pipelines, and a consumer-app registry. Four tenant-scoped core entities:

Scope (ThingIO)

Like ThingHub scopes but richer: ThingIO scopes filter by identity fields (via ThingHub) and device attributes (location, firmware_version, fleet, region, manufacturer, …). Attribute filters support trailing-* prefix wildcards.

{
  "device_types": ["cleaning_robot", "sensor"],
  "attributes": {
    "location": "warehouse-*",
    "firmware_version": "2.*",
    "fleet": "production"
  }
}

Evaluation: ThingIO fetches the identity-filtered thing list from ThingHub, resolves each thing’s attributes, applies the attribute filters (with wildcards), returns the matched set.

Fields: id, tenant_id, name, description, filter (JSON), thing_count, created_by (user|app|system), app_id (optional ConsumerApp link), timestamps.

Pipeline

A Pipeline is a stream-processing job over device telemetry. Three-tier model:

  • Tier 1 — Managed: auto-provisioned per tenant on first device enrollment; not tenant-editable. Templates: device_health_rollup, telemetry_aggregation, anomaly_baseline.

  • Tier 2 — App: created by tenants from configurable templates (threshold_alert, geo_fence, rolling_average); tenant-controlled lifecycle.

  • Tier 3 — Platform: reserved for platform-level pipelines.

Fields: id, tenant_id, name, description, tier, status (running|stopped|error|provisioning), template, params (JSON), scope_id, app_id, input_topics, output_topics, timestamps.

Pipelines are part of the parked telemetry pipeline (DIAL_ONLY). The entity + API are defined; live stream execution is gated until the consumer is dialled on.

TelemetryRecord

An individual telemetry data point, stored in TimescaleDB (hypertable time-series).

Fields: id, tenant_id, thing_id, key (metric name), value (serialized), ts (epoch-ms), ingested_at.

ConsumerApp

An external OAuth2 application registered to consume ThingIO data.

Fields: id, tenant_id, name, description, client_id (globally unique), scopes_count, pipelines_count, permissions (JSON array), timestamps.

Telemetry data plane (when dialled on)

Devices publish over EMQX (MQTT 5.0, mTLS on 8883; see the DDI-1 contract §4). thingio-consumer ingests from the MQTT ingress and writes TelemetryRecords into TimescaleDB; domain/analytics events flow over MSK; Temporal orchestrates longer-running workflows. Query is exposed as paginated telemetry queries (filters on thing, scope, key, time range) at console.flexgalaxy.ai/thingio/.


ThingLab — device simulation (api-only)

ThingLab lets developers create virtual device fleets, define sensor/software profiles, and execute emulation runs producing realistic telemetry — end-to-end IoT workflow testing without physical hardware. It runs api-only today: the FastAPI management surface (THINGLAB_ROLE=api) is live; the telemetry emitter role (THINGLAB_ROLE=emitter) is parked pending its device-SDK + emulator dependency, which now lives in the separate SiriusVoyager/ddi repo.

Template

A reusable device archetype: base thing type (maps to a ThingHub type), sensor profiles (type, data range, sampling rate, noise model), software profile (firmware version, capabilities, OTA compatibility), connectivity (protocol + behavior), failure modes.

Fleet

A group of virtual devices for a test scenario: template reference, fleet size (1–10,000), variation rules (firmware spread, sensor calibration offsets), optional geographic distribution, scheduling.

Run

A time-bounded simulation execution: fleet references, duration, scenario (e.g. OTA rollout, mass disconnect, load spike), telemetry output (to ThingIO over the same MQTT/HTTP path as real devices — emitter, parked), post-run results (messages sent, errors, latency).

Sensor profiles model realistic telemetry (type, distribution, sampling rate, noise, failure injection); software profiles simulate firmware/OTA cycles and rollback scenarios. These are configuration the api surface manages; actual telemetry emission is gated on the parked emitter role.