@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
@@ -5884,7 +5884,7 @@ var ZodIssueCode = {
5884
5884
  var ZodFirstPartyTypeKind;
5885
5885
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5886
5886
  //#endregion
5887
- //#region ../types/dist/sleep-D_4W7xuQ.mjs
5887
+ //#region ../types/dist/sleep-BaRu0bS5.mjs
5888
5888
  /**
5889
5889
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5890
5890
  * window to float samples (D455).
@@ -10020,7 +10020,7 @@ var ModelFormatEntrySchema = object({
10020
10020
  /** Multi-file payload (directory members or sibling files). */
10021
10021
  files: array(string()).readonly().optional(),
10022
10022
  /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
10023
- runtimes: array(_enum(["node", "python"])).readonly().optional()
10023
+ runtimes: array(_enum(["python"])).readonly().optional()
10024
10024
  });
10025
10025
  /**
10026
10026
  * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
@@ -10101,10 +10101,17 @@ var ModelCatalogEntrySchema = object({
10101
10101
  inputChannels: number().int().positive().optional(),
10102
10102
  labels: array(LabelDefinitionSchema).readonly(),
10103
10103
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
10104
+ /**
10105
+ * `'scrfd'` applies InsightFace's SCRFD input contract, `(x - 127.5) / 128`
10106
+ * (upstream `insightface/model_zoo/scrfd.py`), instead of the historical
10107
+ * plain `/255`. Measured against COCO GT: 188 → 196 faces found on the
10108
+ * `scrfd-2.5g` catalog entry (2026-09-26 model-replacement spike, §2.4-2).
10109
+ */
10104
10110
  inputNormalization: _enum([
10105
10111
  "zero-one",
10106
10112
  "imagenet",
10107
- "none"
10113
+ "none",
10114
+ "scrfd"
10108
10115
  ]).optional(),
10109
10116
  /**
10110
10117
  * The model already applies softmax IN-GRAPH — its raw output is a
@@ -10206,7 +10213,8 @@ var ModelConvertMetadataSchema = object({
10206
10213
  inputNormalization: _enum([
10207
10214
  "zero-one",
10208
10215
  "imagenet",
10209
- "none"
10216
+ "none",
10217
+ "scrfd"
10210
10218
  ]).optional(),
10211
10219
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
10212
10220
  outputFormat: _enum([
@@ -17624,6 +17632,9 @@ var NcSystemEventKindSchema = _enum([
17624
17632
  "device-battery-normal",
17625
17633
  "device-consumable-low",
17626
17634
  "device-consumable-normal",
17635
+ "device-tank-low",
17636
+ "device-tank-full",
17637
+ "device-tank-normal",
17627
17638
  "stream-online",
17628
17639
  "stream-offline",
17629
17640
  "node-online",
@@ -17661,6 +17672,29 @@ var NC_LEGACY_SYSTEM_EVENT_KINDS = new Set([
17661
17672
  ]);
17662
17673
  NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
17663
17674
  /**
17675
+ * The panel's transitions, as ONE list.
17676
+ *
17677
+ * Named here rather than spelled out at each of the four sites that need them
17678
+ * (the emitter, the intake, the editor group, the combined-notification gate),
17679
+ * because a transition added to the enum and forgotten at one of them is an
17680
+ * alarm state nobody can be notified about. That is exactly what this list
17681
+ * failed to prevent while it held three: `alarm-arming` was the fourth.
17682
+ *
17683
+ * In LIFECYCLE order, which is the order an editor lists them in and the order
17684
+ * a person experiences them — not the order they were added.
17685
+ */
17686
+ var NC_ALARM_SYSTEM_EVENT_KINDS = [
17687
+ "alarm-arm-refused",
17688
+ "alarm-check-failed",
17689
+ "alarm-arming",
17690
+ "alarm-armed",
17691
+ "alarm-pending",
17692
+ "alarm-triggered",
17693
+ "alarm-rearmed",
17694
+ "alarm-sensor-bypass",
17695
+ "alarm-disarmed"
17696
+ ];
17697
+ /**
17664
17698
  * One coherent system-event condition. `kinds` is the required opt-in safety
17665
17699
  * gate; the remaining lists are optional narrowing filters relevant to the
17666
17700
  * selected kinds.
@@ -17694,7 +17728,22 @@ var NcSystemEventConditionSchema = object({
17694
17728
  */
17695
17729
  excludeDeviceIds: array(number().int()).min(1).optional(),
17696
17730
  nodeIds: array(string().min(1)).min(1).optional(),
17697
- packageNames: array(string().min(1)).min(1).optional()
17731
+ packageNames: array(string().min(1)).min(1).optional(),
17732
+ /**
17733
+ * At or below this remaining-life %, a consumable is LOW for THIS rule
17734
+ * (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
17735
+ * {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
17736
+ * consumable kinds (`systemEventFilterApplies`) and to the device-events
17737
+ * family; ignored on every other kind.
17738
+ *
17739
+ * It is a property of the RULE because the operator decided so: a rule at
17740
+ * 25 must be told about a filter at 20 %, a rule at 10 must not. The hub
17741
+ * keeps one episode per (threshold, device, item) — its own hysteresis, its
17742
+ * own reminder — and a rule hears only the episode judged at ITS threshold,
17743
+ * exactly as an occupancy rule hears only the edge of its own count. A
17744
+ * provider's own `replace` verdict is low at every threshold.
17745
+ */
17746
+ consumableLowPercent: number().int().min(1).max(95).optional()
17698
17747
  });
17699
17748
  /** Hard ceiling on a window (24h). A snooze that could not expire would be an
17700
17749
  * outage the operator asked for once and forgot. */
@@ -17712,6 +17761,25 @@ var NcScheduleSchema = object({
17712
17761
  /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
17713
17762
  invert: boolean().optional()
17714
17763
  });
