KloradDocs

Raise alerts

Rules over readings and Actions raise alerts, delivered only to the Worlds whose scope contains the object.

@klorad/notify watches a scene's events and raises an alert when a rule's condition becomes true. Alerts are addressed to Worlds, and an alert reaches a World only if the object is in that World's scope, so a crew is never alerted about what it cannot see.

alerts.ts
import { createAccessPolicy } from "@klorad/access";
import { createScene } from "@klorad/api/world";
import { createNotifier, inAppChannel } from "@klorad/notify";

const scene = createScene({ coordinateSystem: { origin: { lat: 40.62637, lon: 22.94838 } } });
scene.add({
  id: "door-3",
  name: "Loading door 3",
  position: { east: 40, north: 10 },
  correspondence: "shadow",
  binding: { source: "acs", entity: "door-3" },
  properties: { zone: "dock" },
});

const policy = createAccessPolicy({
  scene,
  roles: { guard: "*" },
  worlds: [{ id: "control", name: "Control room", scope: { all: true }, capabilities: { actions: "*" }, principals: [{ kind: "role", id: "guard" }] }],
});

const inbox = inAppChannel();
const notifier = createNotifier({
  scene,
  policy,
  channels: [inbox],
  rules: [
    { id: "forced", name: "Door forced", condition: { kind: "equals", quantity: "state", value: "forced" }, severity: "critical", worlds: ["control"] },
    {
      id: "warm",
      name: "Dock too warm",
      condition: { kind: "threshold", quantity: "temperature", op: "gt", value: 30 },
      objects: { properties: { zone: "dock" } },
      severity: "warning",
      worlds: ["control"],
    },
  ],
});

scene.observe({ objectId: "door-3", quantity: "state", value: "forced", observedAt: Date.now(), source: "acs" });

const [alert] = inbox.list(["control"]); // "Door forced: Loading door 3 state is forced"
if (alert) notifier.acknowledge(alert.id, { id: "maria", roles: ["guard"] });

export { notifier };

Conditions

KindFires when
thresholdA numeric reading crosses a limit (gt, gte, lt, lte).
equalsA reading equals a value: a door state of "forced", a pump running of false.
actionAn Action ends with one of the given outcomes, for example every denied request.

Threshold and equality rules fire on the edge: once when the condition becomes true for an object, and again only after it has stopped holding. A temperature hovering above the limit raises one alert, not one per reading. A late reading never fires a rule, because it does not change the current state.

objects narrows a rule to some objects, by id or by properties.

Channels

A channel delivers alerts. inAppChannel() keeps them in memory for a notification centre: it lists alerts per World and can be subscribed to (it works with React's useSyncExternalStore). A channel is one deliver(alert) method, so web push, email or a message queue are small adapters in your server code. One failing channel does not stop the others.

Acknowledging

notifier.acknowledge(alertId, actor) marks an alert acknowledged, and only for an actor who may enter one of the alert's Worlds.

Something wrong or unclear? Tell your contact at Prieston Technologies.