@camstack/addon-agent-ui 1.2.146 → 1.2.148

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/addon.js +1325 -736
  2. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -5361,7 +5361,7 @@ var ZodIssueCode = {
5361
5361
  var ZodFirstPartyTypeKind;
5362
5362
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5363
5363
  //#endregion
5364
- //#region ../types/dist/sleep-D_4W7xuQ.mjs
5364
+ //#region ../types/dist/sleep-BaRu0bS5.mjs
5365
5365
  /**
5366
5366
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5367
5367
  * window to float samples (D455).
@@ -9434,7 +9434,7 @@ var ModelFormatEntrySchema = object({
9434
9434
  /** Multi-file payload (directory members or sibling files). */
9435
9435
  files: array(string()).readonly().optional(),
9436
9436
  /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
9437
- runtimes: array(_enum(["node", "python"])).readonly().optional()
9437
+ runtimes: array(_enum(["python"])).readonly().optional()
9438
9438
  });
9439
9439
  /**
9440
9440
  * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
@@ -9515,10 +9515,17 @@ var ModelCatalogEntrySchema = object({
9515
9515
  inputChannels: number().int().positive().optional(),
9516
9516
  labels: array(LabelDefinitionSchema).readonly(),
9517
9517
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
9518
+ /**
9519
+ * `'scrfd'` applies InsightFace's SCRFD input contract, `(x - 127.5) / 128`
9520
+ * (upstream `insightface/model_zoo/scrfd.py`), instead of the historical
9521
+ * plain `/255`. Measured against COCO GT: 188 → 196 faces found on the
9522
+ * `scrfd-2.5g` catalog entry (2026-09-26 model-replacement spike, §2.4-2).
9523
+ */
9518
9524
  inputNormalization: _enum([
9519
9525
  "zero-one",
9520
9526
  "imagenet",
9521
- "none"
9527
+ "none",
9528
+ "scrfd"
9522
9529
  ]).optional(),
9523
9530
  /**
9524
9531
  * The model already applies softmax IN-GRAPH — its raw output is a
@@ -9620,7 +9627,8 @@ var ModelConvertMetadataSchema = object({
9620
9627
  inputNormalization: _enum([
9621
9628
  "zero-one",
9622
9629
  "imagenet",
9623
- "none"
9630
+ "none",
9631
+ "scrfd"
9624
9632
  ]).optional(),
9625
9633
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
9626
9634
  outputFormat: _enum([
@@ -16998,6 +17006,9 @@ var NcSystemEventKindSchema = _enum([
16998
17006
  "device-battery-normal",
16999
17007
  "device-consumable-low",
17000
17008
  "device-consumable-normal",
17009
+ "device-tank-low",
17010
+ "device-tank-full",
17011
+ "device-tank-normal",
17001
17012
  "stream-online",
17002
17013
  "stream-offline",
17003
17014
  "node-online",
@@ -17035,6 +17046,29 @@ var NC_LEGACY_SYSTEM_EVENT_KINDS = new Set([
17035
17046
  ]);
17036
17047
  NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
17037
17048
  /**
17049
+ * The panel's transitions, as ONE list.
17050
+ *
17051
+ * Named here rather than spelled out at each of the four sites that need them
17052
+ * (the emitter, the intake, the editor group, the combined-notification gate),
17053
+ * because a transition added to the enum and forgotten at one of them is an
17054
+ * alarm state nobody can be notified about. That is exactly what this list
17055
+ * failed to prevent while it held three: `alarm-arming` was the fourth.
17056
+ *
17057
+ * In LIFECYCLE order, which is the order an editor lists them in and the order
17058
+ * a person experiences them — not the order they were added.
17059
+ */
17060
+ var NC_ALARM_SYSTEM_EVENT_KINDS = [
17061
+ "alarm-arm-refused",
17062
+ "alarm-check-failed",
17063
+ "alarm-arming",
17064
+ "alarm-armed",
17065
+ "alarm-pending",
17066
+ "alarm-triggered",
17067
+ "alarm-rearmed",
17068
+ "alarm-sensor-bypass",
17069
+ "alarm-disarmed"
17070
+ ];
17071
+ /**
17038
17072
  * One coherent system-event condition. `kinds` is the required opt-in safety
17039
17073
  * gate; the remaining lists are optional narrowing filters relevant to the
17040
17074
  * selected kinds.
@@ -17068,7 +17102,22 @@ var NcSystemEventConditionSchema = object({
17068
17102
  */
17069
17103
  excludeDeviceIds: array(number().int()).min(1).optional(),
17070
17104
  nodeIds: array(string().min(1)).min(1).optional(),
17071
- packageNames: array(string().min(1)).min(1).optional()
17105
+ packageNames: array(string().min(1)).min(1).optional(),
17106
+ /**
17107
+ * At or below this remaining-life %, a consumable is LOW for THIS rule
17108
+ * (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
17109
+ * {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
17110
+ * consumable kinds (`systemEventFilterApplies`) and to the device-events
17111
+ * family; ignored on every other kind.
17112
+ *
17113
+ * It is a property of the RULE because the operator decided so: a rule at
17114
+ * 25 must be told about a filter at 20 %, a rule at 10 must not. The hub
17115
+ * keeps one episode per (threshold, device, item) — its own hysteresis, its
17116
+ * own reminder — and a rule hears only the episode judged at ITS threshold,
17117
+ * exactly as an occupancy rule hears only the edge of its own count. A
17118
+ * provider's own `replace` verdict is low at every threshold.
17119
+ */
17120
+ consumableLowPercent: number().int().min(1).max(95).optional()
17072
17121
  });
17073
17122
  /** Hard ceiling on a window (24h). A snooze that could not expire would be an
17074
17123
  * outage the operator asked for once and forgot. */
@@ -17086,6 +17135,25 @@ var NcScheduleSchema = object({
17086
17135
  /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
17087
17136
  invert: boolean().optional()
17088
17137
  });
