@camstack/addon-mqtt-broker 1.2.116 → 1.2.117

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.
@@ -44,7 +44,7 @@ let crypto$10 = __toESM(crypto$1, 1);
44
44
  crypto$1 = __toESM(crypto$1);
45
45
  let node_fs = require("node:fs");
46
46
  let node_path = require("node:path");
47
- //#region ../types/dist/event-category-C5xZWqz6.mjs
47
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
48
48
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
49
49
  EventCategory["SystemBoot"] = "system.boot";
50
50
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -577,6 +577,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
577
577
  */
578
578
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
579
579
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
580
+ /**
581
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
582
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
583
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
584
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
585
+ * for a camera with no attached sensor). Providers must not emit
586
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
587
+ * keeps it so (camera-grid is a named, deferred exception).
588
+ */
580
589
  EventCategory["ProviderMotion"] = "provider.motion";
581
590
  EventCategory["ProviderDetection"] = "provider.detection";
582
591
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5415,7 +5424,7 @@ var ZodIssueCode = {
5415
5424
  var ZodFirstPartyTypeKind;
5416
5425
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5417
5426
  //#endregion
5418
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5427
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5419
5428
  /**
5420
5429
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5421
5430
  * window to float samples (D455).
@@ -7444,7 +7453,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
7444
7453
  return DeviceRole;
7445
7454
  }({});
7446
7455
  //#endregion
7447
- //#region ../types/dist/composition-Dq42XWp0.mjs
7456
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7448
7457
  /**
7449
7458
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7450
7459
  *
@@ -7498,21 +7507,20 @@ function portable(target) {
7498
7507
  }
7499
7508
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7500
7509
  /**
7501
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7502
- * valutatore puro del suo FRONTE.
7510
+ * THE table "which boolean of this device type counts as HIGH", and the
7511
+ * pure evaluator of its EDGE.
7503
7512
  *
7504
- * Viveva dentro il builtin virtual-doorbell
7505
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7506
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7507
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7508
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7509
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7510
- * SPOSTA qui e il doorbell la ri-esporta.
7513
+ * ONE table for everyone who asks "is this device HIGH?": the
7514
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7515
+ * sensor markers, the orchestrator's camera-sensors bridge and
7516
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7517
+ * A second copy would diverge at the first cap added, and the symptom — "the
7518
+ * sensor rings the doorbell but does not record" — is exactly D62.
7511
7519
  *
7512
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7513
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7514
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7515
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7520
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7521
+ * two must not be unified: that one answers "what is the state WORD for a
7522
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7523
+ * boolean's EDGE counts". Deliberately different vocabularies.
7516
7524
  */
7517
7525
  /**
7518
7526
  * Known binary / switch source caps → the boolean slice field whose
@@ -7533,6 +7541,8 @@ var SOURCE_CAP_ACTIVE_FIELD = {
7533
7541
  vibration: "detected",
7534
7542
  tamper: "tampered"
7535
7543
  };
7544
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7545
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7536
7546
  /**
7537
7547
  * Error types for the safe expression engine. Two distinct classes so callers
7538
7548
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -9119,22 +9129,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9119
9129
  "package"
9120
9130
  ];
9121
9131
  /**
9122
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9123
- * un operatore può selezionare.
9132
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9133
+ * an operator can select.
9124
9134
  *
9125
- * Sono le tre offerte dallo step `object-detection`
9135
+ * They are the three offered by the `object-detection` step
9126
9136
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9127
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9128
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9129
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9130
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9137
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9138
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9139
+ * `package-detection`, whose enablement is driven by zones, and offering the
9140
+ * same word twice has already made an operator turn on the COCO proxy
9131
9141
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9132
9142
  *
9133
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9134
- * dello step e una seconda volta come union `FirstLevelMacro`
9135
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9136
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9137
- * successiva.
9143
+ * ONE list. Before today the same three were hand-written in the step's offer
9144
+ * and a second time as the union `FirstLevelMacro`
9145
+ * (`types/detection.ts`); a third copy for the recording trigger
9146
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9147
+ * invisible.
9138
9148
  */
9139
9149
  var FIRST_LEVEL_MACRO_CLASSES = [
9140
9150
  "person",
@@ -9199,17 +9209,17 @@ var RecordingStorageModeSchema = _enum([
9199
9209
  "on-device-decision"
9200
9210
  ]);
9201
9211
  /**
9202
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9203
- * tre offerte dallo step `object-detection`, da UNA lista
9212
+ * The macro classes that can open a recording window — the same
9213
+ * three offered by the `object-detection` step, from ONE list
9204
9214
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9205
9215
  */
9206
9216
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9207
9217
  /**
9208
- * True quando `values` non ripete un elemento.
9218
+ * True when `values` does not repeat an element.
9209
9219
  *
9210
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9211
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9212
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9220
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9221
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9222
+ * would count its windows twice in `segmentMissedByMs`.
9213
9223
  */
9214
9224
  var noDuplicates = (values) => new Set(values).size === values.length;
9215
9225
  /** Which detectors trigger an `events`-mode band. */
@@ -9217,34 +9227,34 @@ var RecordingTriggersSchema = object({
9217
9227
  motion: boolean().optional(),
9218
9228
  audioThresholdDbfs: number().optional(),
9219
9229
  /**
9220
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9221
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9222
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9223
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9230
+ * The macro classes whose detection opens a window. ABSENT = the source
9231
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9232
+ * trigger on, no class" is the same shape "enabled and records
9233
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9224
9234
  *
9225
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9226
- * quelle che hanno attraversato `enabledMacroClasses`, i
9227
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9228
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9229
- * finestre — vedi `recorder/object-trigger.ts`.
9235
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9236
+ * those that went through `enabledMacroClasses`, the
9237
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9238
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9239
+ * windows — see `recorder/object-trigger.ts`.
9230
9240
  */
9231
9241
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9232
9242
  /**
9233
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9234
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9243
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9244
+ * is not listened to; an empty array is rejected for the same reason as
9235
9245
  * `objectClasses`.
9236
9246
  *
9237
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9238
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9239
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9240
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9241
- * device (D12) — mai un elenco globale di cap.
9247
+ * These are SOURCE device ids, not cameras: the band names them, so the
9248
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9249
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9250
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9251
+ * device (D12) — never a global list of caps.
9242
9252
  *
9243
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9244
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9245
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9246
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9247
- * registrare.
9253
+ * What "high" means depends on the device TYPE and is not decided here:
9254
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9255
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9256
+ * the level: a contact found already open at runner restart does not
9257
+ * record.
9248
9258
  */
9249
9259
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9250
9260
  });
@@ -10321,7 +10331,7 @@ var DecoderSessionConfigSchema = object({
10321
10331
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10322
10332
  * thing: the logging settings document on the `system` cap. Two authorities
10323
10333
  * over the values is the exact defect
10324
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10334
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10325
10335
  * remove; re-introducing it from the cure side would be grotesque.
10326
10336
  *
10327
10337
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -12032,7 +12042,7 @@ var BrokerStatusEnum = _enum([
12032
12042
  "unreachable",
12033
12043
  "error"
12034
12044
  ]);
12035
- var BrokerInfoSchema$1 = object({
12045
+ var BrokerInfoSchema = object({
12036
12046
  /** Stable broker id. Persisted; survives addon restarts. */
12037
12047
  id: string(),
12038
12048
  /** Addon id of the provider that OWNS this broker.
@@ -12175,8 +12185,8 @@ var brokerCapability = {
12175
12185
  kind: "poll"
12176
12186
  },
12177
12187
  methods: {
12178
- list: method(ListInputSchema, array(BrokerInfoSchema$1)),
12179
- get: method(GetInputSchema, BrokerInfoSchema$1.nullable()),
12188
+ list: method(ListInputSchema, array(BrokerInfoSchema)),
12189
+ get: method(GetInputSchema, BrokerInfoSchema.nullable()),
12180
12190
  /** Enumerate which addon provides which broker kind(s) for the
12181
12191
  * unified create picker. The auto-mount fans this array across
12182
12192
  * every registered `broker` provider (array-output method), so the
@@ -13823,7 +13833,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13823
13833
  *
13824
13834
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13825
13835
  * was wrong — corrected 2026-08-26 after the hop map
13826
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13836
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13827
13837
  * There is **no Zod parse at all** between the door and the engine: the
13828
13838
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13829
13839
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14579,7 +14589,17 @@ var SiteZoneSchema = object({
14579
14589
  var ProviderStatusSchema = object({
14580
14590
  connected: boolean(),
14581
14591
  deviceCount: number(),
14582
- error: string().optional()
14592
+ error: string().optional(),
14593
+ /**
14594
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14595
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14596
+ * which every provider bundles, so only a provider BUILT from D696-amended
14597
+ * code says it. ABSENT means an older provider that may still write it (raw
14598
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14599
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14600
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14601
+ */
14602
+ motionSliceWriter: literal("orchestrator").optional()
14583
14603
  });
14584
14604
  object({
14585
14605
  externalId: string(),
@@ -15377,28 +15397,10 @@ method(object({
15377
15397
  deviceIds: array(number()).readonly(),
15378
15398
  caps: array(string()).readonly().optional()
15379
15399
  }), record(string(), record(string(), unknown().nullable())));
15380
- object({
15381
- /** Ms epoch of the last press. null = never observed since this provider started. */
15382
- lastPressedAt: number().nullable(),
15383
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15384
- pressCountSinceStart: number()
15385
- });
15386
- object({
15387
- deviceId: number(),
15388
- timestamp: number()
15389
- });
15390
- /**
15391
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15392
- * virtual-doorbell trigger devices, exactly what the wrapper's
15393
- * `doorbellSources` persists (as `{ deviceId }` records).
15394
- */
15395
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15400
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15396
15401
  DeviceType.Camera;
15397
- DeviceType.Button, DeviceType.Camera;
15398
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15399
- DeviceType.Camera, DeviceType.Camera;
15400
15402
  /** A slot's editable value, discriminated on `slot`. */
15401
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15403
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15402
15404
  DeviceType.Camera;
15403
15405
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15404
15406
  state: literal("unreachable"),
@@ -15435,10 +15437,11 @@ var ExtensionSlotSourceSchema = object({
15435
15437
  });
15436
15438
  /**
15437
15439
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15438
- * reported read-only, named, never written through this contract (D62). The
15439
- * virtual doorbell's sources ring only while the wrapper is bound to the
15440
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15441
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15440
+ * reported read-only, named, never written through this contract (D62) — e.g.
15441
+ * a slot whose sources act only while a wrapper is bound to the host, a
15442
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15443
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15444
+ * not be read; it is not "inactive" (D315).
15442
15445
  */
15443
15446
  var ExtensionSlotActivationSchema = object({
15444
15447
  state: _enum([
@@ -15454,7 +15457,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15454
15457
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15455
15458
  slotRef: string(),
15456
15459
  slot: ExtensionSlotIdSchema,
15457
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15460
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15458
15461
  authority: string(),
15459
15462
  activation: ExtensionSlotActivationSchema.nullable(),
15460
15463
  value: ExtensionSlotValueSchema,
@@ -15732,7 +15735,7 @@ method(object({
15732
15735
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15733
15736
  * ends in a log line, and a log line is the thing the operator asked to stop
15734
15737
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15735
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15738
+ * ricontrolliamo tutti questi punti"* [can we instrument these errors in the meantime? That way next round we recheck all these points]. Reading them meant grepping Loki and
15736
15739
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15737
15740
  * media blackout were both diagnosed. The counters stay; this is where they can
15738
15741
  * be READ.
@@ -17108,7 +17111,7 @@ var BrokerStatusSchema$1 = _enum([
17108
17111
  "unreachable",
17109
17112
  "tls-error"
17110
17113
  ]);
17111
- var BrokerInfoSchema = object({
17114
+ var BrokerInfoSchema$1 = object({
17112
17115
  id: string(),
17113
17116
  name: string(),
17114
17117
  url: string(),
@@ -17181,7 +17184,7 @@ var mqttBrokerCapability = {
17181
17184
  kind: "poll"
17182
17185
  },
17183
17186
  methods: {
17184
- listBrokers: method(_void(), array(BrokerInfoSchema)),
17187
+ listBrokers: method(_void(), array(BrokerInfoSchema$1)),
17185
17188
  getBrokerConfig: method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }),
17186
17189
  addBroker: method(AddBrokerInputSchema, AddBrokerResultSchema, {
17187
17190
  kind: "mutation",
@@ -19366,78 +19369,16 @@ var NcRuleInputSchema = object({
19366
19369
  */
19367
19370
  confirm: NcConfirmSchema.optional(),
19368
19371
  /**
19369
- * WAIT for face/plate recognition before saying anything.
19370
- *
19371
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19372
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19373
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19374
- * measured on this hub — and an `immediate` rule enqueues on the first object
19375
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19376
- * immediate path, and no amount of media re-resolution fixes a sentence.
19377
- *
19378
- * Only two honest answers exist, and this flag picks between them. It has
19379
- * effect ONLY on a rule that declares a recognition scope
19380
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19381
- * other rule there is nothing to wait for and the flag is inert.
19382
- *
19383
- * | value | what happens |
19384
- * | --- | --- |
19385
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19386
- * | absent / `false` | it fires at once WITHOUT the name, and if recognition lands before the track closes a SECOND, "…is Gianluca" notification follows (one per track, per rule, per target) |
19387
- *
19388
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19389
- * on the addon cap path, and absent has to keep meaning exactly what every
19390
- * rule authored before this field meant.
19391
- *
19392
- * The cost of `true` is stated here because the editor states it too: a rule
19393
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19394
- * tests every zone the track visited and a `crossing` condition can no longer
19395
- * be satisfied, because a closed track carries no crossing.
19396
- */
19397
- waitForEnhancement: boolean().optional(),
19398
- /**
19399
- * GROUP a burst of subjects into ONE notification that grows.
19400
- *
19401
- * Seconds of quiet after the last matching subject before the burst is
19402
- * considered over. While it is open, the first subject enqueues immediately —
19403
- * **exactly as today, with no added latency** — and every real growth (a new
19404
- * subject, or a name confirmed on one already in it) REPLACES that
19405
- * notification with an updated one naming everybody. The push carries the
19406
- * group's own coalescing tag, so the phone replaces rather than stacks.
19407
- *
19408
- * `0` / absent = off, and off is today's behaviour byte for byte.
19409
- *
19410
- * ### Why an idle cutoff and not a window
19411
- *
19412
- * The measured seven-person arrival on device 590 spans 110 s with every
19413
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19414
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19415
- * 30 s is a widely shipped value for the same decision.
19416
- *
19417
- * ### What it replaces
19418
- *
19419
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19420
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19421
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19422
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19423
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19424
- * budget over GROUPS — which is what it always meant — and a growth is never
19425
- * throttled by the window its own first member spent.
19426
- *
19427
- * ### Interaction with {@link waitForEnhancement}
19428
- *
19429
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19430
- * TRACK CLOSE, so with both set the group is opened by the first member to
19431
- * CLOSE — already carrying its name — and grows as later members close. That
19432
- * is later, and complete. With grouping alone the group opens on the first
19433
- * object event and picks up names as they are confirmed, through the growth
19434
- * path. Neither combination fires twice for one subject.
19435
- *
19436
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19437
- * on the addon cap path, so absent must keep meaning what it meant before this
19438
- * field existed.
19372
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19373
+ * seconds; the rule sends ONE message when the window closes — early, once every
19374
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19375
+ * the rule matched, with one mosaic tile per subject.
19376
+ * Absent = plain immediate (one message per track, at the first match).
19377
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19378
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19379
+ * path does not run Zod defaults.
19439
19380
  */
19440
- groupIdleSec: number().int().min(0).max(600).optional()
19381
+ groupingSec: number().int().min(5).max(60).optional()
19441
19382
  });
19442
19383
  /**
19443
19384
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19485,9 +19426,10 @@ var NcRuleClearableKeySchema = _enum([
19485
19426
  "targetUsers",
19486
19427
  "snoozeOptions",
19487
19428
  "snoozeAllowGlobal",
19488
- "waitForEnhancement",
19489
19429
  "actions",
19490
19430
  "confirm",
19431
+ "groupingSec",
19432
+ "waitForEnhancement",
19491
19433
  "groupIdleSec"
19492
19434
  ]);
19493
19435
  var NcRuleUpdateInputSchema = object({
@@ -19509,9 +19451,25 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19509
19451
  */
19510
19452
  disabledTargetIds: array(string()).default([])
19511
19453
  });
19454
+ /**
19455
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19456
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19457
+ * job is not an open state, and the alarm stays instant.
19458
+ */
19459
+ var NcStateSummarySectionIdSchema = _enum([
19460
+ "device-state",
19461
+ "streams",
19462
+ "battery",
19463
+ "consumables",
19464
+ "tanks",
19465
+ "detection-blind",
19466
+ "nodes",
19467
+ "addons",
19468
+ "updates"
19469
+ ]);
19512
19470
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19513
19471
  var NcStateSummaryPreviewSectionSchema = object({
19514
- id: string(),
19472
+ id: NcStateSummarySectionIdSchema,
19515
19473
  status: _enum(["read", "unavailable"]),
19516
19474
  items: number().int().nonnegative(),
19517
19475
  reason: string().optional()
@@ -19521,7 +19479,7 @@ var NcStateSummaryPreviewSectionSchema = object({
19521
19479
  * a `rule` split (no `section`), one section's on a `section` split.
19522
19480
  */
19523
19481
  var NcStateSummaryPreviewRowSchema = object({
19524
- section: string().optional(),
19482
+ section: NcStateSummarySectionIdSchema.optional(),
19525
19483
  /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19526
19484
  title: string(),
19527
19485
  text: string(),
@@ -19958,6 +19916,29 @@ object({
19958
19916
  firstAt: number(),
19959
19917
  lastAt: number()
19960
19918
  });
19919
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
19920
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
19921
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
19922
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
19923
+ var NcAlarmModeDelaysListSchema = array(object({
19924
+ mode: AlarmArmModeSchema,
19925
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
19926
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
19927
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
19928
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
19929
+ var NcAlarmModeDelaysEditListSchema = array(object({
19930
+ mode: AlarmArmModeSchema,
19931
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19932
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19933
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
19934
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
19935
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
19936
+ var NcAlarmNonBlockingEditSchema = object({
19937
+ mode: AlarmArmModeSchema,
19938
+ deviceId: number().int().positive(),
19939
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
19940
+ nonBlocking: boolean()
19941
+ });
19961
19942
  /**
19962
19943
  * The per-mode lists, at most one per mode. Two lists for one mode have no
19963
19944
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -19966,10 +19947,10 @@ object({
19966
19947
  var NcAlarmNonBlockingListSchema = array(object({
19967
19948
  mode: AlarmArmModeSchema,
19968
19949
  deviceIds: array(number().int()).max(200)
19969
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
19950
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
19970
19951
  /**
19971
- * The three durations the panel's state machine runs on, who hears about its
19972
- * transitions, and which openings each mode tolerates.
19952
+ * The three durations — global, and per mode since D697 — the panel's state
19953
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
19973
19954
  *
19974
19955
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
19975
19956
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -19979,15 +19960,15 @@ var NcAlarmNonBlockingListSchema = array(object({
19979
19960
  */
19980
19961
  var NcAlarmSettingsSchema = object({
19981
19962
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
19982
- exitDelaySec: number().int().min(0).max(600),
19963
+ exitDelaySec: NcAlarmDelaySecSchema,
19983
19964
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
19984
- entryDelaySec: number().int().min(0).max(600),
19965
+ entryDelaySec: NcAlarmDelaySecSchema,
19985
19966
  /**
19986
19967
  * How long `triggered` lasts before the panel re-arms itself.
19987
19968
  * **0 = until an operator disarms it** — the behaviour before this field
19988
19969
  * existed, and therefore what an untouched install keeps doing.
19989
19970
  */
19990
- triggeredDurationSec: number().int().min(0).max(3600),
19971
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
19991
19972
  /**
19992
19973
  * Deprecated by D630 — every transition is announced. Parsed, never read;
19993
19974
  * remove after one release. Optional so an older admin's patch (which still
@@ -20010,17 +19991,30 @@ var NcAlarmSettingsSchema = object({
20010
19991
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20011
19992
  * parses as "every opening blocks" — the behaviour it was written under.
20012
19993
  */
20013
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
19994
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
19995
+ /**
19996
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
19997
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
19998
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
19999
+ */
20000
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20014
20001
  });
20015
20002
  /**
20016
20003
  * Every field optional — a tab edits one control at a time.
20017
20004
  *
20018
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20019
- * remove an inner `.default()`, so a patch that never named the field would
20020
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20021
- * (the same trap {@link NcRulePatchSchema} documents).
20005
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20006
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20007
+ * named the field would parse to `[]` and wipe every mode's list on an
20008
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20009
+ * `modeDelays` means EDITS, not the stored overrides.
20022
20010
  */
20023
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20011
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20012
+ nonBlocking: NcAlarmNonBlockingListSchema,
20013
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20014
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20015
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20016
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20017
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20024
20018
  /**
20025
20019
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20026
20020
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20120,6 +20114,28 @@ var NcAlarmConfigSchema = object({
20120
20114
  */
20121
20115
  live: NcAlarmLiveSchema.optional()
20122
20116
  });
20117
+ var NcAlarmSensorReadingSchema = _enum([
20118
+ "open",
20119
+ "closed",
20120
+ "unknown",
20121
+ "not-opening"
20122
+ ]);
20123
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20124
+ var NcAlarmSensorStateSchema = object({
20125
+ deviceId: number().int().positive(),
20126
+ reading: NcAlarmSensorReadingSchema,
20127
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20128
+ state: string().nullable(),
20129
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20130
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20131
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20132
+ online: boolean().nullable()
20133
+ });
20134
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20135
+ var NcAlarmSensorsSchema = object({
20136
+ sensors: array(NcAlarmSensorStateSchema),
20137
+ readAt: number().int().nonnegative()
20138
+ });
20123
20139
  /**
20124
20140
  * ONE rule's demand on ONE camera's clip ring.
20125
20141
  *
@@ -20210,7 +20226,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20210
20226
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20211
20227
  kind: "mutation",
20212
20228
  auth: "admin"
20213
- });
20229
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20214
20230
  /**
20215
20231
  * Zod schemas for persisted record types.
20216
20232
  *
@@ -20557,12 +20573,26 @@ var CameraSensorRefSchema = object({
20557
20573
  function cameraSensorRefKey(ref) {
20558
20574
  return `${ref.addonId}/${ref.stableId}`;
20559
20575
  }
20560
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20561
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20562
- var CameraSensorsConfigSchema = object({
20563
- mode: CameraSensorsModeSchema,
20564
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20565
- }).superRefine((value, ctx) => {
20576
+ /**
20577
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20578
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20579
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20580
+ * camera's own motion counts only while a confirm sensor is active — with or
20581
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20582
+ */
20583
+ var CameraSensorModeSchema = _enum([
20584
+ "in-addition",
20585
+ "confirm",
20586
+ "replace"
20587
+ ]);
20588
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20589
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20590
+ /**
20591
+ * A camera's attached sensors. A CS-1 row carried an envelope
20592
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20593
+ * what the write stores (D672 rule 3), so it is never written again.
20594
+ */
20595
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20566
20596
  const seen = /* @__PURE__ */ new Set();
20567
20597
  value.sensors.forEach((ref, index) => {
20568
20598
  const key = cameraSensorRefKey(ref);
@@ -20574,39 +20604,41 @@ var CameraSensorsConfigSchema = object({
20574
20604
  seen.add(key);
20575
20605
  });
20576
20606
  });
20577
- var RefFields = {
20607
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20608
+ var AttachmentFields = {
20578
20609
  addonId: string(),
20579
- stableId: string()
20610
+ stableId: string(),
20611
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20580
20612
  };
20581
20613
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20582
20614
  object({
20583
- ...RefFields,
20615
+ ...AttachmentFields,
20584
20616
  state: literal("ready"),
20585
20617
  deviceId: number(),
20586
20618
  name: string(),
20587
20619
  caps: array(string()).readonly()
20588
20620
  }),
20589
20621
  object({
20590
- ...RefFields,
20622
+ ...AttachmentFields,
20591
20623
  state: literal("offline"),
20592
20624
  deviceId: number(),
20593
20625
  name: string(),
20594
20626
  reason: _enum(["offline", "disabled"])
20595
20627
  }),
20596
20628
  object({
20597
- ...RefFields,
20629
+ ...AttachmentFields,
20598
20630
  state: literal("missing"),
20599
20631
  reason: string()
20600
20632
  }),
20601
20633
  object({
20602
- ...RefFields,
20634
+ ...AttachmentFields,
20603
20635
  state: literal("unsupported"),
20604
20636
  deviceId: number(),
20605
20637
  name: string(),
20606
20638
  reason: string()
20607
20639
  }),
20608
20640
  object({
20609
- ...RefFields,
20641
+ ...AttachmentFields,
20610
20642
  state: literal("unreadable"),
20611
20643
  reason: string()
20612
20644
  })
@@ -20619,17 +20651,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20619
20651
  * say.
20620
20652
  */
20621
20653
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20654
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20655
+ var CameraOwnMotionSchema = _enum([
20656
+ "always",
20657
+ "when-confirmed",
20658
+ "never"
20659
+ ]);
20660
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20661
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20662
+ /**
20663
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20664
+ * never the effective value. The camera's `motion` slice is the effective one,
20665
+ * so a view of the camera's own row reads this.
20666
+ * - `atMs`: when the bridge received the camera's last own edge.
20667
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20668
+ * AND it is younger than the window the bridge counts the camera's own motion
20669
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20670
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20671
+ * within the window; a status read is a snapshot, so a live view re-asks.
20672
+ * Absent = this orchestrator has not received the camera's own motion since it
20673
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20674
+ */
20675
+ var CameraRawLevelSchema = object({
20676
+ detected: boolean(),
20677
+ atMs: number()
20678
+ });
20622
20679
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20623
- object({ state: literal("not-configured") }),
20680
+ object({
20681
+ state: literal("not-configured"),
20682
+ cameraLevel: CameraRawLevelSchema.optional()
20683
+ }),
20624
20684
  object({
20625
20685
  state: literal("unreadable"),
20626
- reason: string()
20686
+ reason: string(),
20687
+ cameraLevel: CameraRawLevelSchema.optional()
20627
20688
  }),
20628
20689
  object({
20629
20690
  state: literal("configured"),
20630
- mode: CameraSensorsModeSchema,
20691
+ cameraLevel: CameraRawLevelSchema.optional(),
20631
20692
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20632
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20693
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20694
+ cameraMotion: CameraOwnMotionSchema.optional(),
20695
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20696
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20633
20697
  })
20634
20698
  ]);
20635
20699
  Object.freeze({
@@ -21314,14 +21378,14 @@ var TrackSchema = object({
21314
21378
  * `derive-returned-null tile=standin`,
21315
21379
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21316
21380
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21317
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21381
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21318
21382
  * tile is a face/plate stand-in, a raster crop, or an icon.
21319
21383
  *
21320
21384
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21321
21385
  * the TILE ranked below native, so a track whose thumbnail landed natively
21322
21386
  * while its key frame missed carried no reason at all — three of the four
21323
21387
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21324
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21388
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21325
21389
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21326
21390
  * lastFrame copy source`), so its absence is now on this line whatever the
21327
21391
  * tile ended up being.
@@ -21408,7 +21472,7 @@ var MotionEventSchema = object({
21408
21472
  /**
21409
21473
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21410
21474
  * first entry is always `0`) of every genuine off→on transition the
21411
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21475
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21412
21476
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21413
21477
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21414
21478
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -21932,6 +21996,7 @@ var KeyEventSchema = object({
21932
21996
  bestEventId: string(),
21933
21997
  /** Track lifetime in ms (lastSeen - firstSeen). */
21934
21998
  windowMs: number().optional(),
21999
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
21935
22000
  ...TrackFlagFields,
21936
22001
  ...TrackRetrainFields
21937
22002
  });
@@ -23820,8 +23885,10 @@ var RunnerCameraConfigSchema = object({
23820
23885
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23821
23886
  * analyzer providers) running on decoded frames in this runner.
23822
23887
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
23823
- * analytics). The provider emits ProviderMotion
23824
- * events; the orchestrator forwards to `reportMotion`.
23888
+ * analytics). The provider emits its raw push as `ProviderMotion`
23889
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
23890
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
23891
+ * `reportMotion`.
23825
23892
  */
23826
23893
  motionSources: MotionSourcesSchema.default(["analyzer"]),
23827
23894
  /**
@@ -23998,7 +24065,15 @@ var RunnerLocalLoadSchema = object({
23998
24065
  hasGpu: boolean(),
23999
24066
  inferenceBackend: string().optional(),
24000
24067
  cpuCores: number().optional()
24001
- })
24068
+ }),
24069
+ /**
24070
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24071
+ * runner never writes it. ABSENT = a runner older than D696, which still
24072
+ * writes the slice from its phase machine; the orchestrator does not write
24073
+ * while any runner node is absent-or-unanswered, so one slice never has two
24074
+ * writers.
24075
+ */
24076
+ motionSliceWriter: literal("orchestrator").optional()
24002
24077
  });
24003
24078
  /**
24004
24079
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -26634,7 +26709,7 @@ var ClipPlaybackSchema = object({
26634
26709
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26635
26710
  * time the camera is awake for its own reasons — is deliberately not a member:
26636
26711
  * it was proposed, it is free on the battery, and the operator declined it on
26637
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
26712
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26638
26713
  * D558 "Considered and not taken").
26639
26714
  */
26640
26715
  var ClipWakeSchema = _enum(["authorised"]);
@@ -26895,7 +26970,7 @@ DeviceType.Camera, method(object({
26895
26970
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
26896
26971
  * assumed the answer was a fan-out over everything a camera has. The
26897
26972
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
26898
- * solo"* — so the list is asked of one provider at a time and there is
26973
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
26899
26974
  * nothing to filter out of it.
26900
26975
  */
26901
26976
  provider: string().min(1)
@@ -28839,6 +28914,17 @@ object({
28839
28914
  deviceId: number(),
28840
28915
  status: DeviceStatusSchema
28841
28916
  });
28917
+ object({
28918
+ /** Ms epoch of the last press. null = never observed since this provider started. */
28919
+ lastPressedAt: number().nullable(),
28920
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
28921
+ pressCountSinceStart: number()
28922
+ });
28923
+ object({
28924
+ deviceId: number(),
28925
+ timestamp: number()
28926
+ });
28927
+ DeviceType.Button, DeviceType.Camera;
28842
28928
  /**
28843
28929
  * Enum-state sensor — a string value picked from a finite option set.
28844
28930
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30483,9 +30569,9 @@ object({
30483
30569
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30484
30570
  lastDetectedAt: number().nullable(),
30485
30571
  /**
30486
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30487
- * `null` while false and on every `Sensor` device (D475) — see that
30488
- * constant's doc for the one-authority rule.
30572
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30573
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30574
+ * device (D475) — see that constant's doc for the one-authority rule.
30489
30575
  *
30490
30576
  * ## Reading this field still arms nothing
30491
30577
  *
@@ -30503,38 +30589,41 @@ object({
30503
30589
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30504
30590
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30505
30591
  *
30506
- * ## Who writes it
30592
+ * ## Who writes it (D696)
30507
30593
  *
30508
- * - **Cameras** — the runner's phase machine, `active → watching` on
30509
- * `cooldown_expired`, which then writes this slice with
30510
- * `detected: false` (`handlePhaseChanged` in
30511
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30512
- * matters most for the sources that only ever push a rising one:
30513
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30514
- * a false.
30515
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30516
- * provider pushes the false itself, from the upstream system's own
30517
- * state change. No phase machine is involved.
30594
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30595
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30596
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30597
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30598
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30599
+ * device activity, as the camera's `motionSources` counts them. A source
30600
+ * that never sends a clear falls one window after its last `true`.
30601
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30602
+ * writes this slice (not even a boot seed), and neither does the runner's
30603
+ * phase machine: its phase is "a session is attached" for every on-motion
30604
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30605
+ * A provider's `isDetected` answers from the hub's slice.
30606
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30607
+ * imports) — the provider pushes both edges itself, from the upstream
30608
+ * system's own state change. That is the sensor's own fact.
30518
30609
  *
30519
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30610
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30611
+ * `motion` slice; each sensor writer is a named exemption.
30520
30612
  *
30521
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30522
- * phase machine is the sole writer for a camera. It is not.
30523
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30524
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30525
- * Hikvision's comment says why: it read THIS docblock, agreed the
30526
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30527
- * two authorities can disagree about one slice, which this repo
30528
- * forbids, and the doc said otherwise — which is worse than saying
30529
- * nothing, because it reads as verification.
30530
- *
30531
- * This predates D475 and is not fixed there: the fix touches every
30532
- * camera provider. Recorded in D475's Consequences. Do not restore the
30533
- * "sole writer" wording without also removing the other writers.
30613
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30614
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30615
+ * it, every provider seeded it at boot, and every session left it
30616
+ * `detected: true` after its detach.
30534
30617
  */
30535
30618
  autoClearAfterMs: number().nullable()
30536
30619
  });
30537
- object({
30620
+ /**
30621
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30622
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30623
+ * — both the cap event surface and the bus payload type alias to this
30624
+ * schema.
30625
+ */
30626
+ var MotionOnMotionChangedDataSchema = object({
30538
30627
  deviceId: number(),
30539
30628
  detected: boolean(),
30540
30629
  timestamp: number(),
@@ -32999,7 +33088,7 @@ var ReadGopBytesResultSchema = object({
32999
33088
  /**
33000
33089
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
33001
33090
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
33002
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
33091
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
33003
33092
  * a replay needs several seconds of native pixels, not one frame.
33004
33093
  *
33005
33094
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -35615,6 +35704,7 @@ DeviceType.Camera, method(object({
35615
35704
  detection: array(ZoneRuleSchema).readonly(),
35616
35705
  package: array(ZoneRuleSchema).readonly()
35617
35706
  });
35707
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
35618
35708
  /**
35619
35709
  * Accessory device helpers — shared across drivers.
35620
35710
  *
@@ -40593,6 +40683,12 @@ Object.freeze({
40593
40683
  addonId: null,
40594
40684
  access: "view"
40595
40685
  },
40686
+ "notificationRules.getAlarmSensors": {
40687
+ capName: "notification-rules",
40688
+ capScope: "system",
40689
+ addonId: null,
40690
+ access: "view"
40691
+ },
40596
40692
  "notificationRules.getClipRetentionAsks": {
40597
40693
  capName: "notification-rules",
40598
40694
  capScope: "system",
@@ -44171,6 +44267,11 @@ Object.freeze({
44171
44267
  }
44172
44268
  });
44173
44269
  Object.freeze({
44270
+ "accessories.getStatus": [{
44271
+ name: "deviceId",
44272
+ form: "single",
44273
+ optional: false
44274
+ }],
44174
44275
  "accessories.setChildHidden": [{
44175
44276
  name: "childDeviceId",
44176
44277
  form: "single",
@@ -44190,6 +44291,11 @@ Object.freeze({
44190
44291
  form: "single",
44191
44292
  optional: false
44192
44293
  }],
44294
+ "airQualitySensor.getStatus": [{
44295
+ name: "deviceId",
44296
+ form: "single",
44297
+ optional: false
44298
+ }],
44193
44299
  "alarmPanel.arm": [{
44194
44300
  name: "deviceId",
44195
44301
  form: "single",
@@ -44200,11 +44306,36 @@ Object.freeze({
44200
44306
  form: "single",
44201
44307
  optional: false
44202
44308
  }],
44309
+ "alarmPanel.getStatus": [{
44310
+ name: "deviceId",
44311
+ form: "single",
44312
+ optional: false
44313
+ }],
44203
44314
  "alarmPanel.trigger": [{
44204
44315
  name: "deviceId",
44205
44316
  form: "single",
44206
44317
  optional: false
44207
44318
  }],
44319
+ "ambientLightSensor.getStatus": [{
44320
+ name: "deviceId",
44321
+ form: "single",
44322
+ optional: false
44323
+ }],
44324
+ "audioAnalysis.applyDeviceSettingsPatch": [{
44325
+ name: "deviceId",
44326
+ form: "single",
44327
+ optional: false
44328
+ }],
44329
+ "audioAnalysis.getDeviceLiveContribution": [{
44330
+ name: "deviceId",
44331
+ form: "single",
44332
+ optional: false
44333
+ }],
44334
+ "audioAnalysis.getDeviceSettingsContribution": [{
44335
+ name: "deviceId",
44336
+ form: "single",
44337
+ optional: false
44338
+ }],
44208
44339
  "audioAnalysis.resolveDeviceSettings": [{
44209
44340
  name: "deviceId",
44210
44341
  form: "single",
@@ -44245,16 +44376,36 @@ Object.freeze({
44245
44376
  form: "single",
44246
44377
  optional: false
44247
44378
  }],
44379
+ "automationControl.getStatus": [{
44380
+ name: "deviceId",
44381
+ form: "single",
44382
+ optional: false
44383
+ }],
44248
44384
  "automationControl.trigger": [{
44249
44385
  name: "deviceId",
44250
44386
  form: "single",
44251
44387
  optional: false
44252
44388
  }],
44389
+ "battery.getStatus": [{
44390
+ name: "deviceId",
44391
+ form: "single",
44392
+ optional: false
44393
+ }],
44253
44394
  "battery.wakeForStream": [{
44254
44395
  name: "deviceId",
44255
44396
  form: "single",
44256
44397
  optional: false
44257
44398
  }],
44399
+ "binary.getStatus": [{
44400
+ name: "deviceId",
44401
+ form: "single",
44402
+ optional: false
44403
+ }],
44404
+ "brightness.getStatus": [{
44405
+ name: "deviceId",
44406
+ form: "single",
44407
+ optional: false
44408
+ }],
44258
44409
  "brightness.setBrightness": [{
44259
44410
  name: "deviceId",
44260
44411
  form: "single",
@@ -44270,6 +44421,26 @@ Object.freeze({
44270
44421
  form: "single",
44271
44422
  optional: false
44272
44423
  }],
44424
+ "cameraCredentials.getStatus": [{
44425
+ name: "deviceId",
44426
+ form: "single",
44427
+ optional: false
44428
+ }],
44429
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
44430
+ name: "deviceId",
44431
+ form: "single",
44432
+ optional: false
44433
+ }],
44434
+ "cameraGridLayout.getDeviceLiveContribution": [{
44435
+ name: "deviceId",
44436
+ form: "single",
44437
+ optional: false
44438
+ }],
44439
+ "cameraGridLayout.getDeviceSettingsContribution": [{
44440
+ name: "deviceId",
44441
+ form: "single",
44442
+ optional: false
44443
+ }],
44273
44444
  "cameraGridLayout.getLayout": [{
44274
44445
  name: "deviceId",
44275
44446
  form: "single",
@@ -44285,6 +44456,21 @@ Object.freeze({
44285
44456
  form: "single",
44286
44457
  optional: false
44287
44458
  }],
44459
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
44460
+ name: "deviceId",
44461
+ form: "single",
44462
+ optional: false
44463
+ }],
44464
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
44465
+ name: "deviceId",
44466
+ form: "single",
44467
+ optional: false
44468
+ }],
44469
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
44470
+ name: "deviceId",
44471
+ form: "single",
44472
+ optional: false
44473
+ }],
44288
44474
  "cameraStreams.getBrokerStreams": [{
44289
44475
  name: "deviceId",
44290
44476
  form: "single",
@@ -44310,6 +44496,16 @@ Object.freeze({
44310
44496
  form: "single",
44311
44497
  optional: false
44312
44498
  }],
44499
+ "carbonMonoxide.getStatus": [{
44500
+ name: "deviceId",
44501
+ form: "single",
44502
+ optional: false
44503
+ }],
44504
+ "climateControl.getStatus": [{
44505
+ name: "deviceId",
44506
+ form: "single",
44507
+ optional: false
44508
+ }],
44313
44509
  "climateControl.setFanMode": [{
44314
44510
  name: "deviceId",
44315
44511
  form: "single",
@@ -44350,16 +44546,41 @@ Object.freeze({
44350
44546
  form: "single",
44351
44547
  optional: false
44352
44548
  }],
44549
+ "color.getStatus": [{
44550
+ name: "deviceId",
44551
+ form: "single",
44552
+ optional: false
44553
+ }],
44353
44554
  "color.setColor": [{
44354
44555
  name: "deviceId",
44355
44556
  form: "single",
44356
44557
  optional: false
44357
44558
  }],
44559
+ "connectivity.getStatus": [{
44560
+ name: "deviceId",
44561
+ form: "single",
44562
+ optional: false
44563
+ }],
44564
+ "consumables.getStatus": [{
44565
+ name: "deviceId",
44566
+ form: "single",
44567
+ optional: false
44568
+ }],
44358
44569
  "consumables.reset": [{
44359
44570
  name: "deviceId",
44360
44571
  form: "single",
44361
44572
  optional: false
44362
44573
  }],
44574
+ "contact.getStatus": [{
44575
+ name: "deviceId",
44576
+ form: "single",
44577
+ optional: false
44578
+ }],
44579
+ "control.getStatus": [{
44580
+ name: "deviceId",
44581
+ form: "single",
44582
+ optional: false
44583
+ }],
44363
44584
  "control.setValue": [{
44364
44585
  name: "deviceId",
44365
44586
  form: "single",
@@ -44370,6 +44591,11 @@ Object.freeze({
44370
44591
  form: "single",
44371
44592
  optional: false
44372
44593
  }],
44594
+ "cover.getStatus": [{
44595
+ name: "deviceId",
44596
+ form: "single",
44597
+ optional: false
44598
+ }],
44373
44599
  "cover.open": [{
44374
44600
  name: "deviceId",
44375
44601
  form: "single",
@@ -44395,6 +44621,11 @@ Object.freeze({
44395
44621
  form: "single",
44396
44622
  optional: false
44397
44623
  }],
44624
+ "dayNight.getStatus": [{
44625
+ name: "deviceId",
44626
+ form: "single",
44627
+ optional: false
44628
+ }],
44398
44629
  "dayNight.setSettings": [{
44399
44630
  name: "deviceId",
44400
44631
  form: "single",
@@ -44405,11 +44636,31 @@ Object.freeze({
44405
44636
  form: "single",
44406
44637
  optional: true
44407
44638
  }],
44639
+ "detectionPipeline.applyDeviceSettingsPatch": [{
44640
+ name: "deviceId",
44641
+ form: "single",
44642
+ optional: false
44643
+ }],
44644
+ "detectionPipeline.getDeviceLiveContribution": [{
44645
+ name: "deviceId",
44646
+ form: "single",
44647
+ optional: false
44648
+ }],
44649
+ "detectionPipeline.getDeviceSettingsContribution": [{
44650
+ name: "deviceId",
44651
+ form: "single",
44652
+ optional: false
44653
+ }],
44408
44654
  "deviceAdminLink.getAdminLink": [{
44409
44655
  name: "deviceId",
44410
44656
  form: "single",
44411
44657
  optional: false
44412
44658
  }],
44659
+ "deviceAdoption.getStatus": [{
44660
+ name: "deviceId",
44661
+ form: "single",
44662
+ optional: false
44663
+ }],
44413
44664
  "deviceAdoption.release": [{
44414
44665
  name: "camDeviceId",
44415
44666
  form: "single",
@@ -44425,6 +44676,11 @@ Object.freeze({
44425
44676
  form: "single",
44426
44677
  optional: false
44427
44678
  }],
44679
+ "deviceDiscovery.getStatus": [{
44680
+ name: "deviceId",
44681
+ form: "single",
44682
+ optional: false
44683
+ }],
44428
44684
  "deviceDiscovery.listDiscovered": [{
44429
44685
  name: "deviceId",
44430
44686
  form: "single",
@@ -44444,6 +44700,21 @@ Object.freeze({
44444
44700
  form: "single",
44445
44701
  optional: false
44446
44702
  }],
44703
+ "deviceExport.applyDeviceSettingsPatch": [{
44704
+ name: "deviceId",
44705
+ form: "single",
44706
+ optional: false
44707
+ }],
44708
+ "deviceExport.getDeviceLiveContribution": [{
44709
+ name: "deviceId",
44710
+ form: "single",
44711
+ optional: false
44712
+ }],
44713
+ "deviceExport.getDeviceSettingsContribution": [{
44714
+ name: "deviceId",
44715
+ form: "single",
44716
+ optional: false
44717
+ }],
44447
44718
  "deviceExtension.listCandidates": [{
44448
44719
  name: "deviceId",
44449
44720
  form: "single",
@@ -44792,6 +45063,26 @@ Object.freeze({
44792
45063
  form: "single",
44793
45064
  optional: false
44794
45065
  }],
45066
+ "deviceStatus.getStatus": [{
45067
+ name: "deviceId",
45068
+ form: "single",
45069
+ optional: false
45070
+ }],
45071
+ "doorbell.getStatus": [{
45072
+ name: "deviceId",
45073
+ form: "single",
45074
+ optional: false
45075
+ }],
45076
+ "enumSensor.getStatus": [{
45077
+ name: "deviceId",
45078
+ form: "single",
45079
+ optional: false
45080
+ }],
45081
+ "eventEmitter.getStatus": [{
45082
+ name: "deviceId",
45083
+ form: "single",
45084
+ optional: false
45085
+ }],
44795
45086
  "faceGallery.getFaceByTrack": [{
44796
45087
  name: "deviceId",
44797
45088
  form: "single",
@@ -44806,6 +45097,11 @@ Object.freeze({
44806
45097
  form: "array",
44807
45098
  optional: true
44808
45099
  }],
45100
+ "fanControl.getStatus": [{
45101
+ name: "deviceId",
45102
+ form: "single",
45103
+ optional: false
45104
+ }],
44809
45105
  "fanControl.setDirection": [{
44810
45106
  name: "deviceId",
44811
45107
  form: "single",
@@ -44826,6 +45122,26 @@ Object.freeze({
44826
45122
  form: "single",
44827
45123
  optional: false
44828
45124
  }],
45125
+ "featureProbe.getStatus": [{
45126
+ name: "deviceId",
45127
+ form: "single",
45128
+ optional: false
45129
+ }],
45130
+ "flood.getStatus": [{
45131
+ name: "deviceId",
45132
+ form: "single",
45133
+ optional: false
45134
+ }],
45135
+ "gas.getStatus": [{
45136
+ name: "deviceId",
45137
+ form: "single",
45138
+ optional: false
45139
+ }],
45140
+ "humidifier.getStatus": [{
45141
+ name: "deviceId",
45142
+ form: "single",
45143
+ optional: false
45144
+ }],
44829
45145
  "humidifier.setMode": [{
44830
45146
  name: "deviceId",
44831
45147
  form: "single",
@@ -44841,11 +45157,26 @@ Object.freeze({
44841
45157
  form: "single",
44842
45158
  optional: false
44843
45159
  }],
45160
+ "humiditySensor.getStatus": [{
45161
+ name: "deviceId",
45162
+ form: "single",
45163
+ optional: false
45164
+ }],
45165
+ "image.getStatus": [{
45166
+ name: "deviceId",
45167
+ form: "single",
45168
+ optional: false
45169
+ }],
44844
45170
  "imageSettings.getOptions": [{
44845
45171
  name: "deviceId",
44846
45172
  form: "single",
44847
45173
  optional: false
44848
45174
  }],
45175
+ "imageSettings.getStatus": [{
45176
+ name: "deviceId",
45177
+ form: "single",
45178
+ optional: false
45179
+ }],
44849
45180
  "imageSettings.setSettings": [{
44850
45181
  name: "deviceId",
44851
45182
  form: "single",
@@ -44856,6 +45187,11 @@ Object.freeze({
44856
45187
  form: "single",
44857
45188
  optional: false
44858
45189
  }],
45190
+ "intercom.getStatus": [{
45191
+ name: "deviceId",
45192
+ form: "single",
45193
+ optional: false
45194
+ }],
44859
45195
  "intercom.handleAnswer": [{
44860
45196
  name: "deviceId",
44861
45197
  form: "single",
@@ -44886,6 +45222,11 @@ Object.freeze({
44886
45222
  form: "single",
44887
45223
  optional: false
44888
45224
  }],
45225
+ "lawnMowerControl.getStatus": [{
45226
+ name: "deviceId",
45227
+ form: "single",
45228
+ optional: false
45229
+ }],
44889
45230
  "lawnMowerControl.pause": [{
44890
45231
  name: "deviceId",
44891
45232
  form: "single",
@@ -44896,6 +45237,11 @@ Object.freeze({
44896
45237
  form: "single",
44897
45238
  optional: false
44898
45239
  }],
45240
+ "lockControl.getStatus": [{
45241
+ name: "deviceId",
45242
+ form: "single",
45243
+ optional: false
45244
+ }],
44899
45245
  "lockControl.lock": [{
44900
45246
  name: "deviceId",
44901
45247
  form: "single",
@@ -44911,6 +45257,11 @@ Object.freeze({
44911
45257
  form: "single",
44912
45258
  optional: false
44913
45259
  }],
45260
+ "mediaPlayer.getStatus": [{
45261
+ name: "deviceId",
45262
+ form: "single",
45263
+ optional: false
45264
+ }],
44914
45265
  "mediaPlayer.next": [{
44915
45266
  name: "deviceId",
44916
45267
  form: "single",
@@ -44971,6 +45322,11 @@ Object.freeze({
44971
45322
  form: "single",
44972
45323
  optional: false
44973
45324
  }],
45325
+ "motion.getStatus": [{
45326
+ name: "deviceId",
45327
+ form: "single",
45328
+ optional: false
45329
+ }],
44974
45330
  "motion.isDetected": [{
44975
45331
  name: "deviceId",
44976
45332
  form: "single",
@@ -44981,11 +45337,31 @@ Object.freeze({
44981
45337
  form: "single",
44982
45338
  optional: false
44983
45339
  }],
45340
+ "motionDetection.applyDeviceSettingsPatch": [{
45341
+ name: "deviceId",
45342
+ form: "single",
45343
+ optional: false
45344
+ }],
45345
+ "motionDetection.getDeviceLiveContribution": [{
45346
+ name: "deviceId",
45347
+ form: "single",
45348
+ optional: false
45349
+ }],
45350
+ "motionDetection.getDeviceSettingsContribution": [{
45351
+ name: "deviceId",
45352
+ form: "single",
45353
+ optional: false
45354
+ }],
44984
45355
  "motionDetection.removeCamera": [{
44985
45356
  name: "deviceId",
44986
45357
  form: "single",
44987
45358
  optional: false
44988
45359
  }],
45360
+ "motionTrigger.getStatus": [{
45361
+ name: "deviceId",
45362
+ form: "single",
45363
+ optional: false
45364
+ }],
44989
45365
  "motionTrigger.setMotionTrigger": [{
44990
45366
  name: "deviceId",
44991
45367
  form: "single",
@@ -44996,6 +45372,11 @@ Object.freeze({
44996
45372
  form: "single",
44997
45373
  optional: false
44998
45374
  }],
45375
+ "motionZones.getStatus": [{
45376
+ name: "deviceId",
45377
+ form: "single",
45378
+ optional: false
45379
+ }],
44999
45380
  "motionZones.setZone": [{
45000
45381
  name: "deviceId",
45001
45382
  form: "single",
@@ -45006,6 +45387,11 @@ Object.freeze({
45006
45387
  form: "single",
45007
45388
  optional: false
45008
45389
  }],
45390
+ "nativeObjectDetection.getStatus": [{
45391
+ name: "deviceId",
45392
+ form: "single",
45393
+ optional: false
45394
+ }],
45009
45395
  "nativeObjectDetection.setEnabled": [{
45010
45396
  name: "deviceId",
45011
45397
  form: "single",
@@ -45021,6 +45407,11 @@ Object.freeze({
45021
45407
  form: "single",
45022
45408
  optional: false
45023
45409
  }],
45410
+ "navigation.getStatus": [{
45411
+ name: "deviceId",
45412
+ form: "single",
45413
+ optional: false
45414
+ }],
45024
45415
  "navigation.goToPoint": [{
45025
45416
  name: "deviceId",
45026
45417
  form: "single",
@@ -45066,6 +45457,11 @@ Object.freeze({
45066
45457
  form: "single",
45067
45458
  optional: false
45068
45459
  }],
45460
+ "networkLink.getStatus": [{
45461
+ name: "deviceId",
45462
+ form: "single",
45463
+ optional: false
45464
+ }],
45069
45465
  "networkQuality.getDeviceStats": [{
45070
45466
  name: "deviceId",
45071
45467
  form: "single",
@@ -45076,6 +45472,11 @@ Object.freeze({
45076
45472
  form: "single",
45077
45473
  optional: false
45078
45474
  }],
45475
+ "notificationRules.getAlarmSensors": [{
45476
+ name: "deviceIds",
45477
+ form: "array",
45478
+ optional: false
45479
+ }],
45079
45480
  "notificationRules.setDeviceMuted": [{
45080
45481
  name: "deviceId",
45081
45482
  form: "single",
@@ -45086,11 +45487,26 @@ Object.freeze({
45086
45487
  form: "single",
45087
45488
  optional: false
45088
45489
  }],
45490
+ "notifier.getStatus": [{
45491
+ name: "deviceId",
45492
+ form: "single",
45493
+ optional: false
45494
+ }],
45089
45495
  "notifier.send": [{
45090
45496
  name: "deviceId",
45091
45497
  form: "single",
45092
45498
  optional: false
45093
45499
  }],
45500
+ "numericSensor.getStatus": [{
45501
+ name: "deviceId",
45502
+ form: "single",
45503
+ optional: false
45504
+ }],
45505
+ "osd.getStatus": [{
45506
+ name: "deviceId",
45507
+ form: "single",
45508
+ optional: false
45509
+ }],
45094
45510
  "osd.setOverlay": [{
45095
45511
  name: "deviceId",
45096
45512
  form: "single",
@@ -45150,6 +45566,11 @@ Object.freeze({
45150
45566
  form: "single",
45151
45567
  optional: false
45152
45568
  }],
45569
+ "petFeeder.getStatus": [{
45570
+ name: "deviceId",
45571
+ form: "single",
45572
+ optional: false
45573
+ }],
45153
45574
  "petFeeder.markFoodReplenished": [{
45154
45575
  name: "deviceId",
45155
45576
  form: "single",
@@ -45185,6 +45606,11 @@ Object.freeze({
45185
45606
  form: "single",
45186
45607
  optional: false
45187
45608
  }],
45609
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
45610
+ name: "deviceId",
45611
+ form: "single",
45612
+ optional: false
45613
+ }],
45188
45614
  "pipelineAnalytics.clearTrackRelation": [{
45189
45615
  name: "deviceId",
45190
45616
  form: "single",
@@ -45225,6 +45651,16 @@ Object.freeze({
45225
45651
  form: "single",
45226
45652
  optional: false
45227
45653
  }],
45654
+ "pipelineAnalytics.getDeviceLiveContribution": [{
45655
+ name: "deviceId",
45656
+ form: "single",
45657
+ optional: false
45658
+ }],
45659
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
45660
+ name: "deviceId",
45661
+ form: "single",
45662
+ optional: false
45663
+ }],
45228
45664
  "pipelineAnalytics.getEventDensity": [{
45229
45665
  name: "deviceId",
45230
45666
  form: "single",
@@ -45440,6 +45876,11 @@ Object.freeze({
45440
45876
  form: "single",
45441
45877
  optional: true
45442
45878
  }],
45879
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
45880
+ name: "deviceId",
45881
+ form: "single",
45882
+ optional: false
45883
+ }],
45443
45884
  "pipelineOrchestrator.assignAudio": [{
45444
45885
  name: "deviceId",
45445
45886
  form: "single",
@@ -45485,6 +45926,16 @@ Object.freeze({
45485
45926
  form: "single",
45486
45927
  optional: false
45487
45928
  }],
45929
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
45930
+ name: "deviceId",
45931
+ form: "single",
45932
+ optional: false
45933
+ }],
45934
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
45935
+ name: "deviceId",
45936
+ form: "single",
45937
+ optional: false
45938
+ }],
45488
45939
  "pipelineOrchestrator.getPipelineAssignment": [{
45489
45940
  name: "deviceId",
45490
45941
  form: "single",
@@ -45605,11 +46056,31 @@ Object.freeze({
45605
46056
  form: "single",
45606
46057
  optional: true
45607
46058
  }],
46059
+ "powerMeter.getStatus": [{
46060
+ name: "deviceId",
46061
+ form: "single",
46062
+ optional: false
46063
+ }],
46064
+ "presence.getStatus": [{
46065
+ name: "deviceId",
46066
+ form: "single",
46067
+ optional: false
46068
+ }],
46069
+ "pressureSensor.getStatus": [{
46070
+ name: "deviceId",
46071
+ form: "single",
46072
+ optional: false
46073
+ }],
45608
46074
  "privacyMask.getOptions": [{
45609
46075
  name: "deviceId",
45610
46076
  form: "single",
45611
46077
  optional: false
45612
46078
  }],
46079
+ "privacyMask.getStatus": [{
46080
+ name: "deviceId",
46081
+ form: "single",
46082
+ optional: false
46083
+ }],
45613
46084
  "privacyMask.setAudioEnabled": [{
45614
46085
  name: "deviceId",
45615
46086
  form: "single",
@@ -45660,6 +46131,11 @@ Object.freeze({
45660
46131
  form: "single",
45661
46132
  optional: false
45662
46133
  }],
46134
+ "ptz.getStatus": [{
46135
+ name: "deviceId",
46136
+ form: "single",
46137
+ optional: false
46138
+ }],
45663
46139
  "ptz.goHome": [{
45664
46140
  name: "deviceId",
45665
46141
  form: "single",
@@ -45750,6 +46226,11 @@ Object.freeze({
45750
46226
  form: "single",
45751
46227
  optional: false
45752
46228
  }],
46229
+ "recordingArchive.applyDeviceSettingsPatch": [{
46230
+ name: "deviceId",
46231
+ form: "single",
46232
+ optional: false
46233
+ }],
45753
46234
  "recordingArchive.deleteFootprint": [{
45754
46235
  name: "deviceId",
45755
46236
  form: "single",
@@ -45770,6 +46251,21 @@ Object.freeze({
45770
46251
  form: "single",
45771
46252
  optional: false
45772
46253
  }],
46254
+ "recordingArchive.getDeviceLiveContribution": [{
46255
+ name: "deviceId",
46256
+ form: "single",
46257
+ optional: false
46258
+ }],
46259
+ "recordingArchive.getDeviceSettingsContribution": [{
46260
+ name: "deviceId",
46261
+ form: "single",
46262
+ optional: false
46263
+ }],
46264
+ "recordingArchive.getStatus": [{
46265
+ name: "deviceId",
46266
+ form: "single",
46267
+ optional: false
46268
+ }],
45773
46269
  "recordingArchive.listOpsLog": [{
45774
46270
  name: "deviceId",
45775
46271
  form: "single",
@@ -45855,6 +46351,11 @@ Object.freeze({
45855
46351
  form: "single",
45856
46352
  optional: false
45857
46353
  }],
46354
+ "recordingOnboard.getStatus": [{
46355
+ name: "deviceId",
46356
+ form: "single",
46357
+ optional: false
46358
+ }],
45858
46359
  "recordingOnboard.setSettings": [{
45859
46360
  name: "deviceId",
45860
46361
  form: "single",
@@ -45885,6 +46386,11 @@ Object.freeze({
45885
46386
  form: "single",
45886
46387
  optional: false
45887
46388
  }],
46389
+ "sceneMonitor.getStatus": [{
46390
+ name: "deviceId",
46391
+ form: "single",
46392
+ optional: false
46393
+ }],
45888
46394
  "sceneMonitor.listScenes": [{
45889
46395
  name: "deviceId",
45890
46396
  form: "single",
@@ -45910,6 +46416,11 @@ Object.freeze({
45910
46416
  form: "single",
45911
46417
  optional: false
45912
46418
  }],
46419
+ "scriptRunner.getStatus": [{
46420
+ name: "deviceId",
46421
+ form: "single",
46422
+ optional: false
46423
+ }],
45913
46424
  "scriptRunner.run": [{
45914
46425
  name: "deviceId",
45915
46426
  form: "single",
@@ -45920,11 +46431,31 @@ Object.freeze({
45920
46431
  form: "single",
45921
46432
  optional: false
45922
46433
  }],
46434
+ "smoke.getStatus": [{
46435
+ name: "deviceId",
46436
+ form: "single",
46437
+ optional: false
46438
+ }],
46439
+ "snapshot.applyDeviceSettingsPatch": [{
46440
+ name: "deviceId",
46441
+ form: "single",
46442
+ optional: false
46443
+ }],
45923
46444
  "snapshot.getDebugState": [{
45924
46445
  name: "deviceId",
45925
46446
  form: "single",
45926
46447
  optional: false
45927
46448
  }],
46449
+ "snapshot.getDeviceLiveContribution": [{
46450
+ name: "deviceId",
46451
+ form: "single",
46452
+ optional: false
46453
+ }],
46454
+ "snapshot.getDeviceSettingsContribution": [{
46455
+ name: "deviceId",
46456
+ form: "single",
46457
+ optional: false
46458
+ }],
45928
46459
  "snapshot.getSnapshot": [{
45929
46460
  name: "deviceId",
45930
46461
  form: "single",
@@ -45941,6 +46472,11 @@ Object.freeze({
45941
46472
  form: "array",
45942
46473
  optional: false
45943
46474
  }],
46475
+ "snapshot.getStatus": [{
46476
+ name: "deviceId",
46477
+ form: "single",
46478
+ optional: false
46479
+ }],
45944
46480
  "snapshot.invalidateCache": [{
45945
46481
  name: "deviceId",
45946
46482
  form: "single",
@@ -45951,6 +46487,11 @@ Object.freeze({
45951
46487
  form: "single",
45952
46488
  optional: false
45953
46489
  }],
46490
+ "streamBroker.applyDeviceSettingsPatch": [{
46491
+ name: "deviceId",
46492
+ form: "single",
46493
+ optional: false
46494
+ }],
45954
46495
  "streamBroker.assignProfile": [{
45955
46496
  name: "deviceId",
45956
46497
  form: "single",
@@ -45966,6 +46507,16 @@ Object.freeze({
45966
46507
  form: "single",
45967
46508
  optional: false
45968
46509
  }],
46510
+ "streamBroker.getDeviceLiveContribution": [{
46511
+ name: "deviceId",
46512
+ form: "single",
46513
+ optional: false
46514
+ }],
46515
+ "streamBroker.getDeviceSettingsContribution": [{
46516
+ name: "deviceId",
46517
+ form: "single",
46518
+ optional: false
46519
+ }],
45969
46520
  "streamBroker.getStreamWithCodec": [{
45970
46521
  name: "deviceId",
45971
46522
  form: "single",
@@ -46026,16 +46577,46 @@ Object.freeze({
46026
46577
  form: "single",
46027
46578
  optional: false
46028
46579
  }],
46580
+ "streamParams.getStatus": [{
46581
+ name: "deviceId",
46582
+ form: "single",
46583
+ optional: false
46584
+ }],
46029
46585
  "streamParams.setProfile": [{
46030
46586
  name: "deviceId",
46031
46587
  form: "single",
46032
46588
  optional: false
46033
46589
  }],
46590
+ "switch.getStatus": [{
46591
+ name: "deviceId",
46592
+ form: "single",
46593
+ optional: false
46594
+ }],
46034
46595
  "switch.setState": [{
46035
46596
  name: "deviceId",
46036
46597
  form: "single",
46037
46598
  optional: false
46038
46599
  }],
46600
+ "tamper.getStatus": [{
46601
+ name: "deviceId",
46602
+ form: "single",
46603
+ optional: false
46604
+ }],
46605
+ "temperatureSensor.getStatus": [{
46606
+ name: "deviceId",
46607
+ form: "single",
46608
+ optional: false
46609
+ }],
46610
+ "update.getStatus": [{
46611
+ name: "deviceId",
46612
+ form: "single",
46613
+ optional: false
46614
+ }],
46615
+ "vacuumControl.getStatus": [{
46616
+ name: "deviceId",
46617
+ form: "single",
46618
+ optional: false
46619
+ }],
46039
46620
  "vacuumControl.locate": [{
46040
46621
  name: "deviceId",
46041
46622
  form: "single",
@@ -46071,6 +46652,11 @@ Object.freeze({
46071
46652
  form: "single",
46072
46653
  optional: false
46073
46654
  }],
46655
+ "valve.getStatus": [{
46656
+ name: "deviceId",
46657
+ form: "single",
46658
+ optional: false
46659
+ }],
46074
46660
  "valve.open": [{
46075
46661
  name: "deviceId",
46076
46662
  form: "single",
@@ -46086,6 +46672,11 @@ Object.freeze({
46086
46672
  form: "single",
46087
46673
  optional: false
46088
46674
  }],
46675
+ "vibration.getStatus": [{
46676
+ name: "deviceId",
46677
+ form: "single",
46678
+ optional: false
46679
+ }],
46089
46680
  "videoclips.dialClipStream": [{
46090
46681
  name: "deviceId",
46091
46682
  form: "single",
@@ -46121,6 +46712,11 @@ Object.freeze({
46121
46712
  form: "single",
46122
46713
  optional: false
46123
46714
  }],
46715
+ "waterHeater.getStatus": [{
46716
+ name: "deviceId",
46717
+ form: "single",
46718
+ optional: false
46719
+ }],
46124
46720
  "waterHeater.setAway": [{
46125
46721
  name: "deviceId",
46126
46722
  form: "single",
@@ -46136,6 +46732,11 @@ Object.freeze({
46136
46732
  form: "single",
46137
46733
  optional: false
46138
46734
  }],
46735
+ "weather.getStatus": [{
46736
+ name: "deviceId",
46737
+ form: "single",
46738
+ optional: false
46739
+ }],
46139
46740
  "webrtcSession.addIceCandidate": [{
46140
46741
  name: "deviceId",
46141
46742
  form: "single",
@@ -46873,7 +47474,7 @@ var PreviewTextField = string().max(200);
46873
47474
  * Whether the notification's preview is a STILL or a short animation.
46874
47475
  *
46875
47476
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
46876
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
47477
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
46877
47478
  * night reads better as three seconds of motion than as one frame of it. Both
46878
47479
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
46879
47480
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -47011,6 +47612,7 @@ TimelapseRuleInputSchema.extend({
47011
47612
  createdAt: number(),
47012
47613
  updatedAt: number()
47013
47614
  });
47615
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47014
47616
  object({
47015
47617
  /**
47016
47618
  * Fraction of the box's own size added on EACH side before cutting.