Commands
PartialA command is the “do” half of the telemetry model, the third of the triad alongside a
property (know) and an event (happen).
Where a property’s samples record what a device reports and an event records what happened, a command
records what a component was told. Its registry is command_type, the driver-owned catalog of
what a component can be told, the twin of property_type and event_type.
The command_type registry
Section titled “The command_type registry”command_type describes every command: (name, display_name, params_schema, settle_window_seconds, target_property_type_id, official). Two facts are the driver’s, not the abstract signal’s:
settle_window_secondsis how long the device physically takes to actuate. The driver knows a projector warms up in twenty seconds and a matrix switch flips in one; the settle window is that fact, so a difference from the reported value is not called drift until the device has been given time to act.target_property_type_idis the uuid FK to the property a settleable command sets (set_inputtargetsvideo.input). A command with no target is fire-and-forget (reboot): it records the invocation and a caused event, with no value to settle.
The registry is seeded official and operator-extensible, official rows read-only, on the same shape
as the property and
event catalogs (a console Command Types page,
/command-types CRUD gated by command_type:*).
Issuing a command composes the whole model
Section titled “Issuing a command composes the whole model”Issuing a command is one transaction that writes three things, which is where the three pillars meet:
- the
commandrow (the invocation: owner, command_type, params, actor), over the same exclusive owner arc as every sample and event; - a caused
event(origin=caused, typedcommand.issued), the lineage record that a command happened; and - for a settleable command, an
intendedvalue in the property cache (provenance=intended, itststhe moment of issue), the told in the want/told/is pivot.
So a command is not a side channel: it feeds the same cache and event log everything else reads.
POST /components/{name}/commands:issue is the write, gated by command:issue and scope-injected
through the component.
Settlement is computed, never stored
Section titled “Settlement is computed, never stored”A command’s effect is judged, not recorded. The verdict is a pure function of the intended value it opened, the observed value the device reports, and the settle window:
- none: nothing was told (a fire-and-forget command, or no intended value).
- pending: still within the settle window since the command was issued, so a difference from observed is not yet drift, the device is given time to actuate.
- settled: past the window, the observed value matches the intended one, the command took effect.
- failed: past the window, the observed value does not match (or is absent), the command did not take effect.
This closes the loop the reconciliation read opens: a command sets
told, the device reports is, and settlement is whether they agree within the window the
driver declares. It is the windowed form of the same told versus is comparison, so nothing new
is stored, only judged on read.
Storage
Section titled “Storage”The physical layout (the owner arc, the caused-event lineage, partitioning) lives on storage.
command_type is the do registry; the settle window and the target property are driver facts.
| Column | Type | Constraints | Notes |
|---|---|---|---|
id | uuid | PK, default uuidv7() | |
name | text | not null | |
display_name | text | ||
description | text | not null, default ''::text | |
params_schema | jsonb | The JSON Schema an invocation's params must satisfy | |
settle_window_seconds | integer | not null, default 0 | How long settlement watches for the observed value to agree |
target_property_type_id | uuid | FK → property_type.id | The property the command intends to change, when it targets one |
official | boolean | not null, default false | Shipped-canonical versus org-local; seed-owned rows are read-only |
registered_at | timestamp with time zone | not null, default now() |
CHECK constraints and unique indexes on command_type
-
command_type_name_key:CREATE UNIQUE INDEX command_type_name_key ON public.command_type USING btree (name)
command is the invocation log; caused_event_id points at the event the command recorded.
| Column | Type | Constraints | Notes |
|---|---|---|---|
id | bigint | PK | |
ts | timestamp with time zone | not null, default now() | |
owner_kind | text | not null | |
instance | text | not null, default ''::text | |
params | jsonb | The invocation arguments, validated against the registry schema | |
actor | uuid | Who told it | |
component_id | uuid | FK → component.id | |
system_id | uuid | FK → system.id | |
location_id | uuid | FK → location.id | |
node_id | uuid | FK → node.principal_id | |
command_type_id | uuid | FK → command_type.id, not null | What the component was told to do |
caused_event_id | bigint | FK → event.id | The recorded event this invocation caused |
CHECK constraints and unique indexes on command
-
command_owner_arc_check:CHECK ((((owner_kind = 'component'::text) AND (component_id IS NOT NULL) AND (system_id IS NULL) AND (location_id IS NULL) AND (node_id IS NULL)) OR ((owner_kind = 'system'::text) AND (system_id IS NOT NULL) AND (component_id IS NULL) AND (location_id IS NULL) AND (node_id IS NULL)) OR ((owner_kind = 'location'::text) AND (location_id IS NOT NULL) AND (component_id IS NULL) AND (system_id IS NULL) AND (node_id IS NULL)) OR ((owner_kind = 'node'::text) AND (node_id IS NOT NULL) AND (component_id IS NULL) AND (system_id IS NULL) AND (location_id IS NULL)))) -
command_owner_kind_check:CHECK ((owner_kind = ANY (ARRAY['component'::text, 'system'::text, 'location'::text, 'node'::text])))
Related: properties (the intended value a command opens), events (the caused event it records), config, secrets, and variables (the reconciliation read settlement closes), and alarms and actions (where a reconcile policy issues a command).