17138
+ /**
17139
+ * How a system-event rule of the `device-events` or `system` family speaks
17140
+ * (D647): at once, per event (`instant`, the only behaviour before D647), or
17141
+ * as a STATE SUMMARY at the end of each window of `schedule`.
17142
+ */
17143
+ var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
17144
+ /**
17145
+ * `summary`: the rule sends NOTHING per event. When a window of `schedule`
17146
+ * CLOSES, it sends one notification with the current state of its kinds
17147
+ * (low batteries, consumables to replace, devices offline, updates
17148
+ * available, ...), after its own filters. Nothing to report sends nothing.
17149
+ * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
17150
+ * midnight crossing); the send time is each window's END. Refused on the
17151
+ * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
17152
+ */
17153
+ var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
17154
+ mode: literal("summary"),
17155
+ schedule: NcScheduleSchema
17156
+ })]);
17089
17157
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
17090
17158
  var NcPlateMatcherSchema = object({
17091
17159
  /**
@@ -17211,6 +17279,16 @@ var NcCrossingSchema = _enum([
17211
17279
  "exit",
17212
17280
  "any"
17213
17281
  ]);
17282
+ /**
17283
+ * Which edge of a BOOLEAN sensor fires a device-event rule (D640).
17284
+ * - `raised` — the sensor went high: water detected, smoke, open, tampered.
17285
+ * - `cleared` — it went back: dry, clear, closed.
17286
+ * - absent — both edges, which is what every rule authored before this did.
17287
+ *
17288
+ * There is no third member for "both": absence already says it, and a value
17289
+ * that means the same as absence is a second way to write one rule.
17290
+ */
17291
+ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
17214
17292
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
17215
17293
  var NcZoneConditionSchema = object({
17216
17294
  ids: array(string().min(1)).min(1),
@@ -17400,6 +17478,20 @@ var NcConditionsSchema = object({
17400
17478
  scene: NcSceneConditionSchema.optional(),
17401
17479
  /** Device scope — absent = all devices. */
17402
17480
  devices: array(number()).optional(),
17481
+ /**
17482
+ * Device EXCLUSION (D641) — "every device except these". Judged on the SAME
17483
+ * subject ids {@link devices} is judged on (the camera, the sensor that
17484
+ * fired, a linked set's sensor and row cameras), so a sensor fanned out per
17485
+ * linked camera is excluded exactly where an include would have tested it.
17486
+ * `min(1)`: an empty exclusion excludes nothing and is written as absent.
17487
+ *
17488
+ * Not for `system-event` rules — those carry their own
17489
+ * `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
17490
+ * Editors present the two lists as ONE field (All | Only these | All except
17491
+ * these); the engine evaluates each independently, so a legacy rule
17492
+ * carrying both still means "these, minus those".
17493
+ */
17494
+ devicesExclude: array(number()).min(1).optional(),
17403
17495
  /** Detector class names (any overlap with the record's class set). */
17404
17496
  classes: array(string().min(1)).optional(),
17405
17497
  /** Veto classes — any overlap fails the rule. */
@@ -17533,6 +17625,16 @@ var NcConditionsSchema = object({
17533
17625
  */
17534
17626
  sensorKinds: array(string().min(1)).optional(),
17535
17627
  /**
17628
+ * DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
17629
+ * {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
17630
+ * is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
17631
+ * engine cannot place (a kind with no raise, an unreadable slice) PASSES:
17632
+ * the condition fails open towards notifying, never towards silence. Editors
17633
+ * offer it only when every chosen {@link sensorKinds} member has a raise
17634
+ * (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
17635
+ */
17636
+ sensorTransition: NcSensorTransitionSchema.optional(),
17637
+ /**
17536
17638
  * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
17537
17639
  * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
17538
17640
  * when the subject's phase does not match (a subject always carries a phase
@@ -17787,6 +17889,16 @@ var NcRuleInputSchema = object({
17787
17889
  delivery: NcDeliverySchema,
17788
17890
  conditions: NcConditionsSchema.default({}),
17789
17891
  schedule: NcScheduleSchema.optional(),
17892
+ /**
17893
+ * A `device-events` / `system` rule's delivery (D647): absent or `instant`
17894
+ * is a notification per event; `summary` is one state summary per closed
17895
+ * window of its schedule. `.optional()` and never defaulted: absent must
17896
+ * keep meaning what every rule authored before D647 meant, with no
17897
+ * migration. Top-level rather than inside `conditions.systemEvent`, so a
17898
+ * client that does not know it (an older viewer re-sending `conditions`
17899
+ * whole) leaves it standing through the store's patch merge.
17900
+ */
17901
+ systemEventDelivery: NcSystemEventDeliverySchema.optional(),
17790
17902
  /** May be empty when `targetUsers` addresses at least one user — the
17791
17903
  * "at least one addressee" invariant is enforced by the provider, because
17792
17904
  * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
@@ -18015,6 +18127,28 @@ var NcRuleSchema = NcRuleInputSchema.extend({
18015
18127
  */
18016
18128
  disabledTargetIds: array(string()).default([])
18017
18129
  });
18130
+ /** One section of a previewed state summary. `unavailable` = its state could not be read. */
18131
+ var NcStateSummaryPreviewSectionSchema = object({
18132
+ id: string(),
18133
+ status: _enum(["read", "unavailable"]),
18134
+ items: number().int().nonnegative(),
18135
+ reason: string().optional()
18136
+ });
18137
+ /**
18138
+ * The summary a rule would send now. `empty` = nothing to report and every
18139
+ * section read: such a summary is NOT sent. `dropped` counts the open states
18140
+ * the rule's own filters (or a camera mute) left out, by reason.
18141
+ * `excludedGroups` names the kind groups selected that have no state to report
18142
+ * (backups & exports).
18143
+ */
18144
+ var NcStateSummaryPreviewSchema = object({
18145
+ empty: boolean(),
18146
+ count: number().int().nonnegative(),
18147
+ text: string(),
18148
+ sections: array(NcStateSummaryPreviewSectionSchema),
18149
+ dropped: record(string(), number().int().nonnegative()),
18150
+ excludedGroups: array(string())
18151
+ });
18018
18152
  var NcTestResultSchema = object({
18019
18153
  recordId: string(),
18020
18154
  recordKind: _enum([
@@ -18106,6 +18240,13 @@ var NcTemplateVarDescriptorSchema = object({
18106
18240
  kinds: array(NcTemplateVarKindSchema).optional(),
18107
18241
  deliveries: array(NcDeliverySchema).optional(),
18108
18242
  systemEventKinds: array(NcSystemEventKindSchema).optional(),
18243
+ /**
18244
+ * Which system-event DELIVERY has a value for it (D647). Absent = an
18245
+ * instant row only when the descriptor names `systemEventKinds` (a
18246
+ * per-event fact: a summary row has no single device), and both otherwise.
18247
+ * `['summary']` marks the state-summary vocabulary (`{{summaryText}}`, ...).
18248
+ */
18249
+ systemEventDeliveries: array(NcSystemEventDeliveryModeSchema).optional(),
18109
18250
  dynamic: object({
18110
18251
  prefix: literal("count_"),
18111
18252
  from: literal("classes")
@@ -18118,7 +18259,9 @@ var NcTemplatePreviewInputSchema = object({
18118
18259
  field: NcTemplateFieldSchema,
18119
18260
  kind: NcRuleKindSchema.optional(),
18120
18261
  delivery: NcDeliverySchema.optional(),
18121
- systemEventKinds: array(NcSystemEventKindSchema).optional()
18262
+ systemEventKinds: array(NcSystemEventKindSchema).optional(),
18263
+ /** The rule's system-event delivery (D647); absent = instant. */
18264
+ systemEventDelivery: NcSystemEventDeliveryModeSchema.optional()
18122
18265
  }),
18123
18266
  template: object({
18124
18267
  title: string().max(500).optional(),
@@ -18151,6 +18294,7 @@ var NcConditionDescriptorSchema = object({
18151
18294
  /** Editor widget the UI renders — never hardcode per-condition forms. */
18152
18295
  valueType: _enum([
18153
18296
  "deviceIdList",
18297
+ "deviceIdExcludeList",
18154
18298
  "stringList",
18155
18299
  "number01",
18156
18300
  "number",
@@ -18161,6 +18305,7 @@ var NcConditionDescriptorSchema = object({
18161
18305
  "plateMatcher",
18162
18306
  "packagePhase",
18163
18307
  "crossingSelect",
18308
+ "sensorTransition",
18164
18309
  "polygonDraw",
18165
18310
  "occupancy",
18166
18311
  "audio",
@@ -18311,6 +18456,28 @@ var NcHistoryFilterSchema = object({
18311
18456
  limit: number().int().min(1).max(500).default(100)
18312
18457
  });
18313
18458
  /**
18459
+ * Every rule's last run, in one answer.
18460
+ *
18461
+ * A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
18462
+ * back this answer looked, in ms before the hub's now. It is the outbox prune
18463
+ * window (7 days by default, operator-configurable), NARROWED when the read hit
18464
+ * its row bound: the answer never claims a window it did not read. `null` means
18465
+ * the operator keeps the outbox forever AND the read reached its end, so absent
18466
+ * means "never on record". A UI derives its "not in the last N days" from this
18467
+ * value, never from a constant — and only when the call ANSWERED (D315).
18468
+ */
18469
+ var NcRuleLastRunsSchema = object({
18470
+ runs: array(object({
18471
+ ruleId: string(),
18472
+ /** Fire time of the newest row (`createdAt`, epoch ms). */
18473
+ lastAt: number(),
18474
+ status: NcHistoryStatusSchema,
18475
+ /** The camera/device of that row (a digest row files under its first camera). */
18476
+ deviceId: number()
18477
+ })),
18478
+ horizonMs: number().nonnegative().nullable()
18479
+ });
18480
+ /**
18314
18481
  * What a snooze covers. Broader scopes win when several overlap, so one window
18315
18482
  * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
18316
18483
  * summarising the same silence.
@@ -18491,6 +18658,14 @@ var NcAlarmModeCoverageSchema = object({
18491
18658
  /** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
18492
18659
  deviceIds: array(number().int()),
18493
18660
  /**
18661
+ * D641: the ids NOT covered although `allDevices` is true — every unscoped
18662
+ * covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
18663
+ * covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
18664
+ * which is not the same as skipped by a switch: never in `skippedDevices`.
18665
+ * Defaulted so an answer computed before this field parses as "none".
18666
+ */
18667
+ allDevicesExcept: array(number().int()).default([]),
18668
+ /**
18494
18669
  * Devices this mode NAMES but cannot actually arm, each with the switch that
18495
18670
  * excludes it.
18496
18671
  *
@@ -18620,13 +18795,17 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
18620
18795
  }), object({ results: array(NcTestResultSchema) }), {
18621
18796
  kind: "mutation",
18622
18797
  auth: "admin"
18798
+ }), method(object({ rule: NcRuleInputSchema }), NcStateSummaryPreviewSchema, {
18799
+ kind: "mutation",
18800
+ auth: "admin",
18801
+ access: "view"
18623
18802
  }), method(object({}), object({
18624
18803
  catalog: array(NcConditionDescriptorSchema),
18625
18804
  taxonomy: NcTaxonomySchema.optional()
18626
18805
  })), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
18627
18806
  kind: "mutation",
18628
18807
  access: "view"
18629
- }), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
18808
+ }), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), NcRuleLastRunsSchema, { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
18630
18809
  kind: "mutation",
18631
18810
  caller: "required"
18632
18811
  }), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
@@ -18966,606 +19145,6 @@ var OauthIntegrationDescriptorSchema = object({
18966
19145
  });
18967
19146
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
18968
19147
  /**
18969
- * Reference to the frame's retained NATIVE surface + the parent crop's placement
18970
- * within the frame, so the executor can re-cut a leaf child ROI at native
18971
- * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
18972
- */
18973
- var NativeCropRefSchema = object({
18974
- /** Handle keying the retained native surface (node-pinned to its owner). */
18975
- handle: FrameHandleSchema,
18976
- /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
18977
- cropFrameSpace: object({
18978
- x: number(),
18979
- y: number(),
18980
- w: number(),
18981
- h: number()
18982
- })
18983
- });
18984
- object({
18985
- crop: object({
18986
- left: number(),
18987
- top: number(),
18988
- width: number().positive(),
18989
- height: number().positive()
18990
- }).optional(),
18991
- content: object({
18992
- width: number().int().positive(),
18993
- height: number().int().positive()
18994
- }),
18995
- fit: _enum(["stretch", "contain"]),
18996
- format: _enum([
18997
- "rgb",
18998
- "gray",
18999
- "jpeg"
19000
- ])
19001
- });
19002
- /**
19003
- * Process-local frame identity. It is serializable so it can ride an in-process
19004
- * capability call, but `registryId` deliberately prevents resolution in any
19005
- * other process or execution group.
19006
- */
19007
- var FrameRefSchema = object({
19008
- registryId: string().min(1),
19009
- id: string().min(1),
19010
- width: number().int().positive(),
19011
- height: number().int().positive(),
19012
- format: _enum(["rgb", "gray"]),
19013
- timestamp: number(),
19014
- capturedAt: number().optional()
19015
- });
19016
- var ModelFormatSchema$1 = _enum([
19017
- "onnx",
19018
- "coreml",
19019
- "openvino",
19020
- "tflite",
19021
- "pt",
19022
- "gguf"
19023
- ]);
19024
- var PipelineSlotSchema = _enum([
19025
- "detector",
19026
- "cropper",
19027
- "classifier",
19028
- "refiner",
19029
- "audio-classifier"
19030
- ]);
19031
- var PipelineEngineChoiceSchema = object({
19032
- runtime: _enum(["node", "python"]),
19033
- backend: string(),
19034
- format: ModelFormatSchema$1,
19035
- device: string().optional()
19036
- });
19037
- var AvailableEngineSchema = object({
19038
- engine: PipelineEngineChoiceSchema,
19039
- devices: array(object({
19040
- id: string(),
19041
- label: string(),
19042
- description: string().optional()
19043
- })).readonly(),
19044
- defaultDevice: string()
19045
- });
19046
- var PipelineDefaultStepSchema = lazy(() => object({
19047
- addonId: string(),
19048
- addonName: string(),
19049
- slot: PipelineSlotSchema,
19050
- inputClasses: array(string()).readonly(),
19051
- outputClasses: array(string()).readonly(),
19052
- enabled: boolean(),
19053
- modelId: string(),
19054
- children: array(PipelineDefaultStepSchema).readonly(),
19055
- group: string().optional(),
19056
- settings: record(string(), unknown()).optional()
19057
- }));
19058
- var PipelineTemplateStepSchema = lazy(() => object({
19059
- addonId: string(),
19060
- enabled: boolean(),
19061
- modelId: string(),
19062
- children: array(PipelineTemplateStepSchema).readonly(),
19063
- settings: record(string(), unknown()).optional()
19064
- }));
19065
- var PipelineTemplateSchema$1 = object({
19066
- id: string(),
19067
- name: string(),
19068
- createdAt: string(),
19069
- updatedAt: string(),
19070
- engine: PipelineEngineChoiceSchema,
19071
- steps: array(PipelineTemplateStepSchema).readonly()
19072
- });
19073
- var PipelineModelOptionSchema = object({
19074
- id: string(),
19075
- name: string(),
19076
- formats: record(string(), object({
19077
- downloaded: boolean(),
19078
- sizeMB: number()
19079
- })),
19080
- group: ModelVariantGroupSchema.optional(),
19081
- legacy: boolean().optional(),
19082
- provider: ModelProviderIdSchema.optional()
19083
- });
19084
- var ConfigFieldBridge = custom();
19085
- var PipelineAddonSchemaSchema = object({
19086
- id: string(),
19087
- name: string(),
19088
- slot: PipelineSlotSchema,
19089
- inputClasses: array(string()).readonly(),
19090
- outputClasses: array(string()).readonly(),
19091
- childSlots: array(PipelineSlotSchema).readonly(),
19092
- models: array(PipelineModelOptionSchema).readonly(),
19093
- defaultModelId: string(),
19094
- defaultModelIdByFormat: record(string(), string()).optional(),
19095
- enabledByDefault: boolean().optional(),
19096
- backfillIntoExistingOverrides: boolean().optional(),
19097
- defaultConfidence: number(),
19098
- group: string().optional(),
19099
- configSchema: array(ConfigFieldBridge).readonly().optional()
19100
- });
19101
- var PipelineSlotSchemaSchema = object({
19102
- id: PipelineSlotSchema,
19103
- label: string(),
19104
- priority: number(),
19105
- parentSlot: PipelineSlotSchema.nullable(),
19106
- addons: array(PipelineAddonSchemaSchema).readonly()
19107
- });
19108
- var PipelineSchemaSchema = object({
19109
- availableEngines: array(AvailableEngineSchema).readonly(),
19110
- selectedEngine: PipelineEngineChoiceSchema,
19111
- slots: array(PipelineSlotSchemaSchema).readonly()
19112
- });
19113
- var EngineProvisioningSchema = object({
19114
- runtimeId: _enum([
19115
- "onnx",
19116
- "openvino",
19117
- "coreml",
19118
- "edgetpu"
19119
- ]).nullable(),
19120
- device: string().nullable(),
19121
- state: _enum([
19122
- "idle",
19123
- "installing",
19124
- "verifying",
19125
- "ready",
19126
- "failed"
19127
- ]),
19128
- progress: number().optional(),
19129
- error: string().optional(),
19130
- nextRetryAt: number().optional(),
19131
- /**
19132
- * Gate A (config-correctness gate at engine change): human-readable
19133
- * config issues surfaced EAGERLY when the node's engine changes — model
19134
- * substitutions ("chose X, running Y") and zero-build steps ("no model
19135
- * has a <format> build"). Additive/optional: informational only, never
19136
- * enforced here — `assertEngineReady` (readiness) still gates inference.
19137
- * Absent/empty when the node-default tree resolves cleanly.
19138
- */
19139
- configIssues: array(string()).optional()
19140
- });
19141
- var PipelineStepInputSchema = lazy(() => object({
19142
- addonId: string(),
19143
- modelId: string().optional(),
19144
- enabled: boolean().default(true),
19145
- children: array(PipelineStepInputSchema).optional(),
19146
- settings: record(string(), unknown()).optional(),
19147
- jumpDeviceKey: string().optional()
19148
- }));
19149
- var ModelSubstitutionSchema = object({
19150
- addonId: string(),
19151
- chosen: string(),
19152
- running: string(),
19153
- format: string()
19154
- });
19155
- var PipelineValidationIssueSchema = object({
19156
- addonId: string(),
19157
- kind: _enum(["unknown-addon", "no-format-build"]),
19158
- detail: string()
19159
- });
19160
- var PipelineValidationResultSchema = object({
19161
- ok: boolean(),
19162
- issues: array(PipelineValidationIssueSchema).readonly(),
19163
- substitutions: array(ModelSubstitutionSchema).readonly(),
19164
- /** The node's `currentEngine.format` this validation ran against. */
19165
- format: string()
19166
- });
19167
- var ReferenceImageEntrySchema = object({
19168
- filename: string(),
19169
- stepIds: array(string()).readonly().optional()
19170
- });
19171
- var ReferenceImageBodySchema = object({
19172
- base64: string(),
19173
- filename: string()
19174
- });
19175
- var ReferenceAudioEntrySchema = object({
19176
- filename: string(),
19177
- sizeKb: number()
19178
- });
19179
- var ReferenceAudioBodySchema = object({ base64: string() });
19180
- var AudioBackendSchema = object({
19181
- id: string(),
19182
- name: string(),
19183
- description: string(),
19184
- available: boolean(),
19185
- /**
19186
- * Raw classifier labels this backend can emit (e.g. YAMNet's
19187
- * 521-class set or Apple SoundAnalysis's 303-class set). Used by
19188
- * the benchmark UI to populate the `enabledMicroClasses` filter
19189
- * specific to the selected backend without a separate fetch.
19190
- */
19191
- rawLabels: array(string()).readonly().optional()
19192
- });
19193
- var AudioCapabilitiesSchema = object({
19194
- activeBackend: string(),
19195
- availableBackends: array(AudioBackendSchema).readonly(),
19196
- sampleRate: number(),
19197
- chunkDurationMs: number()
19198
- });
19199
- var DownloadModelResultSchema = object({
19200
- filePath: string(),
19201
- sizeMB: number(),
19202
- durationMs: number()
19203
- });
19204
- /**
19205
- * Wrapper carrying a single test run's result. Replaces the legacy
19206
- * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
19207
- * canonical `AudioResult` from the Phase 6 output rework: one
19208
- * `AudioDetection` per class above `minScore`, top-N candidates in
19209
- * `debug.alternateLabels['audio-classifier']`, per-source timings in
19210
- * `debug.stepTimings`. The outer `success`/`error` fields stay so the
19211
- * benchmark UI can still report a clean failure when the classifier
19212
- * cap isn't available.
19213
- */
19214
- var AudioTestResultSchema = object({
19215
- success: boolean(),
19216
- error: string().optional(),
19217
- frame: custom().optional()
19218
- });
19219
- var PipelineConfigBridge = custom();
19220
- var ConfigUISchemaBridge = custom();
19221
- var ConfigUISchemaNullableBridge = custom();
19222
- var InferenceCapabilitiesBridge = custom();
19223
- var ModelAvailabilityListBridge = custom();
19224
- var PipelineRunResultBridge = custom();
19225
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
19226
- modelId: string(),
19227
- settings: record(string(), unknown()).readonly()
19228
- }))), method(object({ steps: record(string(), object({
19229
- modelId: string(),
19230
- settings: record(string(), unknown()).readonly()
19231
- })) }), object({ success: literal(true) }), {
19232
- kind: "mutation",
19233
- auth: "admin"
19234
- }), method(object({ nodeId: string() }), object({
19235
- success: literal(true),
19236
- clearedDevices: number()
19237
- }), {
19238
- kind: "mutation",
19239
- auth: "admin"
19240
- }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
19241
- /** `<backend>:<device>`, e.g. `openvino:gpu`. */
19242
- deviceKey: string(),
19243
- /**
19244
- * `failed` — the per-device restart budget is exhausted; no pool
19245
- * will be spawned until an operator re-arms it or the runner
19246
- * respawns. `backoff` — under budget, waiting out the backoff (or
19247
- * a cached pool observed dead and not yet condemned).
19248
- */
19249
- state: _enum(["failed", "backoff"]),
19250
- /** Epoch ms of the death that produced this state. */
19251
- since: number(),
19252
- /** Pool deaths inside the current window. */
19253
- deaths: number(),
19254
- /** The last death's message. */
19255
- lastError: string()
19256
- })).readonly() })), method(object({
19257
- nodeId: string(),
19258
- deviceKey: string()
19259
- }), object({ rearmed: boolean() }), {
19260
- kind: "mutation",
19261
- auth: "admin"
19262
- }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({ steps: array(PipelineStepInputSchema) }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
19263
- name: string(),
19264
- steps: array(PipelineTemplateStepSchema).readonly(),
19265
- engine: PipelineEngineChoiceSchema
19266
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
19267
- id: string(),
19268
- name: string().optional(),
19269
- steps: array(PipelineTemplateStepSchema).readonly().optional()
19270
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
19271
- addonId: string(),
19272
- modelId: string(),
19273
- format: ModelFormatSchema$1
19274
- }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
19275
- addonId: string(),
19276
- modelId: string(),
19277
- format: ModelFormatSchema$1
19278
- }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
19279
- engine: PipelineEngineChoiceSchema.optional(),
19280
- steps: array(PipelineStepInputSchema).min(1),
19281
- frame: FrameInputSchema.optional(),
19282
- /**
19283
- * Process-local lazy frame. Valid only when caller and provider resolve
19284
- * in the same execution-group process; split/cross-node callers use
19285
- * `frame`/`image` inline compatibility instead.
19286
- */
19287
- frameRef: FrameRefSchema.optional(),
19288
- /**
19289
- * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
19290
- * the decoded pixels live in. One more member of the one-of
19291
- * frame/frameHandle/image/imageBase64/referenceImage group.
19292
- */
19293
- frameHandle: FrameHandleSchema.optional(),
19294
- imageBase64: string().optional(),
19295
- /**
19296
- * Binary JPEG bytes — preferred over `imageBase64` on internal
19297
- * hops (hub → forked worker via Moleculer MsgPack) because it
19298
- * skips the 33% base64 overhead + the per-call base64 decode on
19299
- * the detection-pipeline worker. Callers can pass either; exactly
19300
- * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
19301
- */
19302
- image: _instanceof(Uint8Array).optional(),
19303
- referenceImage: string().optional(),
19304
- deviceId: number().optional(),
19305
- sessionId: string().optional(),
19306
- /**
19307
- * Execution plane. 'full' (default) runs the whole tree — benchmark,
19308
- * reference-image, and detail-subtree calls. 'frame' is the live
19309
- * per-frame dispatch: ONLY root-plane steps run; crop children
19310
- * (inputClasses ≠ null) are skipped and served per-track via
19311
- * pipelineRunner.runDetailSubtree (two-plane design).
19312
- */
19313
- plane: _enum(["full", "frame"]).optional(),
19314
- /**
19315
- * Inference-device selector (Phase 2 multi-device). Format
19316
- * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
19317
- * Omitted ⇒ the runner's default device (current single-engine
19318
- * behaviour). Selects WHICH device pool of the node runs the call.
19319
- */
19320
- deviceKey: string().optional(),
19321
- /**
19322
- * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
19323
- * when the parent crop was resolved from the frame's retained NATIVE
19324
- * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
19325
- * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
19326
- * resolution from that surface — the SAME quality path faces already
19327
- * had — instead of the downscaled parent tile. `handle` keys the native
19328
- * surface (node-pinned to its owner); `cropFrameSpace` is the parent
19329
- * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
19330
- * the executor's crop-normalized child ROI back into frame-normalized
19331
- * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
19332
- * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
19333
- * (today's behaviour on the fallback path).
19334
- */
19335
- nativeCropRef: NativeCropRefSchema.optional()
19336
- }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
19337
- engine: PipelineEngineChoiceSchema.optional(),
19338
- steps: array(PipelineStepInputSchema).min(1),
19339
- frames: array(FrameInputSchema).min(1).max(255),
19340
- deviceId: number().optional(),
19341
- sessionId: string().optional(),
19342
- /**
19343
- * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
19344
- * the batch to the Python pool's bench preprocess cache
19345
- * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
19346
- * preprocessed ONCE and every later inference is a pure-inference cache
19347
- * hit — the sustained-throughput run measures inference, not
19348
- * decode+preprocess+infer. Omitted/0 for live frames (all different →
19349
- * full preprocess every call, correct). Fresh per sustained run;
19350
- * released via `uncacheFrame`.
19351
- */
19352
- frameId: number().int().nonnegative().optional(),
19353
- /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
19354
- deviceKey: string().optional()
19355
- }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
19356
- data: _instanceof(Uint8Array),
19357
- width: number().int().positive(),
19358
- height: number().int().positive(),
19359
- format: _enum([
19360
- "rgb",
19361
- "bgr",
19362
- "gray"
19363
- ])
19364
- }), object({
19365
- frameId: number(),
19366
- width: number(),
19367
- height: number()
19368
- }), { kind: "mutation" }), method(object({
19369
- stepId: string(),
19370
- frameId: number().int()
19371
- }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(_void(), object({
19372
- batchMode: string(),
19373
- windowMs: number(),
19374
- maxBatchSize: number(),
19375
- concurrency: number()
19376
- })), method(_void(), array(object({
19377
- engineKey: string(),
19378
- engine: PipelineEngineChoiceSchema,
19379
- modelsLoaded: array(string()).readonly(),
19380
- inUseByCameras: array(number()).readonly(),
19381
- /**
19382
- * Origin of this resident factory.
19383
- * - `runtime` — main camera-serving engine (no idle TTL).
19384
- * - `warm-override` — benchmark/test override held in the warm
19385
- * cache; auto-disposed after the idle TTL.
19386
- * - `device-pool` — a concurrent per-device pool (Phase 2
19387
- * multi-device, keyed by `deviceKey`) resolved
19388
- * via `resolveDeviceFactory`. Runs alongside the
19389
- * `runtime` engine on a DIFFERENT accelerator
19390
- * (NPU / iGPU / Coral) — this is how the
19391
- * Engines tab shows all pools running at once.
19392
- */
19393
- kind: _enum([
19394
- "runtime",
19395
- "warm-override",
19396
- "device-pool"
19397
- ]),
19398
- /** Native pid of the underlying Python pool (null when no pool). */
19399
- poolPid: number().nullable(),
19400
- /** ms since this factory was last used (null when not warm-tracked). */
19401
- idleMs: number().nullable(),
19402
- /** Idle TTL after which `warm-override` factories self-evict (null when not applicable). */
19403
- idleTtlMs: number().nullable()
19404
- })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
19405
- kind: "mutation",
19406
- auth: "admin"
19407
- }), method(object({
19408
- engine: PipelineEngineChoiceSchema,
19409
- force: boolean().optional()
19410
- }), object({
19411
- success: boolean(),
19412
- reason: string().optional()
19413
- }), {
19414
- kind: "mutation",
19415
- auth: "admin"
19416
- }), method(_void(), array(ReferenceImageEntrySchema).readonly()), method(object({ filename: string() }), ReferenceImageBodySchema.nullable()), method(_void(), array(ReferenceAudioEntrySchema).readonly()), method(object({ filename: string() }), ReferenceAudioBodySchema.nullable()), method(_void(), AudioCapabilitiesSchema), method(object({
19417
- addonId: string(),
19418
- modelId: string(),
19419
- filename: string().optional(),
19420
- settings: record(string(), unknown()).optional()
19421
- }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
19422
- /**
19423
- * Per-stage gating mode applied to the zones a rule references.
19424
- *
19425
- * - `include`: the rule contributes to a **whitelist** for its stage.
19426
- * When at least one `include` rule fires for a stage, only entities
19427
- * inside one of those zones pass that stage.
19428
- * - `exclude`: the rule contributes to a **blacklist** for its stage.
19429
- * Entities inside one of those zones are dropped at that stage.
19430
- *
19431
- * `monitor`-style observation (count without filtering) is not a rule
19432
- * mode — zones without any matching rule are observed naturally by
19433
- * `zone-analytics` (live snapshot + history), so an "I just want to
19434
- * count, not filter" use case needs no rule at all.
19435
- */
19436
- var ZoneRuleModeEnum = _enum(["include", "exclude"]);
19437
- /**
19438
- * Per-consumer rule that references existing zones (geometry) and
19439
- * defines how a specific pipeline stage should treat them. Each
19440
- * consumer addon owns its own `ZoneRule[]` array in its per-device
19441
- * settings:
19442
- *
19443
- * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
19444
- * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
19445
- * - future: notification rules, audio gating, etc.
19446
- *
19447
- * One rule applies to N zones (`zoneIds[]`) so the operator can
19448
- * express "ignore motion in ALL of {garden, street}" with a single
19449
- * rule. `classFilter` narrows the rule to specific object classes —
19450
- * "drop person detections in the street, but keep cars" is one
19451
- * `exclude` rule with `classFilter: ['person']`.
19452
- *
19453
- * `enabled` is a soft toggle — the operator can keep the rule
19454
- * configured but inert without deleting it.
19455
- */
19456
- var ZoneRuleSchema = object({
19457
- /** Stable rule id — survives edits, used by the UI for diffing. */
19458
- id: string(),
19459
- /** Optional human-readable label rendered in the rule editor. */
19460
- name: string().optional(),
19461
- /** Zones this rule targets. The rule's `mode` applies to ALL
19462
- * listed zones (OR-set: a detection in any one of them counts).
19463
- * At least one zone id required — a rule with no targets is a
19464
- * configuration mistake and the form validator rejects it. */
19465
- zoneIds: array(string()).min(1).readonly(),
19466
- mode: ZoneRuleModeEnum,
19467
- /**
19468
- * Class names this rule applies to. Empty / undefined ⇒ rule
19469
- * applies to every class. Class strings match the `macroClass`
19470
- * field on detections (e.g. `person`, `car`, `dog`).
19471
- */
19472
- classFilter: array(string()).readonly().optional(),
19473
- /**
19474
- * Minimum bbox/mask overlap (0–1) with any of the rule's zones
19475
- * required to consider an entity "in the zone". Defaults to the
19476
- * consumer's stage default when omitted. Kept for back-compat with
19477
- * existing per-rule overrides; new operators pick the value via
19478
- * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
19479
- * set, the lower-level engine reads it as a 0–1 fraction.
19480
- */
19481
- overlapThreshold: number().min(0).max(1).optional(),
19482
- /**
19483
- * Operator-friendly version of `overlapThreshold` — the percentage
19484
- * of the detection's bbox that must lie inside the zone for the
19485
- * rule to match. Documented default is 85%; the engine substitutes
19486
- * that when the field is omitted (kept optional so existing rules
19487
- * stored without it stay valid).
19488
- *
19489
- * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
19490
- * rule, the engine prefers `bboxInclusionPct` because it's the
19491
- * field exposed in the UI. Internally both feed the same gate.
19492
- */
19493
- bboxInclusionPct: number().min(0).max(100).optional(),
19494
- /**
19495
- * When `true` and a detection has a segmentation mask, use the
19496
- * mask for overlap instead of the bbox. Detection-stage only;
19497
- * motion rules ignore this field.
19498
- */
19499
- preferMask: boolean().optional(),
19500
- /**
19501
- * Soft-toggle: `false` disables the rule without deleting it.
19502
- * Defaults to `true` so operators creating a rule via the UI
19503
- * see it active immediately.
19504
- */
19505
- enabled: boolean().default(true)
19506
- });
19507
- array(ZoneRuleSchema).readonly();
19508
- /**
19509
- * Zone — pure geometry + identity. NO filtering behaviour.
19510
- *
19511
- * Zones describe **where** in the frame the operator wants to flag
19512
- * something; consumer-owned {@link ZoneRule} arrays describe **how**
19513
- * each pipeline stage uses them. Splitting the two means a single
19514
- * polygon "Driveway" can simultaneously back a motion-exclude rule,
19515
- * a detection-include rule on `['car']`, and an occupancy aggregate
19516
- * — without three duplicated polygons.
19517
- *
19518
- * Owned by the orchestrator addon (provider) and mirrored into the
19519
- * `zones` device-state slice on every mutation. Consumers
19520
- * (motion-wasm, pipeline-executor, analytics, admin UI) read either
19521
- * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
19522
- * mirror with `onChanged`).
19523
- *
19524
- * Coordinates are normalised fractions of the frame (0–1) so zones
19525
- * survive resolution changes and stream profile switches.
19526
- *
19527
- * `kind` discriminates between full polygons (closed regions used
19528
- * for intrusion / occupancy filters) and tripwires (open 2-point
19529
- * line segments used for cross events). Onboard / firmware-reported
19530
- * zones (Reolink, ONVIF) are out of scope for now — see the deferred
19531
- * task list.
19532
- */
19533
- var ZoneKindEnum = _enum(["polygon", "tripwire"]);
19534
- /** Polygon vertex in fraction-of-frame coordinates (0–1). */
19535
- var PolygonPointSchema = object({
19536
- x: number(),
19537
- y: number()
19538
- });
19539
- /** A camera detection zone — pure geometry/identity. */
19540
- var ZoneSchema = object({
19541
- id: string(),
19542
- name: string(),
19543
- kind: ZoneKindEnum.default("polygon"),
19544
- /** Polygon vertices, fraction of frame (0–1). */
19545
- polygon: array(PolygonPointSchema).readonly(),
19546
- /** Visual color for UI rendering. */
19547
- color: string().default("#3b82f6")
19548
- });
19549
- DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
19550
- deviceId: number(),
19551
- zone: ZoneSchema
19552
- }), _void(), {
19553
- kind: "mutation",
19554
- auth: "admin"
19555
- }), method(object({
19556
- deviceId: number(),
19557
- zoneId: string()
19558
- }), _void(), {
19559
- kind: "mutation",
19560
- auth: "admin"
19561
- }), method(object({
19562
- deviceId: number(),
19563
- zone: ZoneSchema
19564
- }), _void(), {
19565
- kind: "mutation",
19566
- auth: "admin"
19567
- }), object({ zones: array(ZoneSchema).readonly() });
19568
- /**
19569
19148
  * pipeline-analytics — device-scoped wrapper cap. Refines raw
19570
19149
  * per-frame detections emitted by the pipeline runner into tracked
19571
19150
  * objects, per-kind event collections (motion / object / audio), and
@@ -20136,6 +19715,22 @@ var TrackSchema = object({
20136
19715
  * omit it. Render nothing when absent.
20137
19716
  */
20138
19717
  previewMissReason: string().optional(),
19718
+ /**
19719
+ * The ROOT model that produced this track — the `FrameResult.rootModelId` of
19720
+ * the frame it was BORN on (D646). What a later replay compares against, so
19721
+ * "the original model" is named rather than assumed. A birth fact: never
19722
+ * rewritten by a later frame. **Absent ≠ unknown-model**: a row from before
19723
+ * the column, and a track the camera's own detector produced, carry none.
19724
+ */
19725
+ modelId: string().optional(),
19726
+ /**
19727
+ * Size (px) of the detection frame this track's `positions[].bbox` live in —
19728
+ * the frame it was born on, else the camera's latest analysis frame at
19729
+ * persist time. Divide a position by these to normalise it. Absent when the
19730
+ * size was never known.
19731
+ */
19732
+ frameWidth: number().optional(),
19733
+ frameHeight: number().optional(),
20139
19734
  ...TrackFlagFields,
20140
19735
  ...TrackRetrainFields
20141
19736
  });
@@ -21142,24 +20737,6 @@ var MediaReclaimStatusSchema = object({
21142
20737
  finishedAtMs: number().nullable(),
21143
20738
  error: string().nullable()
21144
20739
  });
21145
- var ReplayFrameInputSchema = object({
21146
- timestamp: number(),
21147
- frame: PipelineRunResultBridge
21148
- });
21149
- var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
21150
- className: string(),
21151
- firstSeenMs: number(),
21152
- lastSeenMs: number(),
21153
- /** Bbox of the track's FIRST matched detection, pixel-space in the clip's
21154
- * frame — a representative box for the diff's `(className, window, IoU)`
21155
- * pairing (`replay-diff.ts`). A replay does not need the full per-frame
21156
- * trajectory production's `Track.positions` keeps. */
21157
- bbox: BoundingBoxSchema,
21158
- /** How many of the input frames this track matched a real detection on
21159
- * (never a coasted/extrapolated frame) — the replay's own signal for "how
21160
- * solid is this track", cheaper than re-deriving it from a trajectory. */
21161
- framesMatched: number().int()
21162
- })).readonly() });
21163
20740
  /**
21164
20741
  * The long-term series a camera produces. A closed union so a typo cannot
21165
20742
  * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
@@ -21373,108 +20950,570 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21373
20950
  }), {
21374
20951
  kind: "mutation",
21375
20952
  auth: "admin"
21376
- }), method(object({ frameId: string() }), object({
21377
- base64: string(),
21378
- width: number().int(),
21379
- height: number().int()
20953
+ }), method(object({ frameId: string() }), object({
20954
+ base64: string(),
20955
+ width: number().int(),
20956
+ height: number().int()
20957
+ }), {
20958
+ kind: "query",
20959
+ auth: "admin"
20960
+ }), method(object({
20961
+ deviceId: number(),
20962
+ trackId: string(),
20963
+ frameId: string(),
20964
+ subject: RetrainAssistSubjectSchema,
20965
+ /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
20966
+ nodeId: string().optional()
20967
+ }), RetrainAssistResultSchema, {
20968
+ kind: "mutation",
20969
+ auth: "admin"
20970
+ }), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
20971
+ kind: "query",
20972
+ auth: "admin"
20973
+ }), method(object({
20974
+ deviceId: number(),
20975
+ trackId: string(),
20976
+ frameId: string(),
20977
+ annotations: array(RetrainAnnotationDraftSchema)
20978
+ }), array(RetrainAnnotationSchema).readonly(), {
20979
+ kind: "mutation",
20980
+ auth: "admin"
20981
+ }), method(object({
20982
+ deviceId: number(),
20983
+ trackId: string()
20984
+ }), RetrainTransitionResultSchema, {
20985
+ kind: "mutation",
20986
+ auth: "admin"
20987
+ }), method(object({
20988
+ deviceId: number(),
20989
+ trackId: string()
20990
+ }), RetrainTransitionResultSchema, {
20991
+ kind: "mutation",
20992
+ auth: "admin"
20993
+ }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
20994
+ kind: "query",
20995
+ auth: "admin"
20996
+ }), method(object({
20997
+ /** Which of the three event tables owns the row. */
20998
+ ownerType: EventOwnerTypeSchema,
20999
+ /** The event's rowid in that table (D474: an event id is a number). */
21000
+ eventId: number().int(),
21001
+ kind: MediaFileKindEnum.optional(),
21002
+ deviceId: number()
21003
+ }), array(MediaFileSchema).readonly()), method(object({
21004
+ trackId: string(),
21005
+ kinds: array(MediaFileKindEnum).optional(),
21006
+ deviceId: number()
21007
+ }), array(MediaFileSchema).readonly()), method(object({
21008
+ trackId: string(),
21009
+ deviceId: number()
21010
+ }), array(MediaFileInfoSchema).readonly()), method(object({
21011
+ ownerType: EventOwnerTypeSchema,
21012
+ eventId: number().int(),
21013
+ deviceId: number()
21014
+ }), array(MediaFileInfoSchema).readonly()), method(object({
21015
+ deviceId: number(),
21016
+ ownerKind: MediaPresenceOwnerKindSchema,
21017
+ ownerIds: array(string()).max(5e3)
21018
+ }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21019
+ kind: "mutation",
21020
+ auth: "admin"
21021
+ }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
21022
+ kind: "mutation",
21023
+ auth: "admin"
21024
+ }), method(object({}), RebuildStatusSchema), object({
21025
+ deviceId: number(),
21026
+ timestamp: number(),
21027
+ frameWidth: number(),
21028
+ frameHeight: number(),
21029
+ detections: array(OverlayDetectionSchema).readonly()
21030
+ }), object({
21031
+ deviceId: number(),
21032
+ trackId: string(),
21033
+ className: string()
21034
+ }), object({
21035
+ deviceId: number(),
21036
+ trackId: string(),
21037
+ className: string(),
21038
+ durationMs: number()
21039
+ }), object({
21040
+ deviceId: number(),
21041
+ kind: EventKindSchema,
21042
+ eventId: string(),
21043
+ timestamp: number()
21044
+ });
21045
+ /**
21046
+ * Reference to the frame's retained NATIVE surface + the parent crop's placement
21047
+ * within the frame, so the executor can re-cut a leaf child ROI at native
21048
+ * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
21049
+ */
21050
+ var NativeCropRefSchema = object({
21051
+ /** Handle keying the retained native surface (node-pinned to its owner). */
21052
+ handle: FrameHandleSchema,
21053
+ /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
21054
+ cropFrameSpace: object({
21055
+ x: number(),
21056
+ y: number(),
21057
+ w: number(),
21058
+ h: number()
21059
+ })
21060
+ });
21061
+ object({
21062
+ crop: object({
21063
+ left: number(),
21064
+ top: number(),
21065
+ width: number().positive(),
21066
+ height: number().positive()
21067
+ }).optional(),
21068
+ content: object({
21069
+ width: number().int().positive(),
21070
+ height: number().int().positive()
21071
+ }),
21072
+ fit: _enum(["stretch", "contain"]),
21073
+ format: _enum([
21074
+ "rgb",
21075
+ "gray",
21076
+ "jpeg"
21077
+ ])
21078
+ });
21079
+ /**
21080
+ * Process-local frame identity. It is serializable so it can ride an in-process
21081
+ * capability call, but `registryId` deliberately prevents resolution in any
21082
+ * other process or execution group.
21083
+ */
21084
+ var FrameRefSchema = object({
21085
+ registryId: string().min(1),
21086
+ id: string().min(1),
21087
+ width: number().int().positive(),
21088
+ height: number().int().positive(),
21089
+ format: _enum(["rgb", "gray"]),
21090
+ timestamp: number(),
21091
+ capturedAt: number().optional()
21092
+ });
21093
+ var ModelFormatSchema$1 = _enum([
21094
+ "onnx",
21095
+ "coreml",
21096
+ "openvino",
21097
+ "tflite",
21098
+ "pt",
21099
+ "gguf"
21100
+ ]);
21101
+ var PipelineSlotSchema = _enum([
21102
+ "detector",
21103
+ "cropper",
21104
+ "classifier",
21105
+ "refiner",
21106
+ "audio-classifier"
21107
+ ]);
21108
+ var PipelineEngineChoiceSchema = object({
21109
+ runtime: literal("python"),
21110
+ backend: string(),
21111
+ format: ModelFormatSchema$1,
21112
+ device: string().optional()
21113
+ });
21114
+ /**
21115
+ * One pool's resolved provisioning (`getEffectiveTuning`). `numWorkers` is the
21116
+ * knob that lifts a saturated pool (`inference-devices.md`); `concurrency` is
21117
+ * per-worker in-flight requests. `deviceKey` echoes the pool asked about —
21118
+ * `null` for the node-default pool.
21119
+ */
21120
+ var EffectiveTuningSchema = object({
21121
+ deviceKey: string().nullable(),
21122
+ runtime: string(),
21123
+ batchMode: string(),
21124
+ windowMs: number(),
21125
+ maxBatchSize: number(),
21126
+ concurrency: number(),
21127
+ numWorkers: number()
21128
+ });
21129
+ var AvailableEngineSchema = object({
21130
+ engine: PipelineEngineChoiceSchema,
21131
+ devices: array(object({
21132
+ id: string(),
21133
+ label: string(),
21134
+ description: string().optional()
21135
+ })).readonly(),
21136
+ defaultDevice: string()
21137
+ });
21138
+ var PipelineDefaultStepSchema = lazy(() => object({
21139
+ addonId: string(),
21140
+ addonName: string(),
21141
+ slot: PipelineSlotSchema,
21142
+ inputClasses: array(string()).readonly(),
21143
+ outputClasses: array(string()).readonly(),
21144
+ enabled: boolean(),
21145
+ modelId: string(),
21146
+ children: array(PipelineDefaultStepSchema).readonly(),
21147
+ group: string().optional(),
21148
+ settings: record(string(), unknown()).optional()
21149
+ }));
21150
+ var PipelineTemplateStepSchema = lazy(() => object({
21151
+ addonId: string(),
21152
+ enabled: boolean(),
21153
+ modelId: string(),
21154
+ children: array(PipelineTemplateStepSchema).readonly(),
21155
+ settings: record(string(), unknown()).optional()
21156
+ }));
21157
+ var PipelineTemplateSchema$1 = object({
21158
+ id: string(),
21159
+ name: string(),
21160
+ createdAt: string(),
21161
+ updatedAt: string(),
21162
+ engine: PipelineEngineChoiceSchema,
21163
+ steps: array(PipelineTemplateStepSchema).readonly()
21164
+ });
21165
+ var PipelineModelOptionSchema = object({
21166
+ id: string(),
21167
+ name: string(),
21168
+ formats: record(string(), object({
21169
+ downloaded: boolean(),
21170
+ sizeMB: number()
21171
+ })),
21172
+ group: ModelVariantGroupSchema.optional(),
21173
+ legacy: boolean().optional(),
21174
+ provider: ModelProviderIdSchema.optional()
21175
+ });
21176
+ var ConfigFieldBridge = custom();
21177
+ var PipelineAddonSchemaSchema = object({
21178
+ id: string(),
21179
+ name: string(),
21180
+ slot: PipelineSlotSchema,
21181
+ inputClasses: array(string()).readonly(),
21182
+ outputClasses: array(string()).readonly(),
21183
+ childSlots: array(PipelineSlotSchema).readonly(),
21184
+ models: array(PipelineModelOptionSchema).readonly(),
21185
+ defaultModelId: string(),
21186
+ defaultModelIdByFormat: record(string(), string()).optional(),
21187
+ enabledByDefault: boolean().optional(),
21188
+ backfillIntoExistingOverrides: boolean().optional(),
21189
+ defaultConfidence: number(),
21190
+ group: string().optional(),
21191
+ configSchema: array(ConfigFieldBridge).readonly().optional()
21192
+ });
21193
+ var PipelineSlotSchemaSchema = object({
21194
+ id: PipelineSlotSchema,
21195
+ label: string(),
21196
+ priority: number(),
21197
+ parentSlot: PipelineSlotSchema.nullable(),
21198
+ addons: array(PipelineAddonSchemaSchema).readonly()
21199
+ });
21200
+ var PipelineSchemaSchema = object({
21201
+ availableEngines: array(AvailableEngineSchema).readonly(),
21202
+ selectedEngine: PipelineEngineChoiceSchema,
21203
+ slots: array(PipelineSlotSchemaSchema).readonly()
21204
+ });
21205
+ var EngineProvisioningSchema = object({
21206
+ runtimeId: _enum([
21207
+ "onnx",
21208
+ "openvino",
21209
+ "coreml",
21210
+ "edgetpu"
21211
+ ]).nullable(),
21212
+ device: string().nullable(),
21213
+ state: _enum([
21214
+ "idle",
21215
+ "installing",
21216
+ "verifying",
21217
+ "ready",
21218
+ "failed"
21219
+ ]),
21220
+ progress: number().optional(),
21221
+ error: string().optional(),
21222
+ nextRetryAt: number().optional(),
21223
+ /**
21224
+ * Gate A (config-correctness gate at engine change): human-readable
21225
+ * config issues surfaced EAGERLY when the node's engine changes — model
21226
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
21227
+ * has a <format> build"). Additive/optional: informational only, never
21228
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
21229
+ * Absent/empty when the node-default tree resolves cleanly.
21230
+ */
21231
+ configIssues: array(string()).optional()
21232
+ });
21233
+ var PipelineStepInputSchema = lazy(() => object({
21234
+ addonId: string(),
21235
+ modelId: string().optional(),
21236
+ enabled: boolean().default(true),
21237
+ children: array(PipelineStepInputSchema).optional(),
21238
+ settings: record(string(), unknown()).optional(),
21239
+ jumpDeviceKey: string().optional()
21240
+ }));
21241
+ var ModelSubstitutionSchema = object({
21242
+ addonId: string(),
21243
+ chosen: string(),
21244
+ running: string(),
21245
+ format: string()
21246
+ });
21247
+ var PipelineValidationIssueSchema = object({
21248
+ addonId: string(),
21249
+ kind: _enum([
21250
+ "unknown-addon",
21251
+ "no-format-build",
21252
+ "unknown-device"
21253
+ ]),
21254
+ detail: string()
21255
+ });
21256
+ var PipelineValidationResultSchema = object({
21257
+ ok: boolean(),
21258
+ issues: array(PipelineValidationIssueSchema).readonly(),
21259
+ substitutions: array(ModelSubstitutionSchema).readonly(),
21260
+ /** The node's `currentEngine.format` this validation ran against. */
21261
+ format: string()
21262
+ });
21263
+ var ReferenceImageEntrySchema = object({
21264
+ filename: string(),
21265
+ stepIds: array(string()).readonly().optional()
21266
+ });
21267
+ var ReferenceImageBodySchema = object({
21268
+ base64: string(),
21269
+ filename: string()
21270
+ });
21271
+ var ReferenceAudioEntrySchema = object({
21272
+ filename: string(),
21273
+ sizeKb: number()
21274
+ });
21275
+ var ReferenceAudioBodySchema = object({ base64: string() });
21276
+ var AudioBackendSchema = object({
21277
+ id: string(),
21278
+ name: string(),
21279
+ description: string(),
21280
+ available: boolean(),
21281
+ /**
21282
+ * Raw classifier labels this backend can emit (e.g. YAMNet's
21283
+ * 521-class set or Apple SoundAnalysis's 303-class set). Used by
21284
+ * the benchmark UI to populate the `enabledMicroClasses` filter
21285
+ * specific to the selected backend without a separate fetch.
21286
+ */
21287
+ rawLabels: array(string()).readonly().optional()
21288
+ });
21289
+ var AudioCapabilitiesSchema = object({
21290
+ activeBackend: string(),
21291
+ availableBackends: array(AudioBackendSchema).readonly(),
21292
+ sampleRate: number(),
21293
+ chunkDurationMs: number()
21294
+ });
21295
+ var DownloadModelResultSchema = object({
21296
+ filePath: string(),
21297
+ sizeMB: number(),
21298
+ durationMs: number()
21299
+ });
21300
+ /**
21301
+ * Wrapper carrying a single test run's result. Replaces the legacy
21302
+ * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
21303
+ * canonical `AudioResult` from the Phase 6 output rework: one
21304
+ * `AudioDetection` per class above `minScore`, top-N candidates in
21305
+ * `debug.alternateLabels['audio-classifier']`, per-source timings in
21306
+ * `debug.stepTimings`. The outer `success`/`error` fields stay so the
21307
+ * benchmark UI can still report a clean failure when the classifier
21308
+ * cap isn't available.
21309
+ */
21310
+ var AudioTestResultSchema = object({
21311
+ success: boolean(),
21312
+ error: string().optional(),
21313
+ frame: custom().optional()
21314
+ });
21315
+ var PipelineConfigBridge = custom();
21316
+ var ConfigUISchemaBridge = custom();
21317
+ var ConfigUISchemaNullableBridge = custom();
21318
+ var InferenceCapabilitiesBridge = custom();
21319
+ var ModelAvailabilityListBridge = custom();
21320
+ var PipelineRunResultBridge = custom();
21321
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
21322
+ modelId: string(),
21323
+ settings: record(string(), unknown()).readonly()
21324
+ }))), method(object({ steps: record(string(), object({
21325
+ modelId: string(),
21326
+ settings: record(string(), unknown()).readonly()
21327
+ })) }), object({ success: literal(true) }), {
21328
+ kind: "mutation",
21329
+ auth: "admin"
21330
+ }), method(object({ nodeId: string() }), object({
21331
+ success: literal(true),
21332
+ clearedDevices: number()
21380
21333
  }), {
21381
- kind: "query",
21382
- auth: "admin"
21383
- }), method(object({
21384
- deviceId: number(),
21385
- trackId: string(),
21386
- frameId: string(),
21387
- subject: RetrainAssistSubjectSchema,
21388
- /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
21389
- nodeId: string().optional()
21390
- }), RetrainAssistResultSchema, {
21391
- kind: "mutation",
21392
- auth: "admin"
21393
- }), method(object({
21394
- deviceId: number(),
21395
- source: DetectionSourceSchema,
21396
- zones: array(ZoneSchema).readonly().optional(),
21397
- detectionRules: array(ZoneRuleSchema).readonly().optional(),
21398
- zoneMembershipMinOverlap: number().min(0).max(1).optional(),
21399
- frames: array(ReplayFrameInputSchema).min(1)
21400
- }), RunReplayFrameProcessorResultSchema, {
21401
- kind: "mutation",
21402
- auth: "admin"
21403
- }), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
21404
- kind: "query",
21405
- auth: "admin"
21406
- }), method(object({
21407
- deviceId: number(),
21408
- trackId: string(),
21409
- frameId: string(),
21410
- annotations: array(RetrainAnnotationDraftSchema)
21411
- }), array(RetrainAnnotationSchema).readonly(), {
21412
21334
  kind: "mutation",
21413
21335
  auth: "admin"
21414
- }), method(object({
21415
- deviceId: number(),
21416
- trackId: string()
21417
- }), RetrainTransitionResultSchema, {
21336
+ }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
21337
+ /** `<backend>:<device>`, e.g. `openvino:gpu`. */
21338
+ deviceKey: string(),
21339
+ /**
21340
+ * `failed` — the per-device restart budget is exhausted; no pool
21341
+ * will be spawned until an operator re-arms it or the runner
21342
+ * respawns. `backoff` — under budget, waiting out the backoff (or
21343
+ * a cached pool observed dead and not yet condemned).
21344
+ */
21345
+ state: _enum(["failed", "backoff"]),
21346
+ /** Epoch ms of the death that produced this state. */
21347
+ since: number(),
21348
+ /** Pool deaths inside the current window. */
21349
+ deaths: number(),
21350
+ /** The last death's message. */
21351
+ lastError: string()
21352
+ })).readonly() })), method(object({
21353
+ nodeId: string(),
21354
+ deviceKey: string()
21355
+ }), object({ rearmed: boolean() }), {
21418
21356
  kind: "mutation",
21419
21357
  auth: "admin"
21420
- }), method(object({
21421
- deviceId: number(),
21422
- trackId: string()
21423
- }), RetrainTransitionResultSchema, {
21358
+ }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({
21359
+ steps: array(PipelineStepInputSchema),
21360
+ deviceKey: string().optional()
21361
+ }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
21362
+ name: string(),
21363
+ steps: array(PipelineTemplateStepSchema).readonly(),
21364
+ engine: PipelineEngineChoiceSchema
21365
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
21366
+ id: string(),
21367
+ name: string().optional(),
21368
+ steps: array(PipelineTemplateStepSchema).readonly().optional()
21369
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
21370
+ addonId: string(),
21371
+ modelId: string(),
21372
+ format: ModelFormatSchema$1
21373
+ }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
21374
+ addonId: string(),
21375
+ modelId: string(),
21376
+ format: ModelFormatSchema$1
21377
+ }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
21378
+ steps: array(PipelineStepInputSchema).min(1),
21379
+ frame: FrameInputSchema.optional(),
21380
+ /**
21381
+ * Process-local lazy frame. Valid only when caller and provider resolve
21382
+ * in the same execution-group process; split/cross-node callers use
21383
+ * `frame`/`image` inline compatibility instead.
21384
+ */
21385
+ frameRef: FrameRefSchema.optional(),
21386
+ /**
21387
+ * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
21388
+ * the decoded pixels live in. One more member of the one-of
21389
+ * frame/frameHandle/image/imageBase64/referenceImage group.
21390
+ */
21391
+ frameHandle: FrameHandleSchema.optional(),
21392
+ imageBase64: string().optional(),
21393
+ /**
21394
+ * Binary JPEG bytes — preferred over `imageBase64` on internal
21395
+ * hops (hub → forked worker via Moleculer MsgPack) because it
21396
+ * skips the 33% base64 overhead + the per-call base64 decode on
21397
+ * the detection-pipeline worker. Callers can pass either; exactly
21398
+ * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
21399
+ */
21400
+ image: _instanceof(Uint8Array).optional(),
21401
+ referenceImage: string().optional(),
21402
+ deviceId: number().optional(),
21403
+ sessionId: string().optional(),
21404
+ /**
21405
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
21406
+ * reference-image, and detail-subtree calls. 'frame' is the live
21407
+ * per-frame dispatch: ONLY root-plane steps run; crop children
21408
+ * (inputClasses ≠ null) are skipped and served per-track via
21409
+ * pipelineRunner.runDetailSubtree (two-plane design).
21410
+ */
21411
+ plane: _enum(["full", "frame"]).optional(),
21412
+ /**
21413
+ * Inference-device selector (Phase 2 multi-device). Format
21414
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
21415
+ * Omitted ⇒ the runner's default device (current single-engine
21416
+ * behaviour). Selects WHICH device pool of the node runs the call.
21417
+ */
21418
+ deviceKey: string().optional(),
21419
+ /**
21420
+ * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
21421
+ * when the parent crop was resolved from the frame's retained NATIVE
21422
+ * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
21423
+ * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
21424
+ * resolution from that surface — the SAME quality path faces already
21425
+ * had — instead of the downscaled parent tile. `handle` keys the native
21426
+ * surface (node-pinned to its owner); `cropFrameSpace` is the parent
21427
+ * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
21428
+ * the executor's crop-normalized child ROI back into frame-normalized
21429
+ * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
21430
+ * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
21431
+ * (today's behaviour on the fallback path).
21432
+ */
21433
+ nativeCropRef: NativeCropRefSchema.optional(),
21434
+ /**
21435
+ * Offline REPLAY of stored media for `deviceId` (D56, D646): the
21436
+ * camera's zone gate and overrides apply as live, every executing
21437
+ * step runs EXACTLY the model it names on the `deviceKey` pool it
21438
+ * names or the call refuses (`model-not-servable` /
21439
+ * `device-not-servable`), and nothing reaches the camera's live
21440
+ * trace stream. Absent ⇒ unchanged behaviour.
21441
+ */
21442
+ replay: literal(true).optional()
21443
+ }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
21444
+ steps: array(PipelineStepInputSchema).min(1),
21445
+ frames: array(FrameInputSchema).min(1).max(255),
21446
+ deviceId: number().optional(),
21447
+ sessionId: string().optional(),
21448
+ /**
21449
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
21450
+ * the batch to the Python pool's bench preprocess cache
21451
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
21452
+ * preprocessed ONCE and every later inference is a pure-inference cache
21453
+ * hit — the sustained-throughput run measures inference, not
21454
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
21455
+ * full preprocess every call, correct). Fresh per sustained run;
21456
+ * released via `uncacheFrame`.
21457
+ */
21458
+ frameId: number().int().nonnegative().optional(),
21459
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
21460
+ deviceKey: string().optional()
21461
+ }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
21462
+ data: _instanceof(Uint8Array),
21463
+ width: number().int().positive(),
21464
+ height: number().int().positive(),
21465
+ format: _enum([
21466
+ "rgb",
21467
+ "bgr",
21468
+ "gray"
21469
+ ])
21470
+ }), object({
21471
+ frameId: number(),
21472
+ width: number(),
21473
+ height: number()
21474
+ }), { kind: "mutation" }), method(object({
21475
+ stepId: string(),
21476
+ frameId: number().int()
21477
+ }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(object({ deviceKey: string().optional() }), EffectiveTuningSchema), method(_void(), array(object({
21478
+ engineKey: string(),
21479
+ engine: PipelineEngineChoiceSchema,
21480
+ modelsLoaded: array(string()).readonly(),
21481
+ inUseByCameras: array(number()).readonly(),
21482
+ /**
21483
+ * Origin of this resident factory.
21484
+ * - `runtime` — main camera-serving engine (no idle TTL).
21485
+ * - `device-pool` — a concurrent per-device pool (Phase 2
21486
+ * multi-device, keyed by `deviceKey`) resolved
21487
+ * via `resolveDeviceFactory`. Runs alongside the
21488
+ * `runtime` engine on a DIFFERENT accelerator
21489
+ * (NPU / iGPU / Coral) — this is how the
21490
+ * Engines tab shows all pools running at once.
21491
+ */
21492
+ kind: _enum(["runtime", "device-pool"]),
21493
+ /** Native pid of the underlying Python pool (null when no pool). */
21494
+ poolPid: number().nullable(),
21495
+ /** ms since this factory was last used (null when not warm-tracked). */
21496
+ idleMs: number().nullable(),
21497
+ /** Idle TTL after which the factory self-evicts (null when not applicable). */
21498
+ idleTtlMs: number().nullable()
21499
+ })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
21424
21500
  kind: "mutation",
21425
21501
  auth: "admin"
21426
- }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
21427
- kind: "query",
21428
- auth: "admin"
21429
21502
  }), method(object({
21430
- /** Which of the three event tables owns the row. */
21431
- ownerType: EventOwnerTypeSchema,
21432
- /** The event's rowid in that table (D474: an event id is a number). */
21433
- eventId: number().int(),
21434
- kind: MediaFileKindEnum.optional(),
21435
- deviceId: number()
21436
- }), array(MediaFileSchema).readonly()), method(object({
21437
- trackId: string(),
21438
- kinds: array(MediaFileKindEnum).optional(),
21439
- deviceId: number()
21440
- }), array(MediaFileSchema).readonly()), method(object({
21441
- trackId: string(),
21442
- deviceId: number()
21443
- }), array(MediaFileInfoSchema).readonly()), method(object({
21444
- ownerType: EventOwnerTypeSchema,
21445
- eventId: number().int(),
21446
- deviceId: number()
21447
- }), array(MediaFileInfoSchema).readonly()), method(object({
21448
- deviceId: number(),
21449
- ownerKind: MediaPresenceOwnerKindSchema,
21450
- ownerIds: array(string()).max(5e3)
21451
- }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21452
- kind: "mutation",
21453
- auth: "admin"
21454
- }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
21503
+ engine: PipelineEngineChoiceSchema,
21504
+ force: boolean().optional()
21505
+ }), object({
21506
+ success: boolean(),
21507
+ reason: string().optional()
21508
+ }), {
21455
21509
  kind: "mutation",
21456
21510
  auth: "admin"
21457
- }), method(object({}), RebuildStatusSchema), object({
21458
- deviceId: number(),
21459
- timestamp: number(),
21460
- frameWidth: number(),
21461
- frameHeight: number(),
21462
- detections: array(OverlayDetectionSchema).readonly()
21463
- }), object({
21464
- deviceId: number(),
21465
- trackId: string(),
21466
- className: string()
21467
- }), object({
21468
- deviceId: number(),
21469
- trackId: string(),
21470
- className: string(),
21471
- durationMs: number()
21472
- }), object({
21473
- deviceId: number(),
21474
- kind: EventKindSchema,
21475
- eventId: string(),
21476
- timestamp: number()
21477
- });
21511
+ }), method(_void(), array(ReferenceImageEntrySchema).readonly()), method(object({ filename: string() }), ReferenceImageBodySchema.nullable()), method(_void(), array(ReferenceAudioEntrySchema).readonly()), method(object({ filename: string() }), ReferenceAudioBodySchema.nullable()), method(_void(), AudioCapabilitiesSchema), method(object({
21512
+ addonId: string(),
21513
+ modelId: string(),
21514
+ filename: string().optional(),
21515
+ settings: record(string(), unknown()).optional()
21516
+ }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
21478
21517
  object({
21479
21518
  activeCameras: number(),
21480
21519
  throttledCameras: number(),
@@ -21500,6 +21539,66 @@ var CameraMetricsSchema = object({
21500
21539
  });
21501
21540
  var CameraMetricsWithDeviceIdSchema = CameraMetricsSchema.extend({ deviceId: number() });
21502
21541
  /**
21542
+ * Zone — pure geometry + identity. NO filtering behaviour.
21543
+ *
21544
+ * Zones describe **where** in the frame the operator wants to flag
21545
+ * something; consumer-owned {@link ZoneRule} arrays describe **how**
21546
+ * each pipeline stage uses them. Splitting the two means a single
21547
+ * polygon "Driveway" can simultaneously back a motion-exclude rule,
21548
+ * a detection-include rule on `['car']`, and an occupancy aggregate
21549
+ * — without three duplicated polygons.
21550
+ *
21551
+ * Owned by the orchestrator addon (provider) and mirrored into the
21552
+ * `zones` device-state slice on every mutation. Consumers
21553
+ * (motion-wasm, pipeline-executor, analytics, admin UI) read either
21554
+ * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
21555
+ * mirror with `onChanged`).
21556
+ *
21557
+ * Coordinates are normalised fractions of the frame (0–1) so zones
21558
+ * survive resolution changes and stream profile switches.
21559
+ *
21560
+ * `kind` discriminates between full polygons (closed regions used
21561
+ * for intrusion / occupancy filters) and tripwires (open 2-point
21562
+ * line segments used for cross events). Onboard / firmware-reported
21563
+ * zones (Reolink, ONVIF) are out of scope for now — see the deferred
21564
+ * task list.
21565
+ */
21566
+ var ZoneKindEnum = _enum(["polygon", "tripwire"]);
21567
+ /** Polygon vertex in fraction-of-frame coordinates (0–1). */
21568
+ var PolygonPointSchema = object({
21569
+ x: number(),
21570
+ y: number()
21571
+ });
21572
+ /** A camera detection zone — pure geometry/identity. */
21573
+ var ZoneSchema = object({
21574
+ id: string(),
21575
+ name: string(),
21576
+ kind: ZoneKindEnum.default("polygon"),
21577
+ /** Polygon vertices, fraction of frame (0–1). */
21578
+ polygon: array(PolygonPointSchema).readonly(),
21579
+ /** Visual color for UI rendering. */
21580
+ color: string().default("#3b82f6")
21581
+ });
21582
+ DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
21583
+ deviceId: number(),
21584
+ zone: ZoneSchema
21585
+ }), _void(), {
21586
+ kind: "mutation",
21587
+ auth: "admin"
21588
+ }), method(object({
21589
+ deviceId: number(),
21590
+ zoneId: string()
21591
+ }), _void(), {
21592
+ kind: "mutation",
21593
+ auth: "admin"
21594
+ }), method(object({
21595
+ deviceId: number(),
21596
+ zone: ZoneSchema
21597
+ }), _void(), {
21598
+ kind: "mutation",
21599
+ auth: "admin"
21600
+ }), object({ zones: array(ZoneSchema).readonly() });
21601
+ /**
21503
21602
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
21504
21603
  * decode worker resolves it against the RETAINED native frame's real pixel dims,
21505
21604
  * so the caller supplies only the detection-res bbox divided by the detection
@@ -21686,7 +21785,17 @@ var DetailResultSchema = object({
21686
21785
  /** Face short side (px) measured on the NATIVE crop surface. The `bbox`
21687
21786
  * above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
21688
21787
  * consumers MUST prefer this when present (2026-07-22 native-gate fix). */
21689
- nativeFaceShortSidePx: number().optional()
21788
+ nativeFaceShortSidePx: number().optional(),
21789
+ /**
21790
+ * The five facial landmarks of a `face` detail, in the SAME frame space as
21791
+ * `bbox` (D644). Additive and optional: post-analysis stores them beside the
21792
+ * face's source frame so a future alignment can be re-derived exactly; a
21793
+ * runner that predates the field sends none and the face is stored without.
21794
+ */
21795
+ landmarks: array(object({
21796
+ x: number(),
21797
+ y: number()
21798
+ })).optional()
21690
21799
  });
21691
21800
  /**
21692
21801
  * Why an executing node REFUSED a stateless step run (`runStatelessStep`).
@@ -23072,7 +23181,11 @@ function buildDescriptor(capName, kind) {
23072
23181
  icon: legacyIcon(t.iconId)
23073
23182
  };
23074
23183
  }
23075
- Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
23184
+ /**
23185
+ * Sensor / control cap name → static event-kind descriptor. A linked device
23186
+ * contributes one entry per bound cap present in this map.
23187
+ */
23188
+ var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
23076
23189
  /**
23077
23190
  * server-management — per-NODE singleton capability for a node's ROOT
23078
23191
  * package lifecycle (runtime-updatable node packages).
@@ -30007,7 +30120,7 @@ var HardwareInfoSchema = object({
30007
30120
  coral: CoralInfoSchema.nullable().optional()
30008
30121
  });
30009
30122
  var PlatformScoreSchema = object({
30010
- runtime: _enum(["node", "python"]),
30123
+ runtime: literal("python"),
30011
30124
  backend: string(),
30012
30125
  format: _enum([
30013
30126
  "onnx",
@@ -30044,7 +30157,7 @@ var ModelRequirementSchema = object({
30044
30157
  });
30045
30158
  var ResolvedInferenceConfigSchema = object({
30046
30159
  modelId: string(),
30047
- runtime: _enum(["node", "python"]),
30160
+ runtime: literal("python"),
30048
30161
  backend: string(),
30049
30162
  format: ModelFormatSchema,
30050
30163
  reason: string()
@@ -32251,6 +32364,92 @@ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSche
32251
32364
  kind: "mutation",
32252
32365
  auth: "admin"
32253
32366
  });
32367
+ /**
32368
+ * Per-stage gating mode applied to the zones a rule references.
32369
+ *
32370
+ * - `include`: the rule contributes to a **whitelist** for its stage.
32371
+ * When at least one `include` rule fires for a stage, only entities
32372
+ * inside one of those zones pass that stage.
32373
+ * - `exclude`: the rule contributes to a **blacklist** for its stage.
32374
+ * Entities inside one of those zones are dropped at that stage.
32375
+ *
32376
+ * `monitor`-style observation (count without filtering) is not a rule
32377
+ * mode — zones without any matching rule are observed naturally by
32378
+ * `zone-analytics` (live snapshot + history), so an "I just want to
32379
+ * count, not filter" use case needs no rule at all.
32380
+ */
32381
+ var ZoneRuleModeEnum = _enum(["include", "exclude"]);
32382
+ /**
32383
+ * Per-consumer rule that references existing zones (geometry) and
32384
+ * defines how a specific pipeline stage should treat them. Each
32385
+ * consumer addon owns its own `ZoneRule[]` array in its per-device
32386
+ * settings:
32387
+ *
32388
+ * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
32389
+ * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
32390
+ * - future: notification rules, audio gating, etc.
32391
+ *
32392
+ * One rule applies to N zones (`zoneIds[]`) so the operator can
32393
+ * express "ignore motion in ALL of {garden, street}" with a single
32394
+ * rule. `classFilter` narrows the rule to specific object classes —
32395
+ * "drop person detections in the street, but keep cars" is one
32396
+ * `exclude` rule with `classFilter: ['person']`.
32397
+ *
32398
+ * `enabled` is a soft toggle — the operator can keep the rule
32399
+ * configured but inert without deleting it.
32400
+ */
32401
+ var ZoneRuleSchema = object({
32402
+ /** Stable rule id — survives edits, used by the UI for diffing. */
32403
+ id: string(),
32404
+ /** Optional human-readable label rendered in the rule editor. */
32405
+ name: string().optional(),
32406
+ /** Zones this rule targets. The rule's `mode` applies to ALL
32407
+ * listed zones (OR-set: a detection in any one of them counts).
32408
+ * At least one zone id required — a rule with no targets is a
32409
+ * configuration mistake and the form validator rejects it. */
32410
+ zoneIds: array(string()).min(1).readonly(),
32411
+ mode: ZoneRuleModeEnum,
32412
+ /**
32413
+ * Class names this rule applies to. Empty / undefined ⇒ rule
32414
+ * applies to every class. Class strings match the `macroClass`
32415
+ * field on detections (e.g. `person`, `car`, `dog`).
32416
+ */
32417
+ classFilter: array(string()).readonly().optional(),
32418
+ /**
32419
+ * Minimum bbox/mask overlap (0–1) with any of the rule's zones
32420
+ * required to consider an entity "in the zone". Defaults to the
32421
+ * consumer's stage default when omitted. Kept for back-compat with
32422
+ * existing per-rule overrides; new operators pick the value via
32423
+ * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
32424
+ * set, the lower-level engine reads it as a 0–1 fraction.
32425
+ */
32426
+ overlapThreshold: number().min(0).max(1).optional(),
32427
+ /**
32428
+ * Operator-friendly version of `overlapThreshold` — the percentage
32429
+ * of the detection's bbox that must lie inside the zone for the
32430
+ * rule to match. Documented default is 85%; the engine substitutes
32431
+ * that when the field is omitted (kept optional so existing rules
32432
+ * stored without it stay valid).
32433
+ *
32434
+ * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
32435
+ * rule, the engine prefers `bboxInclusionPct` because it's the
32436
+ * field exposed in the UI. Internally both feed the same gate.
32437
+ */
32438
+ bboxInclusionPct: number().min(0).max(100).optional(),
32439
+ /**
32440
+ * When `true` and a detection has a segmentation mask, use the
32441
+ * mask for overlap instead of the bbox. Detection-stage only;
32442
+ * motion rules ignore this field.
32443
+ */
32444
+ preferMask: boolean().optional(),
32445
+ /**
32446
+ * Soft-toggle: `false` disables the rule without deleting it.
32447
+ * Defaults to `true` so operators creating a rule via the UI
32448
+ * see it active immediately.
32449
+ */
32450
+ enabled: boolean().default(true)
32451
+ });
32452
+ array(ZoneRuleSchema).readonly();
32254
32453
  object({
32255
32454
  /** Whether the script is currently executing. */
32256
32455
  isRunning: boolean(),
@@ -33632,6 +33831,42 @@ DeviceType.Camera, method(object({
33632
33831
  package: array(ZoneRuleSchema).readonly()
33633
33832
  });
33634
33833
  /**
33834
+ * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
33835
+ * valutatore puro del suo FRONTE.
33836
+ *
33837
+ * Viveva dentro il builtin virtual-doorbell
33838
+ * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
33839
+ * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
33840
+ * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
33841
+ * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
33842
+ * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
33843
+ * SPOSTA qui e il doorbell la ri-esporta.
33844
+ *
33845
+ * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
33846
+ * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
33847
+ * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
33848
+ * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
33849
+ */
33850
+ /**
33851
+ * Known binary / switch source caps → the boolean slice field whose
33852
+ * false→true rise counts as ACTIVE. Every entry is "fire on active".
33853
+ * Sensors whose "active" reading is not a plain boolean (presence's string
33854
+ * state, connectivity's connected flag) are deliberately excluded — a
33855
+ * reconnect is not a doorbell press, and it is not a recording either.
33856
+ */
33857
+ var SOURCE_CAP_ACTIVE_FIELD = {
33858
+ contact: "entryOpen",
33859
+ binary: "on",
33860
+ switch: "on",
33861
+ motion: "detected",
33862
+ flood: "flooded",
33863
+ gas: "detected",
33864
+ smoke: "detected",
33865
+ "carbon-monoxide": "detected",
33866
+ vibration: "detected",
33867
+ tamper: "tampered"
33868
+ };
33869
+ /**
33635
33870
  * Accessory device helpers — shared across drivers.
33636
33871
  *
33637
33872
  * Many vendor-specific drivers register accessory child devices on
@@ -38537,6 +38772,12 @@ Object.freeze({
38537
38772
  addonId: null,
38538
38773
  access: "view"
38539
38774
  },
38775
+ "notificationRules.getRuleLastRuns": {
38776
+ capName: "notification-rules",
38777
+ capScope: "system",
38778
+ addonId: null,
38779
+ access: "view"
38780
+ },
38540
38781
  "notificationRules.getTemplateCatalog": {
38541
38782
  capName: "notification-rules",
38542
38783
  capScope: "system",
@@ -38561,6 +38802,12 @@ Object.freeze({
38561
38802
  addonId: null,
38562
38803
  access: "view"
38563
38804
  },
38805
+ "notificationRules.previewStateSummary": {
38806
+ capName: "notification-rules",
38807
+ capScope: "system",
38808
+ addonId: null,
38809
+ access: "view"
38810
+ },
38564
38811
  "notificationRules.previewTemplate": {
38565
38812
  capName: "notification-rules",
38566
38813
  capScope: "system",
@@ -39137,12 +39384,6 @@ Object.freeze({
39137
39384
  addonId: null,
39138
39385
  access: "create"
39139
39386
  },
39140
- "pipelineAnalytics.runReplayFrameProcessor": {
39141
- capName: "pipeline-analytics",
39142
- capScope: "device",
39143
- addonId: null,
39144
- access: "create"
39145
- },
39146
39387
  "pipelineAnalytics.saveRetrainAnnotations": {
39147
39388
  capName: "pipeline-analytics",
39148
39389
  capScope: "device",
@@ -43240,11 +43481,6 @@ Object.freeze({
43240
43481
  form: "single",
43241
43482
  optional: false
43242
43483
  }],
43243
- "pipelineAnalytics.runReplayFrameProcessor": [{
43244
- name: "deviceId",
43245
- form: "single",
43246
- optional: false
43247
- }],
43248
43484
  "pipelineAnalytics.saveRetrainAnnotations": [{
43249
43485
  name: "deviceId",
43250
43486
  form: "single",
@@ -44133,12 +44369,55 @@ var NC_AUDIO_DEFAULTS = {
44133
44369
  samplingSeconds: 10
44134
44370
  };
44135
44371
  new Set(["devices", "classes"]);
44136
- /** Every filter, for the callers that must cover all four (guards, tests). */
44372
+ /**
44373
+ * WHICH narrowing filter speaks about WHICH system-event kind — one predicate,
44374
+ * consumed by the rule ENGINE and by every rule EDITOR.
44375
+ *
44376
+ * ── The failure this file exists for ───────────────────────────────────────
44377
+ * `NcSystemEventConditionSchema` carries four optional narrowing lists
44378
+ * (`deviceIds`, `deviceTypes`, `nodeIds`, `packageNames`) beside a required
44379
+ * `kinds` gate. They are not four filters over one vocabulary: each belongs to
44380
+ * a FAMILY of kinds and is meaningless outside it. A node going away has no
44381
+ * device type; a completed export has no package; `stream-offline` deliberately
44382
+ * carries no device type at all (a stream belongs to a camera by construction).
44383
+ *
44384
+ * The engine applied all four to every kind, so a filter from one family failed
44385
+ * a kind from another family CLOSED. The operator's live rule "Eventi di
44386
+ * sistema" selected eleven kinds and set `deviceTypes: ['camera']` — the only
44387
+ * way to say "only my cameras" about the two liveness kinds — and NINE of the
44388
+ * eleven silently stopped being deliverable. Four days of history contained
44389
+ * `device-online` and `device-offline` and nothing else, across a window in
44390
+ * which agents were restarted and addons were updated.
44391
+ *
44392
+ * Both editors already computed this scoping, to decide which filter boxes to
44393
+ * render; the cap's own docblock already promised it ("Narrow a `device-*` kind
44394
+ * to these device TYPES"). Only the engine disagreed, and it disagreed in the
44395
+ * destructive direction. So the rule is stated ONCE, here, and the engine and
44396
+ * the editors are readers of it rather than three independent authors.
44397
+ *
44398
+ * ── The direction ─────────────────────────────────────────────────────────
44399
+ * Out of family ⇒ the filter is IGNORED, never fail-closed. An editor that
44400
+ * hides a box has already told the operator this filter does not speak about
44401
+ * these kinds, so honouring it as an exclusion contradicts the only
44402
+ * explanation the operator was ever given. Inside its family the filter still
44403
+ * narrows, and still fails CLOSED on a subject that cannot produce the identity
44404
+ * it tests (a device the mirror cannot name is not evidence of a camera).
44405
+ */
44406
+ /**
44407
+ * Every filter, for the callers that must cover all of them (guards, tests).
44408
+ *
44409
+ * `consumableLowPercent` (D642) is not a list but it is a filter in exactly
44410
+ * this sense: it belongs to a family of kinds (the consumable pair) and the
44411
+ * engine consults it only there. It differs in one way the readers must know —
44412
+ * its ABSENCE is not "no narrowing" but the default threshold, because a
44413
+ * consumable episode is always judged at SOME threshold.
44414
+ */
44137
44415
  var NC_SYSTEM_EVENT_FILTER_KEYS = [
44138
44416
  "deviceIds",
44139
44417
  "deviceTypes",
44140
44418
  "nodeIds",
44141
- "packageNames"
44419
+ "packageNames",
44420
+ "consumableLowPercent"
44142
44421
  ];
44143
44422
  new Map(Object.entries({
44144
44423
  "device-online": "device-events",
@@ -44149,6 +44428,9 @@ new Map(Object.entries({
44149
44428
  "device-battery-normal": "device-events",
44150
44429
  "device-consumable-low": "device-events",
44151
44430
  "device-consumable-normal": "device-events",
44431
+ "device-tank-low": "device-events",
44432
+ "device-tank-full": "device-events",
44433
+ "device-tank-normal": "device-events",
44152
44434
  "stream-online": "device-events",
44153
44435
  "stream-offline": "device-events",
44154
44436
  "detection-blind": "device-events",
@@ -44178,11 +44460,141 @@ new Map(Object.entries({
44178
44460
  "camera-enabled": null
44179
44461
  }));
44180
44462
  [...NC_SYSTEM_EVENT_FILTER_KEYS];
44463
+ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
44464
+ {
44465
+ id: "device-state",
44466
+ family: "device-events",
44467
+ label: "Device state",
44468
+ kinds: [
44469
+ "device-offline",
44470
+ "device-online",
44471
+ "device-disabled",
44472
+ "device-enabled"
44473
+ ]
44474
+ },
44475
+ {
44476
+ id: "streams",
44477
+ family: "device-events",
44478
+ label: "Streams",
44479
+ kinds: ["stream-offline", "stream-online"]
44480
+ },
44481
+ {
44482
+ id: "battery",
44483
+ family: "device-events",
44484
+ label: "Battery",
44485
+ kinds: ["device-battery-low", "device-battery-normal"]
44486
+ },
44487
+ {
44488
+ id: "consumables",
44489
+ family: "device-events",
44490
+ label: "Consumables",
44491
+ kinds: ["device-consumable-low", "device-consumable-normal"]
44492
+ },
44493
+ {
44494
+ id: "tanks",
44495
+ family: "device-events",
44496
+ label: "Tanks",
44497
+ kinds: [
44498
+ "device-tank-low",
44499
+ "device-tank-full",
44500
+ "device-tank-normal"
44501
+ ]
44502
+ },
44503
+ {
44504
+ id: "detection-blind",
44505
+ family: "device-events",
44506
+ label: "Detecting nothing",
44507
+ kinds: ["detection-blind"]
44508
+ },
44509
+ {
44510
+ id: "nodes",
44511
+ family: "system",
44512
+ label: "Nodes",
44513
+ kinds: [
44514
+ "node-offline",
44515
+ "node-online",
44516
+ "node-inference-unavailable"
44517
+ ]
44518
+ },
44519
+ {
44520
+ id: "addons",
44521
+ family: "system",
44522
+ label: "Addons",
44523
+ kinds: [
44524
+ "addon-crash-loop",
44525
+ "addon-update-available",
44526
+ "addon-updated"
44527
+ ]
44528
+ },
44529
+ {
44530
+ id: "updates",
44531
+ family: "system",
44532
+ label: "Server & container updates",
44533
+ kinds: [
44534
+ "server-update-available",
44535
+ "server-updated",
44536
+ "wrapper-update-available"
44537
+ ]
44538
+ },
44539
+ {
44540
+ id: "backups-exports",
44541
+ family: "system",
44542
+ label: "Backups & exports",
44543
+ kinds: ["backup-completed", "export-completed"]
44544
+ },
44545
+ {
44546
+ id: "alarm",
44547
+ family: "alarm",
44548
+ label: "Alarm",
44549
+ kinds: NC_ALARM_SYSTEM_EVENT_KINDS
44550
+ }
44551
+ ];
44552
+ function systemEventKindsOfFamily(family) {
44553
+ return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
44554
+ }
44181
44555
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
44556
+ /**
44557
+ * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
44558
+ * persists (D640 "a sensor rule fires on the raise, the clear, or both").
44559
+ *
44560
+ * A device-event rule over a flood sensor notified on BOTH edges: water
44561
+ * detected, and back to dry. `NcConditions.sensorTransition` lets the rule
44562
+ * name one edge; this file answers, for one `SensorEvent`, which edge it is.
44563
+ *
44564
+ * NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
44565
+ * already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
44566
+ * (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
44567
+ * table), and a sensor event names its kind, not its cap. So the table here is
44568
+ * DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
44569
+ * Only kinds whose taxonomy category is `sensor` take part — a switch has an
44570
+ * `on` boolean too, but "it triggers / it clears" is not a sentence about a
44571
+ * light, and offering it there would be a knob with a misleading label.
44572
+ * `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
44573
+ * absent from the source table, so it is absent here.
44574
+ *
44575
+ * `undefined` = never guessed: a kind with no raise (a doorbell press, a
44576
+ * button, a cover), or a slice whose field is missing or not a boolean. The
44577
+ * engine then does NOT filter — a flood alert must not go silent because one
44578
+ * read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
44579
+ *
44580
+ * Pure, RN-safe: the viewer imports the support rule from here.
44581
+ */
44582
+ /** Event kind → the boolean field of its cap slice that means "raised". */
44583
+ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
44584
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
44585
+ if (field === void 0 || descriptor.category !== "sensor") return [];
44586
+ return [[descriptor.kind, field]];
44587
+ })));
44588
+ Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
44182
44589
  var RULE_BODY_ONLY = ["body"];
44183
44590
  var RULE_TEXT = ["title", "body"];
44184
44591
  var BATTERY = ["device-battery-low", "device-battery-normal"];
44185
44592
  var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
44593
+ var TANK = [
44594
+ "device-tank-low",
44595
+ "device-tank-full",
44596
+ "device-tank-normal"
44597
+ ];
44186
44598
  var UPDATES = [
44187
44599
  "addon-update-available",
44188
44600
  "server-update-available",
@@ -44208,12 +44620,189 @@ var DEVICE_EVENTS = [
44208
44620
  "device-enabled",
44209
44621
  ...BATTERY,
44210
44622
  ...CONSUMABLE,
44623
+ ...TANK,
44211
44624
  "stream-online",
44212
44625
  "stream-offline",
44213
44626
  "detection-blind",
44214
44627
  ...ALARM
44215
44628
  ];
44216
- [...DEVICE_EVENTS], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_BODY_ONLY], [...RULE_TEXT], [...RULE_TEXT], [...DEVICE_EVENTS], [...RULE_TEXT], [...DEVICE_EVENTS], [...RULE_TEXT], [...BATTERY, ...CONSUMABLE], [...RULE_TEXT], [...CONSUMABLE], [...RULE_TEXT], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT];
44629
+ /** Every kind a summary may speak about — the two summarisable families. */
44630
+ var SUMMARISABLE = [...systemEventKindsOfFamily("device-events"), ...systemEventKindsOfFamily("system")];
44631
+ /** One per summary SECTION (`NC_STATE_SUMMARY_SECTIONS` in the notification
44632
+ * centre): the section's lines, offered when one of its kinds is selected. */
44633
+ var STATE_SUMMARY_SECTION_VARS = [
44634
+ {
44635
+ name: "summaryDevices",
44636
+ label: "Summary: devices offline / switched off",
44637
+ example: "Garage — offline since 07:12",
44638
+ kinds: [
44639
+ "device-offline",
44640
+ "device-online",
44641
+ "device-disabled",
44642
+ "device-enabled"
44643
+ ]
44644
+ },
44645
+ {
44646
+ name: "summaryStreams",
44647
+ label: "Summary: streams offline",
44648
+ example: "Ingresso (high) — since 06:40",
44649
+ kinds: ["stream-offline", "stream-online"]
44650
+ },
44651
+ {
44652
+ name: "summaryBattery",
44653
+ label: "Summary: low batteries",
44654
+ example: "Sensore porta — 8 %",
44655
+ kinds: BATTERY
44656
+ },
44657
+ {
44658
+ name: "summaryConsumables",
44659
+ label: "Summary: consumables",
44660
+ example: "Robot — filter 10 %",
44661
+ kinds: CONSUMABLE
44662
+ },
44663
+ {
44664
+ name: "summaryTanks",
44665
+ label: "Summary: tanks",
44666
+ example: "Robot — clean water low",
44667
+ kinds: TANK
44668
+ },
44669
+ {
44670
+ name: "summaryDetection",
44671
+ label: "Summary: cameras detecting nothing",
44672
+ example: "Ingresso — since 05:10",
44673
+ kinds: ["detection-blind"]
44674
+ },
44675
+ {
44676
+ name: "summaryNodes",
44677
+ label: "Summary: nodes",
44678
+ example: "little-unraid — offline since 03:00",
44679
+ kinds: [
44680
+ "node-offline",
44681
+ "node-online",
44682
+ "node-inference-unavailable"
44683
+ ]
44684
+ },
44685
+ {
44686
+ name: "summaryAddons",
44687
+ label: "Summary: addons",
44688
+ example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
44689
+ kinds: [
44690
+ "addon-crash-loop",
44691
+ "addon-update-available",
44692
+ "addon-updated"
44693
+ ]
44694
+ },
44695
+ {
44696
+ name: "summaryUpdates",
44697
+ label: "Summary: server & container updates",
44698
+ example: "server 1.2.377 → 1.2.378",
44699
+ kinds: [
44700
+ "server-update-available",
44701
+ "server-updated",
44702
+ "wrapper-update-available"
44703
+ ]
44704
+ }
44705
+ ];
44706
+ var STATE_SUMMARY_VARS = [
44707
+ {
44708
+ name: "summaryText",
44709
+ label: "Summary: every section",
44710
+ description: "One block per section with something to report, one line per item.",
44711
+ example: "Batterie scariche: Sensore porta — 8 %",
44712
+ group: "system",
44713
+ families: ["rule"],
44714
+ fields: [...RULE_TEXT],
44715
+ kinds: ["system"],
44716
+ systemEventKinds: [...SUMMARISABLE],
44717
+ systemEventDeliveries: ["summary"]
44718
+ },
44719
+ {
44720
+ name: "summaryCount",
44721
+ label: "Summary: items reported",
44722
+ example: "3",
44723
+ group: "system",
44724
+ families: ["rule"],
44725
+ fields: [...RULE_TEXT],
44726
+ kinds: ["system"],
44727
+ systemEventKinds: [...SUMMARISABLE],
44728
+ systemEventDeliveries: ["summary"]
44729
+ },
44730
+ ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
44731
+ name: v.name,
44732
+ label: v.label,
44733
+ example: v.example,
44734
+ group: "system",
44735
+ families: ["rule"],
44736
+ fields: [...RULE_TEXT],
44737
+ kinds: ["system"],
44738
+ systemEventKinds: [...v.kinds],
44739
+ systemEventDeliveries: ["summary"]
44740
+ }))
44741
+ ];
44742
+ [
44743
+ [...DEVICE_EVENTS],
44744
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
44745
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
44746
+ [...RULE_TEXT],
44747
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
44748
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
44749
+ [...RULE_TEXT],
44750
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
44751
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
44752
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
44753
+ [...RULE_TEXT],
44754
+ [...RULE_TEXT],
44755
+ [...RULE_TEXT],
44756
+ [...RULE_TEXT],
44757
+ [...RULE_TEXT],
44758
+ [...RULE_TEXT],
44759
+ [...RULE_TEXT],
44760
+ [...RULE_TEXT],
44761
+ [...RULE_BODY_ONLY],
44762
+ [...RULE_TEXT],
44763
+ ([...RULE_TEXT], [...DEVICE_EVENTS]),
44764
+ ([...RULE_TEXT], [...DEVICE_EVENTS]),
44765
+ ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
44766
+ ([...RULE_TEXT], [...CONSUMABLE]),
44767
+ ([...RULE_TEXT], [...TANK]),
44768
+ [...RULE_TEXT],
44769
+ ([...RULE_TEXT], [...UPDATES]),
44770
+ ([...RULE_TEXT], [...UPDATES]),
44771
+ [...RULE_TEXT],
44772
+ [...RULE_TEXT],
44773
+ [...RULE_TEXT],
44774
+ ([...RULE_TEXT], [...UPDATES]),
44775
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
44776
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
44777
+ ([...RULE_TEXT], [...UPDATES]),
44778
+ ([...RULE_TEXT], [...UPDATES]),
44779
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
44780
+ [...RULE_TEXT],
44781
+ [...RULE_TEXT],
44782
+ [...RULE_TEXT],
44783
+ [...RULE_TEXT],
44784
+ [...RULE_TEXT],
44785
+ [...RULE_TEXT],
44786
+ [...RULE_TEXT],
44787
+ [...RULE_TEXT],
44788
+ [...RULE_TEXT],
44789
+ [...RULE_TEXT],
44790
+ [...RULE_TEXT],
44791
+ [...RULE_TEXT],
44792
+ [...RULE_TEXT],
44793
+ [...RULE_TEXT],
44794
+ [...RULE_TEXT],
44795
+ [...RULE_TEXT],
44796
+ [...RULE_TEXT],
44797
+ [...RULE_TEXT],
44798
+ [...RULE_TEXT],
44799
+ [...RULE_TEXT],
44800
+ [...RULE_TEXT],
44801
+ [...RULE_TEXT],
44802
+ [...RULE_TEXT],
44803
+ [...RULE_TEXT],
44804
+ ...STATE_SUMMARY_VARS
44805
+ ];
44217
44806
  /**
44218
44807
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
44219
44808
  *
@@ -44710,7 +45299,7 @@ var AgentUIAddon = class extends BaseAddon {
44710
45299
  capability: adminUiCapability,
44711
45300
  provider: {
44712
45301
  getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
44713
- getVersion: async () => ({ version: "1.2.146" })
45302
+ getVersion: async () => ({ version: "1.2.148" })
44714
45303
  }
44715
45304
  }];
44716
45305
  }