17764
+ /**
17765
+ * How a system-event rule of the `device-events` or `system` family speaks
17766
+ * (D647): at once, per event (`instant`, the only behaviour before D647), or
17767
+ * as a STATE SUMMARY at the end of each window of `schedule`.
17768
+ */
17769
+ var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
17770
+ /**
17771
+ * `summary`: the rule sends NOTHING per event. When a window of `schedule`
17772
+ * CLOSES, it sends one notification with the current state of its kinds
17773
+ * (low batteries, consumables to replace, devices offline, updates
17774
+ * available, ...), after its own filters. Nothing to report sends nothing.
17775
+ * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
17776
+ * midnight crossing); the send time is each window's END. Refused on the
17777
+ * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
17778
+ */
17779
+ var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
17780
+ mode: literal("summary"),
17781
+ schedule: NcScheduleSchema
17782
+ })]);
17715
17783
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
17716
17784
  var NcPlateMatcherSchema = object({
17717
17785
  /**
@@ -17837,6 +17905,16 @@ var NcCrossingSchema = _enum([
17837
17905
  "exit",
17838
17906
  "any"
17839
17907
  ]);
17908
+ /**
17909
+ * Which edge of a BOOLEAN sensor fires a device-event rule (D640).
17910
+ * - `raised` — the sensor went high: water detected, smoke, open, tampered.
17911
+ * - `cleared` — it went back: dry, clear, closed.
17912
+ * - absent — both edges, which is what every rule authored before this did.
17913
+ *
17914
+ * There is no third member for "both": absence already says it, and a value
17915
+ * that means the same as absence is a second way to write one rule.
17916
+ */
17917
+ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
17840
17918
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
17841
17919
  var NcZoneConditionSchema = object({
17842
17920
  ids: array(string().min(1)).min(1),
@@ -18026,6 +18104,20 @@ var NcConditionsSchema = object({
18026
18104
  scene: NcSceneConditionSchema.optional(),
18027
18105
  /** Device scope — absent = all devices. */
18028
18106
  devices: array(number()).optional(),
18107
+ /**
18108
+ * Device EXCLUSION (D641) — "every device except these". Judged on the SAME
18109
+ * subject ids {@link devices} is judged on (the camera, the sensor that
18110
+ * fired, a linked set's sensor and row cameras), so a sensor fanned out per
18111
+ * linked camera is excluded exactly where an include would have tested it.
18112
+ * `min(1)`: an empty exclusion excludes nothing and is written as absent.
18113
+ *
18114
+ * Not for `system-event` rules — those carry their own
18115
+ * `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
18116
+ * Editors present the two lists as ONE field (All | Only these | All except
18117
+ * these); the engine evaluates each independently, so a legacy rule
18118
+ * carrying both still means "these, minus those".
18119
+ */
18120
+ devicesExclude: array(number()).min(1).optional(),
18029
18121
  /** Detector class names (any overlap with the record's class set). */
18030
18122
  classes: array(string().min(1)).optional(),
18031
18123
  /** Veto classes — any overlap fails the rule. */
@@ -18159,6 +18251,16 @@ var NcConditionsSchema = object({
18159
18251
  */
18160
18252
  sensorKinds: array(string().min(1)).optional(),
18161
18253
  /**
18254
+ * DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
18255
+ * {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
18256
+ * is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
18257
+ * engine cannot place (a kind with no raise, an unreadable slice) PASSES:
18258
+ * the condition fails open towards notifying, never towards silence. Editors
18259
+ * offer it only when every chosen {@link sensorKinds} member has a raise
18260
+ * (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
18261
+ */
18262
+ sensorTransition: NcSensorTransitionSchema.optional(),
18263
+ /**
18162
18264
  * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
18163
18265
  * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
18164
18266
  * when the subject's phase does not match (a subject always carries a phase
@@ -18413,6 +18515,16 @@ var NcRuleInputSchema = object({
18413
18515
  delivery: NcDeliverySchema,
18414
18516
  conditions: NcConditionsSchema.default({}),
18415
18517
  schedule: NcScheduleSchema.optional(),
18518
+ /**
18519
+ * A `device-events` / `system` rule's delivery (D647): absent or `instant`
18520
+ * is a notification per event; `summary` is one state summary per closed
18521
+ * window of its schedule. `.optional()` and never defaulted: absent must
18522
+ * keep meaning what every rule authored before D647 meant, with no
18523
+ * migration. Top-level rather than inside `conditions.systemEvent`, so a
18524
+ * client that does not know it (an older viewer re-sending `conditions`
18525
+ * whole) leaves it standing through the store's patch merge.
18526
+ */
18527
+ systemEventDelivery: NcSystemEventDeliverySchema.optional(),
18416
18528
  /** May be empty when `targetUsers` addresses at least one user — the
18417
18529
  * "at least one addressee" invariant is enforced by the provider, because
18418
18530
  * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
@@ -18641,6 +18753,28 @@ var NcRuleSchema = NcRuleInputSchema.extend({
18641
18753
  */
18642
18754
  disabledTargetIds: array(string()).default([])
18643
18755
  });
18756
+ /** One section of a previewed state summary. `unavailable` = its state could not be read. */
18757
+ var NcStateSummaryPreviewSectionSchema = object({
18758
+ id: string(),
18759
+ status: _enum(["read", "unavailable"]),
18760
+ items: number().int().nonnegative(),
18761
+ reason: string().optional()
18762
+ });
18763
+ /**
18764
+ * The summary a rule would send now. `empty` = nothing to report and every
18765
+ * section read: such a summary is NOT sent. `dropped` counts the open states
18766
+ * the rule's own filters (or a camera mute) left out, by reason.
18767
+ * `excludedGroups` names the kind groups selected that have no state to report
18768
+ * (backups & exports).
18769
+ */
18770
+ var NcStateSummaryPreviewSchema = object({
18771
+ empty: boolean(),
18772
+ count: number().int().nonnegative(),
18773
+ text: string(),
18774
+ sections: array(NcStateSummaryPreviewSectionSchema),
18775
+ dropped: record(string(), number().int().nonnegative()),
18776
+ excludedGroups: array(string())
18777
+ });
18644
18778
  var NcTestResultSchema = object({
18645
18779
  recordId: string(),
18646
18780
  recordKind: _enum([
@@ -18732,6 +18866,13 @@ var NcTemplateVarDescriptorSchema = object({
18732
18866
  kinds: array(NcTemplateVarKindSchema).optional(),
18733
18867
  deliveries: array(NcDeliverySchema).optional(),
18734
18868
  systemEventKinds: array(NcSystemEventKindSchema).optional(),
18869
+ /**
18870
+ * Which system-event DELIVERY has a value for it (D647). Absent = an
18871
+ * instant row only when the descriptor names `systemEventKinds` (a
18872
+ * per-event fact: a summary row has no single device), and both otherwise.
18873
+ * `['summary']` marks the state-summary vocabulary (`{{summaryText}}`, ...).
18874
+ */
18875
+ systemEventDeliveries: array(NcSystemEventDeliveryModeSchema).optional(),
18735
18876
  dynamic: object({
18736
18877
  prefix: literal("count_"),
18737
18878
  from: literal("classes")
@@ -18744,7 +18885,9 @@ var NcTemplatePreviewInputSchema = object({
18744
18885
  field: NcTemplateFieldSchema,
18745
18886
  kind: NcRuleKindSchema.optional(),
18746
18887
  delivery: NcDeliverySchema.optional(),
18747
- systemEventKinds: array(NcSystemEventKindSchema).optional()
18888
+ systemEventKinds: array(NcSystemEventKindSchema).optional(),
18889
+ /** The rule's system-event delivery (D647); absent = instant. */
18890
+ systemEventDelivery: NcSystemEventDeliveryModeSchema.optional()
18748
18891
  }),
18749
18892
  template: object({
18750
18893
  title: string().max(500).optional(),
@@ -18777,6 +18920,7 @@ var NcConditionDescriptorSchema = object({
18777
18920
  /** Editor widget the UI renders — never hardcode per-condition forms. */
18778
18921
  valueType: _enum([
18779
18922
  "deviceIdList",
18923
+ "deviceIdExcludeList",
18780
18924
  "stringList",
18781
18925
  "number01",
18782
18926
  "number",
@@ -18787,6 +18931,7 @@ var NcConditionDescriptorSchema = object({
18787
18931
  "plateMatcher",
18788
18932
  "packagePhase",
18789
18933
  "crossingSelect",
18934
+ "sensorTransition",
18790
18935
  "polygonDraw",
18791
18936
  "occupancy",
18792
18937
  "audio",
@@ -18937,6 +19082,28 @@ var NcHistoryFilterSchema = object({
18937
19082
  limit: number().int().min(1).max(500).default(100)
18938
19083
  });
18939
19084
  /**
19085
+ * Every rule's last run, in one answer.
19086
+ *
19087
+ * A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
19088
+ * back this answer looked, in ms before the hub's now. It is the outbox prune
19089
+ * window (7 days by default, operator-configurable), NARROWED when the read hit
19090
+ * its row bound: the answer never claims a window it did not read. `null` means
19091
+ * the operator keeps the outbox forever AND the read reached its end, so absent
19092
+ * means "never on record". A UI derives its "not in the last N days" from this
19093
+ * value, never from a constant — and only when the call ANSWERED (D315).
19094
+ */
19095
+ var NcRuleLastRunsSchema = object({
19096
+ runs: array(object({
19097
+ ruleId: string(),
19098
+ /** Fire time of the newest row (`createdAt`, epoch ms). */
19099
+ lastAt: number(),
19100
+ status: NcHistoryStatusSchema,
19101
+ /** The camera/device of that row (a digest row files under its first camera). */
19102
+ deviceId: number()
19103
+ })),
19104
+ horizonMs: number().nonnegative().nullable()
19105
+ });
19106
+ /**
18940
19107
  * What a snooze covers. Broader scopes win when several overlap, so one window
18941
19108
  * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
18942
19109
  * summarising the same silence.
@@ -19117,6 +19284,14 @@ var NcAlarmModeCoverageSchema = object({
19117
19284
  /** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
19118
19285
  deviceIds: array(number().int()),
19119
19286
  /**
19287
+ * D641: the ids NOT covered although `allDevices` is true — every unscoped
19288
+ * covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
19289
+ * covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
19290
+ * which is not the same as skipped by a switch: never in `skippedDevices`.
19291
+ * Defaulted so an answer computed before this field parses as "none".
19292
+ */
19293
+ allDevicesExcept: array(number().int()).default([]),
19294
+ /**
19120
19295
  * Devices this mode NAMES but cannot actually arm, each with the switch that
19121
19296
  * excludes it.
19122
19297
  *
@@ -19246,13 +19421,17 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
19246
19421
  }), object({ results: array(NcTestResultSchema) }), {
19247
19422
  kind: "mutation",
19248
19423
  auth: "admin"
19424
+ }), method(object({ rule: NcRuleInputSchema }), NcStateSummaryPreviewSchema, {
19425
+ kind: "mutation",
19426
+ auth: "admin",
19427
+ access: "view"
19249
19428
  }), method(object({}), object({
19250
19429
  catalog: array(NcConditionDescriptorSchema),
19251
19430
  taxonomy: NcTaxonomySchema.optional()
19252
19431
  })), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
19253
19432
  kind: "mutation",
19254
19433
  access: "view"
19255
- }), 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 }), {
19434
+ }), 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 }), {
19256
19435
  kind: "mutation",
19257
19436
  caller: "required"
19258
19437
  }), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
@@ -19592,606 +19771,6 @@ var OauthIntegrationDescriptorSchema = object({
19592
19771
  });
19593
19772
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
19594
19773
  /**
19595
- * Reference to the frame's retained NATIVE surface + the parent crop's placement
19596
- * within the frame, so the executor can re-cut a leaf child ROI at native
19597
- * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
19598
- */
19599
- var NativeCropRefSchema = object({
19600
- /** Handle keying the retained native surface (node-pinned to its owner). */
19601
- handle: FrameHandleSchema,
19602
- /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
19603
- cropFrameSpace: object({
19604
- x: number(),
19605
- y: number(),
19606
- w: number(),
19607
- h: number()
19608
- })
19609
- });
19610
- object({
19611
- crop: object({
19612
- left: number(),
19613
- top: number(),
19614
- width: number().positive(),
19615
- height: number().positive()
19616
- }).optional(),
19617
- content: object({
19618
- width: number().int().positive(),
19619
- height: number().int().positive()
19620
- }),
19621
- fit: _enum(["stretch", "contain"]),
19622
- format: _enum([
19623
- "rgb",
19624
- "gray",
19625
- "jpeg"
19626
- ])
19627
- });
19628
- /**
19629
- * Process-local frame identity. It is serializable so it can ride an in-process
19630
- * capability call, but `registryId` deliberately prevents resolution in any
19631
- * other process or execution group.
19632
- */
19633
- var FrameRefSchema = object({
19634
- registryId: string().min(1),
19635
- id: string().min(1),
19636
- width: number().int().positive(),
19637
- height: number().int().positive(),
19638
- format: _enum(["rgb", "gray"]),
19639
- timestamp: number(),
19640
- capturedAt: number().optional()
19641
- });
19642
- var ModelFormatSchema$1 = _enum([
19643
- "onnx",
19644
- "coreml",
19645
- "openvino",
19646
- "tflite",
19647
- "pt",
19648
- "gguf"
19649
- ]);
19650
- var PipelineSlotSchema = _enum([
19651
- "detector",
19652
- "cropper",
19653
- "classifier",
19654
- "refiner",
19655
- "audio-classifier"
19656
- ]);
19657
- var PipelineEngineChoiceSchema = object({
19658
- runtime: _enum(["node", "python"]),
19659
- backend: string(),
19660
- format: ModelFormatSchema$1,
19661
- device: string().optional()
19662
- });
19663
- var AvailableEngineSchema = object({
19664
- engine: PipelineEngineChoiceSchema,
19665
- devices: array(object({
19666
- id: string(),
19667
- label: string(),
19668
- description: string().optional()
19669
- })).readonly(),
19670
- defaultDevice: string()
19671
- });
19672
- var PipelineDefaultStepSchema = lazy(() => object({
19673
- addonId: string(),
19674
- addonName: string(),
19675
- slot: PipelineSlotSchema,
19676
- inputClasses: array(string()).readonly(),
19677
- outputClasses: array(string()).readonly(),
19678
- enabled: boolean(),
19679
- modelId: string(),
19680
- children: array(PipelineDefaultStepSchema).readonly(),
19681
- group: string().optional(),
19682
- settings: record(string(), unknown()).optional()
19683
- }));
19684
- var PipelineTemplateStepSchema = lazy(() => object({
19685
- addonId: string(),
19686
- enabled: boolean(),
19687
- modelId: string(),
19688
- children: array(PipelineTemplateStepSchema).readonly(),
19689
- settings: record(string(), unknown()).optional()
19690
- }));
19691
- var PipelineTemplateSchema$1 = object({
19692
- id: string(),
19693
- name: string(),
19694
- createdAt: string(),
19695
- updatedAt: string(),
19696
- engine: PipelineEngineChoiceSchema,
19697
- steps: array(PipelineTemplateStepSchema).readonly()
19698
- });
19699
- var PipelineModelOptionSchema = object({
19700
- id: string(),
19701
- name: string(),
19702
- formats: record(string(), object({
19703
- downloaded: boolean(),
19704
- sizeMB: number()
19705
- })),
19706
- group: ModelVariantGroupSchema.optional(),
19707
- legacy: boolean().optional(),
19708
- provider: ModelProviderIdSchema.optional()
19709
- });
19710
- var ConfigFieldBridge = custom();
19711
- var PipelineAddonSchemaSchema = object({
19712
- id: string(),
19713
- name: string(),
19714
- slot: PipelineSlotSchema,
19715
- inputClasses: array(string()).readonly(),
19716
- outputClasses: array(string()).readonly(),
19717
- childSlots: array(PipelineSlotSchema).readonly(),
19718
- models: array(PipelineModelOptionSchema).readonly(),
19719
- defaultModelId: string(),
19720
- defaultModelIdByFormat: record(string(), string()).optional(),
19721
- enabledByDefault: boolean().optional(),
19722
- backfillIntoExistingOverrides: boolean().optional(),
19723
- defaultConfidence: number(),
19724
- group: string().optional(),
19725
- configSchema: array(ConfigFieldBridge).readonly().optional()
19726
- });
19727
- var PipelineSlotSchemaSchema = object({
19728
- id: PipelineSlotSchema,
19729
- label: string(),
19730
- priority: number(),
19731
- parentSlot: PipelineSlotSchema.nullable(),
19732
- addons: array(PipelineAddonSchemaSchema).readonly()
19733
- });
19734
- var PipelineSchemaSchema = object({
19735
- availableEngines: array(AvailableEngineSchema).readonly(),
19736
- selectedEngine: PipelineEngineChoiceSchema,
19737
- slots: array(PipelineSlotSchemaSchema).readonly()
19738
- });
19739
- var EngineProvisioningSchema = object({
19740
- runtimeId: _enum([
19741
- "onnx",
19742
- "openvino",
19743
- "coreml",
19744
- "edgetpu"
19745
- ]).nullable(),
19746
- device: string().nullable(),
19747
- state: _enum([
19748
- "idle",
19749
- "installing",
19750
- "verifying",
19751
- "ready",
19752
- "failed"
19753
- ]),
19754
- progress: number().optional(),
19755
- error: string().optional(),
19756
- nextRetryAt: number().optional(),
19757
- /**
19758
- * Gate A (config-correctness gate at engine change): human-readable
19759
- * config issues surfaced EAGERLY when the node's engine changes — model
19760
- * substitutions ("chose X, running Y") and zero-build steps ("no model
19761
- * has a <format> build"). Additive/optional: informational only, never
19762
- * enforced here — `assertEngineReady` (readiness) still gates inference.
19763
- * Absent/empty when the node-default tree resolves cleanly.
19764
- */
19765
- configIssues: array(string()).optional()
19766
- });
19767
- var PipelineStepInputSchema = lazy(() => object({
19768
- addonId: string(),
19769
- modelId: string().optional(),
19770
- enabled: boolean().default(true),
19771
- children: array(PipelineStepInputSchema).optional(),
19772
- settings: record(string(), unknown()).optional(),
19773
- jumpDeviceKey: string().optional()
19774
- }));
19775
- var ModelSubstitutionSchema = object({
19776
- addonId: string(),
19777
- chosen: string(),
19778
- running: string(),
19779
- format: string()
19780
- });
19781
- var PipelineValidationIssueSchema = object({
19782
- addonId: string(),
19783
- kind: _enum(["unknown-addon", "no-format-build"]),
19784
- detail: string()
19785
- });
19786
- var PipelineValidationResultSchema = object({
19787
- ok: boolean(),
19788
- issues: array(PipelineValidationIssueSchema).readonly(),
19789
- substitutions: array(ModelSubstitutionSchema).readonly(),
19790
- /** The node's `currentEngine.format` this validation ran against. */
19791
- format: string()
19792
- });
19793
- var ReferenceImageEntrySchema = object({
19794
- filename: string(),
19795
- stepIds: array(string()).readonly().optional()
19796
- });
19797
- var ReferenceImageBodySchema = object({
19798
- base64: string(),
19799
- filename: string()
19800
- });
19801
- var ReferenceAudioEntrySchema = object({
19802
- filename: string(),
19803
- sizeKb: number()
19804
- });
19805
- var ReferenceAudioBodySchema = object({ base64: string() });
19806
- var AudioBackendSchema = object({
19807
- id: string(),
19808
- name: string(),
19809
- description: string(),
19810
- available: boolean(),
19811
- /**
19812
- * Raw classifier labels this backend can emit (e.g. YAMNet's
19813
- * 521-class set or Apple SoundAnalysis's 303-class set). Used by
19814
- * the benchmark UI to populate the `enabledMicroClasses` filter
19815
- * specific to the selected backend without a separate fetch.
19816
- */
19817
- rawLabels: array(string()).readonly().optional()
19818
- });
19819
- var AudioCapabilitiesSchema = object({
19820
- activeBackend: string(),
19821
- availableBackends: array(AudioBackendSchema).readonly(),
19822
- sampleRate: number(),
19823
- chunkDurationMs: number()
19824
- });
19825
- var DownloadModelResultSchema = object({
19826
- filePath: string(),
19827
- sizeMB: number(),
19828
- durationMs: number()
19829
- });
19830
- /**
19831
- * Wrapper carrying a single test run's result. Replaces the legacy
19832
- * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
19833
- * canonical `AudioResult` from the Phase 6 output rework: one
19834
- * `AudioDetection` per class above `minScore`, top-N candidates in
19835
- * `debug.alternateLabels['audio-classifier']`, per-source timings in
19836
- * `debug.stepTimings`. The outer `success`/`error` fields stay so the
19837
- * benchmark UI can still report a clean failure when the classifier
19838
- * cap isn't available.
19839
- */
19840
- var AudioTestResultSchema = object({
19841
- success: boolean(),
19842
- error: string().optional(),
19843
- frame: custom().optional()
19844
- });
19845
- var PipelineConfigBridge = custom();
19846
- var ConfigUISchemaBridge = custom();
19847
- var ConfigUISchemaNullableBridge = custom();
19848
- var InferenceCapabilitiesBridge = custom();
19849
- var ModelAvailabilityListBridge = custom();
19850
- var PipelineRunResultBridge = custom();
19851
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
19852
- modelId: string(),
19853
- settings: record(string(), unknown()).readonly()
19854
- }))), method(object({ steps: record(string(), object({
19855
- modelId: string(),
19856
- settings: record(string(), unknown()).readonly()
19857
- })) }), object({ success: literal(true) }), {
19858
- kind: "mutation",
19859
- auth: "admin"
19860
- }), method(object({ nodeId: string() }), object({
19861
- success: literal(true),
19862
- clearedDevices: number()
19863
- }), {
19864
- kind: "mutation",
19865
- auth: "admin"
19866
- }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
19867
- /** `<backend>:<device>`, e.g. `openvino:gpu`. */
19868
- deviceKey: string(),
19869
- /**
19870
- * `failed` — the per-device restart budget is exhausted; no pool
19871
- * will be spawned until an operator re-arms it or the runner
19872
- * respawns. `backoff` — under budget, waiting out the backoff (or
19873
- * a cached pool observed dead and not yet condemned).
19874
- */
19875
- state: _enum(["failed", "backoff"]),
19876
- /** Epoch ms of the death that produced this state. */
19877
- since: number(),
19878
- /** Pool deaths inside the current window. */
19879
- deaths: number(),
19880
- /** The last death's message. */
19881
- lastError: string()
19882
- })).readonly() })), method(object({
19883
- nodeId: string(),
19884
- deviceKey: string()
19885
- }), object({ rearmed: boolean() }), {
19886
- kind: "mutation",
19887
- auth: "admin"
19888
- }), 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({
19889
- name: string(),
19890
- steps: array(PipelineTemplateStepSchema).readonly(),
19891
- engine: PipelineEngineChoiceSchema
19892
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
19893
- id: string(),
19894
- name: string().optional(),
19895
- steps: array(PipelineTemplateStepSchema).readonly().optional()
19896
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
19897
- addonId: string(),
19898
- modelId: string(),
19899
- format: ModelFormatSchema$1
19900
- }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
19901
- addonId: string(),
19902
- modelId: string(),
19903
- format: ModelFormatSchema$1
19904
- }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
19905
- engine: PipelineEngineChoiceSchema.optional(),
19906
- steps: array(PipelineStepInputSchema).min(1),
19907
- frame: FrameInputSchema.optional(),
19908
- /**
19909
- * Process-local lazy frame. Valid only when caller and provider resolve
19910
- * in the same execution-group process; split/cross-node callers use
19911
- * `frame`/`image` inline compatibility instead.
19912
- */
19913
- frameRef: FrameRefSchema.optional(),
19914
- /**
19915
- * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
19916
- * the decoded pixels live in. One more member of the one-of
19917
- * frame/frameHandle/image/imageBase64/referenceImage group.
19918
- */
19919
- frameHandle: FrameHandleSchema.optional(),
19920
- imageBase64: string().optional(),
19921
- /**
19922
- * Binary JPEG bytes — preferred over `imageBase64` on internal
19923
- * hops (hub → forked worker via Moleculer MsgPack) because it
19924
- * skips the 33% base64 overhead + the per-call base64 decode on
19925
- * the detection-pipeline worker. Callers can pass either; exactly
19926
- * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
19927
- */
19928
- image: _instanceof(Uint8Array).optional(),
19929
- referenceImage: string().optional(),
19930
- deviceId: number().optional(),
19931
- sessionId: string().optional(),
19932
- /**
19933
- * Execution plane. 'full' (default) runs the whole tree — benchmark,
19934
- * reference-image, and detail-subtree calls. 'frame' is the live
19935
- * per-frame dispatch: ONLY root-plane steps run; crop children
19936
- * (inputClasses ≠ null) are skipped and served per-track via
19937
- * pipelineRunner.runDetailSubtree (two-plane design).
19938
- */
19939
- plane: _enum(["full", "frame"]).optional(),
19940
- /**
19941
- * Inference-device selector (Phase 2 multi-device). Format
19942
- * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
19943
- * Omitted ⇒ the runner's default device (current single-engine
19944
- * behaviour). Selects WHICH device pool of the node runs the call.
19945
- */
19946
- deviceKey: string().optional(),
19947
- /**
19948
- * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
19949
- * when the parent crop was resolved from the frame's retained NATIVE
19950
- * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
19951
- * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
19952
- * resolution from that surface — the SAME quality path faces already
19953
- * had — instead of the downscaled parent tile. `handle` keys the native
19954
- * surface (node-pinned to its owner); `cropFrameSpace` is the parent
19955
- * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
19956
- * the executor's crop-normalized child ROI back into frame-normalized
19957
- * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
19958
- * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
19959
- * (today's behaviour on the fallback path).
19960
- */
19961
- nativeCropRef: NativeCropRefSchema.optional()
19962
- }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
19963
- engine: PipelineEngineChoiceSchema.optional(),
19964
- steps: array(PipelineStepInputSchema).min(1),
19965
- frames: array(FrameInputSchema).min(1).max(255),
19966
- deviceId: number().optional(),
19967
- sessionId: string().optional(),
19968
- /**
19969
- * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
19970
- * the batch to the Python pool's bench preprocess cache
19971
- * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
19972
- * preprocessed ONCE and every later inference is a pure-inference cache
19973
- * hit — the sustained-throughput run measures inference, not
19974
- * decode+preprocess+infer. Omitted/0 for live frames (all different →
19975
- * full preprocess every call, correct). Fresh per sustained run;
19976
- * released via `uncacheFrame`.
19977
- */
19978
- frameId: number().int().nonnegative().optional(),
19979
- /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
19980
- deviceKey: string().optional()
19981
- }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
19982
- data: _instanceof(Uint8Array),
19983
- width: number().int().positive(),
19984
- height: number().int().positive(),
19985
- format: _enum([
19986
- "rgb",
19987
- "bgr",
19988
- "gray"
19989
- ])
19990
- }), object({
19991
- frameId: number(),
19992
- width: number(),
19993
- height: number()
19994
- }), { kind: "mutation" }), method(object({
19995
- stepId: string(),
19996
- frameId: number().int()
19997
- }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(_void(), object({
19998
- batchMode: string(),
19999
- windowMs: number(),
20000
- maxBatchSize: number(),
20001
- concurrency: number()
20002
- })), method(_void(), array(object({
20003
- engineKey: string(),
20004
- engine: PipelineEngineChoiceSchema,
20005
- modelsLoaded: array(string()).readonly(),
20006
- inUseByCameras: array(number()).readonly(),
20007
- /**
20008
- * Origin of this resident factory.
20009
- * - `runtime` — main camera-serving engine (no idle TTL).
20010
- * - `warm-override` — benchmark/test override held in the warm
20011
- * cache; auto-disposed after the idle TTL.
20012
- * - `device-pool` — a concurrent per-device pool (Phase 2
20013
- * multi-device, keyed by `deviceKey`) resolved
20014
- * via `resolveDeviceFactory`. Runs alongside the
20015
- * `runtime` engine on a DIFFERENT accelerator
20016
- * (NPU / iGPU / Coral) — this is how the
20017
- * Engines tab shows all pools running at once.
20018
- */
20019
- kind: _enum([
20020
- "runtime",
20021
- "warm-override",
20022
- "device-pool"
20023
- ]),
20024
- /** Native pid of the underlying Python pool (null when no pool). */
20025
- poolPid: number().nullable(),
20026
- /** ms since this factory was last used (null when not warm-tracked). */
20027
- idleMs: number().nullable(),
20028
- /** Idle TTL after which `warm-override` factories self-evict (null when not applicable). */
20029
- idleTtlMs: number().nullable()
20030
- })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
20031
- kind: "mutation",
20032
- auth: "admin"
20033
- }), method(object({
20034
- engine: PipelineEngineChoiceSchema,
20035
- force: boolean().optional()
20036
- }), object({
20037
- success: boolean(),
20038
- reason: string().optional()
20039
- }), {
20040
- kind: "mutation",
20041
- auth: "admin"
20042
- }), 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({
20043
- addonId: string(),
20044
- modelId: string(),
20045
- filename: string().optional(),
20046
- settings: record(string(), unknown()).optional()
20047
- }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
20048
- /**
20049
- * Per-stage gating mode applied to the zones a rule references.
20050
- *
20051
- * - `include`: the rule contributes to a **whitelist** for its stage.
20052
- * When at least one `include` rule fires for a stage, only entities
20053
- * inside one of those zones pass that stage.
20054
- * - `exclude`: the rule contributes to a **blacklist** for its stage.
20055
- * Entities inside one of those zones are dropped at that stage.
20056
- *
20057
- * `monitor`-style observation (count without filtering) is not a rule
20058
- * mode — zones without any matching rule are observed naturally by
20059
- * `zone-analytics` (live snapshot + history), so an "I just want to
20060
- * count, not filter" use case needs no rule at all.
20061
- */
20062
- var ZoneRuleModeEnum = _enum(["include", "exclude"]);
20063
- /**
20064
- * Per-consumer rule that references existing zones (geometry) and
20065
- * defines how a specific pipeline stage should treat them. Each
20066
- * consumer addon owns its own `ZoneRule[]` array in its per-device
20067
- * settings:
20068
- *
20069
- * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
20070
- * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
20071
- * - future: notification rules, audio gating, etc.
20072
- *
20073
- * One rule applies to N zones (`zoneIds[]`) so the operator can
20074
- * express "ignore motion in ALL of {garden, street}" with a single
20075
- * rule. `classFilter` narrows the rule to specific object classes —
20076
- * "drop person detections in the street, but keep cars" is one
20077
- * `exclude` rule with `classFilter: ['person']`.
20078
- *
20079
- * `enabled` is a soft toggle — the operator can keep the rule
20080
- * configured but inert without deleting it.
20081
- */
20082
- var ZoneRuleSchema = object({
20083
- /** Stable rule id — survives edits, used by the UI for diffing. */
20084
- id: string(),
20085
- /** Optional human-readable label rendered in the rule editor. */
20086
- name: string().optional(),
20087
- /** Zones this rule targets. The rule's `mode` applies to ALL
20088
- * listed zones (OR-set: a detection in any one of them counts).
20089
- * At least one zone id required — a rule with no targets is a
20090
- * configuration mistake and the form validator rejects it. */
20091
- zoneIds: array(string()).min(1).readonly(),
20092
- mode: ZoneRuleModeEnum,
20093
- /**
20094
- * Class names this rule applies to. Empty / undefined ⇒ rule
20095
- * applies to every class. Class strings match the `macroClass`
20096
- * field on detections (e.g. `person`, `car`, `dog`).
20097
- */
20098
- classFilter: array(string()).readonly().optional(),
20099
- /**
20100
- * Minimum bbox/mask overlap (0–1) with any of the rule's zones
20101
- * required to consider an entity "in the zone". Defaults to the
20102
- * consumer's stage default when omitted. Kept for back-compat with
20103
- * existing per-rule overrides; new operators pick the value via
20104
- * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
20105
- * set, the lower-level engine reads it as a 0–1 fraction.
20106
- */
20107
- overlapThreshold: number().min(0).max(1).optional(),
20108
- /**
20109
- * Operator-friendly version of `overlapThreshold` — the percentage
20110
- * of the detection's bbox that must lie inside the zone for the
20111
- * rule to match. Documented default is 85%; the engine substitutes
20112
- * that when the field is omitted (kept optional so existing rules
20113
- * stored without it stay valid).
20114
- *
20115
- * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
20116
- * rule, the engine prefers `bboxInclusionPct` because it's the
20117
- * field exposed in the UI. Internally both feed the same gate.
20118
- */
20119
- bboxInclusionPct: number().min(0).max(100).optional(),
20120
- /**
20121
- * When `true` and a detection has a segmentation mask, use the
20122
- * mask for overlap instead of the bbox. Detection-stage only;
20123
- * motion rules ignore this field.
20124
- */
20125
- preferMask: boolean().optional(),
20126
- /**
20127
- * Soft-toggle: `false` disables the rule without deleting it.
20128
- * Defaults to `true` so operators creating a rule via the UI
20129
- * see it active immediately.
20130
- */
20131
- enabled: boolean().default(true)
20132
- });
20133
- array(ZoneRuleSchema).readonly();
20134
- /**
20135
- * Zone — pure geometry + identity. NO filtering behaviour.
20136
- *
20137
- * Zones describe **where** in the frame the operator wants to flag
20138
- * something; consumer-owned {@link ZoneRule} arrays describe **how**
20139
- * each pipeline stage uses them. Splitting the two means a single
20140
- * polygon "Driveway" can simultaneously back a motion-exclude rule,
20141
- * a detection-include rule on `['car']`, and an occupancy aggregate
20142
- * — without three duplicated polygons.
20143
- *
20144
- * Owned by the orchestrator addon (provider) and mirrored into the
20145
- * `zones` device-state slice on every mutation. Consumers
20146
- * (motion-wasm, pipeline-executor, analytics, admin UI) read either
20147
- * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
20148
- * mirror with `onChanged`).
20149
- *
20150
- * Coordinates are normalised fractions of the frame (0–1) so zones
20151
- * survive resolution changes and stream profile switches.
20152
- *
20153
- * `kind` discriminates between full polygons (closed regions used
20154
- * for intrusion / occupancy filters) and tripwires (open 2-point
20155
- * line segments used for cross events). Onboard / firmware-reported
20156
- * zones (Reolink, ONVIF) are out of scope for now — see the deferred
20157
- * task list.
20158
- */
20159
- var ZoneKindEnum = _enum(["polygon", "tripwire"]);
20160
- /** Polygon vertex in fraction-of-frame coordinates (0–1). */
20161
- var PolygonPointSchema = object({
20162
- x: number(),
20163
- y: number()
20164
- });
20165
- /** A camera detection zone — pure geometry/identity. */
20166
- var ZoneSchema = object({
20167
- id: string(),
20168
- name: string(),
20169
- kind: ZoneKindEnum.default("polygon"),
20170
- /** Polygon vertices, fraction of frame (0–1). */
20171
- polygon: array(PolygonPointSchema).readonly(),
20172
- /** Visual color for UI rendering. */
20173
- color: string().default("#3b82f6")
20174
- });
20175
- DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
20176
- deviceId: number(),
20177
- zone: ZoneSchema
20178
- }), _void(), {
20179
- kind: "mutation",
20180
- auth: "admin"
20181
- }), method(object({
20182
- deviceId: number(),
20183
- zoneId: string()
20184
- }), _void(), {
20185
- kind: "mutation",
20186
- auth: "admin"
20187
- }), method(object({
20188
- deviceId: number(),
20189
- zone: ZoneSchema
20190
- }), _void(), {
20191
- kind: "mutation",
20192
- auth: "admin"
20193
- }), object({ zones: array(ZoneSchema).readonly() });
20194
- /**
20195
19774
  * pipeline-analytics — device-scoped wrapper cap. Refines raw
20196
19775
  * per-frame detections emitted by the pipeline runner into tracked
20197
19776
  * objects, per-kind event collections (motion / object / audio), and
@@ -20762,6 +20341,22 @@ var TrackSchema = object({
20762
20341
  * omit it. Render nothing when absent.
20763
20342
  */
20764
20343
  previewMissReason: string().optional(),
20344
+ /**
20345
+ * The ROOT model that produced this track — the `FrameResult.rootModelId` of
20346
+ * the frame it was BORN on (D646). What a later replay compares against, so
20347
+ * "the original model" is named rather than assumed. A birth fact: never
20348
+ * rewritten by a later frame. **Absent ≠ unknown-model**: a row from before
20349
+ * the column, and a track the camera's own detector produced, carry none.
20350
+ */
20351
+ modelId: string().optional(),
20352
+ /**
20353
+ * Size (px) of the detection frame this track's `positions[].bbox` live in —
20354
+ * the frame it was born on, else the camera's latest analysis frame at
20355
+ * persist time. Divide a position by these to normalise it. Absent when the
20356
+ * size was never known.
20357
+ */
20358
+ frameWidth: number().optional(),
20359
+ frameHeight: number().optional(),
20765
20360
  ...TrackFlagFields,
20766
20361
  ...TrackRetrainFields
20767
20362
  });
@@ -21768,24 +21363,6 @@ var MediaReclaimStatusSchema = object({
21768
21363
  finishedAtMs: number().nullable(),
21769
21364
  error: string().nullable()
21770
21365
  });
21771
- var ReplayFrameInputSchema = object({
21772
- timestamp: number(),
21773
- frame: PipelineRunResultBridge
21774
- });
21775
- var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
21776
- className: string(),
21777
- firstSeenMs: number(),
21778
- lastSeenMs: number(),
21779
- /** Bbox of the track's FIRST matched detection, pixel-space in the clip's
21780
- * frame — a representative box for the diff's `(className, window, IoU)`
21781
- * pairing (`replay-diff.ts`). A replay does not need the full per-frame
21782
- * trajectory production's `Track.positions` keeps. */
21783
- bbox: BoundingBoxSchema,
21784
- /** How many of the input frames this track matched a real detection on
21785
- * (never a coasted/extrapolated frame) — the replay's own signal for "how
21786
- * solid is this track", cheaper than re-deriving it from a trajectory. */
21787
- framesMatched: number().int()
21788
- })).readonly() });
21789
21366
  /**
21790
21367
  * The long-term series a camera produces. A closed union so a typo cannot
21791
21368
  * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
@@ -21999,108 +21576,570 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21999
21576
  }), {
22000
21577
  kind: "mutation",
22001
21578
  auth: "admin"
22002
- }), method(object({ frameId: string() }), object({
22003
- base64: string(),
22004
- width: number().int(),
22005
- height: number().int()
21579
+ }), method(object({ frameId: string() }), object({
21580
+ base64: string(),
21581
+ width: number().int(),
21582
+ height: number().int()
21583
+ }), {
21584
+ kind: "query",
21585
+ auth: "admin"
21586
+ }), method(object({
21587
+ deviceId: number(),
21588
+ trackId: string(),
21589
+ frameId: string(),
21590
+ subject: RetrainAssistSubjectSchema,
21591
+ /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
21592
+ nodeId: string().optional()
21593
+ }), RetrainAssistResultSchema, {
21594
+ kind: "mutation",
21595
+ auth: "admin"
21596
+ }), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
21597
+ kind: "query",
21598
+ auth: "admin"
21599
+ }), method(object({
21600
+ deviceId: number(),
21601
+ trackId: string(),
21602
+ frameId: string(),
21603
+ annotations: array(RetrainAnnotationDraftSchema)
21604
+ }), array(RetrainAnnotationSchema).readonly(), {
21605
+ kind: "mutation",
21606
+ auth: "admin"
21607
+ }), method(object({
21608
+ deviceId: number(),
21609
+ trackId: string()
21610
+ }), RetrainTransitionResultSchema, {
21611
+ kind: "mutation",
21612
+ auth: "admin"
21613
+ }), method(object({
21614
+ deviceId: number(),
21615
+ trackId: string()
21616
+ }), RetrainTransitionResultSchema, {
21617
+ kind: "mutation",
21618
+ auth: "admin"
21619
+ }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
21620
+ kind: "query",
21621
+ auth: "admin"
21622
+ }), method(object({
21623
+ /** Which of the three event tables owns the row. */
21624
+ ownerType: EventOwnerTypeSchema,
21625
+ /** The event's rowid in that table (D474: an event id is a number). */
21626
+ eventId: number().int(),
21627
+ kind: MediaFileKindEnum.optional(),
21628
+ deviceId: number()
21629
+ }), array(MediaFileSchema).readonly()), method(object({
21630
+ trackId: string(),
21631
+ kinds: array(MediaFileKindEnum).optional(),
21632
+ deviceId: number()
21633
+ }), array(MediaFileSchema).readonly()), method(object({
21634
+ trackId: string(),
21635
+ deviceId: number()
21636
+ }), array(MediaFileInfoSchema).readonly()), method(object({
21637
+ ownerType: EventOwnerTypeSchema,
21638
+ eventId: number().int(),
21639
+ deviceId: number()
21640
+ }), array(MediaFileInfoSchema).readonly()), method(object({
21641
+ deviceId: number(),
21642
+ ownerKind: MediaPresenceOwnerKindSchema,
21643
+ ownerIds: array(string()).max(5e3)
21644
+ }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21645
+ kind: "mutation",
21646
+ auth: "admin"
21647
+ }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
21648
+ kind: "mutation",
21649
+ auth: "admin"
21650
+ }), method(object({}), RebuildStatusSchema), object({
21651
+ deviceId: number(),
21652
+ timestamp: number(),
21653
+ frameWidth: number(),
21654
+ frameHeight: number(),
21655
+ detections: array(OverlayDetectionSchema).readonly()
21656
+ }), object({
21657
+ deviceId: number(),
21658
+ trackId: string(),
21659
+ className: string()
21660
+ }), object({
21661
+ deviceId: number(),
21662
+ trackId: string(),
21663
+ className: string(),
21664
+ durationMs: number()
21665
+ }), object({
21666
+ deviceId: number(),
21667
+ kind: EventKindSchema,
21668
+ eventId: string(),
21669
+ timestamp: number()
21670
+ });
21671
+ /**
21672
+ * Reference to the frame's retained NATIVE surface + the parent crop's placement
21673
+ * within the frame, so the executor can re-cut a leaf child ROI at native
21674
+ * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
21675
+ */
21676
+ var NativeCropRefSchema = object({
21677
+ /** Handle keying the retained native surface (node-pinned to its owner). */
21678
+ handle: FrameHandleSchema,
21679
+ /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
21680
+ cropFrameSpace: object({
21681
+ x: number(),
21682
+ y: number(),
21683
+ w: number(),
21684
+ h: number()
21685
+ })
21686
+ });
21687
+ object({
21688
+ crop: object({
21689
+ left: number(),
21690
+ top: number(),
21691
+ width: number().positive(),
21692
+ height: number().positive()
21693
+ }).optional(),
21694
+ content: object({
21695
+ width: number().int().positive(),
21696
+ height: number().int().positive()
21697
+ }),
21698
+ fit: _enum(["stretch", "contain"]),
21699
+ format: _enum([
21700
+ "rgb",
21701
+ "gray",
21702
+ "jpeg"
21703
+ ])
21704
+ });
21705
+ /**
21706
+ * Process-local frame identity. It is serializable so it can ride an in-process
21707
+ * capability call, but `registryId` deliberately prevents resolution in any
21708
+ * other process or execution group.
21709
+ */
21710
+ var FrameRefSchema = object({
21711
+ registryId: string().min(1),
21712
+ id: string().min(1),
21713
+ width: number().int().positive(),
21714
+ height: number().int().positive(),
21715
+ format: _enum(["rgb", "gray"]),
21716
+ timestamp: number(),
21717
+ capturedAt: number().optional()
21718
+ });
21719
+ var ModelFormatSchema$1 = _enum([
21720
+ "onnx",
21721
+ "coreml",
21722
+ "openvino",
21723
+ "tflite",
21724
+ "pt",
21725
+ "gguf"
21726
+ ]);
21727
+ var PipelineSlotSchema = _enum([
21728
+ "detector",
21729
+ "cropper",
21730
+ "classifier",
21731
+ "refiner",
21732
+ "audio-classifier"
21733
+ ]);
21734
+ var PipelineEngineChoiceSchema = object({
21735
+ runtime: literal("python"),
21736
+ backend: string(),
21737
+ format: ModelFormatSchema$1,
21738
+ device: string().optional()
21739
+ });
21740
+ /**
21741
+ * One pool's resolved provisioning (`getEffectiveTuning`). `numWorkers` is the
21742
+ * knob that lifts a saturated pool (`inference-devices.md`); `concurrency` is
21743
+ * per-worker in-flight requests. `deviceKey` echoes the pool asked about —
21744
+ * `null` for the node-default pool.
21745
+ */
21746
+ var EffectiveTuningSchema = object({
21747
+ deviceKey: string().nullable(),
21748
+ runtime: string(),
21749
+ batchMode: string(),
21750
+ windowMs: number(),
21751
+ maxBatchSize: number(),
21752
+ concurrency: number(),
21753
+ numWorkers: number()
21754
+ });
21755
+ var AvailableEngineSchema = object({
21756
+ engine: PipelineEngineChoiceSchema,
21757
+ devices: array(object({
21758
+ id: string(),
21759
+ label: string(),
21760
+ description: string().optional()
21761
+ })).readonly(),
21762
+ defaultDevice: string()
21763
+ });
21764
+ var PipelineDefaultStepSchema = lazy(() => object({
21765
+ addonId: string(),
21766
+ addonName: string(),
21767
+ slot: PipelineSlotSchema,
21768
+ inputClasses: array(string()).readonly(),
21769
+ outputClasses: array(string()).readonly(),
21770
+ enabled: boolean(),
21771
+ modelId: string(),
21772
+ children: array(PipelineDefaultStepSchema).readonly(),
21773
+ group: string().optional(),
21774
+ settings: record(string(), unknown()).optional()
21775
+ }));
21776
+ var PipelineTemplateStepSchema = lazy(() => object({
21777
+ addonId: string(),
21778
+ enabled: boolean(),
21779
+ modelId: string(),
21780
+ children: array(PipelineTemplateStepSchema).readonly(),
21781
+ settings: record(string(), unknown()).optional()
21782
+ }));
21783
+ var PipelineTemplateSchema$1 = object({
21784
+ id: string(),
21785
+ name: string(),
21786
+ createdAt: string(),
21787
+ updatedAt: string(),
21788
+ engine: PipelineEngineChoiceSchema,
21789
+ steps: array(PipelineTemplateStepSchema).readonly()
21790
+ });
21791
+ var PipelineModelOptionSchema = object({
21792
+ id: string(),
21793
+ name: string(),
21794
+ formats: record(string(), object({
21795
+ downloaded: boolean(),
21796
+ sizeMB: number()
21797
+ })),
21798
+ group: ModelVariantGroupSchema.optional(),
21799
+ legacy: boolean().optional(),
21800
+ provider: ModelProviderIdSchema.optional()
21801
+ });
21802
+ var ConfigFieldBridge = custom();
21803
+ var PipelineAddonSchemaSchema = object({
21804
+ id: string(),
21805
+ name: string(),
21806
+ slot: PipelineSlotSchema,
21807
+ inputClasses: array(string()).readonly(),
21808
+ outputClasses: array(string()).readonly(),
21809
+ childSlots: array(PipelineSlotSchema).readonly(),
21810
+ models: array(PipelineModelOptionSchema).readonly(),
21811
+ defaultModelId: string(),
21812
+ defaultModelIdByFormat: record(string(), string()).optional(),
21813
+ enabledByDefault: boolean().optional(),
21814
+ backfillIntoExistingOverrides: boolean().optional(),
21815
+ defaultConfidence: number(),
21816
+ group: string().optional(),
21817
+ configSchema: array(ConfigFieldBridge).readonly().optional()
21818
+ });
21819
+ var PipelineSlotSchemaSchema = object({
21820
+ id: PipelineSlotSchema,
21821
+ label: string(),
21822
+ priority: number(),
21823
+ parentSlot: PipelineSlotSchema.nullable(),
21824
+ addons: array(PipelineAddonSchemaSchema).readonly()
21825
+ });
21826
+ var PipelineSchemaSchema = object({
21827
+ availableEngines: array(AvailableEngineSchema).readonly(),
21828
+ selectedEngine: PipelineEngineChoiceSchema,
21829
+ slots: array(PipelineSlotSchemaSchema).readonly()
21830
+ });
21831
+ var EngineProvisioningSchema = object({
21832
+ runtimeId: _enum([
21833
+ "onnx",
21834
+ "openvino",
21835
+ "coreml",
21836
+ "edgetpu"
21837
+ ]).nullable(),
21838
+ device: string().nullable(),
21839
+ state: _enum([
21840
+ "idle",
21841
+ "installing",
21842
+ "verifying",
21843
+ "ready",
21844
+ "failed"
21845
+ ]),
21846
+ progress: number().optional(),
21847
+ error: string().optional(),
21848
+ nextRetryAt: number().optional(),
21849
+ /**
21850
+ * Gate A (config-correctness gate at engine change): human-readable
21851
+ * config issues surfaced EAGERLY when the node's engine changes — model
21852
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
21853
+ * has a <format> build"). Additive/optional: informational only, never
21854
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
21855
+ * Absent/empty when the node-default tree resolves cleanly.
21856
+ */
21857
+ configIssues: array(string()).optional()
21858
+ });
21859
+ var PipelineStepInputSchema = lazy(() => object({
21860
+ addonId: string(),
21861
+ modelId: string().optional(),
21862
+ enabled: boolean().default(true),
21863
+ children: array(PipelineStepInputSchema).optional(),
21864
+ settings: record(string(), unknown()).optional(),
21865
+ jumpDeviceKey: string().optional()
21866
+ }));
21867
+ var ModelSubstitutionSchema = object({
21868
+ addonId: string(),
21869
+ chosen: string(),
21870
+ running: string(),
21871
+ format: string()
21872
+ });
21873
+ var PipelineValidationIssueSchema = object({
21874
+ addonId: string(),
21875
+ kind: _enum([
21876
+ "unknown-addon",
21877
+ "no-format-build",
21878
+ "unknown-device"
21879
+ ]),
21880
+ detail: string()
21881
+ });
21882
+ var PipelineValidationResultSchema = object({
21883
+ ok: boolean(),
21884
+ issues: array(PipelineValidationIssueSchema).readonly(),
21885
+ substitutions: array(ModelSubstitutionSchema).readonly(),
21886
+ /** The node's `currentEngine.format` this validation ran against. */
21887
+ format: string()
21888
+ });
21889
+ var ReferenceImageEntrySchema = object({
21890
+ filename: string(),
21891
+ stepIds: array(string()).readonly().optional()
21892
+ });
21893
+ var ReferenceImageBodySchema = object({
21894
+ base64: string(),
21895
+ filename: string()
21896
+ });
21897
+ var ReferenceAudioEntrySchema = object({
21898
+ filename: string(),
21899
+ sizeKb: number()
21900
+ });
21901
+ var ReferenceAudioBodySchema = object({ base64: string() });
21902
+ var AudioBackendSchema = object({
21903
+ id: string(),
21904
+ name: string(),
21905
+ description: string(),
21906
+ available: boolean(),
21907
+ /**
21908
+ * Raw classifier labels this backend can emit (e.g. YAMNet's
21909
+ * 521-class set or Apple SoundAnalysis's 303-class set). Used by
21910
+ * the benchmark UI to populate the `enabledMicroClasses` filter
21911
+ * specific to the selected backend without a separate fetch.
21912
+ */
21913
+ rawLabels: array(string()).readonly().optional()
21914
+ });
21915
+ var AudioCapabilitiesSchema = object({
21916
+ activeBackend: string(),
21917
+ availableBackends: array(AudioBackendSchema).readonly(),
21918
+ sampleRate: number(),
21919
+ chunkDurationMs: number()
21920
+ });
21921
+ var DownloadModelResultSchema = object({
21922
+ filePath: string(),
21923
+ sizeMB: number(),
21924
+ durationMs: number()
21925
+ });
21926
+ /**
21927
+ * Wrapper carrying a single test run's result. Replaces the legacy
21928
+ * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
21929
+ * canonical `AudioResult` from the Phase 6 output rework: one
21930
+ * `AudioDetection` per class above `minScore`, top-N candidates in
21931
+ * `debug.alternateLabels['audio-classifier']`, per-source timings in
21932
+ * `debug.stepTimings`. The outer `success`/`error` fields stay so the
21933
+ * benchmark UI can still report a clean failure when the classifier
21934
+ * cap isn't available.
21935
+ */
21936
+ var AudioTestResultSchema = object({
21937
+ success: boolean(),
21938
+ error: string().optional(),
21939
+ frame: custom().optional()
21940
+ });
21941
+ var PipelineConfigBridge = custom();
21942
+ var ConfigUISchemaBridge = custom();
21943
+ var ConfigUISchemaNullableBridge = custom();
21944
+ var InferenceCapabilitiesBridge = custom();
21945
+ var ModelAvailabilityListBridge = custom();
21946
+ var PipelineRunResultBridge = custom();
21947
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
21948
+ modelId: string(),
21949
+ settings: record(string(), unknown()).readonly()
21950
+ }))), method(object({ steps: record(string(), object({
21951
+ modelId: string(),
21952
+ settings: record(string(), unknown()).readonly()
21953
+ })) }), object({ success: literal(true) }), {
21954
+ kind: "mutation",
21955
+ auth: "admin"
21956
+ }), method(object({ nodeId: string() }), object({
21957
+ success: literal(true),
21958
+ clearedDevices: number()
22006
21959
  }), {
22007
- kind: "query",
22008
- auth: "admin"
22009
- }), method(object({
22010
- deviceId: number(),
22011
- trackId: string(),
22012
- frameId: string(),
22013
- subject: RetrainAssistSubjectSchema,
22014
- /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
22015
- nodeId: string().optional()
22016
- }), RetrainAssistResultSchema, {
22017
- kind: "mutation",
22018
- auth: "admin"
22019
- }), method(object({
22020
- deviceId: number(),
22021
- source: DetectionSourceSchema,
22022
- zones: array(ZoneSchema).readonly().optional(),
22023
- detectionRules: array(ZoneRuleSchema).readonly().optional(),
22024
- zoneMembershipMinOverlap: number().min(0).max(1).optional(),
22025
- frames: array(ReplayFrameInputSchema).min(1)
22026
- }), RunReplayFrameProcessorResultSchema, {
22027
- kind: "mutation",
22028
- auth: "admin"
22029
- }), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
22030
- kind: "query",
22031
- auth: "admin"
22032
- }), method(object({
22033
- deviceId: number(),
22034
- trackId: string(),
22035
- frameId: string(),
22036
- annotations: array(RetrainAnnotationDraftSchema)
22037
- }), array(RetrainAnnotationSchema).readonly(), {
22038
21960
  kind: "mutation",
22039
21961
  auth: "admin"
22040
- }), method(object({
22041
- deviceId: number(),
22042
- trackId: string()
22043
- }), RetrainTransitionResultSchema, {
21962
+ }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
21963
+ /** `<backend>:<device>`, e.g. `openvino:gpu`. */
21964
+ deviceKey: string(),
21965
+ /**
21966
+ * `failed` — the per-device restart budget is exhausted; no pool
21967
+ * will be spawned until an operator re-arms it or the runner
21968
+ * respawns. `backoff` — under budget, waiting out the backoff (or
21969
+ * a cached pool observed dead and not yet condemned).
21970
+ */
21971
+ state: _enum(["failed", "backoff"]),
21972
+ /** Epoch ms of the death that produced this state. */
21973
+ since: number(),
21974
+ /** Pool deaths inside the current window. */
21975
+ deaths: number(),
21976
+ /** The last death's message. */
21977
+ lastError: string()
21978
+ })).readonly() })), method(object({
21979
+ nodeId: string(),
21980
+ deviceKey: string()
21981
+ }), object({ rearmed: boolean() }), {
22044
21982
  kind: "mutation",
22045
21983
  auth: "admin"
22046
- }), method(object({
22047
- deviceId: number(),
22048
- trackId: string()
22049
- }), RetrainTransitionResultSchema, {
21984
+ }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({
21985
+ steps: array(PipelineStepInputSchema),
21986
+ deviceKey: string().optional()
21987
+ }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
21988
+ name: string(),
21989
+ steps: array(PipelineTemplateStepSchema).readonly(),
21990
+ engine: PipelineEngineChoiceSchema
21991
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
21992
+ id: string(),
21993
+ name: string().optional(),
21994
+ steps: array(PipelineTemplateStepSchema).readonly().optional()
21995
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
21996
+ addonId: string(),
21997
+ modelId: string(),
21998
+ format: ModelFormatSchema$1
21999
+ }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
22000
+ addonId: string(),
22001
+ modelId: string(),
22002
+ format: ModelFormatSchema$1
22003
+ }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
22004
+ steps: array(PipelineStepInputSchema).min(1),
22005
+ frame: FrameInputSchema.optional(),
22006
+ /**
22007
+ * Process-local lazy frame. Valid only when caller and provider resolve
22008
+ * in the same execution-group process; split/cross-node callers use
22009
+ * `frame`/`image` inline compatibility instead.
22010
+ */
22011
+ frameRef: FrameRefSchema.optional(),
22012
+ /**
22013
+ * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
22014
+ * the decoded pixels live in. One more member of the one-of
22015
+ * frame/frameHandle/image/imageBase64/referenceImage group.
22016
+ */
22017
+ frameHandle: FrameHandleSchema.optional(),
22018
+ imageBase64: string().optional(),
22019
+ /**
22020
+ * Binary JPEG bytes — preferred over `imageBase64` on internal
22021
+ * hops (hub → forked worker via Moleculer MsgPack) because it
22022
+ * skips the 33% base64 overhead + the per-call base64 decode on
22023
+ * the detection-pipeline worker. Callers can pass either; exactly
22024
+ * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
22025
+ */
22026
+ image: _instanceof(Uint8Array).optional(),
22027
+ referenceImage: string().optional(),
22028
+ deviceId: number().optional(),
22029
+ sessionId: string().optional(),
22030
+ /**
22031
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
22032
+ * reference-image, and detail-subtree calls. 'frame' is the live
22033
+ * per-frame dispatch: ONLY root-plane steps run; crop children
22034
+ * (inputClasses ≠ null) are skipped and served per-track via
22035
+ * pipelineRunner.runDetailSubtree (two-plane design).
22036
+ */
22037
+ plane: _enum(["full", "frame"]).optional(),
22038
+ /**
22039
+ * Inference-device selector (Phase 2 multi-device). Format
22040
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
22041
+ * Omitted ⇒ the runner's default device (current single-engine
22042
+ * behaviour). Selects WHICH device pool of the node runs the call.
22043
+ */
22044
+ deviceKey: string().optional(),
22045
+ /**
22046
+ * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
22047
+ * when the parent crop was resolved from the frame's retained NATIVE
22048
+ * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
22049
+ * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
22050
+ * resolution from that surface — the SAME quality path faces already
22051
+ * had — instead of the downscaled parent tile. `handle` keys the native
22052
+ * surface (node-pinned to its owner); `cropFrameSpace` is the parent
22053
+ * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
22054
+ * the executor's crop-normalized child ROI back into frame-normalized
22055
+ * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
22056
+ * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
22057
+ * (today's behaviour on the fallback path).
22058
+ */
22059
+ nativeCropRef: NativeCropRefSchema.optional(),
22060
+ /**
22061
+ * Offline REPLAY of stored media for `deviceId` (D56, D646): the
22062
+ * camera's zone gate and overrides apply as live, every executing
22063
+ * step runs EXACTLY the model it names on the `deviceKey` pool it
22064
+ * names or the call refuses (`model-not-servable` /
22065
+ * `device-not-servable`), and nothing reaches the camera's live
22066
+ * trace stream. Absent ⇒ unchanged behaviour.
22067
+ */
22068
+ replay: literal(true).optional()
22069
+ }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
22070
+ steps: array(PipelineStepInputSchema).min(1),
22071
+ frames: array(FrameInputSchema).min(1).max(255),
22072
+ deviceId: number().optional(),
22073
+ sessionId: string().optional(),
22074
+ /**
22075
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
22076
+ * the batch to the Python pool's bench preprocess cache
22077
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
22078
+ * preprocessed ONCE and every later inference is a pure-inference cache
22079
+ * hit — the sustained-throughput run measures inference, not
22080
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
22081
+ * full preprocess every call, correct). Fresh per sustained run;
22082
+ * released via `uncacheFrame`.
22083
+ */
22084
+ frameId: number().int().nonnegative().optional(),
22085
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
22086
+ deviceKey: string().optional()
22087
+ }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
22088
+ data: _instanceof(Uint8Array),
22089
+ width: number().int().positive(),
22090
+ height: number().int().positive(),
22091
+ format: _enum([
22092
+ "rgb",
22093
+ "bgr",
22094
+ "gray"
22095
+ ])
22096
+ }), object({
22097
+ frameId: number(),
22098
+ width: number(),
22099
+ height: number()
22100
+ }), { kind: "mutation" }), method(object({
22101
+ stepId: string(),
22102
+ frameId: number().int()
22103
+ }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(object({ deviceKey: string().optional() }), EffectiveTuningSchema), method(_void(), array(object({
22104
+ engineKey: string(),
22105
+ engine: PipelineEngineChoiceSchema,
22106
+ modelsLoaded: array(string()).readonly(),
22107
+ inUseByCameras: array(number()).readonly(),
22108
+ /**
22109
+ * Origin of this resident factory.
22110
+ * - `runtime` — main camera-serving engine (no idle TTL).
22111
+ * - `device-pool` — a concurrent per-device pool (Phase 2
22112
+ * multi-device, keyed by `deviceKey`) resolved
22113
+ * via `resolveDeviceFactory`. Runs alongside the
22114
+ * `runtime` engine on a DIFFERENT accelerator
22115
+ * (NPU / iGPU / Coral) — this is how the
22116
+ * Engines tab shows all pools running at once.
22117
+ */
22118
+ kind: _enum(["runtime", "device-pool"]),
22119
+ /** Native pid of the underlying Python pool (null when no pool). */
22120
+ poolPid: number().nullable(),
22121
+ /** ms since this factory was last used (null when not warm-tracked). */
22122
+ idleMs: number().nullable(),
22123
+ /** Idle TTL after which the factory self-evicts (null when not applicable). */
22124
+ idleTtlMs: number().nullable()
22125
+ })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
22050
22126
  kind: "mutation",
22051
22127
  auth: "admin"
22052
- }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
22053
- kind: "query",
22054
- auth: "admin"
22055
22128
  }), method(object({
22056
- /** Which of the three event tables owns the row. */
22057
- ownerType: EventOwnerTypeSchema,
22058
- /** The event's rowid in that table (D474: an event id is a number). */
22059
- eventId: number().int(),
22060
- kind: MediaFileKindEnum.optional(),
22061
- deviceId: number()
22062
- }), array(MediaFileSchema).readonly()), method(object({
22063
- trackId: string(),
22064
- kinds: array(MediaFileKindEnum).optional(),
22065
- deviceId: number()
22066
- }), array(MediaFileSchema).readonly()), method(object({
22067
- trackId: string(),
22068
- deviceId: number()
22069
- }), array(MediaFileInfoSchema).readonly()), method(object({
22070
- ownerType: EventOwnerTypeSchema,
22071
- eventId: number().int(),
22072
- deviceId: number()
22073
- }), array(MediaFileInfoSchema).readonly()), method(object({
22074
- deviceId: number(),
22075
- ownerKind: MediaPresenceOwnerKindSchema,
22076
- ownerIds: array(string()).max(5e3)
22077
- }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
22078
- kind: "mutation",
22079
- auth: "admin"
22080
- }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
22129
+ engine: PipelineEngineChoiceSchema,
22130
+ force: boolean().optional()
22131
+ }), object({
22132
+ success: boolean(),
22133
+ reason: string().optional()
22134
+ }), {
22081
22135
  kind: "mutation",
22082
22136
  auth: "admin"
22083
- }), method(object({}), RebuildStatusSchema), object({
22084
- deviceId: number(),
22085
- timestamp: number(),
22086
- frameWidth: number(),
22087
- frameHeight: number(),
22088
- detections: array(OverlayDetectionSchema).readonly()
22089
- }), object({
22090
- deviceId: number(),
22091
- trackId: string(),
22092
- className: string()
22093
- }), object({
22094
- deviceId: number(),
22095
- trackId: string(),
22096
- className: string(),
22097
- durationMs: number()
22098
- }), object({
22099
- deviceId: number(),
22100
- kind: EventKindSchema,
22101
- eventId: string(),
22102
- timestamp: number()
22103
- });
22137
+ }), 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({
22138
+ addonId: string(),
22139
+ modelId: string(),
22140
+ filename: string().optional(),
22141
+ settings: record(string(), unknown()).optional()
22142
+ }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
22104
22143
  object({
22105
22144
  activeCameras: number(),
22106
22145
  throttledCameras: number(),
@@ -22126,6 +22165,66 @@ var CameraMetricsSchema = object({
22126
22165
  });
22127
22166
  var CameraMetricsWithDeviceIdSchema = CameraMetricsSchema.extend({ deviceId: number() });
22128
22167
  /**
22168
+ * Zone — pure geometry + identity. NO filtering behaviour.
22169
+ *
22170
+ * Zones describe **where** in the frame the operator wants to flag
22171
+ * something; consumer-owned {@link ZoneRule} arrays describe **how**
22172
+ * each pipeline stage uses them. Splitting the two means a single
22173
+ * polygon "Driveway" can simultaneously back a motion-exclude rule,
22174
+ * a detection-include rule on `['car']`, and an occupancy aggregate
22175
+ * — without three duplicated polygons.
22176
+ *
22177
+ * Owned by the orchestrator addon (provider) and mirrored into the
22178
+ * `zones` device-state slice on every mutation. Consumers
22179
+ * (motion-wasm, pipeline-executor, analytics, admin UI) read either
22180
+ * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
22181
+ * mirror with `onChanged`).
22182
+ *
22183
+ * Coordinates are normalised fractions of the frame (0–1) so zones
22184
+ * survive resolution changes and stream profile switches.
22185
+ *
22186
+ * `kind` discriminates between full polygons (closed regions used
22187
+ * for intrusion / occupancy filters) and tripwires (open 2-point
22188
+ * line segments used for cross events). Onboard / firmware-reported
22189
+ * zones (Reolink, ONVIF) are out of scope for now — see the deferred
22190
+ * task list.
22191
+ */
22192
+ var ZoneKindEnum = _enum(["polygon", "tripwire"]);
22193
+ /** Polygon vertex in fraction-of-frame coordinates (0–1). */
22194
+ var PolygonPointSchema = object({
22195
+ x: number(),
22196
+ y: number()
22197
+ });
22198
+ /** A camera detection zone — pure geometry/identity. */
22199
+ var ZoneSchema = object({
22200
+ id: string(),
22201
+ name: string(),
22202
+ kind: ZoneKindEnum.default("polygon"),
22203
+ /** Polygon vertices, fraction of frame (0–1). */
22204
+ polygon: array(PolygonPointSchema).readonly(),
22205
+ /** Visual color for UI rendering. */
22206
+ color: string().default("#3b82f6")
22207
+ });
22208
+ DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
22209
+ deviceId: number(),
22210
+ zone: ZoneSchema
22211
+ }), _void(), {
22212
+ kind: "mutation",
22213
+ auth: "admin"
22214
+ }), method(object({
22215
+ deviceId: number(),
22216
+ zoneId: string()
22217
+ }), _void(), {
22218
+ kind: "mutation",
22219
+ auth: "admin"
22220
+ }), method(object({
22221
+ deviceId: number(),
22222
+ zone: ZoneSchema
22223
+ }), _void(), {
22224
+ kind: "mutation",
22225
+ auth: "admin"
22226
+ }), object({ zones: array(ZoneSchema).readonly() });
22227
+ /**
22129
22228
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
22130
22229
  * decode worker resolves it against the RETAINED native frame's real pixel dims,
22131
22230
  * so the caller supplies only the detection-res bbox divided by the detection
@@ -22312,7 +22411,17 @@ var DetailResultSchema = object({
22312
22411
  /** Face short side (px) measured on the NATIVE crop surface. The `bbox`
22313
22412
  * above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
22314
22413
  * consumers MUST prefer this when present (2026-07-22 native-gate fix). */
22315
- nativeFaceShortSidePx: number().optional()
22414
+ nativeFaceShortSidePx: number().optional(),
22415
+ /**
22416
+ * The five facial landmarks of a `face` detail, in the SAME frame space as
22417
+ * `bbox` (D644). Additive and optional: post-analysis stores them beside the
22418
+ * face's source frame so a future alignment can be re-derived exactly; a
22419
+ * runner that predates the field sends none and the face is stored without.
22420
+ */
22421
+ landmarks: array(object({
22422
+ x: number(),
22423
+ y: number()
22424
+ })).optional()
22316
22425
  });
22317
22426
  /**
22318
22427
  * Why an executing node REFUSED a stateless step run (`runStatelessStep`).
@@ -23698,7 +23807,11 @@ function buildDescriptor(capName, kind) {
23698
23807
  icon: legacyIcon(t.iconId)
23699
23808
  };
23700
23809
  }
23701
- Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
23810
+ /**
23811
+ * Sensor / control cap name → static event-kind descriptor. A linked device
23812
+ * contributes one entry per bound cap present in this map.
23813
+ */
23814
+ var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
23702
23815
  /**
23703
23816
  * server-management — per-NODE singleton capability for a node's ROOT
23704
23817
  * package lifecycle (runtime-updatable node packages).
@@ -30633,7 +30746,7 @@ var HardwareInfoSchema = object({
30633
30746
  coral: CoralInfoSchema.nullable().optional()
30634
30747
  });
30635
30748
  var PlatformScoreSchema = object({
30636
- runtime: _enum(["node", "python"]),
30749
+ runtime: literal("python"),
30637
30750
  backend: string(),
30638
30751
  format: _enum([
30639
30752
  "onnx",
@@ -30670,7 +30783,7 @@ var ModelRequirementSchema = object({
30670
30783
  });
30671
30784
  var ResolvedInferenceConfigSchema = object({
30672
30785
  modelId: string(),
30673
- runtime: _enum(["node", "python"]),
30786
+ runtime: literal("python"),
30674
30787
  backend: string(),
30675
30788
  format: ModelFormatSchema,
30676
30789
  reason: string()
@@ -32877,6 +32990,92 @@ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSche
32877
32990
  kind: "mutation",
32878
32991
  auth: "admin"
32879
32992
  });
32993
+ /**
32994
+ * Per-stage gating mode applied to the zones a rule references.
32995
+ *
32996
+ * - `include`: the rule contributes to a **whitelist** for its stage.
32997
+ * When at least one `include` rule fires for a stage, only entities
32998
+ * inside one of those zones pass that stage.
32999
+ * - `exclude`: the rule contributes to a **blacklist** for its stage.
33000
+ * Entities inside one of those zones are dropped at that stage.
33001
+ *
33002
+ * `monitor`-style observation (count without filtering) is not a rule
33003
+ * mode — zones without any matching rule are observed naturally by
33004
+ * `zone-analytics` (live snapshot + history), so an "I just want to
33005
+ * count, not filter" use case needs no rule at all.
33006
+ */
33007
+ var ZoneRuleModeEnum = _enum(["include", "exclude"]);
33008
+ /**
33009
+ * Per-consumer rule that references existing zones (geometry) and
33010
+ * defines how a specific pipeline stage should treat them. Each
33011
+ * consumer addon owns its own `ZoneRule[]` array in its per-device
33012
+ * settings:
33013
+ *
33014
+ * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
33015
+ * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
33016
+ * - future: notification rules, audio gating, etc.
33017
+ *
33018
+ * One rule applies to N zones (`zoneIds[]`) so the operator can
33019
+ * express "ignore motion in ALL of {garden, street}" with a single
33020
+ * rule. `classFilter` narrows the rule to specific object classes —
33021
+ * "drop person detections in the street, but keep cars" is one
33022
+ * `exclude` rule with `classFilter: ['person']`.
33023
+ *
33024
+ * `enabled` is a soft toggle — the operator can keep the rule
33025
+ * configured but inert without deleting it.
33026
+ */
33027
+ var ZoneRuleSchema = object({
33028
+ /** Stable rule id — survives edits, used by the UI for diffing. */
33029
+ id: string(),
33030
+ /** Optional human-readable label rendered in the rule editor. */
33031
+ name: string().optional(),
33032
+ /** Zones this rule targets. The rule's `mode` applies to ALL
33033
+ * listed zones (OR-set: a detection in any one of them counts).
33034
+ * At least one zone id required — a rule with no targets is a
33035
+ * configuration mistake and the form validator rejects it. */
33036
+ zoneIds: array(string()).min(1).readonly(),
33037
+ mode: ZoneRuleModeEnum,
33038
+ /**
33039
+ * Class names this rule applies to. Empty / undefined ⇒ rule
33040
+ * applies to every class. Class strings match the `macroClass`
33041
+ * field on detections (e.g. `person`, `car`, `dog`).
33042
+ */
33043
+ classFilter: array(string()).readonly().optional(),
33044
+ /**
33045
+ * Minimum bbox/mask overlap (0–1) with any of the rule's zones
33046
+ * required to consider an entity "in the zone". Defaults to the
33047
+ * consumer's stage default when omitted. Kept for back-compat with
33048
+ * existing per-rule overrides; new operators pick the value via
33049
+ * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
33050
+ * set, the lower-level engine reads it as a 0–1 fraction.
33051
+ */
33052
+ overlapThreshold: number().min(0).max(1).optional(),
33053
+ /**
33054
+ * Operator-friendly version of `overlapThreshold` — the percentage
33055
+ * of the detection's bbox that must lie inside the zone for the
33056
+ * rule to match. Documented default is 85%; the engine substitutes
33057
+ * that when the field is omitted (kept optional so existing rules
33058
+ * stored without it stay valid).
33059
+ *
33060
+ * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
33061
+ * rule, the engine prefers `bboxInclusionPct` because it's the
33062
+ * field exposed in the UI. Internally both feed the same gate.
33063
+ */
33064
+ bboxInclusionPct: number().min(0).max(100).optional(),
33065
+ /**
33066
+ * When `true` and a detection has a segmentation mask, use the
33067
+ * mask for overlap instead of the bbox. Detection-stage only;
33068
+ * motion rules ignore this field.
33069
+ */
33070
+ preferMask: boolean().optional(),
33071
+ /**
33072
+ * Soft-toggle: `false` disables the rule without deleting it.
33073
+ * Defaults to `true` so operators creating a rule via the UI
33074
+ * see it active immediately.
33075
+ */
33076
+ enabled: boolean().default(true)
33077
+ });
33078
+ array(ZoneRuleSchema).readonly();
32880
33079
  object({
32881
33080
  /** Whether the script is currently executing. */
32882
33081
  isRunning: boolean(),
@@ -34258,6 +34457,42 @@ DeviceType.Camera, method(object({
34258
34457
  package: array(ZoneRuleSchema).readonly()
34259
34458
  });
34260
34459
  /**
34460
+ * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
34461
+ * valutatore puro del suo FRONTE.
34462
+ *
34463
+ * Viveva dentro il builtin virtual-doorbell
34464
+ * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
34465
+ * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
34466
+ * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
34467
+ * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
34468
+ * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
34469
+ * SPOSTA qui e il doorbell la ri-esporta.
34470
+ *
34471
+ * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
34472
+ * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
34473
+ * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
34474
+ * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
34475
+ */
34476
+ /**
34477
+ * Known binary / switch source caps → the boolean slice field whose
34478
+ * false→true rise counts as ACTIVE. Every entry is "fire on active".
34479
+ * Sensors whose "active" reading is not a plain boolean (presence's string
34480
+ * state, connectivity's connected flag) are deliberately excluded — a
34481
+ * reconnect is not a doorbell press, and it is not a recording either.
34482
+ */
34483
+ var SOURCE_CAP_ACTIVE_FIELD = {
34484
+ contact: "entryOpen",
34485
+ binary: "on",
34486
+ switch: "on",
34487
+ motion: "detected",
34488
+ flood: "flooded",
34489
+ gas: "detected",
34490
+ smoke: "detected",
34491
+ "carbon-monoxide": "detected",
34492
+ vibration: "detected",
34493
+ tamper: "tampered"
34494
+ };
34495
+ /**
34261
34496
  * Accessory device helpers — shared across drivers.
34262
34497
  *
34263
34498
  * Many vendor-specific drivers register accessory child devices on
@@ -39163,6 +39398,12 @@ Object.freeze({
39163
39398
  addonId: null,
39164
39399
  access: "view"
39165
39400
  },
39401
+ "notificationRules.getRuleLastRuns": {
39402
+ capName: "notification-rules",
39403
+ capScope: "system",
39404
+ addonId: null,
39405
+ access: "view"
39406
+ },
39166
39407
  "notificationRules.getTemplateCatalog": {
39167
39408
  capName: "notification-rules",
39168
39409
  capScope: "system",
@@ -39187,6 +39428,12 @@ Object.freeze({
39187
39428
  addonId: null,
39188
39429
  access: "view"
39189
39430
  },
39431
+ "notificationRules.previewStateSummary": {
39432
+ capName: "notification-rules",
39433
+ capScope: "system",
39434
+ addonId: null,
39435
+ access: "view"
39436
+ },
39190
39437
  "notificationRules.previewTemplate": {
39191
39438
  capName: "notification-rules",
39192
39439
  capScope: "system",
@@ -39763,12 +40010,6 @@ Object.freeze({
39763
40010
  addonId: null,
39764
40011
  access: "create"
39765
40012
  },
39766
- "pipelineAnalytics.runReplayFrameProcessor": {
39767
- capName: "pipeline-analytics",
39768
- capScope: "device",
39769
- addonId: null,
39770
- access: "create"
39771
- },
39772
40013
  "pipelineAnalytics.saveRetrainAnnotations": {
39773
40014
  capName: "pipeline-analytics",
39774
40015
  capScope: "device",
@@ -43866,11 +44107,6 @@ Object.freeze({
43866
44107
  form: "single",
43867
44108
  optional: false
43868
44109
  }],
43869
- "pipelineAnalytics.runReplayFrameProcessor": [{
43870
- name: "deviceId",
43871
- form: "single",
43872
- optional: false
43873
- }],
43874
44110
  "pipelineAnalytics.saveRetrainAnnotations": [{
43875
44111
  name: "deviceId",
43876
44112
  form: "single",
@@ -44759,12 +44995,55 @@ var NC_AUDIO_DEFAULTS = {
44759
44995
  samplingSeconds: 10
44760
44996
  };
44761
44997
  new Set(["devices", "classes"]);
44762
- /** Every filter, for the callers that must cover all four (guards, tests). */
44998
+ /**
44999
+ * WHICH narrowing filter speaks about WHICH system-event kind — one predicate,
45000
+ * consumed by the rule ENGINE and by every rule EDITOR.
45001
+ *
45002
+ * ── The failure this file exists for ───────────────────────────────────────
45003
+ * `NcSystemEventConditionSchema` carries four optional narrowing lists
45004
+ * (`deviceIds`, `deviceTypes`, `nodeIds`, `packageNames`) beside a required
45005
+ * `kinds` gate. They are not four filters over one vocabulary: each belongs to
45006
+ * a FAMILY of kinds and is meaningless outside it. A node going away has no
45007
+ * device type; a completed export has no package; `stream-offline` deliberately
45008
+ * carries no device type at all (a stream belongs to a camera by construction).
45009
+ *
45010
+ * The engine applied all four to every kind, so a filter from one family failed
45011
+ * a kind from another family CLOSED. The operator's live rule "Eventi di
45012
+ * sistema" selected eleven kinds and set `deviceTypes: ['camera']` — the only
45013
+ * way to say "only my cameras" about the two liveness kinds — and NINE of the
45014
+ * eleven silently stopped being deliverable. Four days of history contained
45015
+ * `device-online` and `device-offline` and nothing else, across a window in
45016
+ * which agents were restarted and addons were updated.
45017
+ *
45018
+ * Both editors already computed this scoping, to decide which filter boxes to
45019
+ * render; the cap's own docblock already promised it ("Narrow a `device-*` kind
45020
+ * to these device TYPES"). Only the engine disagreed, and it disagreed in the
45021
+ * destructive direction. So the rule is stated ONCE, here, and the engine and
45022
+ * the editors are readers of it rather than three independent authors.
45023
+ *
45024
+ * ── The direction ─────────────────────────────────────────────────────────
45025
+ * Out of family ⇒ the filter is IGNORED, never fail-closed. An editor that
45026
+ * hides a box has already told the operator this filter does not speak about
45027
+ * these kinds, so honouring it as an exclusion contradicts the only
45028
+ * explanation the operator was ever given. Inside its family the filter still
45029
+ * narrows, and still fails CLOSED on a subject that cannot produce the identity
45030
+ * it tests (a device the mirror cannot name is not evidence of a camera).
45031
+ */
45032
+ /**
45033
+ * Every filter, for the callers that must cover all of them (guards, tests).
45034
+ *
45035
+ * `consumableLowPercent` (D642) is not a list but it is a filter in exactly
45036
+ * this sense: it belongs to a family of kinds (the consumable pair) and the
45037
+ * engine consults it only there. It differs in one way the readers must know —
45038
+ * its ABSENCE is not "no narrowing" but the default threshold, because a
45039
+ * consumable episode is always judged at SOME threshold.
45040
+ */
44763
45041
  var NC_SYSTEM_EVENT_FILTER_KEYS = [
44764
45042
  "deviceIds",
44765
45043
  "deviceTypes",
44766
45044
  "nodeIds",
44767
- "packageNames"
45045
+ "packageNames",
45046
+ "consumableLowPercent"
44768
45047
  ];
44769
45048
  new Map(Object.entries({
44770
45049
  "device-online": "device-events",
@@ -44775,6 +45054,9 @@ new Map(Object.entries({
44775
45054
  "device-battery-normal": "device-events",
44776
45055
  "device-consumable-low": "device-events",
44777
45056
  "device-consumable-normal": "device-events",
45057
+ "device-tank-low": "device-events",
45058
+ "device-tank-full": "device-events",
45059
+ "device-tank-normal": "device-events",
44778
45060
  "stream-online": "device-events",
44779
45061
  "stream-offline": "device-events",
44780
45062
  "detection-blind": "device-events",
@@ -44804,11 +45086,141 @@ new Map(Object.entries({
44804
45086
  "camera-enabled": null
44805
45087
  }));
44806
45088
  [...NC_SYSTEM_EVENT_FILTER_KEYS];
45089
+ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
45090
+ {
45091
+ id: "device-state",
45092
+ family: "device-events",
45093
+ label: "Device state",
45094
+ kinds: [
45095
+ "device-offline",
45096
+ "device-online",
45097
+ "device-disabled",
45098
+ "device-enabled"
45099
+ ]
45100
+ },
45101
+ {
45102
+ id: "streams",
45103
+ family: "device-events",
45104
+ label: "Streams",
45105
+ kinds: ["stream-offline", "stream-online"]
45106
+ },
45107
+ {
45108
+ id: "battery",
45109
+ family: "device-events",
45110
+ label: "Battery",
45111
+ kinds: ["device-battery-low", "device-battery-normal"]
45112
+ },
45113
+ {
45114
+ id: "consumables",
45115
+ family: "device-events",
45116
+ label: "Consumables",
45117
+ kinds: ["device-consumable-low", "device-consumable-normal"]
45118
+ },
45119
+ {
45120
+ id: "tanks",
45121
+ family: "device-events",
45122
+ label: "Tanks",
45123
+ kinds: [
45124
+ "device-tank-low",
45125
+ "device-tank-full",
45126
+ "device-tank-normal"
45127
+ ]
45128
+ },
45129
+ {
45130
+ id: "detection-blind",
45131
+ family: "device-events",
45132
+ label: "Detecting nothing",
45133
+ kinds: ["detection-blind"]
45134
+ },
45135
+ {
45136
+ id: "nodes",
45137
+ family: "system",
45138
+ label: "Nodes",
45139
+ kinds: [
45140
+ "node-offline",
45141
+ "node-online",
45142
+ "node-inference-unavailable"
45143
+ ]
45144
+ },
45145
+ {
45146
+ id: "addons",
45147
+ family: "system",
45148
+ label: "Addons",
45149
+ kinds: [
45150
+ "addon-crash-loop",
45151
+ "addon-update-available",
45152
+ "addon-updated"
45153
+ ]
45154
+ },
45155
+ {
45156
+ id: "updates",
45157
+ family: "system",
45158
+ label: "Server & container updates",
45159
+ kinds: [
45160
+ "server-update-available",
45161
+ "server-updated",
45162
+ "wrapper-update-available"
45163
+ ]
45164
+ },
45165
+ {
45166
+ id: "backups-exports",
45167
+ family: "system",
45168
+ label: "Backups & exports",
45169
+ kinds: ["backup-completed", "export-completed"]
45170
+ },
45171
+ {
45172
+ id: "alarm",
45173
+ family: "alarm",
45174
+ label: "Alarm",
45175
+ kinds: NC_ALARM_SYSTEM_EVENT_KINDS
45176
+ }
45177
+ ];
45178
+ function systemEventKindsOfFamily(family) {
45179
+ return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
45180
+ }
44807
45181
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
45182
+ /**
45183
+ * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
45184
+ * persists (D640 "a sensor rule fires on the raise, the clear, or both").
45185
+ *
45186
+ * A device-event rule over a flood sensor notified on BOTH edges: water
45187
+ * detected, and back to dry. `NcConditions.sensorTransition` lets the rule
45188
+ * name one edge; this file answers, for one `SensorEvent`, which edge it is.
45189
+ *
45190
+ * NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
45191
+ * already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
45192
+ * (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
45193
+ * table), and a sensor event names its kind, not its cap. So the table here is
45194
+ * DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
45195
+ * Only kinds whose taxonomy category is `sensor` take part — a switch has an
45196
+ * `on` boolean too, but "it triggers / it clears" is not a sentence about a
45197
+ * light, and offering it there would be a knob with a misleading label.
45198
+ * `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
45199
+ * absent from the source table, so it is absent here.
45200
+ *
45201
+ * `undefined` = never guessed: a kind with no raise (a doorbell press, a
45202
+ * button, a cover), or a slice whose field is missing or not a boolean. The
45203
+ * engine then does NOT filter — a flood alert must not go silent because one
45204
+ * read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
45205
+ *
45206
+ * Pure, RN-safe: the viewer imports the support rule from here.
45207
+ */
45208
+ /** Event kind → the boolean field of its cap slice that means "raised". */
45209
+ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
45210
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
45211
+ if (field === void 0 || descriptor.category !== "sensor") return [];
45212
+ return [[descriptor.kind, field]];
45213
+ })));
45214
+ Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
44808
45215
  var RULE_BODY_ONLY = ["body"];
44809
45216
  var RULE_TEXT = ["title", "body"];
44810
45217
  var BATTERY = ["device-battery-low", "device-battery-normal"];
44811
45218
  var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
45219
+ var TANK = [
45220
+ "device-tank-low",
45221
+ "device-tank-full",
45222
+ "device-tank-normal"
45223
+ ];
44812
45224
  var UPDATES = [
44813
45225
  "addon-update-available",
44814
45226
  "server-update-available",
@@ -44834,12 +45246,189 @@ var DEVICE_EVENTS = [
44834
45246
  "device-enabled",
44835
45247
  ...BATTERY,
44836
45248
  ...CONSUMABLE,
45249
+ ...TANK,
44837
45250
  "stream-online",
44838
45251
  "stream-offline",
44839
45252
  "detection-blind",
44840
45253
  ...ALARM
44841
45254
  ];
44842
- [...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];
45255
+ /** Every kind a summary may speak about — the two summarisable families. */
45256
+ var SUMMARISABLE = [...systemEventKindsOfFamily("device-events"), ...systemEventKindsOfFamily("system")];
45257
+ /** One per summary SECTION (`NC_STATE_SUMMARY_SECTIONS` in the notification
45258
+ * centre): the section's lines, offered when one of its kinds is selected. */
45259
+ var STATE_SUMMARY_SECTION_VARS = [
45260
+ {
45261
+ name: "summaryDevices",
45262
+ label: "Summary: devices offline / switched off",
45263
+ example: "Garage — offline since 07:12",
45264
+ kinds: [
45265
+ "device-offline",
45266
+ "device-online",
45267
+ "device-disabled",
45268
+ "device-enabled"
45269
+ ]
45270
+ },
45271
+ {
45272
+ name: "summaryStreams",
45273
+ label: "Summary: streams offline",
45274
+ example: "Ingresso (high) — since 06:40",
45275
+ kinds: ["stream-offline", "stream-online"]
45276
+ },
45277
+ {
45278
+ name: "summaryBattery",
45279
+ label: "Summary: low batteries",
45280
+ example: "Sensore porta — 8 %",
45281
+ kinds: BATTERY
45282
+ },
45283
+ {
45284
+ name: "summaryConsumables",
45285
+ label: "Summary: consumables",
45286
+ example: "Robot — filter 10 %",
45287
+ kinds: CONSUMABLE
45288
+ },
45289
+ {
45290
+ name: "summaryTanks",
45291
+ label: "Summary: tanks",
45292
+ example: "Robot — clean water low",
45293
+ kinds: TANK
45294
+ },
45295
+ {
45296
+ name: "summaryDetection",
45297
+ label: "Summary: cameras detecting nothing",
45298
+ example: "Ingresso — since 05:10",
45299
+ kinds: ["detection-blind"]
45300
+ },
45301
+ {
45302
+ name: "summaryNodes",
45303
+ label: "Summary: nodes",
45304
+ example: "little-unraid — offline since 03:00",
45305
+ kinds: [
45306
+ "node-offline",
45307
+ "node-online",
45308
+ "node-inference-unavailable"
45309
+ ]
45310
+ },
45311
+ {
45312
+ name: "summaryAddons",
45313
+ label: "Summary: addons",
45314
+ example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
45315
+ kinds: [
45316
+ "addon-crash-loop",
45317
+ "addon-update-available",
45318
+ "addon-updated"
45319
+ ]
45320
+ },
45321
+ {
45322
+ name: "summaryUpdates",
45323
+ label: "Summary: server & container updates",
45324
+ example: "server 1.2.377 → 1.2.378",
45325
+ kinds: [
45326
+ "server-update-available",
45327
+ "server-updated",
45328
+ "wrapper-update-available"
45329
+ ]
45330
+ }
45331
+ ];
45332
+ var STATE_SUMMARY_VARS = [
45333
+ {
45334
+ name: "summaryText",
45335
+ label: "Summary: every section",
45336
+ description: "One block per section with something to report, one line per item.",
45337
+ example: "Batterie scariche: Sensore porta — 8 %",
45338
+ group: "system",
45339
+ families: ["rule"],
45340
+ fields: [...RULE_TEXT],
45341
+ kinds: ["system"],
45342
+ systemEventKinds: [...SUMMARISABLE],
45343
+ systemEventDeliveries: ["summary"]
45344
+ },
45345
+ {
45346
+ name: "summaryCount",
45347
+ label: "Summary: items reported",
45348
+ example: "3",
45349
+ group: "system",
45350
+ families: ["rule"],
45351
+ fields: [...RULE_TEXT],
45352
+ kinds: ["system"],
45353
+ systemEventKinds: [...SUMMARISABLE],
45354
+ systemEventDeliveries: ["summary"]
45355
+ },
45356
+ ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
45357
+ name: v.name,
45358
+ label: v.label,
45359
+ example: v.example,
45360
+ group: "system",
45361
+ families: ["rule"],
45362
+ fields: [...RULE_TEXT],
45363
+ kinds: ["system"],
45364
+ systemEventKinds: [...v.kinds],
45365
+ systemEventDeliveries: ["summary"]
45366
+ }))
45367
+ ];
45368
+ [
45369
+ [...DEVICE_EVENTS],
45370
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45371
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45372
+ [...RULE_TEXT],
45373
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45374
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45375
+ [...RULE_TEXT],
45376
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45377
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45378
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
45379
+ [...RULE_TEXT],
45380
+ [...RULE_TEXT],
45381
+ [...RULE_TEXT],
45382
+ [...RULE_TEXT],
45383
+ [...RULE_TEXT],
45384
+ [...RULE_TEXT],
45385
+ [...RULE_TEXT],
45386
+ [...RULE_TEXT],
45387
+ [...RULE_BODY_ONLY],
45388
+ [...RULE_TEXT],
45389
+ ([...RULE_TEXT], [...DEVICE_EVENTS]),
45390
+ ([...RULE_TEXT], [...DEVICE_EVENTS]),
45391
+ ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
45392
+ ([...RULE_TEXT], [...CONSUMABLE]),
45393
+ ([...RULE_TEXT], [...TANK]),
45394
+ [...RULE_TEXT],
45395
+ ([...RULE_TEXT], [...UPDATES]),
45396
+ ([...RULE_TEXT], [...UPDATES]),
45397
+ [...RULE_TEXT],
45398
+ [...RULE_TEXT],
45399
+ [...RULE_TEXT],
45400
+ ([...RULE_TEXT], [...UPDATES]),
45401
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45402
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45403
+ ([...RULE_TEXT], [...UPDATES]),
45404
+ ([...RULE_TEXT], [...UPDATES]),
45405
+ ([...RULE_TEXT], [...PACKAGE_UPDATES]),
45406
+ [...RULE_TEXT],
45407
+ [...RULE_TEXT],
45408
+ [...RULE_TEXT],
45409
+ [...RULE_TEXT],
45410
+ [...RULE_TEXT],
45411
+ [...RULE_TEXT],
45412
+ [...RULE_TEXT],
45413
+ [...RULE_TEXT],
45414
+ [...RULE_TEXT],
45415
+ [...RULE_TEXT],
45416
+ [...RULE_TEXT],
45417
+ [...RULE_TEXT],
45418
+ [...RULE_TEXT],
45419
+ [...RULE_TEXT],
45420
+ [...RULE_TEXT],
45421
+ [...RULE_TEXT],
45422
+ [...RULE_TEXT],
45423
+ [...RULE_TEXT],
45424
+ [...RULE_TEXT],
45425
+ [...RULE_TEXT],
45426
+ [...RULE_TEXT],
45427
+ [...RULE_TEXT],
45428
+ [...RULE_TEXT],
45429
+ [...RULE_TEXT],
45430
+ ...STATE_SUMMARY_VARS
45431
+ ];
44843
45432
  /**
44844
45433
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
44845
45434
  *