Docs
/
build knowledge

Surveillance

How the Leumas surveillance system is built and how to extend it. It scales from a single box to millions of cameras across many licensed businesses via a control-plane / data-plane split, and every...

Surveillance — enterprise architecture (build knowledge)

How the Leumas surveillance system is built and how to extend it. It scales from a single box to millions of cameras across many licensed businesses via a control-plane / data-plane split, and every detection capability is a plugin. Read this before touching surveillance.

The shape

CONTROL PLANE  (leumas-api — touches EVENTS, not raw video)
  tenant/fleet/site camera registry · rule engine · licensing/fleet keys · PassNode metering ·
  entitlement quotas · live event index (SSE /api/surveillance/stream + WS /ws/surveillance) · DVR index
        ▲ pull cameras            │ publish events + heartbeat (keyed)         ▲ live walls / rules
        │ GET /node/cameras       ▼ POST /ingest (per-node/-device auth)       │
DATA PLANE  (edge detection nodes — run the CV near the cameras)
  @leumas/surveillance-node  ── reuses the SAME camera-manager services (media/detector/recorder) ──
                             ── loads enabled VISION RECIPES per camera ── keyed publish SDK
  (the API's in-process sidecars ARE "the built-in node" for a single-box/EXE install — no fork)

Never haul raw video to a central GPU. Detection runs near the cameras; only events (+ optional clips) reach the control plane. Edge-first is the whole scaling story.

Where things live

  • Engine (connector-free pipelines + registry): shared/engines/adapters/surveillance/camera-manager/

services/{store,detections,detector,media,recorder,streams,flask,recipe-registry,recipes.builtin,domain-recipes,geometry,trigger,match,boot}.js, python/flask_server.py, index.js (adapter tools).

  • Control-plane HTTP: products/leumas-api/src/routes/surveillance.js; WS: products/leumas-api/src/ws/surveillance.js.
  • Edge node (deployable): shared/services/surveillance-node/ (bin + createSurveillanceNode).
  • SDK: shared/packages/detection-client/ (createDetectionStream SSE · createDetectionSocket WS ·

createNodeClient register/pull/heartbeat/publish/activate).

  • Studio: products/leumas-studio/src/admin/surveillance/ (Live/Cameras/Detections/Rules/Faces/Footage/

Vision Lab/Fleet).

Add a vision recipe (the #1 extension — zero core edits)

A recipe is one defineRecipe({...}) in a definitions file (recipes.builtin.js or domain-recipes.js, loaded by the recipes.js facade). It declares { id, kind, backend, configSchema, triggerProps, renderStyle, run? }:

  • backend: 'node' → pure JS, runs in-process in applyNodeRecipes (handleDetectionPayload) over any

detector's boxes — no model, fully testable (see zones, speed, red-light, retail-interaction).

  • backend: 'yolo-classes' | 'face' | 'python:<name>' → runs in the Python sidecar; register the

detector in flask_server.py (register_detector('<name>', fn)).

  • triggerProps auto-append to the rule manifest → the filter fields appear in the Studio rule editor

automatically (the same seam person used). String props = equality filters; number props = ≥ thresholds.

  • A recipe emits ordinary detection entries { kind, class, confidence, box, ...attrs }; the ring/SSE/

WS/rules pass extra attrs through, so {{plate}}/{{speed}}/{{zoneId}} interpolate in actions.

Multi-tenancy

Cameras carry tenantId (the licensed business = fleet/isolation boundary) + siteId + nodeId. Streams/detectors/recordings/boot/SSE/WS all scope by tenantId (cameraScope/canAccess in the router; DEFAULT_TENANT='leumas' folds legacy rows). Create via POST /api/surveillance/cameras (stamps owner+tenant+site, opt-in surveillance.maxCameras entitlements quota).

Edge nodes & auth

  • A camera's nodeId: empty = the control-plane built-in node runs it; set = an external node owns it

(the built-in ensures + boot SKIP it — no duplicate publisher). Assign in the Studio Fleet tab or via the assignCameraToNode adapter tool.

  • Node routes (before the session gate): POST /node/register, GET /node/cameras, POST /node/heartbeat.
  • requireNodeAuth: (1) a signed per-fleet license token (@leumas/licensing; tenant baked in,

revocable immediately, issue at /licensing/admin/issue with features:{surveillance.node:true}), (2) shared SURVEILLANCE_NODE_TOKEN fallback, (3) loopback.

  • /ingest: an lk_ API key (@leumas/auth verifyKey, per-owner — one-off external cameras), OR

the shared ingest token, OR loopback.

Metering (PassNode — opt-in)

Per-camera pay-per-use (surveillance:camera, hard gate on create) + per-detection token-usage (surveillance:detect, best-effort meter on ingest — NEVER drops a detection). No passnode_rules row ⇒ free. GET /api/surveillance/usage = camera count + quotes; Studio Fleet + Cameras show pricing. The engine gained passnode.charge({ user, target, units }) for machine-driven metering.

Realtime

SSE GET /api/surveillance/stream (simple consumers) + WS /ws/surveillance (many-client live walls; noServer + path-check-that-RETURNS multiplex, cookie-auth, fleet-scoped). Consume with createDetectionStream / createDetectionSocket.

Independence / white-label / packaging

  • Tap into someone else's host: the edge node targets any control plane via

SURVEILLANCE_CONTROL_PLANE_URL — the SDK is host-agnostic. A camera/node can publish to a customer's self-hosted Leumas surveillance cloud.

  • Appliance EXE = control plane + built-in node in one process (today's behavior; no separate node

needed for a single site). Remote nodes ship via pnpm deploy --filter @leumas/surveillance-node.

  • Extraction seam → @leumas/surveillance-server: the control-plane surface is already a factory

(createSurveillanceRouter({ connector, triggers, requireAuth, entitlements, licensing, passnode })) + createSurveillanceWs({ server, authenticate }). To ship a standalone white-label surveillance server (mirroring @leumas/imperium-server), wrap those two with a connector + auth + a thin Express app — no engine changes. Left as a clean seam; build on real demand.

MCP / agent-programmable

The camera-manager adapter auto-projects every tool as an MCP tool + chatbot functioncall via the adapter registry (GET /mcp, POST /api/adapters/surveillance/<fn>). Enterprise tools incl. listRecipes, listNodes, assignCameraToNode, simulateDetection. Run the expose-as-mcp skill to list/price it in Studio.

Verify (no hardware needed)

Node-side recipe logic + the generic matcher are unit-testable (applyNodeRecipes + matchDetections). End-to-end: POST synthetic detections to /ingest (loopback) → they flow through recipes → ring → SSE/WS → rules → actions (e.g. send-webhook a ticket). Missing ffmpeg/python ⇒ everything guards to a no-op.

Source shared/services/knowledge/build-knowledge/surveillance-enterprise.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt