@camstack/addon-model-studio 1.1.172 → 1.1.174

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 (18) hide show
  1. package/dist/{MotionZonesSettings-QVVEC220.mjs → MotionZonesSettings-DavNeZt1.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-xeMI1BTC.mjs → PrivacyMaskSettings-bWW0b0eT.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-C269t1tC.mjs → SceneMonitorEditor-DuXCuccO.mjs} +3 -3
  4. package/dist/_stub.js +10 -10
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-Co04mmEH.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-C_y_ArLj.mjs} +3 -3
  6. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DwoBFtDL.mjs +26 -0
  7. package/dist/{hostInit-GDWPIFmC.mjs → hostInit-nK0BL4EK.mjs} +2 -2
  8. package/dist/model-studio.addon.js +1324 -735
  9. package/dist/model-studio.addon.mjs +1324 -735
  10. package/dist/{player-overlays-CjVxAzYe.mjs → player-overlays-BZLfX48i.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-CH1mGJTo.mjs → responsive-DAticuj8.mjs} +1 -1
  13. package/dist/{scene-monitor-copy-BIVlnlk5.mjs → scene-monitor-copy-CuI6S0HF.mjs} +1 -1
  14. package/dist/{square-Bul4urrS.mjs → square-Dh-K-SIJ.mjs} +1 -1
  15. package/dist/{trash-2-DqNDJcPu.mjs → trash-2-Cuodqp-m.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-SwmSP59i.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-mbmirjVC.mjs} +1 -1
  17. package/package.json +1 -1
  18. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CEMfSnqp.mjs +0 -26
@@ -5863,7 +5863,7 @@ var ZodIssueCode = {
5863
5863
  var ZodFirstPartyTypeKind;
5864
5864
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5865
5865
  //#endregion
5866
- //#region ../types/dist/sleep-D_4W7xuQ.mjs
5866
+ //#region ../types/dist/sleep-BaRu0bS5.mjs
5867
5867
  /**
5868
5868
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5869
5869
  * window to float samples (D455).
@@ -9999,7 +9999,7 @@ var ModelFormatEntrySchema = object({
9999
9999
  /** Multi-file payload (directory members or sibling files). */
10000
10000
  files: array(string()).readonly().optional(),
10001
10001
  /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
10002
- runtimes: array(_enum(["node", "python"])).readonly().optional()
10002
+ runtimes: array(_enum(["python"])).readonly().optional()
10003
10003
  });
10004
10004
  /**
10005
10005
  * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
@@ -10080,10 +10080,17 @@ var ModelCatalogEntrySchema = object({
10080
10080
  inputChannels: number().int().positive().optional(),
10081
10081
  labels: array(LabelDefinitionSchema).readonly(),
10082
10082
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
10083
+ /**
10084
+ * `'scrfd'` applies InsightFace's SCRFD input contract, `(x - 127.5) / 128`
10085
+ * (upstream `insightface/model_zoo/scrfd.py`), instead of the historical
10086
+ * plain `/255`. Measured against COCO GT: 188 → 196 faces found on the
10087
+ * `scrfd-2.5g` catalog entry (2026-09-26 model-replacement spike, §2.4-2).
10088
+ */
10083
10089
  inputNormalization: _enum([
10084
10090
  "zero-one",
10085
10091
  "imagenet",
10086
- "none"
10092
+ "none",
10093
+ "scrfd"
10087
10094
  ]).optional(),
10088
10095
  /**
10089
10096
  * The model already applies softmax IN-GRAPH — its raw output is a
@@ -10185,7 +10192,8 @@ var ModelConvertMetadataSchema = object({
10185
10192
  inputNormalization: _enum([
10186
10193
  "zero-one",
10187
10194
  "imagenet",
10188
- "none"
10195
+ "none",
10196
+ "scrfd"
10189
10197
  ]).optional(),
10190
10198
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
10191
10199
  outputFormat: _enum([
@@ -17603,6 +17611,9 @@ var NcSystemEventKindSchema = _enum([
17603
17611
  "device-battery-normal",
17604
17612
  "device-consumable-low",
17605
17613
  "device-consumable-normal",
17614
+ "device-tank-low",
17615
+ "device-tank-full",
17616
+ "device-tank-normal",
17606
17617
  "stream-online",
17607
17618
  "stream-offline",
17608
17619
  "node-online",
@@ -17640,6 +17651,29 @@ var NC_LEGACY_SYSTEM_EVENT_KINDS = new Set([
17640
17651
  ]);
17641
17652
  NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
17642
17653
  /**
17654
+ * The panel's transitions, as ONE list.
17655
+ *
17656
+ * Named here rather than spelled out at each of the four sites that need them
17657
+ * (the emitter, the intake, the editor group, the combined-notification gate),
17658
+ * because a transition added to the enum and forgotten at one of them is an
17659
+ * alarm state nobody can be notified about. That is exactly what this list
17660
+ * failed to prevent while it held three: `alarm-arming` was the fourth.
17661
+ *
17662
+ * In LIFECYCLE order, which is the order an editor lists them in and the order
17663
+ * a person experiences them — not the order they were added.
17664
+ */
17665
+ var NC_ALARM_SYSTEM_EVENT_KINDS = [
17666
+ "alarm-arm-refused",
17667
+ "alarm-check-failed",
17668
+ "alarm-arming",
17669
+ "alarm-armed",
17670
+ "alarm-pending",
17671
+ "alarm-triggered",
17672
+ "alarm-rearmed",
17673
+ "alarm-sensor-bypass",
17674
+ "alarm-disarmed"
17675
+ ];
17676
+ /**
17643
17677
  * One coherent system-event condition. `kinds` is the required opt-in safety
17644
17678
  * gate; the remaining lists are optional narrowing filters relevant to the
17645
17679
  * selected kinds.
@@ -17673,7 +17707,22 @@ var NcSystemEventConditionSchema = object({
17673
17707
  */
17674
17708
  excludeDeviceIds: array(number().int()).min(1).optional(),
17675
17709
  nodeIds: array(string().min(1)).min(1).optional(),
17676
- packageNames: array(string().min(1)).min(1).optional()
17710
+ packageNames: array(string().min(1)).min(1).optional(),
17711
+ /**
17712
+ * At or below this remaining-life %, a consumable is LOW for THIS rule
17713
+ * (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
17714
+ * {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
17715
+ * consumable kinds (`systemEventFilterApplies`) and to the device-events
17716
+ * family; ignored on every other kind.
17717
+ *
17718
+ * It is a property of the RULE because the operator decided so: a rule at
17719
+ * 25 must be told about a filter at 20 %, a rule at 10 must not. The hub
17720
+ * keeps one episode per (threshold, device, item) — its own hysteresis, its
17721
+ * own reminder — and a rule hears only the episode judged at ITS threshold,
17722
+ * exactly as an occupancy rule hears only the edge of its own count. A
17723
+ * provider's own `replace` verdict is low at every threshold.
17724
+ */
17725
+ consumableLowPercent: number().int().min(1).max(95).optional()
17677
17726
  });
17678
17727
  /** Hard ceiling on a window (24h). A snooze that could not expire would be an
17679
17728
  * outage the operator asked for once and forgot. */
@@ -17691,6 +17740,25 @@ var NcScheduleSchema = object({
17691
17740
  /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
17692
17741
  invert: boolean().optional()
17693
17742
  });
