Run operations and procedures
Define what can be done to a scene as versioned operations, run them through their life cycle, undo model edits, and chain them into a procedure.
An operation is an Action with everything a server and an agent need to know about it: a version, an effect class, a parameter schema, a timeout and, for anything that leaves the scene, a compensation. A registry describes operations; it never decides who may run them.
import { canTransition, createRegistry, createScene, editPatch, validateParams, type OperationDefinition, type ProcedureDefinition } from "@klorad/api/world";
// An actuation: a command to a twin's device. It is never "undone"; its compensation is another setpoint.
const setpoint: OperationDefinition = {
id: "cooling.setpoint",
version: 1,
label: "Set cooling setpoint",
effect: "actuation",
appliesTo: { correspondence: ["twin"], classes: ["crac"] },
params: { celsius: { type: "number", label: "Setpoint", min: 16, max: 30, unit: "°C", required: true } },
command: "setpoint",
timeoutMs: 30_000,
compensation: { operation: "cooling.setpoint", version: 1 },
};
// A model edit: it changes the scene itself, so it can be undone.
const inspected: OperationDefinition = {
id: "maintenance.inspected",
version: 1,
label: "Mark inspected",
effect: "model-edit",
appliesTo: {},
params: {},
edit: { properties: { inspection: { value: "done" } } },
};
// A linear procedure pins the versions of the operations it runs.
const check: ProcedureDefinition = {
id: "cooling.check",
version: 1,
label: "Cooling check",
steps: [
{ operation: "cooling.setpoint", version: 1, label: "Set 21 °C", params: { celsius: 21 } },
{ operation: "maintenance.inspected", version: 1, label: "Record it", params: {} },
],
};
const registry = createRegistry([setpoint, inspected], [check]);
const scene = createScene({ coordinateSystem: { origin: { lat: 40.62637, lon: 22.94838 } } });
const crac = scene.add({ id: "crac", name: "Server room cooling", class: "crac", position: { east: 0, north: 0 }, correspondence: "twin", binding: { source: "bms", entity: "crac-1" } });
registry.forObject(crac, ["model-edit", "actuation"]).map((o) => o.id); // ["cooling.setpoint", "maintenance.inspected"]
validateParams(setpoint, { celsius: 40 }); // "Setpoint must be at most 30"
editPatch(inspected, {}); // { properties: { inspection: "done" } }
canTransition("dispatched", "acknowledged"); // true
canTransition("acknowledged", "failed"); // false: an outcome is final
export { registry, scene };Effect classes
readchanges nothing and is never a command.model-editchanges the scene itself. It is applied at once and keeps the patch that undoes it; undoing is a command on the edit's revision, so a change someone made since is a conflict, not overwritten.side-effectandactuationreach outside the scene. They are never called undoable: each names its compensation, another operation, or says"irreversible".
The command life cycle
A command is accepted, then dispatched toward the system that carries it out, then
acknowledged or failed. A command nobody answers within its timeout becomes unknown,
which never means "not done": an answer that arrives later still settles it.
canTransition(from, to) holds these rules.
Procedures
A procedure is a list of steps, each a pinned version of an operation with its parameters. Each step starts when the one before it is acknowledged; a step that fails or goes unknown fails the run.
In Klorad One
In Klorad One every run is authorised again on the server, whatever the page showed; an actuation is carried to the device by the connector that serves the twin's source, and each transition is journaled and audited. The packages that do this are part of Klorad One, not of the SDK.
Run it liveSandbox: OperationsA command is followed until the device answers, an edit can be undone, and a procedure runs in order.Something wrong or unclear? Tell us.