Registrar, ThingMake & Arsenal — Design Document¶
This document describes the design for the Registrar entity model, the ThingMake manufacturer console, the Arsenal platform-admin console, the provisioning workflow, saved filters, and cross-account registrar access control.
Overview¶
The base ThingHub model stored registrar_id as an account ID string directly on each
Thing. This design introduces Registrar as a first-class entity with a
platform-admin-approved lifecycle, enabling:
ThingMake — a dedicated manufacturer console for enrolling things and managing registrars
Arsenal — a platform admin console for approving registrar creation and access requests
Provisioning — a distinct step after enrollment that generates access/join codes and QR URLs
Saved filters — named filters across accessible registrars for discovering and bulk-adding things
Cross-account registrar access — platform-admin-gated access for browsing registrars and OTA target filters
ThingHub refocus — repository and scope management only; enrollment moves to ThingMake
Data model¶
Registrar¶
A Registrar is a named container of things, owned by a manufacturer account and approved by a platform admin.
Registrar
├── id (UUID, primary key)
├── name (display name, e.g. "RB-200X Production Line")
├── account_id (manufacturer account that owns this registrar)
├── status (PENDING → ACTIVE | REJECTED)
├── created_at
└── updated_at
Rules:
A manufacturer can have multiple registrars (one per product line, factory, etc.)
A thing belongs to exactly one registrar (immutable after enrollment)
Registrar creation requires platform admin approval via Arsenal
The manufacturer gets default access to their own registrar upon approval
Registrar Access Request¶
A RegistrarAccessRequest represents a tenant account requesting blanket access to all things in a registrar.
RegistrarAccessRequest
├── id (UUID, primary key)
├── registrar_id (FK → Registrar)
├── account_id (tenant requesting access)
├── status (PENDING → APPROVED | REFUSED)
├── created_at
└── updated_at
Rules:
Access is blanket — grants visibility to all things in the registrar
Approval is a platform admin decision (via Arsenal), not the manufacturer’s
One active request per (registrar, account) pair
Currently used for OTA target-filter access; reusable for future capabilities
Saved Filter¶
A RegistrarSavedFilter is a named, persisted search across one or more accessible registrars.
RegistrarSavedFilter
├── id (UUID, primary key)
├── account_id (who saved it)
├── name (e.g. "CJ-5000 Warehouse Robots")
├── registrar_ids (comma-separated registrar IDs to search across)
├── filter (attribute filter conditions, e.g. "model=CJ-5000&location=warehouse-b")
├── created_at
└── updated_at
Rules:
Saved filters execute live queries against registrar things (not snapshots)
Can reference multiple registrars — results are merged
Registrar access is validated at execution time (revoked access = execution fails)
Newly enrolled things matching the filter appear on re-execution
Dismissed things disappear on re-execution
Used to discover things, then bulk-add to repository via
POST /api/v1/repository/things
Thing lifecycle changes¶
Things gain a join_code (NanoID) and provisioned_at timestamp. The lifecycle becomes:
ENROLLED (draft)
│ Thing created in ThingMake, assigned to a registrar.
│ No join code yet. Cannot be joined.
│
▼
PROVISIONED
│ Provisioning triggered (manually, via API, or from a device).
│ join_code (NanoID) generated. QR code URL available.
│ Serial number optional — things can be provisioned without SN.
│
▼
JOINED (in repository)
User scans QR or enters join code → thing linked to account's repository.
New fields on Thing:
Thing (additions)
├── join_code (NanoID, unique, generated at provisioning — NULL until provisioned)
├── provisioned_at (timestamp, NULL until provisioned)
└── registrar_id (now references Registrar.id instead of raw account ID)
The join_code is a URL-safe NanoID (21 chars). The QR code URL is computed:
https://console.flexgalaxy.ai/thinghub/join/{join_code}
This deep-links into ThingHub’s join flow.
The existing access_code (XXXX-YYYY-ZZZZ format) remains for manual entry. Both
access_code and join_code can be used to join a thing.
Repository model¶
Each tenant account has one thing repository — the collection of all things joined by that account.
Tenant Account
└── Thing Repository (single, account-scoped)
├── Thing A (from Registrar X, joined via QR scan)
├── Thing B (from Registrar X, joined via QR scan)
└── Thing C (from Registrar Y, joined via batch URL)
The repository is represented by the set of active ThingAssignment records for the account.
No new entity is needed. See ADR-0003
for how the repository scope is materialized in the schema.
Application architecture¶
New apps¶
App |
URL |
Users |
KC Client |
Realm |
|---|---|---|---|---|
ThingMake |
|
Manufacturers |
|
flexgalaxy (tenant) |
Arsenal |
|
Platform admins |
|
master |
Modified apps¶
App |
Changes |
Why |
|---|---|---|
ThingHub |
Remove enroll and join buttons. Add repository listing ( |
ThingHub becomes the repository and scope management app. Enrollment moves to ThingMake. Things enter the repository via access_code or registrar bulk add. |
OTA target filters |
Show accessible registrars. Filter things by registrar for OTA rollout targeting. |
OTA target filters require registrar access (cross-account authorization). |
ThingMake¶
Purpose: Manufacturer console for enrolling things, provisioning, and managing registrars.
Location: console.flexgalaxy.ai/thingmake/
Backend: ThingHub API (same backend, proxied via console-gateway)
KC Client: thingmake (flexgalaxy realm)
Port: 5189 (local dev)
Pages:
Page |
Route |
Description |
|---|---|---|
Things |
|
List of things enrolled by this manufacturer. Filter by registrar. Enroll button. Provision button for selected things. |
Thing Detail |
|
Thing info, provision button (if ENROLLED), join code + QR display (if PROVISIONED), serial number management. |
Settings |
|
Registrar management. List registrars owned by this account. Create new registrar (triggers approval request). Status badges (PENDING/ACTIVE/REJECTED). |
Profile |
|
User profile. |
Enrollment flows:
With serial numbers — manufacturer integrates via API, provides SNs from their system → things can be provisioned immediately
Without serial numbers — enroll by model/type + quantity → things created as ENROLLED (draft), provisioned later manually
Arsenal¶
Purpose: Platform admin console for registrar governance and access-request management.
Location: stargate.flexgalaxy.ai/arsenal
Backend: ThingHub API (cross-cluster via admin-gateway proxy)
KC Client: arsenal (master realm)
Port: 5191 (local dev)
Pages:
Page |
Route |
Description |
|---|---|---|
Registrars |
|
List all registrars. Filter by status. Approve/reject pending requests. View registrar details. |
Access Requests |
|
List registrar access requests. Approve/refuse pending requests. |
Profile |
|
User profile. |
API design¶
New endpoints (ThingHub backend)¶
Registrar API (/api/v1/registrars):
Method |
Path |
Description |
|---|---|---|
POST |
|
Create registrar (manufacturer). Sets status=PENDING. |
GET |
|
List registrars. Filter by |
GET |
|
Get registrar detail. |
PUT |
|
Approve registrar (platform admin only). Sets status=ACTIVE. |
PUT |
|
Reject registrar (platform admin only). Sets status=REJECTED. |
Registrar Access Request API (/api/v1/registrar-access-requests):
Method |
Path |
Description |
|---|---|---|
POST |
|
Request access to a registrar (tenant). |
GET |
|
List requests. Filter by |
PUT |
|
Approve access (platform admin only). |
PUT |
|
Refuse access (platform admin only). |
Repository API (/api/v1/repository):
Method |
Path |
Description |
|---|---|---|
GET |
|
List things in the caller’s repository (with optional attr.* filters). |
POST |
|
Bulk add things to repository by thing_ids (requires registrar access per thing). |
Saved Filter API (/api/v1/saved-filters):
Method |
Path |
Description |
|---|---|---|
GET |
|
List saved filters for the current account. |
POST |
|
Create a saved filter (name, registrar_ids, filter conditions). |
GET |
|
Execute saved filter — live query across referenced registrars. |
DELETE |
|
Delete a saved filter. |
Scope Things API (/api/v1/scopes/{id}/things):
Method |
Path |
Description |
|---|---|---|
GET |
|
List things in a scope. |
POST |
|
Add a repository thing to a scope. |
DELETE |
|
Remove a thing from a scope. |
Provisioning endpoint (/api/v1/things):
Method |
Path |
Description |
|---|---|---|
POST |
|
Provision a thing. Generates join_code (NanoID). Sets status=PROVISIONED. |
POST |
|
Batch provision multiple things. |
Modified join endpoint:
POST /api/v1/things/join now accepts either access_code (existing) or join_code (new
NanoID from QR).
Public API (via KrakenD)¶
ThingMake’s public API for manufacturer system integration is exposed via KrakenD at
api.flexgalaxy.ai:
POST /things/v1/things → Enroll thing(s)
POST /things/v1/things/{id}/provision → Provision thing
POST /things/v1/things/batch-provision → Batch provision
GET /things/v1/things → List things (with filters)
GET /things/v1/things/{id} → Get thing detail
Access control summary¶
Action |
Who |
How |
|---|---|---|
Create registrar |
Manufacturer (with |
ThingMake Settings → Arsenal approves |
Approve/reject registrar |
Platform admin |
Arsenal |
Enroll things |
Manufacturer (with |
ThingMake → select registrar → enroll |
Provision things |
Manufacturer / device / API |
ThingMake or API → generates join_code |
Add thing to repository (access_code) |
Any authenticated user |
Scan QR code or enter access_code → |
Add things to repository (bulk) |
Account with registrar access |
Browse registrar → select things → |
Request registrar access |
Any tenant account |
Request access → Arsenal approves |
Approve/refuse access request |
Platform admin |
Arsenal |
Create OTA target filter |
Account with registrar access + |
Filter over accessible registrar things |
All authorization checks are delegated to the DotID policy-engine — see ADR-0004 and roles and access.
Migration strategy¶
This is a hard cutover — enrollment functionality is removed from ThingHub and moved to ThingMake.
Database migrations:
V6: Create
registrarstableV7: Create
registrar_access_requeststableV8: (removed — batch join tokens deprecated in favor of bulk repository add via registrar access)
V9: Add
join_codeandprovisioned_attothingstableV16: Create
registrar_saved_filterstableData migration: For existing things with
registrar_idset to an account ID, create a default Registrar entity per unique account and update the FK
The existing registrar_id column on things transitions from storing an account ID string
to storing a Registrar entity UUID.