17743
+ /**
17744
+ * How a system-event rule of the `device-events` or `system` family speaks
17745
+ * (D647): at once, per event (`instant`, the only behaviour before D647), or
17746
+ * as a STATE SUMMARY at the end of each window of `schedule`.
17747
+ */
17748
+ var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
17749
+ /**
17750
+ * `summary`: the rule sends NOTHING per event. When a window of `schedule`
17751
+ * CLOSES, it sends one notification with the current state of its kinds
17752
+ * (low batteries, consumables to replace, devices offline, updates
17753
+ * available, ...), after its own filters. Nothing to report sends nothing.
17754
+ * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
17755
+ * midnight crossing); the send time is each window's END. Refused on the
17756
+ * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
17757
+ */
17758
+ var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
17759
+ mode: literal("summary"),
17760
+ schedule: NcScheduleSchema
17761
+ })]);
17694
17762
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
17695
17763
  var NcPlateMatcherSchema = object({
17696
17764
  /**
@@ -17816,6 +17884,16 @@ var NcCrossingSchema = _enum([
17816
17884
  "exit",
17817
17885
  "any"
17818
17886
  ]);
17887
+ /**
17888
+ * Which edge of a BOOLEAN sensor fires a device-event rule (D640).
17889
+ * - `raised` — the sensor went high: water detected, smoke, open, tampered.
17890
+ * - `cleared` — it went back: dry, clear, closed.
17891
+ * - absent — both edges, which is what every rule authored before this did.
17892
+ *
17893
+ * There is no third member for "both": absence already says it, and a value
17894
+ * that means the same as absence is a second way to write one rule.
17895
+ */
17896
+ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
17819
17897
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
17820
17898
  var NcZoneConditionSchema = object({
17821
17899
  ids: array(string().min(1)).min(1),
@@ -18005,6 +18083,20 @@ var NcConditionsSchema = object({
18005
18083
  scene: NcSceneConditionSchema.optional(),
18006
18084
  /** Device scope — absent = all devices. */
18007
18085
  devices: array(number()).optional(),
18086
+ /**
18087
+ * Device EXCLUSION (D641) — "every device except these". Judged on the SAME
18088
+ * subject ids {@link devices} is judged on (the camera, the sensor that
18089
+ * fired, a linked set's sensor and row cameras), so a sensor fanned out per
18090
+ * linked camera is excluded exactly where an include would have tested it.
18091
+ * `min(1)`: an empty exclusion excludes nothing and is written as absent.
18092
+ *
18093
+ * Not for `system-event` rules — those carry their own
18094
+ * `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
18095
+ * Editors present the two lists as ONE field (All | Only these | All except
18096
+ * these); the engine evaluates each independently, so a legacy rule
18097
+ * carrying both still means "these, minus those".
18098
+ */
18099
+ devicesExclude: array(number()).min(1).optional(),
18008
18100
  /** Detector class names (any overlap with the record's class set). */
18009
18101
  classes: array(string().min(1)).optional(),
18010
18102
  /** Veto classes — any overlap fails the rule. */
@@ -18138,6 +18230,16 @@ var NcConditionsSchema = object({
18138
18230
  */
18139
18231
  sensorKinds: array(string().min(1)).optional(),
18140
18232
  /**
18233
+ * DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
18234
+ * {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
18235
+ * is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
18236
+ * engine cannot place (a kind with no raise, an unreadable slice) PASSES:
18237
+ * the condition fails open towards notifying, never towards silence. Editors
18238
+ * offer it only when every chosen {@link sensorKinds} member has a raise
18239
+ * (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
18240
+ */
18241
+ sensorTransition: NcSensorTransitionSchema.optional(),
18242
+ /**
18141
18243
  * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
18142
18244
  * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
18143
18245
  * when the subject's phase does not match (a subject always carries a phase
@@ -18392,6 +18494,16 @@ var NcRuleInputSchema = object({
18392
18494
  delivery: NcDeliverySchema,
18393
18495
  conditions: NcConditionsSchema.default({}),
18394
18496
  schedule: NcScheduleSchema.optional(),
18497
+ /**
18498
+ * A `device-events` / `system` rule's delivery (D647): absent or `instant`
18499
+ * is a notification per event; `summary` is one state summary per closed
18500
+ * window of its schedule. `.optional()` and never defaulted: absent must
18501
+ * keep meaning what every rule authored before D647 meant, with no
18502
+ * migration. Top-level rather than inside `conditions.systemEvent`, so a
18503
+ * client that does not know it (an older viewer re-sending `conditions`
18504
+ * whole) leaves it standing through the store's patch merge.
18505
+ */
18506
+ systemEventDelivery: NcSystemEventDeliverySchema.optional(),
18395
18507
  /** May be empty when `targetUsers` addresses at least one user — the
18396
18508
  * "at least one addressee" invariant is enforced by the provider, because
18397
18509
  * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
@@ -18620,6 +18732,28 @@ var NcRuleSchema = NcRuleInputSchema.extend({
18620
18732
  */
18621
18733
  disabledTargetIds: array(string()).default([])
18622
18734
  });
18735
+ /** One section of a previewed state summary. `unavailable` = its state could not be read. */
18736
+ var NcStateSummaryPreviewSectionSchema = object({
18737
+ id: string(),
18738
+ status: _enum(["read", "unavailable"]),
18739
+ items: number().int().nonnegative(),
18740
+ reason: string().optional()
18741
+ });
18742
+ /**
18743
+ * The summary a rule would send now. `empty` = nothing to report and every
18744
+ * section read: such a summary is NOT sent. `dropped` counts the open states
18745
+ * the rule's own filters (or a camera mute) left out, by reason.
18746
+ * `excludedGroups` names the kind groups selected that have no state to report
18747
+ * (backups & exports).
18748
+ */
18749
+ var NcStateSummaryPreviewSchema = object({
18750
+ empty: boolean(),
18751
+ count: number().int().nonnegative(),
18752
+ text: string(),
18753
+ sections: array(NcStateSummaryPreviewSectionSchema),
18754
+ dropped: record(string(), number().int().nonnegative()),
18755
+ excludedGroups: array(string())
18756
+ });
18623
18757
  var NcTestResultSchema = object({
18624
18758
  recordId: string(),
18625
18759
  recordKind: _enum([
@@ -18711,6 +18845,13 @@ var NcTemplateVarDescriptorSchema = object({
18711
18845
  kinds: array(NcTemplateVarKindSchema).optional(),
18712
18846
  deliveries: array(NcDeliverySchema).optional(),
18713
18847
  systemEventKinds: array(NcSystemEventKindSchema).optional(),
18848
+ /**
18849
+ * Which system-event DELIVERY has a value for it (D647). Absent = an
18850
+ * instant row only when the descriptor names `systemEventKinds` (a
18851
+ * per-event fact: a summary row has no single device), and both otherwise.
18852
+ * `['summary']` marks the state-summary vocabulary (`{{summaryText}}`, ...).
18853
+ */
18854
+ systemEventDeliveries: array(NcSystemEventDeliveryModeSchema).optional(),
18714
18855
  dynamic: object({
18715
18856
  prefix: literal("count_"),
18716
18857
  from: literal("classes")
@@ -18723,7 +18864,9 @@ var NcTemplatePreviewInputSchema = object({
18723
18864
  field: NcTemplateFieldSchema,
18724
18865
  kind: NcRuleKindSchema.optional(),
18725
18866
  delivery: NcDeliverySchema.optional(),
18726
- systemEventKinds: array(NcSystemEventKindSchema).optional()
18867
+ systemEventKinds: array(NcSystemEventKindSchema).optional(),
18868
+ /** The rule's system-event delivery (D647); absent = instant. */
18869
+ systemEventDelivery: NcSystemEventDeliveryModeSchema.optional()
18727
18870
  }),
18728
18871
  template: object({
18729
18872
  title: string().max(500).optional(),
@@ -18756,6 +18899,7 @@ var NcConditionDescriptorSchema = object({
18756
18899
  /** Editor widget the UI renders — never hardcode per-condition forms. */
18757
18900
  valueType: _enum([
18758
18901
  "deviceIdList",
18902
+ "deviceIdExcludeList",
18759
18903
  "stringList",
18760
18904
  "number01",
18761
18905
  "number",
@@ -18766,6 +18910,7 @@ var NcConditionDescriptorSchema = object({
18766
18910
  "plateMatcher",
18767
18911
  "packagePhase",
18768
18912
  "crossingSelect",
18913
+ "sensorTransition",
18769
18914
  "polygonDraw",
18770
18915
  "occupancy",
18771
18916
  "audio",
@@ -18916,6 +19061,28 @@ var NcHistoryFilterSchema = object({
18916
19061
  limit: number().int().min(1).max(500).default(100)
18917
19062
  });
18918
19063
  /**
19064
+ * Every rule's last run, in one answer.
19065
+ *
19066
+ * A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
19067
+ * back this answer looked, in ms before the hub's now. It is the outbox prune
19068
+ * window (7 days by default, operator-configurable), NARROWED when the read hit
19069
+ * its row bound: the answer never claims a window it did not read. `null` means
19070
+ * the operator keeps the outbox forever AND the read reached its end, so absent
19071
+ * means "never on record". A UI derives its "not in the last N days" from this
19072
+ * value, never from a constant — and only when the call ANSWERED (D315).
19073
+ */
19074
+ var NcRuleLastRunsSchema = object({
19075
+ runs: array(object({
19076
+ ruleId: string(),
19077
+ /** Fire time of the newest row (`createdAt`, epoch ms). */
19078
+ lastAt: number(),
19079
+ status: NcHistoryStatusSchema,
19080
+ /** The camera/device of that row (a digest row files under its first camera). */
19081
+ deviceId: number()
19082
+ })),
19083
+ horizonMs: number().nonnegative().nullable()
19084
+ });
19085
+ /**
18919
19086
  * What a snooze covers. Broader scopes win when several overlap, so one window
18920
19087
  * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
18921
19088
  * summarising the same silence.
@@ -19096,6 +19263,14 @@ var NcAlarmModeCoverageSchema = object({
19096
19263
  /** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
19097
19264
  deviceIds: array(number().int()),
19098
19265
  /**
19266
+ * D641: the ids NOT covered although `allDevices` is true — every unscoped
19267
+ * covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
19268
+ * covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
19269
+ * which is not the same as skipped by a switch: never in `skippedDevices`.
19270
+ * Defaulted so an answer computed before this field parses as "none".
19271
+ */
19272
+ allDevicesExcept: array(number().int()).default([]),
19273
+ /**
19099
19274
  * Devices this mode NAMES but cannot actually arm, each with the switch that
19100
19275
  * excludes it.
19101
19276
  *
@@ -19225,13 +19400,17 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
19225
19400
  }), object({ results: array(NcTestResultSchema) }), {
19226
19401
  kind: "mutation",
19227
19402
  auth: "admin"
19403
+ }), method(object({ rule: NcRuleInputSchema }), NcStateSummaryPreviewSchema, {
19404
+ kind: "mutation",
19405
+ auth: "admin",
19406
+ access: "view"
19228
19407
  }), method(object({}), object({
19229
19408
  catalog: array(NcConditionDescriptorSchema),
19230
19409
  taxonomy: NcTaxonomySchema.optional()
19231
19410
  })), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
19232
19411
  kind: "mutation",
19233
19412
  access: "view"
19234
- }), 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 }), {
19413
+ }), 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 }), {
19235
19414
  kind: "mutation",
19236
19415
  caller: "required"
19237
19416
  }), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
@@ -19571,606 +19750,6 @@ var OauthIntegrationDescriptorSchema = object({
19571
19750
  });
19572
19751
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
19573
19752
  /**
19574
- * Reference to the frame's retained NATIVE surface + the parent crop's placement
19575
- * within the frame, so the executor can re-cut a leaf child ROI at native
19576
- * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
19577
- */
19578
- var NativeCropRefSchema = object({
19579
- /** Handle keying the retained native surface (node-pinned to its owner). */
19580
- handle: FrameHandleSchema,
19581
- /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
19582
- cropFrameSpace: object({
19583
- x: number(),
19584
- y: number(),
19585
- w: number(),
19586
- h: number()
19587
- })
19588
- });
19589
- object({
19590
- crop: object({
19591
- left: number(),
19592
- top: number(),
19593
- width: number().positive(),
19594
- height: number().positive()
19595
- }).optional(),
19596
- content: object({
19597
- width: number().int().positive(),
19598
- height: number().int().positive()
19599
- }),
19600
- fit: _enum(["stretch", "contain"]),
19601
- format: _enum([
19602
- "rgb",
19603
- "gray",
19604
- "jpeg"
19605
- ])
19606
- });
19607
- /**
19608
- * Process-local frame identity. It is serializable so it can ride an in-process
19609
- * capability call, but `registryId` deliberately prevents resolution in any
19610
- * other process or execution group.
19611
- */
19612
- var FrameRefSchema = object({
19613
- registryId: string().min(1),
19614
- id: string().min(1),
19615
- width: number().int().positive(),
19616
- height: number().int().positive(),
19617
- format: _enum(["rgb", "gray"]),
19618
- timestamp: number(),
19619
- capturedAt: number().optional()
19620
- });
19621
- var ModelFormatSchema$1 = _enum([
19622
- "onnx",
19623
- "coreml",
19624
- "openvino",
19625
- "tflite",
19626
- "pt",
19627
- "gguf"
19628
- ]);
19629
- var PipelineSlotSchema = _enum([
19630
- "detector",
19631
- "cropper",
19632
- "classifier",
19633
- "refiner",
19634
- "audio-classifier"
19635
- ]);
19636
- var PipelineEngineChoiceSchema = object({
19637
- runtime: _enum(["node", "python"]),
19638
- backend: string(),
19639
- format: ModelFormatSchema$1,
19640
- device: string().optional()
19641
- });
19642
- var AvailableEngineSchema = object({
19643
- engine: PipelineEngineChoiceSchema,
19644
- devices: array(object({
19645
- id: string(),
19646
- label: string(),
19647
- description: string().optional()
19648
- })).readonly(),
19649
- defaultDevice: string()
19650
- });
19651
- var PipelineDefaultStepSchema = lazy(() => object({
19652
- addonId: string(),
19653
- addonName: string(),
19654
- slot: PipelineSlotSchema,
19655
- inputClasses: array(string()).readonly(),
19656
- outputClasses: array(string()).readonly(),
19657
- enabled: boolean(),
19658
- modelId: string(),
19659
- children: array(PipelineDefaultStepSchema).readonly(),
19660
- group: string().optional(),
19661
- settings: record(string(), unknown()).optional()
19662
- }));
19663
- var PipelineTemplateStepSchema = lazy(() => object({
19664
- addonId: string(),
19665
- enabled: boolean(),
19666
- modelId: string(),
19667
- children: array(PipelineTemplateStepSchema).readonly(),
19668
- settings: record(string(), unknown()).optional()
19669
- }));
19670
- var PipelineTemplateSchema$1 = object({
19671
- id: string(),
19672
- name: string(),
19673
- createdAt: string(),
19674
- updatedAt: string(),
19675
- engine: PipelineEngineChoiceSchema,
19676
- steps: array(PipelineTemplateStepSchema).readonly()
19677
- });
19678
- var PipelineModelOptionSchema = object({
19679
- id: string(),
19680
- name: string(),
19681
- formats: record(string(), object({
19682
- downloaded: boolean(),
19683
- sizeMB: number()
19684
- })),
19685
- group: ModelVariantGroupSchema.optional(),
19686
- legacy: boolean().optional(),
19687
- provider: ModelProviderIdSchema.optional()
19688
- });
19689
- var ConfigFieldBridge = custom();
19690
- var PipelineAddonSchemaSchema = object({
19691
- id: string(),
19692
- name: string(),
19693
- slot: PipelineSlotSchema,
19694
- inputClasses: array(string()).readonly(),
19695
- outputClasses: array(string()).readonly(),
19696
- childSlots: array(PipelineSlotSchema).readonly(),
19697
- models: array(PipelineModelOptionSchema).readonly(),
19698
- defaultModelId: string(),
19699
- defaultModelIdByFormat: record(string(), string()).optional(),
19700
- enabledByDefault: boolean().optional(),
19701
- backfillIntoExistingOverrides: boolean().optional(),
19702
- defaultConfidence: number(),
19703
- group: string().optional(),
19704
- configSchema: array(ConfigFieldBridge).readonly().optional()
19705
- });
19706
- var PipelineSlotSchemaSchema = object({
19707
- id: PipelineSlotSchema,
19708
- label: string(),
19709
- priority: number(),
19710
- parentSlot: PipelineSlotSchema.nullable(),
19711
- addons: array(PipelineAddonSchemaSchema).readonly()
19712
- });
19713
- var PipelineSchemaSchema = object({
19714
- availableEngines: array(AvailableEngineSchema).readonly(),
19715
- selectedEngine: PipelineEngineChoiceSchema,
19716
- slots: array(PipelineSlotSchemaSchema).readonly()
19717
- });
19718
- var EngineProvisioningSchema = object({
19719
- runtimeId: _enum([
19720
- "onnx",
19721
- "openvino",
19722
- "coreml",
19723
- "edgetpu"
19724
- ]).nullable(),
19725
- device: string().nullable(),
19726
- state: _enum([
19727
- "idle",
19728
- "installing",
19729
- "verifying",
19730
- "ready",
19731
- "failed"
19732
- ]),
19733
- progress: number().optional(),
19734
- error: string().optional(),
19735
- nextRetryAt: number().optional(),
19736
- /**
19737
- * Gate A (config-correctness gate at engine change): human-readable
19738
- * config issues surfaced EAGERLY when the node's engine changes — model
19739
- * substitutions ("chose X, running Y") and zero-build steps ("no model
19740
- * has a <format> build"). Additive/optional: informational only, never
19741
- * enforced here — `assertEngineReady` (readiness) still gates inference.
19742
- * Absent/empty when the node-default tree resolves cleanly.
19743
- */
19744
- configIssues: array(string()).optional()
19745
- });
19746
- var PipelineStepInputSchema = lazy(() => object({
19747
- addonId: string(),
19748
- modelId: string().optional(),
19749
- enabled: boolean().default(true),
19750
- children: array(PipelineStepInputSchema).optional(),
19751
- settings: record(string(), unknown()).optional(),
19752
- jumpDeviceKey: string().optional()
19753
- }));
19754
- var ModelSubstitutionSchema = object({
19755
- addonId: string(),
19756
- chosen: string(),
19757
- running: string(),
19758
- format: string()
19759
- });
19760
- var PipelineValidationIssueSchema = object({
19761
- addonId: string(),
19762
- kind: _enum(["unknown-addon", "no-format-build"]),
19763
- detail: string()
19764
- });
19765
- var PipelineValidationResultSchema = object({
19766
- ok: boolean(),
19767
- issues: array(PipelineValidationIssueSchema).readonly(),
19768
- substitutions: array(ModelSubstitutionSchema).readonly(),
19769
- /** The node's `currentEngine.format` this validation ran against. */
19770
- format: string()
19771
- });
19772
- var ReferenceImageEntrySchema = object({
19773
- filename: string(),
19774
- stepIds: array(string()).readonly().optional()
19775
- });
19776
- var ReferenceImageBodySchema = object({
19777
- base64: string(),
19778
- filename: string()
19779
- });
19780
- var ReferenceAudioEntrySchema = object({
19781
- filename: string(),
19782
- sizeKb: number()
19783
- });
19784
- var ReferenceAudioBodySchema = object({ base64: string() });
19785
- var AudioBackendSchema = object({
19786
- id: string(),
19787
- name: string(),
19788
- description: string(),
19789
- available: boolean(),
19790
- /**
19791
- * Raw classifier labels this backend can emit (e.g. YAMNet's
19792
- * 521-class set or Apple SoundAnalysis's 303-class set). Used by
19793
- * the benchmark UI to populate the `enabledMicroClasses` filter
19794
- * specific to the selected backend without a separate fetch.
19795
- */
19796
- rawLabels: array(string()).readonly().optional()
19797
- });
19798
- var AudioCapabilitiesSchema = object({
19799
- activeBackend: string(),
19800
- availableBackends: array(AudioBackendSchema).readonly(),
19801
- sampleRate: number(),
19802
- chunkDurationMs: number()
19803
- });
19804
- var DownloadModelResultSchema = object({
19805
- filePath: string(),
19806
- sizeMB: number(),
19807
- durationMs: number()
19808
- });
19809
- /**
19810
- * Wrapper carrying a single test run's result. Replaces the legacy
19811
- * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
19812
- * canonical `AudioResult` from the Phase 6 output rework: one
19813
- * `AudioDetection` per class above `minScore`, top-N candidates in
19814
- * `debug.alternateLabels['audio-classifier']`, per-source timings in
19815
- * `debug.stepTimings`. The outer `success`/`error` fields stay so the
19816
- * benchmark UI can still report a clean failure when the classifier
19817
- * cap isn't available.
19818
- */
19819
- var AudioTestResultSchema = object({
19820
- success: boolean(),
19821
- error: string().optional(),
19822
- frame: custom().optional()
19823
- });
19824
- var PipelineConfigBridge = custom();
19825
- var ConfigUISchemaBridge = custom();
19826
- var ConfigUISchemaNullableBridge = custom();
19827
- var InferenceCapabilitiesBridge = custom();
19828
- var ModelAvailabilityListBridge = custom();
19829
- var PipelineRunResultBridge = custom();
19830
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
19831
- modelId: string(),
19832
- settings: record(string(), unknown()).readonly()
19833
- }))), method(object({ steps: record(string(), object({
19834
- modelId: string(),
19835
- settings: record(string(), unknown()).readonly()
19836
- })) }), object({ success: literal(true) }), {
19837
- kind: "mutation",
19838
- auth: "admin"
19839
- }), method(object({ nodeId: string() }), object({
19840
- success: literal(true),
19841
- clearedDevices: number()
19842
- }), {
19843
- kind: "mutation",
19844
- auth: "admin"
19845
- }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
19846
- /** `<backend>:<device>`, e.g. `openvino:gpu`. */
19847
- deviceKey: string(),
19848
- /**
19849
- * `failed` — the per-device restart budget is exhausted; no pool
19850
- * will be spawned until an operator re-arms it or the runner
19851
- * respawns. `backoff` — under budget, waiting out the backoff (or
19852
- * a cached pool observed dead and not yet condemned).
19853
- */
19854
- state: _enum(["failed", "backoff"]),
19855
- /** Epoch ms of the death that produced this state. */
19856
- since: number(),
19857
- /** Pool deaths inside the current window. */
19858
- deaths: number(),
19859
- /** The last death's message. */
19860
- lastError: string()
19861
- })).readonly() })), method(object({
19862
- nodeId: string(),
19863
- deviceKey: string()
19864
- }), object({ rearmed: boolean() }), {
19865
- kind: "mutation",
19866
- auth: "admin"
19867
- }), 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({
19868
- name: string(),
19869
- steps: array(PipelineTemplateStepSchema).readonly(),
19870
- engine: PipelineEngineChoiceSchema
19871
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
19872
- id: string(),
19873
- name: string().optional(),
19874
- steps: array(PipelineTemplateStepSchema).readonly().optional()
19875
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
19876
- addonId: string(),
19877
- modelId: string(),
19878
- format: ModelFormatSchema$1
19879
- }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
19880
- addonId: string(),
19881
- modelId: string(),
19882
- format: ModelFormatSchema$1
19883
- }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
19884
- engine: PipelineEngineChoiceSchema.optional(),
19885
- steps: array(PipelineStepInputSchema).min(1),
19886
- frame: FrameInputSchema.optional(),
19887
- /**
19888
- * Process-local lazy frame. Valid only when caller and provider resolve
19889
- * in the same execution-group process; split/cross-node callers use
19890
- * `frame`/`image` inline compatibility instead.
19891
- */
19892
- frameRef: FrameRefSchema.optional(),
19893
- /**
19894
- * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
19895
- * the decoded pixels live in. One more member of the one-of
19896
- * frame/frameHandle/image/imageBase64/referenceImage group.
19897
- */
19898
- frameHandle: FrameHandleSchema.optional(),
19899
- imageBase64: string().optional(),
19900
- /**
19901
- * Binary JPEG bytes — preferred over `imageBase64` on internal
19902
- * hops (hub → forked worker via Moleculer MsgPack) because it
19903
- * skips the 33% base64 overhead + the per-call base64 decode on
19904
- * the detection-pipeline worker. Callers can pass either; exactly
19905
- * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
19906
- */
19907
- image: _instanceof(Uint8Array).optional(),
19908
- referenceImage: string().optional(),
19909
- deviceId: number().optional(),
19910
- sessionId: string().optional(),
19911
- /**
19912
- * Execution plane. 'full' (default) runs the whole tree — benchmark,
19913
- * reference-image, and detail-subtree calls. 'frame' is the live
19914
- * per-frame dispatch: ONLY root-plane steps run; crop children
19915
- * (inputClasses ≠ null) are skipped and served per-track via
19916
- * pipelineRunner.runDetailSubtree (two-plane design).
19917
- */
19918
- plane: _enum(["full", "frame"]).optional(),
19919
- /**
19920
- * Inference-device selector (Phase 2 multi-device). Format
19921
- * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
19922
- * Omitted ⇒ the runner's default device (current single-engine
19923
- * behaviour). Selects WHICH device pool of the node runs the call.
19924
- */
19925
- deviceKey: string().optional(),
19926
- /**
19927
- * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
19928
- * when the parent crop was resolved from the frame's retained NATIVE
19929
- * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
19930
- * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
19931
- * resolution from that surface — the SAME quality path faces already
19932
- * had — instead of the downscaled parent tile. `handle` keys the native
19933
- * surface (node-pinned to its owner); `cropFrameSpace` is the parent
19934
- * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
19935
- * the executor's crop-normalized child ROI back into frame-normalized
19936
- * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
19937
- * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
19938
- * (today's behaviour on the fallback path).
19939
- */
19940
- nativeCropRef: NativeCropRefSchema.optional()
19941
- }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
19942
- engine: PipelineEngineChoiceSchema.optional(),
19943
- steps: array(PipelineStepInputSchema).min(1),
19944
- frames: array(FrameInputSchema).min(1).max(255),
19945
- deviceId: number().optional(),
19946
- sessionId: string().optional(),
19947
- /**
19948
- * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
19949
- * the batch to the Python pool's bench preprocess cache
19950
- * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
19951
- * preprocessed ONCE and every later inference is a pure-inference cache
19952
- * hit — the sustained-throughput run measures inference, not
19953
- * decode+preprocess+infer. Omitted/0 for live frames (all different →
19954
- * full preprocess every call, correct). Fresh per sustained run;
19955
- * released via `uncacheFrame`.
19956
- */
19957
- frameId: number().int().nonnegative().optional(),
19958
- /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
19959
- deviceKey: string().optional()
19960
- }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
19961
- data: _instanceof(Uint8Array),
19962
- width: number().int().positive(),
19963
- height: number().int().positive(),
19964
- format: _enum([
19965
- "rgb",
19966
- "bgr",
19967
- "gray"
19968
- ])
19969
- }), object({
19970
- frameId: number(),
19971
- width: number(),
19972
- height: number()
19973
- }), { kind: "mutation" }), method(object({
19974
- stepId: string(),
19975
- frameId: number().int()
19976
- }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(_void(), object({
19977
- batchMode: string(),
19978
- windowMs: number(),
19979
- maxBatchSize: number(),
19980
- concurrency: number()
19981
- })), method(_void(), array(object({
19982
- engineKey: string(),
19983
- engine: PipelineEngineChoiceSchema,
19984
- modelsLoaded: array(string()).readonly(),
19985
- inUseByCameras: array(number()).readonly(),
19986
- /**
19987
- * Origin of this resident factory.
19988
- * - `runtime` — main camera-serving engine (no idle TTL).
19989
- * - `warm-override` — benchmark/test override held in the warm
19990
- * cache; auto-disposed after the idle TTL.
19991
- * - `device-pool` — a concurrent per-device pool (Phase 2
19992
- * multi-device, keyed by `deviceKey`) resolved
19993
- * via `resolveDeviceFactory`. Runs alongside the
19994
- * `runtime` engine on a DIFFERENT accelerator
19995
- * (NPU / iGPU / Coral) — this is how the
19996
- * Engines tab shows all pools running at once.
19997
- */
19998
- kind: _enum([
19999
- "runtime",
20000
- "warm-override",
20001
- "device-pool"
20002
- ]),
20003
- /** Native pid of the underlying Python pool (null when no pool). */
20004
- poolPid: number().nullable(),
20005
- /** ms since this factory was last used (null when not warm-tracked). */
20006
- idleMs: number().nullable(),
20007
- /** Idle TTL after which `warm-override` factories self-evict (null when not applicable). */
20008
- idleTtlMs: number().nullable()
20009
- })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
20010
- kind: "mutation",
20011
- auth: "admin"
20012
- }), method(object({
20013
- engine: PipelineEngineChoiceSchema,
20014
- force: boolean().optional()
20015
- }), object({
20016
- success: boolean(),
20017
- reason: string().optional()
20018
- }), {
20019
- kind: "mutation",
20020
- auth: "admin"
20021
- }), 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({
20022
- addonId: string(),
20023
- modelId: string(),
20024
- filename: string().optional(),
20025
- settings: record(string(), unknown()).optional()
20026
- }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
20027
- /**
20028
- * Per-stage gating mode applied to the zones a rule references.
20029
- *
20030
- * - `include`: the rule contributes to a **whitelist** for its stage.
20031
- * When at least one `include` rule fires for a stage, only entities
20032
- * inside one of those zones pass that stage.
20033
- * - `exclude`: the rule contributes to a **blacklist** for its stage.
20034
- * Entities inside one of those zones are dropped at that stage.
20035
- *
20036
- * `monitor`-style observation (count without filtering) is not a rule
20037
- * mode — zones without any matching rule are observed naturally by
20038
- * `zone-analytics` (live snapshot + history), so an "I just want to
20039
- * count, not filter" use case needs no rule at all.
20040
- */
20041
- var ZoneRuleModeEnum = _enum(["include", "exclude"]);
20042
- /**
20043
- * Per-consumer rule that references existing zones (geometry) and
20044
- * defines how a specific pipeline stage should treat them. Each
20045
- * consumer addon owns its own `ZoneRule[]` array in its per-device
20046
- * settings:
20047
- *
20048
- * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
20049
- * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
20050
- * - future: notification rules, audio gating, etc.
20051
- *
20052
- * One rule applies to N zones (`zoneIds[]`) so the operator can
20053
- * express "ignore motion in ALL of {garden, street}" with a single
20054
- * rule. `classFilter` narrows the rule to specific object classes —
20055
- * "drop person detections in the street, but keep cars" is one
20056
- * `exclude` rule with `classFilter: ['person']`.
20057
- *
20058
- * `enabled` is a soft toggle — the operator can keep the rule
20059
- * configured but inert without deleting it.
20060
- */
20061
- var ZoneRuleSchema = object({
20062
- /** Stable rule id — survives edits, used by the UI for diffing. */
20063
- id: string(),
20064
- /** Optional human-readable label rendered in the rule editor. */
20065
- name: string().optional(),
20066
- /** Zones this rule targets. The rule's `mode` applies to ALL
20067
- * listed zones (OR-set: a detection in any one of them counts).
20068
- * At least one zone id required — a rule with no targets is a
20069
- * configuration mistake and the form validator rejects it. */
20070
- zoneIds: array(string()).min(1).readonly(),
20071
- mode: ZoneRuleModeEnum,
20072
- /**
20073
- * Class names this rule applies to. Empty / undefined ⇒ rule
20074
- * applies to every class. Class strings match the `macroClass`
20075
- * field on detections (e.g. `person`, `car`, `dog`).
20076
- */
20077
- classFilter: array(string()).readonly().optional(),
20078
- /**
20079
- * Minimum bbox/mask overlap (0–1) with any of the rule's zones
20080
- * required to consider an entity "in the zone". Defaults to the
20081
- * consumer's stage default when omitted. Kept for back-compat with
20082
- * existing per-rule overrides; new operators pick the value via
20083
- * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
20084
- * set, the lower-level engine reads it as a 0–1 fraction.
20085
- */
20086
- overlapThreshold: number().min(0).max(1).optional(),
20087
- /**
20088
- * Operator-friendly version of `overlapThreshold` — the percentage
20089
- * of the detection's bbox that must lie inside the zone for the
20090
- * rule to match. Documented default is 85%; the engine substitutes
20091
- * that when the field is omitted (kept optional so existing rules
20092
- * stored without it stay valid).
20093
- *
20094
- * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
20095
- * rule, the engine prefers `bboxInclusionPct` because it's the
20096
- * field exposed in the UI. Internally both feed the same gate.
20097
- */
20098
- bboxInclusionPct: number().min(0).max(100).optional(),
20099
- /**
20100
- * When `true` and a detection has a segmentation mask, use the
20101
- * mask for overlap instead of the bbox. Detection-stage only;
20102
- * motion rules ignore this field.
20103
- */
20104
- preferMask: boolean().optional(),
20105
- /**
20106
- * Soft-toggle: `false` disables the rule without deleting it.
20107
- * Defaults to `true` so operators creating a rule via the UI
20108
- * see it active immediately.
20109
- */
20110
- enabled: boolean().default(true)
20111
- });
20112
- array(ZoneRuleSchema).readonly();
20113
- /**
20114
- * Zone — pure geometry + identity. NO filtering behaviour.
20115
- *
20116
- * Zones describe **where** in the frame the operator wants to flag
20117
- * something; consumer-owned {@link ZoneRule} arrays describe **how**
20118
- * each pipeline stage uses them. Splitting the two means a single
20119
- * polygon "Driveway" can simultaneously back a motion-exclude rule,
20120
- * a detection-include rule on `['car']`, and an occupancy aggregate
20121
- * — without three duplicated polygons.
20122
- *
20123
- * Owned by the orchestrator addon (provider) and mirrored into the
20124
- * `zones` device-state slice on every mutation. Consumers
20125
- * (motion-wasm, pipeline-executor, analytics, admin UI) read either
20126
- * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
20127
- * mirror with `onChanged`).
20128
- *
20129
- * Coordinates are normalised fractions of the frame (0–1) so zones
20130
- * survive resolution changes and stream profile switches.
20131
- *
20132
- * `kind` discriminates between full polygons (closed regions used
20133
- * for intrusion / occupancy filters) and tripwires (open 2-point
20134
- * line segments used for cross events). Onboard / firmware-reported
20135
- * zones (Reolink, ONVIF) are out of scope for now — see the deferred
20136
- * task list.
20137
- */
20138
- var ZoneKindEnum = _enum(["polygon", "tripwire"]);
20139
- /** Polygon vertex in fraction-of-frame coordinates (0–1). */
20140
- var PolygonPointSchema = object({
20141
- x: number(),
20142
- y: number()
20143
- });
20144
- /** A camera detection zone — pure geometry/identity. */
20145
- var ZoneSchema = object({
20146
- id: string(),
20147
- name: string(),
20148
- kind: ZoneKindEnum.default("polygon"),
20149
- /** Polygon vertices, fraction of frame (0–1). */
20150
- polygon: array(PolygonPointSchema).readonly(),
20151
- /** Visual color for UI rendering. */
20152
- color: string().default("#3b82f6")
20153
- });
20154
- DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
20155
- deviceId: number(),
20156
- zone: ZoneSchema
20157
- }), _void(), {
20158
- kind: "mutation",
20159
- auth: "admin"
20160
- }), method(object({
20161
- deviceId: number(),
20162
- zoneId: string()
20163
- }), _void(), {
20164
- kind: "mutation",
20165
- auth: "admin"
20166
- }), method(object({
20167
- deviceId: number(),
20168
- zone: ZoneSchema
20169
- }), _void(), {
20170
- kind: "mutation",
20171
- auth: "admin"
20172
- }), object({ zones: array(ZoneSchema).readonly() });
20173
- /**
20174
19753
  * pipeline-analytics — device-scoped wrapper cap. Refines raw
20175
19754
  * per-frame detections emitted by the pipeline runner into tracked
20176
19755
  * objects, per-kind event collections (motion / object / audio), and
@@ -20741,6 +20320,22 @@ var TrackSchema = object({
20741
20320
  * omit it. Render nothing when absent.
20742
20321
  */
20743
20322
  previewMissReason: string().optional(),
20323
+ /**
20324
+ * The ROOT model that produced this track — the `FrameResult.rootModelId` of
20325
+ * the frame it was BORN on (D646). What a later replay compares against, so
20326
+ * "the original model" is named rather than assumed. A birth fact: never
20327
+ * rewritten by a later frame. **Absent ≠ unknown-model**: a row from before
20328
+ * the column, and a track the camera's own detector produced, carry none.
20329
+ */
20330
+ modelId: string().optional(),
20331
+ /**
20332
+ * Size (px) of the detection frame this track's `positions[].bbox` live in —
20333
+ * the frame it was born on, else the camera's latest analysis frame at
20334
+ * persist time. Divide a position by these to normalise it. Absent when the
20335
+ * size was never known.
20336
+ */
20337
+ frameWidth: number().optional(),
20338
+ frameHeight: number().optional(),
20744
20339
  ...TrackFlagFields,
20745
20340
  ...TrackRetrainFields
20746
20341
  });
@@ -21747,24 +21342,6 @@ var MediaReclaimStatusSchema = object({
21747
21342
  finishedAtMs: number().nullable(),
21748
21343
  error: string().nullable()
21749
21344
  });
21750
- var ReplayFrameInputSchema = object({
21751
- timestamp: number(),
21752
- frame: PipelineRunResultBridge
21753
- });
21754
- var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
21755
- className: string(),
21756
- firstSeenMs: number(),
21757
- lastSeenMs: number(),
21758
- /** Bbox of the track's FIRST matched detection, pixel-space in the clip's
21759
- * frame — a representative box for the diff's `(className, window, IoU)`
21760
- * pairing (`replay-diff.ts`). A replay does not need the full per-frame
21761
- * trajectory production's `Track.positions` keeps. */
21762
- bbox: BoundingBoxSchema,
21763
- /** How many of the input frames this track matched a real detection on
21764
- * (never a coasted/extrapolated frame) — the replay's own signal for "how
21765
- * solid is this track", cheaper than re-deriving it from a trajectory. */
21766
- framesMatched: number().int()
21767
- })).readonly() });
21768
21345
  /**
21769
21346
  * The long-term series a camera produces. A closed union so a typo cannot
21770
21347
  * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
@@ -21978,108 +21555,570 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21978
21555
  }), {
21979
21556
  kind: "mutation",
21980
21557
  auth: "admin"
21981
- }), method(object({ frameId: string() }), object({
21982
- base64: string(),
21983
- width: number().int(),
21984
- height: number().int()
21558
+ }), method(object({ frameId: string() }), object({
21559
+ base64: string(),
21560
+ width: number().int(),
21561
+ height: number().int()
21562
+ }), {
21563
+ kind: "query",
21564
+ auth: "admin"
21565
+ }), method(object({
21566
+ deviceId: number(),
21567
+ trackId: string(),
21568
+ frameId: string(),
21569
+ subject: RetrainAssistSubjectSchema,
21570
+ /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
21571
+ nodeId: string().optional()
21572
+ }), RetrainAssistResultSchema, {
21573
+ kind: "mutation",
21574
+ auth: "admin"
21575
+ }), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
21576
+ kind: "query",
21577
+ auth: "admin"
21578
+ }), method(object({
21579
+ deviceId: number(),
21580
+ trackId: string(),
21581
+ frameId: string(),
21582
+ annotations: array(RetrainAnnotationDraftSchema)
21583
+ }), array(RetrainAnnotationSchema).readonly(), {
21584
+ kind: "mutation",
21585
+ auth: "admin"
21586
+ }), method(object({
21587
+ deviceId: number(),
21588
+ trackId: string()
21589
+ }), RetrainTransitionResultSchema, {
21590
+ kind: "mutation",
21591
+ auth: "admin"
21592
+ }), method(object({
21593
+ deviceId: number(),
21594
+ trackId: string()
21595
+ }), RetrainTransitionResultSchema, {
21596
+ kind: "mutation",
21597
+ auth: "admin"
21598
+ }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
21599
+ kind: "query",
21600
+ auth: "admin"
21601
+ }), method(object({
21602
+ /** Which of the three event tables owns the row. */
21603
+ ownerType: EventOwnerTypeSchema,
21604
+ /** The event's rowid in that table (D474: an event id is a number). */
21605
+ eventId: number().int(),
21606
+ kind: MediaFileKindEnum.optional(),
21607
+ deviceId: number()
21608
+ }), array(MediaFileSchema).readonly()), method(object({
21609
+ trackId: string(),
21610
+ kinds: array(MediaFileKindEnum).optional(),
21611
+ deviceId: number()
21612
+ }), array(MediaFileSchema).readonly()), method(object({
21613
+ trackId: string(),
21614
+ deviceId: number()
21615
+ }), array(MediaFileInfoSchema).readonly()), method(object({
21616
+ ownerType: EventOwnerTypeSchema,
21617
+ eventId: number().int(),
21618
+ deviceId: number()
21619
+ }), array(MediaFileInfoSchema).readonly()), method(object({
21620
+ deviceId: number(),
21621
+ ownerKind: MediaPresenceOwnerKindSchema,
21622
+ ownerIds: array(string()).max(5e3)
21623
+ }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21624
+ kind: "mutation",
21625
+ auth: "admin"
21626
+ }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
21627
+ kind: "mutation",
21628
+ auth: "admin"
21629
+ }), method(object({}), RebuildStatusSchema), object({
21630
+ deviceId: number(),
21631
+ timestamp: number(),
21632
+ frameWidth: number(),
21633
+ frameHeight: number(),
21634
+ detections: array(OverlayDetectionSchema).readonly()
21635
+ }), object({
21636
+ deviceId: number(),
21637
+ trackId: string(),
21638
+ className: string()
21639
+ }), object({
21640
+ deviceId: number(),
21641
+ trackId: string(),
21642
+ className: string(),
21643
+ durationMs: number()
21644
+ }), object({
21645
+ deviceId: number(),
21646
+ kind: EventKindSchema,
21647
+ eventId: string(),
21648
+ timestamp: number()
21649
+ });
21650
+ /**
21651
+ * Reference to the frame's retained NATIVE surface + the parent crop's placement
21652
+ * within the frame, so the executor can re-cut a leaf child ROI at native
21653
+ * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
21654
+ */
21655
+ var NativeCropRefSchema = object({
21656
+ /** Handle keying the retained native surface (node-pinned to its owner). */
21657
+ handle: FrameHandleSchema,
21658
+ /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
21659
+ cropFrameSpace: object({
21660
+ x: number(),
21661
+ y: number(),
21662
+ w: number(),
21663
+ h: number()
21664
+ })
21665
+ });
21666
+ object({
21667
+ crop: object({
21668
+ left: number(),
21669
+ top: number(),
21670
+ width: number().positive(),
21671
+ height: number().positive()
21672
+ }).optional(),
21673
+ content: object({
21674
+ width: number().int().positive(),
21675
+ height: number().int().positive()
21676
+ }),
21677
+ fit: _enum(["stretch", "contain"]),
21678
+ format: _enum([
21679
+ "rgb",
21680
+ "gray",
21681
+ "jpeg"
21682
+ ])
21683
+ });
21684
+ /**
21685
+ * Process-local frame identity. It is serializable so it can ride an in-process
21686
+ * capability call, but `registryId` deliberately prevents resolution in any
21687
+ * other process or execution group.
21688
+ */
21689
+ var FrameRefSchema = object({
21690
+ registryId: string().min(1),
21691
+ id: string().min(1),
21692
+ width: number().int().positive(),
21693
+ height: number().int().positive(),
21694
+ format: _enum(["rgb", "gray"]),
21695
+ timestamp: number(),
21696
+ capturedAt: number().optional()
21697
+ });
21698
+ var ModelFormatSchema$1 = _enum([
21699
+ "onnx",
21700
+ "coreml",
21701
+ "openvino",
21702
+ "tflite",
21703
+ "pt",
21704
+ "gguf"
21705
+ ]);
21706
+ var PipelineSlotSchema = _enum([
21707
+ "detector",
21708
+ "cropper",
21709
+ "classifier",
21710
+ "refiner",
21711
+ "audio-classifier"
21712
+ ]);
21713
+ var PipelineEngineChoiceSchema = object({
21714
+ runtime: literal("python"),
21715
+ backend: string(),
21716
+ format: ModelFormatSchema$1,
21717
+ device: string().optional()
21718
+ });
21719
+ /**
21720
+ * One pool's resolved provisioning (`getEffectiveTuning`). `numWorkers` is the
21721
+ * knob that lifts a saturated pool (`inference-devices.md`); `concurrency` is
21722
+ * per-worker in-flight requests. `deviceKey` echoes the pool asked about —
21723
+ * `null` for the node-default pool.
21724
+ */
21725
+ var EffectiveTuningSchema = object({
21726
+ deviceKey: string().nullable(),
21727
+ runtime: string(),
21728
+ batchMode: string(),
21729
+ windowMs: number(),
21730
+ maxBatchSize: number(),
21731
+ concurrency: number(),
21732
+ numWorkers: number()
21733
+ });
21734
+ var AvailableEngineSchema = object({
21735
+ engine: PipelineEngineChoiceSchema,
21736
+ devices: array(object({
21737
+ id: string(),
21738
+ label: string(),
21739
+ description: string().optional()
21740
+ })).readonly(),
21741
+ defaultDevice: string()
21742
+ });
21743
+ var PipelineDefaultStepSchema = lazy(() => object({
21744
+ addonId: string(),
21745
+ addonName: string(),
21746
+ slot: PipelineSlotSchema,
21747
+ inputClasses: array(string()).readonly(),
21748
+ outputClasses: array(string()).readonly(),
21749
+ enabled: boolean(),
21750
+ modelId: string(),
21751
+ children: array(PipelineDefaultStepSchema).readonly(),
21752
+ group: string().optional(),
21753
+ settings: record(string(), unknown()).optional()
21754
+ }));
21755
+ var PipelineTemplateStepSchema = lazy(() => object({
21756
+ addonId: string(),
21757
+ enabled: boolean(),
21758
+ modelId: string(),
21759
+ children: array(PipelineTemplateStepSchema).readonly(),
21760
+ settings: record(string(), unknown()).optional()
21761
+ }));
21762
+ var PipelineTemplateSchema$1 = object({
21763
+ id: string(),
21764
+ name: string(),
21765
+ createdAt: string(),
21766
+ updatedAt: string(),
21767
+ engine: PipelineEngineChoiceSchema,
21768
+ steps: array(PipelineTemplateStepSchema).readonly()
21769
+ });
21770
+ var PipelineModelOptionSchema = object({
21771
+ id: string(),
21772
+ name: string(),
21773
+ formats: record(string(), object({
21774
+ downloaded: boolean(),
21775
+ sizeMB: number()
21776
+ })),
21777
+ group: ModelVariantGroupSchema.optional(),
21778
+ legacy: boolean().optional(),
21779
+ provider: ModelProviderIdSchema.optional()
21780
+ });
21781
+ var ConfigFieldBridge = custom();
21782
+ var PipelineAddonSchemaSchema = object({
21783
+ id: string(),
21784
+ name: string(),
21785
+ slot: PipelineSlotSchema,
21786
+ inputClasses: array(string()).readonly(),
21787
+ outputClasses: array(string()).readonly(),
21788
+ childSlots: array(PipelineSlotSchema).readonly(),
21789
+ models: array(PipelineModelOptionSchema).readonly(),
21790
+ defaultModelId: string(),
21791
+ defaultModelIdByFormat: record(string(), string()).optional(),
21792
+ enabledByDefault: boolean().optional(),
21793
+ backfillIntoExistingOverrides: boolean().optional(),
21794
+ defaultConfidence: number(),
21795
+ group: string().optional(),
21796
+ configSchema: array(ConfigFieldBridge).readonly().optional()
21797
+ });
21798
+ var PipelineSlotSchemaSchema = object({
21799
+ id: PipelineSlotSchema,
21800
+ label: string(),
21801
+ priority: number(),
21802
+ parentSlot: PipelineSlotSchema.nullable(),
21803
+ addons: array(PipelineAddonSchemaSchema).readonly()
21804
+ });
21805
+ var PipelineSchemaSchema = object({
21806
+ availableEngines: array(AvailableEngineSchema).readonly(),
21807
+ selectedEngine: PipelineEngineChoiceSchema,
21808
+ slots: array(PipelineSlotSchemaSchema).readonly()
21809
+ });
21810
+ var EngineProvisioningSchema = object({
21811
+ runtimeId: _enum([
21812
+ "onnx",
21813
+ "openvino",
21814
+ "coreml",
21815
+ "edgetpu"
21816
+ ]).nullable(),
21817
+ device: string().nullable(),
21818
+ state: _enum([
21819
+ "idle",
21820
+ "installing",
21821
+ "verifying",
21822
+ "ready",
21823
+ "failed"
21824
+ ]),
21825
+ progress: number().optional(),
21826
+ error: string().optional(),
21827
+ nextRetryAt: number().optional(),
21828
+ /**
21829
+ * Gate A (config-correctness gate at engine change): human-readable
21830
+ * config issues surfaced EAGERLY when the node's engine changes — model
21831
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
21832
+ * has a <format> build"). Additive/optional: informational only, never
21833
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
21834
+ * Absent/empty when the node-default tree resolves cleanly.
21835
+ */
21836
+ configIssues: array(string()).optional()
21837
+ });
21838
+ var PipelineStepInputSchema = lazy(() => object({
21839
+ addonId: string(),
21840
+ modelId: string().optional(),
21841
+ enabled: boolean().default(true),
21842
+ children: array(PipelineStepInputSchema).optional(),
21843
+ settings: record(string(), unknown()).optional(),
21844
+ jumpDeviceKey: string().optional()
21845
+ }));
21846
+ var ModelSubstitutionSchema = object({
21847
+ addonId: string(),
21848
+ chosen: string(),
21849
+ running: string(),
21850
+ format: string()
21851
+ });
21852
+ var PipelineValidationIssueSchema = object({
21853
+ addonId: string(),
21854
+ kind: _enum([
21855
+ "unknown-addon",
21856
+ "no-format-build",
21857
+ "unknown-device"
21858
+ ]),
21859
+ detail: string()
21860
+ });
21861
+ var PipelineValidationResultSchema = object({
21862
+ ok: boolean(),
21863
+ issues: array(PipelineValidationIssueSchema).readonly(),
21864
+ substitutions: array(ModelSubstitutionSchema).readonly(),
21865
+ /** The node's `currentEngine.format` this validation ran against. */
21866
+ format: string()
21867
+ });
21868
+ var ReferenceImageEntrySchema = object({
21869
+ filename: string(),
21870
+ stepIds: array(string()).readonly().optional()
21871
+ });
21872
+ var ReferenceImageBodySchema = object({
21873
+ base64: string(),
21874
+ filename: string()
21875
+ });
21876
+ var ReferenceAudioEntrySchema = object({
21877
+ filename: string(),
21878
+ sizeKb: number()
21879
+ });
21880
+ var ReferenceAudioBodySchema = object({ base64: string() });
21881
+ var AudioBackendSchema = object({
21882
+ id: string(),
21883
+ name: string(),
21884
+ description: string(),
21885
+ available: boolean(),
21886
+ /**
21887
+ * Raw classifier labels this backend can emit (e.g. YAMNet's
21888
+ * 521-class set or Apple SoundAnalysis's 303-class set). Used by
21889
+ * the benchmark UI to populate the `enabledMicroClasses` filter
21890
+ * specific to the selected backend without a separate fetch.
21891
+ */
21892
+ rawLabels: array(string()).readonly().optional()
21893
+ });
21894
+ var AudioCapabilitiesSchema = object({
21895
+ activeBackend: string(),
21896
+ availableBackends: array(AudioBackendSchema).readonly(),
21897
+ sampleRate: number(),
21898
+ chunkDurationMs: number()
21899
+ });
21900
+ var DownloadModelResultSchema = object({
21901
+ filePath: string(),
21902
+ sizeMB: number(),
21903
+ durationMs: number()
21904
+ });
21905
+ /**
21906
+ * Wrapper carrying a single test run's result. Replaces the legacy
21907
+ * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
21908
+ * canonical `AudioResult` from the Phase 6 output rework: one
21909
+ * `AudioDetection` per class above `minScore`, top-N candidates in
21910
+ * `debug.alternateLabels['audio-classifier']`, per-source timings in
21911
+ * `debug.stepTimings`. The outer `success`/`error` fields stay so the
21912
+ * benchmark UI can still report a clean failure when the classifier
21913
+ * cap isn't available.
21914
+ */
21915
+ var AudioTestResultSchema = object({
21916
+ success: boolean(),
21917
+ error: string().optional(),
21918
+ frame: custom().optional()
21919
+ });
21920
+ var PipelineConfigBridge = custom();
21921
+ var ConfigUISchemaBridge = custom();
21922
+ var ConfigUISchemaNullableBridge = custom();
21923
+ var InferenceCapabilitiesBridge = custom();
21924
+ var ModelAvailabilityListBridge = custom();
21925
+ var PipelineRunResultBridge = custom();
21926
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
21927
+ modelId: string(),
21928
+ settings: record(string(), unknown()).readonly()
21929
+ }))), method(object({ steps: record(string(), object({
21930
+ modelId: string(),
21931
+ settings: record(string(), unknown()).readonly()
21932
+ })) }), object({ success: literal(true) }), {
21933
+ kind: "mutation",
21934
+ auth: "admin"
21935
+ }), method(object({ nodeId: string() }), object({
21936
+ success: literal(true),
21937
+ clearedDevices: number()
21985
21938
  }), {
21986
- kind: "query",
21987
- auth: "admin"
21988
- }), method(object({
21989
- deviceId: number(),
21990
- trackId: string(),
21991
- frameId: string(),
21992
- subject: RetrainAssistSubjectSchema,
21993
- /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
21994
- nodeId: string().optional()
21995
- }), RetrainAssistResultSchema, {
21996
- kind: "mutation",
21997
- auth: "admin"
21998
- }), method(object({
21999
- deviceId: number(),
22000
- source: DetectionSourceSchema,
22001
- zones: array(ZoneSchema).readonly().optional(),
22002
- detectionRules: array(ZoneRuleSchema).readonly().optional(),
22003
- zoneMembershipMinOverlap: number().min(0).max(1).optional(),
22004
- frames: array(ReplayFrameInputSchema).min(1)
22005
- }), RunReplayFrameProcessorResultSchema, {
22006
- kind: "mutation",
22007
- auth: "admin"
22008
- }), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
22009
- kind: "query",
22010
- auth: "admin"
22011
- }), method(object({
22012
- deviceId: number(),
22013
- trackId: string(),
22014
- frameId: string(),
22015
- annotations: array(RetrainAnnotationDraftSchema)
22016
- }), array(RetrainAnnotationSchema).readonly(), {
22017
21939
  kind: "mutation",
22018
21940
  auth: "admin"
22019
- }), method(object({
22020
- deviceId: number(),
22021
- trackId: string()
22022
- }), RetrainTransitionResultSchema, {
21941
+ }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
21942
+ /** `<backend>:<device>`, e.g. `openvino:gpu`. */
21943
+ deviceKey: string(),
21944
+ /**
21945
+ * `failed` — the per-device restart budget is exhausted; no pool
21946
+ * will be spawned until an operator re-arms it or the runner
21947
+ * respawns. `backoff` — under budget, waiting out the backoff (or
21948
+ * a cached pool observed dead and not yet condemned).
21949
+ */
21950
+ state: _enum(["failed", "backoff"]),
21951
+ /** Epoch ms of the death that produced this state. */
21952
+ since: number(),
21953
+ /** Pool deaths inside the current window. */
21954
+ deaths: number(),
21955
+ /** The last death's message. */
21956
+ lastError: string()
21957
+ })).readonly() })), method(object({
21958
+ nodeId: string(),
21959
+ deviceKey: string()
21960
+ }), object({ rearmed: boolean() }), {
22023
21961
  kind: "mutation",
22024
21962
  auth: "admin"
22025
- }), method(object({
22026
- deviceId: number(),
22027
- trackId: string()
22028
- }), RetrainTransitionResultSchema, {
21963
+ }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({
21964
+ steps: array(PipelineStepInputSchema),
21965
+ deviceKey: string().optional()
21966
+ }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
21967
+ name: string(),
21968
+ steps: array(PipelineTemplateStepSchema).readonly(),
21969
+ engine: PipelineEngineChoiceSchema
21970
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
21971
+ id: string(),
21972
+ name: string().optional(),
21973
+ steps: array(PipelineTemplateStepSchema).readonly().optional()
21974
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
21975
+ addonId: string(),
21976
+ modelId: string(),
21977
+ format: ModelFormatSchema$1
21978
+ }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
21979
+ addonId: string(),
21980
+ modelId: string(),
21981
+ format: ModelFormatSchema$1
21982
+ }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
21983
+ steps: array(PipelineStepInputSchema).min(1),
21984
+ frame: FrameInputSchema.optional(),
21985
+ /**
21986
+ * Process-local lazy frame. Valid only when caller and provider resolve
21987
+ * in the same execution-group process; split/cross-node callers use
21988
+ * `frame`/`image` inline compatibility instead.
21989
+ */
21990
+ frameRef: FrameRefSchema.optional(),
21991
+ /**
21992
+ * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
21993
+ * the decoded pixels live in. One more member of the one-of
21994
+ * frame/frameHandle/image/imageBase64/referenceImage group.
21995
+ */
21996
+ frameHandle: FrameHandleSchema.optional(),
21997
+ imageBase64: string().optional(),
21998
+ /**
21999
+ * Binary JPEG bytes — preferred over `imageBase64` on internal
22000
+ * hops (hub → forked worker via Moleculer MsgPack) because it
22001
+ * skips the 33% base64 overhead + the per-call base64 decode on
22002
+ * the detection-pipeline worker. Callers can pass either; exactly
22003
+ * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
22004
+ */
22005
+ image: _instanceof(Uint8Array).optional(),
22006
+ referenceImage: string().optional(),
22007
+ deviceId: number().optional(),
22008
+ sessionId: string().optional(),
22009
+ /**
22010
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
22011
+ * reference-image, and detail-subtree calls. 'frame' is the live
22012
+ * per-frame dispatch: ONLY root-plane steps run; crop children
22013
+ * (inputClasses ≠ null) are skipped and served per-track via
22014
+ * pipelineRunner.runDetailSubtree (two-plane design).
22015
+ */
22016
+ plane: _enum(["full", "frame"]).optional(),
22017
+ /**
22018
+ * Inference-device selector (Phase 2 multi-device). Format
22019
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
22020
+ * Omitted ⇒ the runner's default device (current single-engine
22021
+ * behaviour). Selects WHICH device pool of the node runs the call.
22022
+ */
22023
+ deviceKey: string().optional(),
22024
+ /**
22025
+ * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
22026
+ * when the parent crop was resolved from the frame's retained NATIVE
22027
+ * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
22028
+ * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
22029
+ * resolution from that surface — the SAME quality path faces already
22030
+ * had — instead of the downscaled parent tile. `handle` keys the native
22031
+ * surface (node-pinned to its owner); `cropFrameSpace` is the parent
22032
+ * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
22033
+ * the executor's crop-normalized child ROI back into frame-normalized
22034
+ * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
22035
+ * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
22036
+ * (today's behaviour on the fallback path).
22037
+ */
22038
+ nativeCropRef: NativeCropRefSchema.optional(),
22039
+ /**
22040
+ * Offline REPLAY of stored media for `deviceId` (D56, D646): the
22041
+ * camera's zone gate and overrides apply as live, every executing
22042
+ * step runs EXACTLY the model it names on the `deviceKey` pool it
22043
+ * names or the call refuses (`model-not-servable` /
22044
+ * `device-not-servable`), and nothing reaches the camera's live
22045
+ * trace stream. Absent ⇒ unchanged behaviour.
22046
+ */
22047
+ replay: literal(true).optional()
22048
+ }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
22049
+ steps: array(PipelineStepInputSchema).min(1),
22050
+ frames: array(FrameInputSchema).min(1).max(255),
22051
+ deviceId: number().optional(),
22052
+ sessionId: string().optional(),
22053
+ /**
22054
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
22055
+ * the batch to the Python pool's bench preprocess cache
22056
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
22057
+ * preprocessed ONCE and every later inference is a pure-inference cache
22058
+ * hit — the sustained-throughput run measures inference, not
22059
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
22060
+ * full preprocess every call, correct). Fresh per sustained run;
22061
+ * released via `uncacheFrame`.
22062
+ */
22063
+ frameId: number().int().nonnegative().optional(),
22064
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
22065
+ deviceKey: string().optional()
22066
+ }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
22067
+ data: _instanceof(Uint8Array),
22068
+ width: number().int().positive(),
22069
+ height: number().int().positive(),
22070
+ format: _enum([
22071
+ "rgb",
22072
+ "bgr",
22073
+ "gray"
22074
+ ])
22075
+ }), object({
22076
+ frameId: number(),
22077
+ width: number(),
22078
+ height: number()
22079
+ }), { kind: "mutation" }), method(object({
22080
+ stepId: string(),
22081
+ frameId: number().int()
22082
+ }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(object({ deviceKey: string().optional() }), EffectiveTuningSchema), method(_void(), array(object({
22083
+ engineKey: string(),
22084
+ engine: PipelineEngineChoiceSchema,
22085
+ modelsLoaded: array(string()).readonly(),
22086
+ inUseByCameras: array(number()).readonly(),
22087
+ /**
22088
+ * Origin of this resident factory.
22089
+ * - `runtime` — main camera-serving engine (no idle TTL).
22090
+ * - `device-pool` — a concurrent per-device pool (Phase 2
22091
+ * multi-device, keyed by `deviceKey`) resolved
22092
+ * via `resolveDeviceFactory`. Runs alongside the
22093
+ * `runtime` engine on a DIFFERENT accelerator
22094
+ * (NPU / iGPU / Coral) — this is how the
22095
+ * Engines tab shows all pools running at once.
22096
+ */
22097
+ kind: _enum(["runtime", "device-pool"]),
22098
+ /** Native pid of the underlying Python pool (null when no pool). */
22099
+ poolPid: number().nullable(),
22100
+ /** ms since this factory was last used (null when not warm-tracked). */
22101
+ idleMs: number().nullable(),
22102
+ /** Idle TTL after which the factory self-evicts (null when not applicable). */
22103
+ idleTtlMs: number().nullable()
22104
+ })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
22029
22105
  kind: "mutation",
22030
22106
  auth: "admin"
22031
- }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
22032
- kind: "query",
22033
- auth: "admin"
22034
22107
  }), method(object({
22035
- /** Which of the three event tables owns the row. */
22036
- ownerType: EventOwnerTypeSchema,
22037
- /** The event's rowid in that table (D474: an event id is a number). */
22038
- eventId: number().int(),
22039
- kind: MediaFileKindEnum.optional(),
22040
- deviceId: number()
22041
- }), array(MediaFileSchema).readonly()), method(object({
22042
- trackId: string(),
22043
- kinds: array(MediaFileKindEnum).optional(),
22044
- deviceId: number()
22045
- }), array(MediaFileSchema).readonly()), method(object({
22046
- trackId: string(),
22047
- deviceId: number()
22048
- }), array(MediaFileInfoSchema).readonly()), method(object({
22049
- ownerType: EventOwnerTypeSchema,
22050
- eventId: number().int(),
22051
- deviceId: number()
22052
- }), array(MediaFileInfoSchema).readonly()), method(object({
22053
- deviceId: number(),
22054
- ownerKind: MediaPresenceOwnerKindSchema,
22055
- ownerIds: array(string()).max(5e3)
22056
- }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
22057
- kind: "mutation",
22058
- auth: "admin"
22059
- }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
22108
+ engine: PipelineEngineChoiceSchema,
22109
+ force: boolean().optional()
22110
+ }), object({
22111
+ success: boolean(),
22112
+ reason: string().optional()
22113
+ }), {
22060
22114
  kind: "mutation",
22061
22115
  auth: "admin"
22062
- }), method(object({}), RebuildStatusSchema), object({
22063
- deviceId: number(),
22064
- timestamp: number(),
22065
- frameWidth: number(),
22066
- frameHeight: number(),
22067
- detections: array(OverlayDetectionSchema).readonly()
22068
- }), object({
22069
- deviceId: number(),
22070
- trackId: string(),
22071
- className: string()
22072
- }), object({
22073
- deviceId: number(),
22074
- trackId: string(),
22075
- className: string(),
22076
- durationMs: number()
22077
- }), object({
22078
- deviceId: number(),
22079
- kind: EventKindSchema,
22080
- eventId: string(),
22081
- timestamp: number()
22082
- });
22116
+ }), 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({
22117
+ addonId: string(),
22118
+ modelId: string(),
22119
+ filename: string().optional(),
22120
+ settings: record(string(), unknown()).optional()
22121
+ }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
22083
22122
  object({
22084
22123
  activeCameras: number(),
22085
22124
  throttledCameras: number(),
@@ -22105,6 +22144,66 @@ var CameraMetricsSchema = object({
22105
22144
  });
22106
22145
  var CameraMetricsWithDeviceIdSchema = CameraMetricsSchema.extend({ deviceId: number() });
22107
22146
  /**
22147
+ * Zone — pure geometry + identity. NO filtering behaviour.
22148
+ *
22149
+ * Zones describe **where** in the frame the operator wants to flag
22150
+ * something; consumer-owned {@link ZoneRule} arrays describe **how**
22151
+ * each pipeline stage uses them. Splitting the two means a single
22152
+ * polygon "Driveway" can simultaneously back a motion-exclude rule,
22153
+ * a detection-include rule on `['car']`, and an occupancy aggregate
22154
+ * — without three duplicated polygons.
22155
+ *
22156
+ * Owned by the orchestrator addon (provider) and mirrored into the
22157
+ * `zones` device-state slice on every mutation. Consumers
22158
+ * (motion-wasm, pipeline-executor, analytics, admin UI) read either
22159
+ * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
22160
+ * mirror with `onChanged`).
22161
+ *
22162
+ * Coordinates are normalised fractions of the frame (0–1) so zones
22163
+ * survive resolution changes and stream profile switches.
22164
+ *
22165
+ * `kind` discriminates between full polygons (closed regions used
22166
+ * for intrusion / occupancy filters) and tripwires (open 2-point
22167
+ * line segments used for cross events). Onboard / firmware-reported
22168
+ * zones (Reolink, ONVIF) are out of scope for now — see the deferred
22169
+ * task list.
22170
+ */
22171
+ var ZoneKindEnum = _enum(["polygon", "tripwire"]);
22172
+ /** Polygon vertex in fraction-of-frame coordinates (0–1). */
22173
+ var PolygonPointSchema = object({
22174
+ x: number(),
22175
+ y: number()
22176
+ });
22177
+ /** A camera detection zone — pure geometry/identity. */
22178
+ var ZoneSchema = object({
22179
+ id: string(),
22180
+ name: string(),
22181
+ kind: ZoneKindEnum.default("polygon"),
22182
+ /** Polygon vertices, fraction of frame (0–1). */
22183
+ polygon: array(PolygonPointSchema).readonly(),
22184
+ /** Visual color for UI rendering. */
22185
+ color: string().default("#3b82f6")
22186
+ });
22187
+ DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
22188
+ deviceId: number(),
22189
+ zone: ZoneSchema
22190
+ }), _void(), {
22191
+ kind: "mutation",
22192
+ auth: "admin"
22193
+ }), method(object({
22194
+ deviceId: number(),
22195
+ zoneId: string()
22196
+ }), _void(), {
22197
+ kind: "mutation",
22198
+ auth: "admin"
22199
+ }), method(object({
22200
+ deviceId: number(),
22201
+ zone: ZoneSchema
22202
+ }), _void(), {
22203
+ kind: "mutation",
22204
+ auth: "admin"
22205
+ }), object({ zones: array(ZoneSchema).readonly() });
22206
+ /**
22108
22207
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
22109
22208
  * decode worker resolves it against the RETAINED native frame's real pixel dims,
22110
22209
  * so the caller supplies only the detection-res bbox divided by the detection
@@ -22291,7 +22390,17 @@ var DetailResultSchema = object({
22291
22390
  /** Face short side (px) measured on the NATIVE crop surface. The `bbox`
22292
22391
  * above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
22293
22392
  * consumers MUST prefer this when present (2026-07-22 native-gate fix). */
