Skip to content

Commands

Partial

A 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.

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_seconds is 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_id is the uuid FK to the property a settleable command sets (set_input targets video.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:

  1. the command row (the invocation: owner, command_type, params, actor), over the same exclusive owner arc as every sample and event;
  2. a caused event (origin=caused, typed command.issued), the lineage record that a command happened; and
  3. for a settleable command, an intended value in the property cache (provenance=intended, its ts the 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.

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.

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.

command_type (telemetry): generated from the migrated schema by make gen
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.

command (telemetry): generated from the migrated schema by make gen
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).