22294
- nativeFaceShortSidePx: number().optional()
22393
+ nativeFaceShortSidePx: number().optional(),
22394
+ /**
22395
+ * The five facial landmarks of a `face` detail, in the SAME frame space as
22396
+ * `bbox` (D644). Additive and optional: post-analysis stores them beside the
22397
+ * face's source frame so a future alignment can be re-derived exactly; a
22398
+ * runner that predates the field sends none and the face is stored without.
22399
+ */
22400
+ landmarks: array(object({
22401
+ x: number(),
22402
+ y: number()
22403
+ })).optional()
22295
22404
  });
22296
22405
  /**
22297
22406
  * Why an executing node REFUSED a stateless step run (`runStatelessStep`).
@@ -23677,7 +23786,11 @@ function buildDescriptor(capName, kind) {
23677
23786
  icon: legacyIcon(t.iconId)
23678
23787
  };
23679
23788
  }
23680
- Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
23789
+ /**
23790
+ * Sensor / control cap name → static event-kind descriptor. A linked device
23791
+ * contributes one entry per bound cap present in this map.
23792
+ */
23793
+ var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
23681
23794
  /**
23682
23795
  * server-management — per-NODE singleton capability for a node's ROOT
23683
23796
  * package lifecycle (runtime-updatable node packages).
@@ -30612,7 +30725,7 @@ var HardwareInfoSchema = object({
30612
30725
  coral: CoralInfoSchema.nullable().optional()
30613
30726
  });
30614
30727
  var PlatformScoreSchema = object({
30615
- runtime: _enum(["node", "python"]),
30728
+ runtime: literal("python"),
30616
30729
  backend: string(),
30617
30730
  format: _enum([
30618
30731
  "onnx",
@@ -30649,7 +30762,7 @@ var ModelRequirementSchema = object({
30649
30762
  });
30650
30763
  var ResolvedInferenceConfigSchema = object({
30651
30764
  modelId: string(),
30652
- runtime: _enum(["node", "python"]),
30765
+ runtime: literal("python"),
30653
30766
  backend: string(),
30654
30767
  format: ModelFormatSchema,
30655
30768
  reason: string()
@@ -32856,6 +32969,92 @@ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSche
32856
32969
  kind: "mutation",
32857
32970
  auth: "admin"
32858
32971
  });
32972
+ /**
32973
+ * Per-stage gating mode applied to the zones a rule references.
32974
+ *
32975
+ * - `include`: the rule contributes to a **whitelist** for its stage.
32976
+ * When at least one `include` rule fires for a stage, only entities
32977
+ * inside one of those zones pass that stage.
32978
+ * - `exclude`: the rule contributes to a **blacklist** for its stage.
32979
+ * Entities inside one of those zones are dropped at that stage.
32980
+ *
32981
+ * `monitor`-style observation (count without filtering) is not a rule
32982
+ * mode — zones without any matching rule are observed naturally by
32983
+ * `zone-analytics` (live snapshot + history), so an "I just want to
32984
+ * count, not filter" use case needs no rule at all.
32985
+ */
32986
+ var ZoneRuleModeEnum = _enum(["include", "exclude"]);
32987
+ /**
32988
+ * Per-consumer rule that references existing zones (geometry) and
32989
+ * defines how a specific pipeline stage should treat them. Each
32990
+ * consumer addon owns its own `ZoneRule[]` array in its per-device
32991
+ * settings:
32992
+ *
32993
+ * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
32994
+ * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
32995
+ * - future: notification rules, audio gating, etc.
32996
+ *
32997
+ * One rule applies to N zones (`zoneIds[]`) so the operator can
32998
+ * express "ignore motion in ALL of {garden, street}" with a single
32999
+ * rule. `classFilter` narrows the rule to specific object classes —
33000
+ * "drop person detections in the street, but keep cars" is one
33001
+ * `exclude` rule with `classFilter: ['person']`.
33002
+ *
33003
+ * `enabled` is a soft toggle — the operator can keep the rule
33004
+ * configured but inert without deleting it.
33005
+ */
33006
+ var ZoneRuleSchema = object({
33007
+ /** Stable rule id — survives edits, used by the UI for diffing. */
33008
+ id: string(),
33009
+ /** Optional human-readable label rendered in the rule editor. */
33010
+ name: string().optional(),
33011
+ /** Zones this rule targets. The rule's `mode` applies to ALL
33012
+ * listed zones (OR-set: a detection in any one of them counts).
33013
+ * At least one zone id required — a rule with no targets is a
33014
+ * configuration mistake and the form validator rejects it. */
33015
+ zoneIds: array(string()).min(1).readonly(),
33016
+ mode: ZoneRuleModeEnum,
33017
+ /**
33018
+ * Class names this rule applies to. Empty / undefined ⇒ rule
33019
+ * applies to every class. Class strings match the `macroClass`
33020
+ * field on detections (e.g. `person`, `car`, `dog`).
33021
+ */
33022
+ classFilter: array(string()).readonly().optional(),
33023
+ /**
33024
+ * Minimum bbox/mask overlap (0–1) with any of the rule's zones
33025
+ * required to consider an entity "in the zone". Defaults to the
33026
+ * consumer's stage default when omitted. Kept for back-compat with
33027
+ * existing per-rule overrides; new operators pick the value via
33028
+ * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
33029
+ * set, the lower-level engine reads it as a 0–1 fraction.
33030
+ */
33031
+ overlapThreshold: number().min(0).max(1).optional(),
33032
+ /**
33033
+ * Operator-friendly version of `overlapThreshold` — the percentage
33034
+ * of the detection's bbox that must lie inside the zone for the
33035
+ * rule to match. Documented default is 85%; the engine substitutes
33036
+ * that when the field is omitted (kept optional so existing rules
33037
+ * stored without it stay valid).
33038
+ *
33039
+ * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
33040
+ * rule, the engine prefers `bboxInclusionPct` because it's the
33041
+ * field exposed in the UI. Internally both feed the same gate.
33042
+ */
33043
+ bboxInclusionPct: number().min(0).max(100).optional(),
33044
+ /**
33045
+ * When `true` and a detection has a segmentation mask, use the
33046
+ * mask for overlap instead of the bbox. Detection-stage only;
33047
+ * motion rules ignore this field.
33048
+ */
33049
+ preferMask: boolean().optional(),
33050
+ /**
33051
+ * Soft-toggle: `false` disables the rule without deleting it.
33052
+ * Defaults to `true` so operators creating a rule via the UI
33053
+ * see it active immediately.
33054
+ */
33055
+ enabled: boolean().default(true)
33056
+ });
33057
+ array(ZoneRuleSchema).readonly();
32859
33058
  object({
32860
33059
  /** Whether the script is currently executing. */
32861
33060
  isRunning: boolean(),
@@ -34237,6 +34436,42 @@ DeviceType.Camera, method(object({
34237
34436
  package: array(ZoneRuleSchema).readonly()
34238
34437
  });
34239
34438
  /**
34439
+ * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
34440
+ * valutatore puro del suo FRONTE.
34441
+ *
34442
+ * Viveva dentro il builtin virtual-doorbell
34443
+ * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
34444
+ * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
34445
+ * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
34446
+ * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
34447
+ * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
34448
+ * SPOSTA qui e il doorbell la ri-esporta.
34449
+ *
34450
+ * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
34451
+ * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
34452
+ * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
34453
+ * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
34454
+ */
34455
+ /**
34456
+ * Known binary / switch source caps → the boolean slice field whose
34457
+ * false→true rise counts as ACTIVE. Every entry is "fire on active".
34458
+ * Sensors whose "active" reading is not a plain boolean (presence's string
34459
+ * state, connectivity's connected flag) are deliberately excluded — a
34460
+ * reconnect is not a doorbell press, and it is not a recording either.
34461
+ */
34462
+ var SOURCE_CAP_ACTIVE_FIELD = {
34463
+ contact: "entryOpen",
34464
+ binary: "on",
34465
+ switch: "on",
34466
+ motion: "detected",
34467
+ flood: "flooded",
34468
+ gas: "detected",
34469
+ smoke: "detected",
34470
+ "carbon-monoxide": "detected",
34471
+ vibration: "detected",
34472
+ tamper: "tampered"
34473
+ };
34474
+ /**
34240
34475
  * Accessory device helpers — shared across drivers.
34241
34476
  *
34242
34477
  * Many vendor-specific drivers register accessory child devices on
@@ -39142,6 +39377,12 @@ Object.freeze({
39142
39377
  addonId: null,
39143
39378
  access: "view"
39144
39379
  },
39380
+ "notificationRules.getRuleLastRuns": {
39381
+ capName: "notification-rules",
39382
+ capScope: "system",
39383
+ addonId: null,
39384
+ access: "view"
39385
+ },
39145
39386
  "notificationRules.getTemplateCatalog": {
39146
39387
  capName: "notification-rules",
39147
39388
  capScope: "system",
@@ -39166,6 +39407,12 @@ Object.freeze({
39166
39407
  addonId: null,
39167
39408
  access: "view"
39168
39409
  },
39410
+ "notificationRules.previewStateSummary": {
39411
+ capName: "notification-rules",
39412
+ capScope: "system",
39413
+ addonId: null,
39414
+ access: "view"
39415
+ },
39169
39416
  "notificationRules.previewTemplate": {
39170
39417
  capName: "notification-rules",
39171
39418
  capScope: "system",
@@ -39742,12 +39989,6 @@ Object.freeze({
39742
39989
  addonId: null,
39743
39990
  access: "create"
39744
39991
  },
39745
- "pipelineAnalytics.runReplayFrameProcessor": {
39746
- capName: "pipeline-analytics",
39747
- capScope: "device",
39748
- addonId: null,
39749
- access: "create"
39750
- },
39751
39992
  "pipelineAnalytics.saveRetrainAnnotations": {
39752
39993
  capName: "pipeline-analytics",
39753
39994
  capScope: "device",
@@ -43845,11 +44086,6 @@ Object.freeze({
43845
44086
  form: "single",
43846
44087
  optional: false
43847
44088
  }],
43848
- "pipelineAnalytics.runReplayFrameProcessor": [{
43849
- name: "deviceId",
43850
- form: "single",
43851
- optional: false
43852
- }],
43853
44089
  "pipelineAnalytics.saveRetrainAnnotations": [{
43854
44090
  name: "deviceId",
43855
44091
  form: "single",
@@ -44738,12 +44974,55 @@ var NC_AUDIO_DEFAULTS = {
44738
44974
  samplingSeconds: 10
44739
44975
  };
44740
44976
  new Set(["devices", "classes"]);
44741
- /** Every filter, for the callers that must cover all four (guards, tests). */
44977
+ /**
44978
+ * WHICH narrowing filter speaks about WHICH system-event kind — one predicate,
44979
+ * consumed by the rule ENGINE and by every rule EDITOR.
44980
+ *
44981
+ * ── The failure this file exists for ───────────────────────────────────────
44982
+ * `NcSystemEventConditionSchema` carries four optional narrowing lists
44983
+ * (`deviceIds`, `deviceTypes`, `nodeIds`, `packageNames`) beside a required
44984
+ * `kinds` gate. They are not four filters over one vocabulary: each belongs to
44985
+ * a FAMILY of kinds and is meaningless outside it. A node going away has no
44986
+ * device type; a completed export has no package; `stream-offline` deliberately
44987
+ * carries no device type at all (a stream belongs to a camera by construction).
44988
+ *
44989
+ * The engine applied all four to every kind, so a filter from one family failed
44990
+ * a kind from another family CLOSED. The operator's live rule "Eventi di
44991
+ * sistema" selected eleven kinds and set `deviceTypes: ['camera']` — the only
44992
+ * way to say "only my cameras" about the two liveness kinds — and NINE of the
44993
+ * eleven silently stopped being deliverable. Four days of history contained
44994
+ * `device-online` and `device-offline` and nothing else, across a window in
44995
+ * which agents were restarted and addons were updated.
44996
+ *
44997
+ * Both editors already computed this scoping, to decide which filter boxes to
44998
+ * render; the cap's own docblock already promised it ("Narrow a `device-*` kind
44999
+ * to these device TYPES"). Only the engine disagreed, and it disagreed in the
45000
+ * destructive direction. So the rule is stated ONCE, here, and the engine and
45001
+ * the editors are readers of it rather than three independent authors.
45002
+ *
45003
+ * ── The direction ─────────────────────────────────────────────────────────
45004
+ * Out of family ⇒ the filter is IGNORED, never fail-closed. An editor that
45005
+ * hides a box has already told the operator this filter does not speak about
45006
+ * these kinds, so honouring it as an exclusion contradicts the only
45007
+ * explanation the operator was ever given. Inside its family the filter still
45008
+ * narrows, and still fails CLOSED on a subject that cannot produce the identity
45009
+ * it tests (a device the mirror cannot name is not evidence of a camera).
45010
+ */
45011
+ /**
45012
+ * Every filter, for the callers that must cover all of them (guards, tests).
45013
+ *
45014
+ * `consumableLowPercent` (D642) is not a list but it is a filter in exactly
45015
+ * this sense: it belongs to a family of kinds (the consumable pair) and the
45016
+ * engine consults it only there. It differs in one way the readers must know —
45017
+ * its ABSENCE is not "no narrowing" but the default threshold, because a
45018
+ * consumable episode is always judged at SOME threshold.
45019
+ */
44742
45020
  var NC_SYSTEM_EVENT_FILTER_KEYS = [
44743
45021
  "deviceIds",
44744
45022
  "deviceTypes",
44745
45023
  "nodeIds",
44746
- "packageNames"
45024
+ "packageNames",
45025
+ "consumableLowPercent"
44747
45026
  ];
44748
45027
  new Map(Object.entries({
44749
45028
  "device-online": "device-events",
@@ -44754,6 +45033,9 @@ new Map(Object.entries({
44754
45033
  "device-battery-normal": "device-events",
44755
45034
  "device-consumable-low": "device-events",
44756
45035
  "device-consumable-normal": "device-events",
45036
+ "device-tank-low": "device-events",
45037
+ "device-tank-full": "device-events",
45038
+ "device-tank-normal": "device-events",
44757
45039
  "stream-online": "device-events",
44758
45040
  "stream-offline": "device-events",
44759
45041
  "detection-blind": "device-events",
@@ -44783,11 +45065,141 @@ new Map(Object.entries({
44783
45065
  "camera-enabled": null
44784
45066
  }));
44785
45067
  [...NC_SYSTEM_EVENT_FILTER_KEYS];
45068
+ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
45069
+ {
45070
+ id: "device-state",
45071
+ family: "device-events",
45072
+ label: "Device state",
45073
+ kinds: [
45074
+ "device-offline",
45075
+ "device-online",
45076
+ "device-disabled",
45077
+ "device-enabled"
45078
+ ]
45079
+ },
45080
+ {
45081
+ id: "streams",
45082
+ family: "device-events",
45083
+ label: "Streams",
45084
+ kinds: ["stream-offline", "stream-online"]
45085
+ },
45086
+ {
45087
+ id: "battery",
45088
+ family: "device-events",
45089
+ label: "Battery",
45090
+ kinds: ["device-battery-low", "device-battery-normal"]
45091
+ },
45092
+ {
45093
+ id: "consumables",
45094
+ family: "device-events",
45095
+ label: "Consumables",
45096
+ kinds: ["device-consumable-low", "device-consumable-normal"]
45097
+ },
45098
+ {
45099
+ id: "tanks",
45100
+ family: "device-events",
45101
+ label: "Tanks",
45102
+ kinds: [
45103
+ "device-tank-low",
45104
+ "device-tank-full",
45105
+ "device-tank-normal"
45106
+ ]
45107
+ },
45108
+ {
45109
+ id: "detection-blind",
45110
+ family: "device-events",
45111
+ label: "Detecting nothing",
45112
+ kinds: ["detection-blind"]
45113
+ },
45114
+ {
45115
+ id: "nodes",
45116
+ family: "system",
45117
+ label: "Nodes",
45118
+ kinds: [
45119
+ "node-offline",
45120
+ "node-online",
45121
+ "node-inference-unavailable"
45122
+ ]
45123
+ },
45124
+ {
45125
+ id: "addons",
45126
+ family: "system",
45127
+ label: "Addons",
45128
+ kinds: [
45129
+ "addon-crash-loop",
45130
+ "addon-update-available",
45131
+ "addon-updated"
45132
+ ]
45133
+ },
45134
+ {
45135
+ id: "updates",
45136
+ family: "system",
45137
+ label: "Server & container updates",
45138
+ kinds: [
45139
+ "server-update-available",
45140
+ "server-updated",
45141
+ "wrapper-update-available"
45142
+ ]
45143
+ },
45144
+ {
45145
+ id: "backups-exports",
45146
+ family: "system",
45147
+ label: "Backups & exports",
45148
+ kinds: ["backup-completed", "export-completed"]
45149
+ },
45150
+ {
45151
+ id: "alarm",
45152
+ family: "alarm",
45153
+ label: "Alarm",
45154
+ kinds: NC_ALARM_SYSTEM_EVENT_KINDS
45155
+ }
45156
+ ];
45157
+ function systemEventKindsOfFamily(family) {
45158
+ return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
45159
+ }
44786
45160
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
45161
+ /**
45162
+ * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
45163
+ * persists (D640 "a sensor rule fires on the raise, the clear, or both").
45164
+ *
45165
+ * A device-event rule over a flood sensor notified on BOTH edges: water
45166
+ * detected, and back to dry. `NcConditions.sensorTransition` lets the rule
45167
+ * name one edge; this file answers, for one `SensorEvent`, which edge it is.
45168
+ *
45169
+ * NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
45170
+ * already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
45171
+ * (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
45172
+ * table), and a sensor event names its kind, not its cap. So the table here is
45173
+ * DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
45174
+ * Only kinds whose taxonomy category is `sensor` take part — a switch has an
45175
+ * `on` boolean too, but "it triggers / it clears" is not a sentence about a
45176
+ * light, and offering it there would be a knob with a misleading label.
45177
+ * `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
45178
+ * absent from the source table, so it is absent here.
45179
+ *
45180
+ * `undefined` = never guessed: a kind with no raise (a doorbell press, a
45181
+ * button, a cover), or a slice whose field is missing or not a boolean. The
45182
+ * engine then does NOT filter — a flood alert must not go silent because one
45183
+ * read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
45184
+ *
45185
+ * Pure, RN-safe: the viewer imports the support rule from here.
45186
+ */
45187
+ /** Event kind → the boolean field of its cap slice that means "raised". */
45188
+ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
45189
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
45190
+ if (field === void 0 || descriptor.category !== "sensor") return [];
45191
+ return [[descriptor.kind, field]];
45192
+ })));
45193
+ Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
44787
45194
  var RULE_BODY_ONLY = ["body"];
44788
45195
  var RULE_TEXT = ["title", "body"];
44789
45196
  var BATTERY = ["device-battery-low", "device-battery-normal"];
44790
45197
  var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
45198
+ var TANK = [
45199
+ "device-tank-low",
45200
+ "device-tank-full",
45201
+ "device-tank-normal"
45202
+ ];
44791
45203
  var UPDATES = [
44792
45204
  "addon-update-available",
44793
45205
  "server-update-available",
@@ -44813,12 +45225,189 @@ var DEVICE_EVENTS = [
44813
45225
  "device-enabled",
44814
45226
  ...BATTERY,
44815
45227
  ...CONSUMABLE,
45228
+ ...TANK,
44816
45229
  "stream-online",
44817
45230
  "stream-offline",
44818
45231
  "detection-blind",
44819
45232
  ...ALARM
44820
45233
  ];
44821
- [...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];
45234
+ /** Every kind a summary may speak about — the two summarisable families. */
45235
+ var SUMMARISABLE = [...systemEventKindsOfFamily("device-events"), ...systemEventKindsOfFamily("system")];
45236
+ /** One per summary SECTION (`NC_STATE_SUMMARY_SECTIONS` in the notification
45237
+ * centre): the section's lines, offered when one of its kinds is selected. */
45238
+ var STATE_SUMMARY_SECTION_VARS = [
45239
+ {
45240
+ name: "summaryDevices",
45241
+ label: "Summary: devices offline / switched off",
45242
+ example: "Garage — offline since 07:12",
45243
+ kinds: [
45244
+ "device-offline",
45245
+ "device-online",
45246
+ "device-disabled",
45247
+ "device-enabled"
45248
+ ]
45249
+ },
45250
+ {
45251
+ name: "summaryStreams",
45252
+ label: "Summary: streams offline",
45253
+ example: "Ingresso (high) — since 06:40",
45254
+ kinds: ["stream-offline", "stream-online"]
45255
+ },
45256
+ {
45257
+ name: "summaryBattery",
45258
+ label: "Summary: low batteries",
45259
+ example: "Sensore porta — 8 %",
45260
+ kinds: BATTERY
45261
+ },
45262
+ {
45263
+ name: "summaryConsumables",
45264
+ label: "Summary: consumables",
45265
+ example: "Robot — filter 10 %",
45266
+ kinds: CONSUMABLE
45267
+ },
45268
+ {
45269
+ name: "summaryTanks",
45270
+ label: "Summary: tanks",
45271
+ example: "Robot — clean water low",
45272
+ kinds: TANK
45273
+ },
45274
+ {
45275
+ name: "summaryDetection",
45276
+ label: "Summary: cameras detecting nothing",
45277
+ example: "Ingresso — since 05:10",
45278
+ kinds: ["detection-blind"]
45279
+ },
45280
+ {
45281
+ name: "summaryNodes",
45282
+ label: "Summary: nodes",
45283
+ example: "little-unraid — offline since 03:00",
45284
+ kinds: [
45285
+ "node-offline",
45286
+ "node-online",
45287
+ "node-inference-unavailable"
45288
+ ]
45289
+ },
45290
+ {
45291
+ name: "summaryAddons",
45292
+ label: "Summary: addons",
45293
+ example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
45294
+ kinds: [
45295
+ "addon-crash-loop",
45296
+ "addon-update-available",
45297
+ "addon-updated"
45298
+ ]
45299
+ },
45300
+ {
45301
+ name: "summaryUpdates",
45302
+ label: "Summary: server & container updates",
45303
+ example: "server 1.2.377 → 1.2.378",
45304
+ kinds: [
45305
+ "server-update-available",
45306
+ "server-updated",
45307
+ "wrapper-update-available"
45308
+ ]
45309
+ }
45310
+ ];
45311
+ var STATE_SUMMARY_VARS = [
45312
+ {
45313
+ name: "summaryText",
45314
+ label: "Summary: every section",
45315
+ description: "One block per section with something to report, one line per item.",
45316
+ example: "Batterie scariche: Sensore porta — 8 %",
45317
+ group: "system",
45318
+ families: ["rule"],
45319
+ fields: [...RULE_TEXT],
45320
+ kinds: ["system"],
45321
+ systemEventKinds: [...SUMMARISABLE],
45322
+ systemEventDeliveries: ["summary"]
45323
+ },
45324
+ {
45325
+ name: "summaryCount",
45326
+ label: "Summary: items reported",
45327
+ example: "3",
45328
+ group: "system",
45329
+ families: ["rule"],
45330
+ fields: [...RULE_TEXT],
45331
+ kinds: ["system"],
45332
+ systemEventKinds: [...SUMMARISABLE],
45333
+ systemEventDeliveries: ["summary"]
45334
+ },
45335
+ ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
45336
+ name: v.name,
45337
+ label: v.label,
45338
+ example: v.example,
45339
+ group: "system",
45340
+ families: ["rule"],
45341
+ fields: [...RULE_TEXT],
45342
+ kinds: ["system"],
45343
+ systemEventKinds: [...v.kinds],
45344
+ systemEventDeliveries: ["summary"]
45345
+ }))
45346
+ ];
45347
+ [
45348
+ [...DEVICE_EVENTS],
45349
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45350
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45351
+ [...RULE_TEXT],
45352
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45353
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45354
+ [...RULE_TEXT],
45355
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45356
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45357
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45358
+ [...RULE_TEXT],
45359
+ [...RULE_TEXT],
45360
+ [...RULE_TEXT],
45361
+ [...RULE_TEXT],
45362
+ [...RULE_TEXT],
45363
+ [...RULE_TEXT],
45364
+ [...RULE_TEXT],
45365
+ [...RULE_TEXT],
45366
+ [...RULE_BODY_ONLY],
45367
+ [...RULE_TEXT],
45368
+ ([...RULE_TEXT], [...DEVICE_EVENTS]),
45369
+ ([...RULE_TEXT], [...DEVICE_EVENTS]),
45370
+ ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
45371
+ ([...RULE_TEXT], [...CONSUMABLE]),
45372
+ ([...RULE_TEXT], [...TANK]),
45373
+ [...RULE_TEXT],
45374
+ ([...RULE_TEXT], [...UPDATES]),
45375
+ ([...RULE_TEXT], [...UPDATES]),
45376
+ [...RULE_TEXT],
45377
+ [...RULE_TEXT],
45378
+ [...RULE_TEXT],
45379
+ ([...RULE_TEXT], [...UPDATES]),
45380
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45381
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45382
+ ([...RULE_TEXT], [...UPDATES]),
45383
+ ([...RULE_TEXT], [...UPDATES]),
45384
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45385
+ [...RULE_TEXT],
45386
+ [...RULE_TEXT],
45387
+ [...RULE_TEXT],
45388
+ [...RULE_TEXT],
45389
+ [...RULE_TEXT],
45390
+ [...RULE_TEXT],
45391
+ [...RULE_TEXT],
45392
+ [...RULE_TEXT],
45393
+ [...RULE_TEXT],
45394
+ [...RULE_TEXT],
45395
+ [...RULE_TEXT],
45396
+ [...RULE_TEXT],
45397
+ [...RULE_TEXT],
45398
+ [...RULE_TEXT],
45399
+ [...RULE_TEXT],
45400
+ [...RULE_TEXT],
45401
+ [...RULE_TEXT],
45402
+ [...RULE_TEXT],
45403
+ [...RULE_TEXT],
45404
+ [...RULE_TEXT],
45405
+ [...RULE_TEXT],
45406
+ [...RULE_TEXT],
45407
+ [...RULE_TEXT],
45408
+ [...RULE_TEXT],
45409
+ ...STATE_SUMMARY_VARS
45410
+ ];
44822
45411
  /**
44823
45412
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
44824
45413
  *