@camstack/addon-remote-storage 1.2.115 → 1.2.116

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.
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as path from "node:path";
3
- //#region ../types/dist/event-category-C5xZWqz6.mjs
3
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -533,6 +533,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
533
533
  */
534
534
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
535
535
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
536
+ /**
537
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
538
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
539
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
540
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
541
+ * for a camera with no attached sensor). Providers must not emit
542
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
543
+ * keeps it so (camera-grid is a named, deferred exception).
544
+ */
536
545
  EventCategory["ProviderMotion"] = "provider.motion";
537
546
  EventCategory["ProviderDetection"] = "provider.detection";
538
547
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5371,7 +5380,7 @@ var ZodIssueCode = {
5371
5380
  var ZodFirstPartyTypeKind;
5372
5381
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5373
5382
  //#endregion
5374
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5383
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5375
5384
  /**
5376
5385
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5377
5386
  * window to float samples (D455).
@@ -7400,7 +7409,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
7400
7409
  return DeviceRole;
7401
7410
  }({});
7402
7411
  //#endregion
7403
- //#region ../types/dist/composition-Dq42XWp0.mjs
7412
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7404
7413
  /**
7405
7414
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7406
7415
  *
@@ -7454,21 +7463,20 @@ function portable(target) {
7454
7463
  }
7455
7464
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7456
7465
  /**
7457
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7458
- * valutatore puro del suo FRONTE.
7466
+ * THE table "which boolean of this device type counts as HIGH", and the
7467
+ * pure evaluator of its EDGE.
7459
7468
  *
7460
- * Viveva dentro il builtin virtual-doorbell
7461
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7462
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7463
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7464
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7465
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7466
- * SPOSTA qui e il doorbell la ri-esporta.
7469
+ * ONE table for everyone who asks "is this device HIGH?": the
7470
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7471
+ * sensor markers, the orchestrator's camera-sensors bridge and
7472
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7473
+ * A second copy would diverge at the first cap added, and the symptom — "the
7474
+ * sensor rings the doorbell but does not record" — is exactly D62.
7467
7475
  *
7468
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7469
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7470
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7471
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7476
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7477
+ * two must not be unified: that one answers "what is the state WORD for a
7478
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7479
+ * boolean's EDGE counts". Deliberately different vocabularies.
7472
7480
  */
7473
7481
  /**
7474
7482
  * Known binary / switch source caps → the boolean slice field whose
@@ -7489,6 +7497,8 @@ var SOURCE_CAP_ACTIVE_FIELD = {
7489
7497
  vibration: "detected",
7490
7498
  tamper: "tampered"
7491
7499
  };
7500
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7501
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7492
7502
  /**
7493
7503
  * Error types for the safe expression engine. Two distinct classes so callers
7494
7504
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -9075,22 +9085,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9075
9085
  "package"
9076
9086
  ];
9077
9087
  /**
9078
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9079
- * un operatore può selezionare.
9088
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9089
+ * an operator can select.
9080
9090
  *
9081
- * Sono le tre offerte dallo step `object-detection`
9091
+ * They are the three offered by the `object-detection` step
9082
9092
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9083
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9084
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9085
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9086
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9093
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9094
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9095
+ * `package-detection`, whose enablement is driven by zones, and offering the
9096
+ * same word twice has already made an operator turn on the COCO proxy
9087
9097
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9088
9098
  *
9089
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9090
- * dello step e una seconda volta come union `FirstLevelMacro`
9091
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9092
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9093
- * successiva.
9099
+ * ONE list. Before today the same three were hand-written in the step's offer
9100
+ * and a second time as the union `FirstLevelMacro`
9101
+ * (`types/detection.ts`); a third copy for the recording trigger
9102
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9103
+ * invisible.
9094
9104
  */
9095
9105
  var FIRST_LEVEL_MACRO_CLASSES = [
9096
9106
  "person",
@@ -9155,17 +9165,17 @@ var RecordingStorageModeSchema = _enum([
9155
9165
  "on-device-decision"
9156
9166
  ]);
9157
9167
  /**
9158
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9159
- * tre offerte dallo step `object-detection`, da UNA lista
9168
+ * The macro classes that can open a recording window — the same
9169
+ * three offered by the `object-detection` step, from ONE list
9160
9170
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9161
9171
  */
9162
9172
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9163
9173
  /**
9164
- * True quando `values` non ripete un elemento.
9174
+ * True when `values` does not repeat an element.
9165
9175
  *
9166
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9167
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9168
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9176
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9177
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9178
+ * would count its windows twice in `segmentMissedByMs`.
9169
9179
  */
9170
9180
  var noDuplicates = (values) => new Set(values).size === values.length;
9171
9181
  /** Which detectors trigger an `events`-mode band. */
@@ -9173,34 +9183,34 @@ var RecordingTriggersSchema = object({
9173
9183
  motion: boolean().optional(),
9174
9184
  audioThresholdDbfs: number().optional(),
9175
9185
  /**
9176
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9177
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9178
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9179
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9186
+ * The macro classes whose detection opens a window. ABSENT = the source
9187
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9188
+ * trigger on, no class" is the same shape "enabled and records
9189
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9180
9190
  *
9181
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9182
- * quelle che hanno attraversato `enabledMacroClasses`, i
9183
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9184
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9185
- * finestre — vedi `recorder/object-trigger.ts`.
9191
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9192
+ * those that went through `enabledMacroClasses`, the
9193
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9194
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9195
+ * windows — see `recorder/object-trigger.ts`.
9186
9196
  */
9187
9197
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9188
9198
  /**
9189
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9190
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9199
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9200
+ * is not listened to; an empty array is rejected for the same reason as
9191
9201
  * `objectClasses`.
9192
9202
  *
9193
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9194
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9195
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9196
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9197
- * device (D12) — mai un elenco globale di cap.
9203
+ * These are SOURCE device ids, not cameras: the band names them, so the
9204
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9205
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9206
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9207
+ * device (D12) — never a global list of caps.
9198
9208
  *
9199
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9200
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9201
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9202
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9203
- * registrare.
9209
+ * What "high" means depends on the device TYPE and is not decided here:
9210
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9211
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9212
+ * the level: a contact found already open at runner restart does not
9213
+ * record.
9204
9214
  */
9205
9215
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9206
9216
  });
@@ -10277,7 +10287,7 @@ var DecoderSessionConfigSchema = object({
10277
10287
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10278
10288
  * thing: the logging settings document on the `system` cap. Two authorities
10279
10289
  * over the values is the exact defect
10280
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10290
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10281
10291
  * remove; re-introducing it from the cure side would be grotesque.
10282
10292
  *
10283
10293
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -11988,7 +11998,7 @@ var BrokerStatusEnum = _enum([
11988
11998
  "unreachable",
11989
11999
  "error"
11990
12000
  ]);
11991
- var BrokerInfoSchema$1 = object({
12001
+ var BrokerInfoSchema = object({
11992
12002
  /** Stable broker id. Persisted; survives addon restarts. */
11993
12003
  id: string(),
11994
12004
  /** Addon id of the provider that OWNS this broker.
@@ -12121,7 +12131,7 @@ var GetStateInputSchema = object({
12121
12131
  * HA: entity_id (returns the cached entity state). */
12122
12132
  key: string()
12123
12133
  });
12124
- method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12134
+ method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12125
12135
  kind: "mutation",
12126
12136
  auth: "admin"
12127
12137
  }), method(RemoveInputSchema, _void(), {
@@ -13730,7 +13740,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13730
13740
  *
13731
13741
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13732
13742
  * was wrong — corrected 2026-08-26 after the hop map
13733
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13743
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13734
13744
  * There is **no Zod parse at all** between the door and the engine: the
13735
13745
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13736
13746
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14486,7 +14496,17 @@ var SiteZoneSchema = object({
14486
14496
  var ProviderStatusSchema = object({
14487
14497
  connected: boolean(),
14488
14498
  deviceCount: number(),
14489
- error: string().optional()
14499
+ error: string().optional(),
14500
+ /**
14501
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14502
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14503
+ * which every provider bundles, so only a provider BUILT from D696-amended
14504
+ * code says it. ABSENT means an older provider that may still write it (raw
14505
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14506
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14507
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14508
+ */
14509
+ motionSliceWriter: literal("orchestrator").optional()
14490
14510
  });
14491
14511
  object({
14492
14512
  externalId: string(),
@@ -15284,28 +15304,10 @@ method(object({
15284
15304
  deviceIds: array(number()).readonly(),
15285
15305
  caps: array(string()).readonly().optional()
15286
15306
  }), record(string(), record(string(), unknown().nullable())));
15287
- object({
15288
- /** Ms epoch of the last press. null = never observed since this provider started. */
15289
- lastPressedAt: number().nullable(),
15290
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15291
- pressCountSinceStart: number()
15292
- });
15293
- object({
15294
- deviceId: number(),
15295
- timestamp: number()
15296
- });
15297
- /**
15298
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15299
- * virtual-doorbell trigger devices, exactly what the wrapper's
15300
- * `doorbellSources` persists (as `{ deviceId }` records).
15301
- */
15302
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15307
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15303
15308
  DeviceType.Camera;
15304
- DeviceType.Button, DeviceType.Camera;
15305
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15306
- DeviceType.Camera, DeviceType.Camera;
15307
15309
  /** A slot's editable value, discriminated on `slot`. */
15308
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15310
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15309
15311
  DeviceType.Camera;
15310
15312
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15311
15313
  state: literal("unreachable"),
@@ -15342,10 +15344,11 @@ var ExtensionSlotSourceSchema = object({
15342
15344
  });
15343
15345
  /**
15344
15346
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15345
- * reported read-only, named, never written through this contract (D62). The
15346
- * virtual doorbell's sources ring only while the wrapper is bound to the
15347
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15348
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15347
+ * reported read-only, named, never written through this contract (D62) — e.g.
15348
+ * a slot whose sources act only while a wrapper is bound to the host, a
15349
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15350
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15351
+ * not be read; it is not "inactive" (D315).
15349
15352
  */
15350
15353
  var ExtensionSlotActivationSchema = object({
15351
15354
  state: _enum([
@@ -15361,7 +15364,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15361
15364
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15362
15365
  slotRef: string(),
15363
15366
  slot: ExtensionSlotIdSchema,
15364
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15367
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15365
15368
  authority: string(),
15366
15369
  activation: ExtensionSlotActivationSchema.nullable(),
15367
15370
  value: ExtensionSlotValueSchema,
@@ -15639,7 +15642,7 @@ method(object({
15639
15642
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15640
15643
  * ends in a log line, and a log line is the thing the operator asked to stop
15641
15644
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15642
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15645
+ * 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
15643
15646
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15644
15647
  * media blackout were both diagnosed. The counters stay; this is where they can
15645
15648
  * be READ.
@@ -17015,7 +17018,7 @@ var BrokerStatusSchema$1 = _enum([
17015
17018
  "unreachable",
17016
17019
  "tls-error"
17017
17020
  ]);
17018
- var BrokerInfoSchema = object({
17021
+ var BrokerInfoSchema$1 = object({
17019
17022
  id: string(),
17020
17023
  name: string(),
17021
17024
  url: string(),
@@ -17078,7 +17081,7 @@ var StatusSchema = object({
17078
17081
  brokerCount: number(),
17079
17082
  embeddedRunning: boolean()
17080
17083
  });
17081
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17084
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17082
17085
  kind: "mutation",
17083
17086
  auth: "admin"
17084
17087
  }), method(IdInputSchema, _void(), {
@@ -19254,78 +19257,16 @@ var NcRuleInputSchema = object({
19254
19257
  */
19255
19258
  confirm: NcConfirmSchema.optional(),
19256
19259
  /**
19257
- * WAIT for face/plate recognition before saying anything.
19258
- *
19259
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19260
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19261
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19262
- * measured on this hub — and an `immediate` rule enqueues on the first object
19263
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19264
- * immediate path, and no amount of media re-resolution fixes a sentence.
19265
- *
19266
- * Only two honest answers exist, and this flag picks between them. It has
19267
- * effect ONLY on a rule that declares a recognition scope
19268
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19269
- * other rule there is nothing to wait for and the flag is inert.
19270
- *
19271
- * | value | what happens |
19272
- * | --- | --- |
19273
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19274
- * | 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) |
19275
- *
19276
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19277
- * on the addon cap path, and absent has to keep meaning exactly what every
19278
- * rule authored before this field meant.
19279
- *
19280
- * The cost of `true` is stated here because the editor states it too: a rule
19281
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19282
- * tests every zone the track visited and a `crossing` condition can no longer
19283
- * be satisfied, because a closed track carries no crossing.
19284
- */
19285
- waitForEnhancement: boolean().optional(),
19286
- /**
19287
- * GROUP a burst of subjects into ONE notification that grows.
19288
- *
19289
- * Seconds of quiet after the last matching subject before the burst is
19290
- * considered over. While it is open, the first subject enqueues immediately —
19291
- * **exactly as today, with no added latency** — and every real growth (a new
19292
- * subject, or a name confirmed on one already in it) REPLACES that
19293
- * notification with an updated one naming everybody. The push carries the
19294
- * group's own coalescing tag, so the phone replaces rather than stacks.
19295
- *
19296
- * `0` / absent = off, and off is today's behaviour byte for byte.
19297
- *
19298
- * ### Why an idle cutoff and not a window
19299
- *
19300
- * The measured seven-person arrival on device 590 spans 110 s with every
19301
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19302
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19303
- * 30 s is a widely shipped value for the same decision.
19304
- *
19305
- * ### What it replaces
19306
- *
19307
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19308
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19309
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19310
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19311
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19312
- * budget over GROUPS — which is what it always meant — and a growth is never
19313
- * throttled by the window its own first member spent.
19314
- *
19315
- * ### Interaction with {@link waitForEnhancement}
19316
- *
19317
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19318
- * TRACK CLOSE, so with both set the group is opened by the first member to
19319
- * CLOSE — already carrying its name — and grows as later members close. That
19320
- * is later, and complete. With grouping alone the group opens on the first
19321
- * object event and picks up names as they are confirmed, through the growth
19322
- * path. Neither combination fires twice for one subject.
19323
- *
19324
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19325
- * on the addon cap path, so absent must keep meaning what it meant before this
19326
- * field existed.
19260
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19261
+ * seconds; the rule sends ONE message when the window closes — early, once every
19262
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19263
+ * the rule matched, with one mosaic tile per subject.
19264
+ * Absent = plain immediate (one message per track, at the first match).
19265
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19266
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19267
+ * path does not run Zod defaults.
19327
19268
  */
19328
- groupIdleSec: number().int().min(0).max(600).optional()
19269
+ groupingSec: number().int().min(5).max(60).optional()
19329
19270
  });
19330
19271
  /**
19331
19272
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19373,9 +19314,10 @@ var NcRuleClearableKeySchema = _enum([
19373
19314
  "targetUsers",
19374
19315
  "snoozeOptions",
19375
19316
  "snoozeAllowGlobal",
19376
- "waitForEnhancement",
19377
19317
  "actions",
19378
19318
  "confirm",
19319
+ "groupingSec",
19320
+ "waitForEnhancement",
19379
19321
  "groupIdleSec"
19380
19322
  ]);
19381
19323
  var NcRuleUpdateInputSchema = object({
@@ -19397,9 +19339,25 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19397
19339
  */
19398
19340
  disabledTargetIds: array(string()).default([])
19399
19341
  });
19342
+ /**
19343
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19344
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19345
+ * job is not an open state, and the alarm stays instant.
19346
+ */
19347
+ var NcStateSummarySectionIdSchema = _enum([
19348
+ "device-state",
19349
+ "streams",
19350
+ "battery",
19351
+ "consumables",
19352
+ "tanks",
19353
+ "detection-blind",
19354
+ "nodes",
19355
+ "addons",
19356
+ "updates"
19357
+ ]);
19400
19358
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19401
19359
  var NcStateSummaryPreviewSectionSchema = object({
19402
- id: string(),
19360
+ id: NcStateSummarySectionIdSchema,
19403
19361
  status: _enum(["read", "unavailable"]),
19404
19362
  items: number().int().nonnegative(),
19405
19363
  reason: string().optional()
@@ -19409,7 +19367,7 @@ var NcStateSummaryPreviewSectionSchema = object({
19409
19367
  * a `rule` split (no `section`), one section's on a `section` split.
19410
19368
  */
19411
19369
  var NcStateSummaryPreviewRowSchema = object({
19412
- section: string().optional(),
19370
+ section: NcStateSummarySectionIdSchema.optional(),
19413
19371
  /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19414
19372
  title: string(),
19415
19373
  text: string(),
@@ -19846,6 +19804,29 @@ object({
19846
19804
  firstAt: number(),
19847
19805
  lastAt: number()
19848
19806
  });
19807
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
19808
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
19809
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
19810
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
19811
+ var NcAlarmModeDelaysListSchema = array(object({
19812
+ mode: AlarmArmModeSchema,
19813
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
19814
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
19815
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
19816
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
19817
+ var NcAlarmModeDelaysEditListSchema = array(object({
19818
+ mode: AlarmArmModeSchema,
19819
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19820
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19821
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
19822
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
19823
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
19824
+ var NcAlarmNonBlockingEditSchema = object({
19825
+ mode: AlarmArmModeSchema,
19826
+ deviceId: number().int().positive(),
19827
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
19828
+ nonBlocking: boolean()
19829
+ });
19849
19830
  /**
19850
19831
  * The per-mode lists, at most one per mode. Two lists for one mode have no
19851
19832
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -19854,10 +19835,10 @@ object({
19854
19835
  var NcAlarmNonBlockingListSchema = array(object({
19855
19836
  mode: AlarmArmModeSchema,
19856
19837
  deviceIds: array(number().int()).max(200)
19857
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
19838
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
19858
19839
  /**
19859
- * The three durations the panel's state machine runs on, who hears about its
19860
- * transitions, and which openings each mode tolerates.
19840
+ * The three durations — global, and per mode since D697 — the panel's state
19841
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
19861
19842
  *
19862
19843
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
19863
19844
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -19867,15 +19848,15 @@ var NcAlarmNonBlockingListSchema = array(object({
19867
19848
  */
19868
19849
  var NcAlarmSettingsSchema = object({
19869
19850
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
19870
- exitDelaySec: number().int().min(0).max(600),
19851
+ exitDelaySec: NcAlarmDelaySecSchema,
19871
19852
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
19872
- entryDelaySec: number().int().min(0).max(600),
19853
+ entryDelaySec: NcAlarmDelaySecSchema,
19873
19854
  /**
19874
19855
  * How long `triggered` lasts before the panel re-arms itself.
19875
19856
  * **0 = until an operator disarms it** — the behaviour before this field
19876
19857
  * existed, and therefore what an untouched install keeps doing.
19877
19858
  */
19878
- triggeredDurationSec: number().int().min(0).max(3600),
19859
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
19879
19860
  /**
19880
19861
  * Deprecated by D630 — every transition is announced. Parsed, never read;
19881
19862
  * remove after one release. Optional so an older admin's patch (which still
@@ -19898,17 +19879,30 @@ var NcAlarmSettingsSchema = object({
19898
19879
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
19899
19880
  * parses as "every opening blocks" — the behaviour it was written under.
19900
19881
  */
19901
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
19882
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
19883
+ /**
19884
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
19885
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
19886
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
19887
+ */
19888
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
19902
19889
  });
19903
19890
  /**
19904
19891
  * Every field optional — a tab edits one control at a time.
19905
19892
  *
19906
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
19907
- * remove an inner `.default()`, so a patch that never named the field would
19908
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
19909
- * (the same trap {@link NcRulePatchSchema} documents).
19893
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
19894
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
19895
+ * named the field would parse to `[]` and wipe every mode's list on an
19896
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
19897
+ * `modeDelays` means EDITS, not the stored overrides.
19910
19898
  */
19911
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
19899
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
19900
+ nonBlocking: NcAlarmNonBlockingListSchema,
19901
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
19902
+ modeDelays: NcAlarmModeDelaysEditListSchema,
19903
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
19904
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
19905
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
19912
19906
  /**
19913
19907
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
19914
19908
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20008,6 +20002,28 @@ var NcAlarmConfigSchema = object({
20008
20002
  */
20009
20003
  live: NcAlarmLiveSchema.optional()
20010
20004
  });
20005
+ var NcAlarmSensorReadingSchema = _enum([
20006
+ "open",
20007
+ "closed",
20008
+ "unknown",
20009
+ "not-opening"
20010
+ ]);
20011
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20012
+ var NcAlarmSensorStateSchema = object({
20013
+ deviceId: number().int().positive(),
20014
+ reading: NcAlarmSensorReadingSchema,
20015
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20016
+ state: string().nullable(),
20017
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20018
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20019
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20020
+ online: boolean().nullable()
20021
+ });
20022
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20023
+ var NcAlarmSensorsSchema = object({
20024
+ sensors: array(NcAlarmSensorStateSchema),
20025
+ readAt: number().int().nonnegative()
20026
+ });
20011
20027
  /**
20012
20028
  * ONE rule's demand on ONE camera's clip ring.
20013
20029
  *
@@ -20098,7 +20114,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20098
20114
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20099
20115
  kind: "mutation",
20100
20116
  auth: "admin"
20101
- });
20117
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20102
20118
  /**
20103
20119
  * Zod schemas for persisted record types.
20104
20120
  *
@@ -20445,12 +20461,26 @@ var CameraSensorRefSchema = object({
20445
20461
  function cameraSensorRefKey(ref) {
20446
20462
  return `${ref.addonId}/${ref.stableId}`;
20447
20463
  }
20448
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20449
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20450
- var CameraSensorsConfigSchema = object({
20451
- mode: CameraSensorsModeSchema,
20452
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20453
- }).superRefine((value, ctx) => {
20464
+ /**
20465
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20466
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20467
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20468
+ * camera's own motion counts only while a confirm sensor is active — with or
20469
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20470
+ */
20471
+ var CameraSensorModeSchema = _enum([
20472
+ "in-addition",
20473
+ "confirm",
20474
+ "replace"
20475
+ ]);
20476
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20477
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20478
+ /**
20479
+ * A camera's attached sensors. A CS-1 row carried an envelope
20480
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20481
+ * what the write stores (D672 rule 3), so it is never written again.
20482
+ */
20483
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20454
20484
  const seen = /* @__PURE__ */ new Set();
20455
20485
  value.sensors.forEach((ref, index) => {
20456
20486
  const key = cameraSensorRefKey(ref);
@@ -20462,39 +20492,41 @@ var CameraSensorsConfigSchema = object({
20462
20492
  seen.add(key);
20463
20493
  });
20464
20494
  });
20465
- var RefFields = {
20495
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20496
+ var AttachmentFields = {
20466
20497
  addonId: string(),
20467
- stableId: string()
20498
+ stableId: string(),
20499
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20468
20500
  };
20469
20501
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20470
20502
  object({
20471
- ...RefFields,
20503
+ ...AttachmentFields,
20472
20504
  state: literal("ready"),
20473
20505
  deviceId: number(),
20474
20506
  name: string(),
20475
20507
  caps: array(string()).readonly()
20476
20508
  }),
20477
20509
  object({
20478
- ...RefFields,
20510
+ ...AttachmentFields,
20479
20511
  state: literal("offline"),
20480
20512
  deviceId: number(),
20481
20513
  name: string(),
20482
20514
  reason: _enum(["offline", "disabled"])
20483
20515
  }),
20484
20516
  object({
20485
- ...RefFields,
20517
+ ...AttachmentFields,
20486
20518
  state: literal("missing"),
20487
20519
  reason: string()
20488
20520
  }),
20489
20521
  object({
20490
- ...RefFields,
20522
+ ...AttachmentFields,
20491
20523
  state: literal("unsupported"),
20492
20524
  deviceId: number(),
20493
20525
  name: string(),
20494
20526
  reason: string()
20495
20527
  }),
20496
20528
  object({
20497
- ...RefFields,
20529
+ ...AttachmentFields,
20498
20530
  state: literal("unreadable"),
20499
20531
  reason: string()
20500
20532
  })
@@ -20507,17 +20539,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20507
20539
  * say.
20508
20540
  */
20509
20541
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20542
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20543
+ var CameraOwnMotionSchema = _enum([
20544
+ "always",
20545
+ "when-confirmed",
20546
+ "never"
20547
+ ]);
20548
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20549
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20550
+ /**
20551
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20552
+ * never the effective value. The camera's `motion` slice is the effective one,
20553
+ * so a view of the camera's own row reads this.
20554
+ * - `atMs`: when the bridge received the camera's last own edge.
20555
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20556
+ * AND it is younger than the window the bridge counts the camera's own motion
20557
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20558
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20559
+ * within the window; a status read is a snapshot, so a live view re-asks.
20560
+ * Absent = this orchestrator has not received the camera's own motion since it
20561
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20562
+ */
20563
+ var CameraRawLevelSchema = object({
20564
+ detected: boolean(),
20565
+ atMs: number()
20566
+ });
20510
20567
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20511
- object({ state: literal("not-configured") }),
20568
+ object({
20569
+ state: literal("not-configured"),
20570
+ cameraLevel: CameraRawLevelSchema.optional()
20571
+ }),
20512
20572
  object({
20513
20573
  state: literal("unreadable"),
20514
- reason: string()
20574
+ reason: string(),
20575
+ cameraLevel: CameraRawLevelSchema.optional()
20515
20576
  }),
20516
20577
  object({
20517
20578
  state: literal("configured"),
20518
- mode: CameraSensorsModeSchema,
20579
+ cameraLevel: CameraRawLevelSchema.optional(),
20519
20580
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20520
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20581
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20582
+ cameraMotion: CameraOwnMotionSchema.optional(),
20583
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20584
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20521
20585
  })
20522
20586
  ]);
20523
20587
  Object.freeze({
@@ -21202,14 +21266,14 @@ var TrackSchema = object({
21202
21266
  * `derive-returned-null tile=standin`,
21203
21267
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21204
21268
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21205
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21269
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21206
21270
  * tile is a face/plate stand-in, a raster crop, or an icon.
21207
21271
  *
21208
21272
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21209
21273
  * the TILE ranked below native, so a track whose thumbnail landed natively
21210
21274
  * while its key frame missed carried no reason at all — three of the four
21211
21275
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21212
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21276
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21213
21277
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21214
21278
  * lastFrame copy source`), so its absence is now on this line whatever the
21215
21279
  * tile ended up being.
@@ -21296,7 +21360,7 @@ var MotionEventSchema = object({
21296
21360
  /**
21297
21361
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21298
21362
  * first entry is always `0`) of every genuine off→on transition the
21299
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21363
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21300
21364
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21301
21365
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21302
21366
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -21820,6 +21884,7 @@ var KeyEventSchema = object({
21820
21884
  bestEventId: string(),
21821
21885
  /** Track lifetime in ms (lastSeen - firstSeen). */
21822
21886
  windowMs: number().optional(),
21887
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
21823
21888
  ...TrackFlagFields,
21824
21889
  ...TrackRetrainFields
21825
21890
  });
@@ -23708,8 +23773,10 @@ var RunnerCameraConfigSchema = object({
23708
23773
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23709
23774
  * analyzer providers) running on decoded frames in this runner.
23710
23775
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
23711
- * analytics). The provider emits ProviderMotion
23712
- * events; the orchestrator forwards to `reportMotion`.
23776
+ * analytics). The provider emits its raw push as `ProviderMotion`
23777
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
23778
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
23779
+ * `reportMotion`.
23713
23780
  */
23714
23781
  motionSources: MotionSourcesSchema.default(["analyzer"]),
23715
23782
  /**
@@ -23886,7 +23953,15 @@ var RunnerLocalLoadSchema = object({
23886
23953
  hasGpu: boolean(),
23887
23954
  inferenceBackend: string().optional(),
23888
23955
  cpuCores: number().optional()
23889
- })
23956
+ }),
23957
+ /**
23958
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
23959
+ * runner never writes it. ABSENT = a runner older than D696, which still
23960
+ * writes the slice from its phase machine; the orchestrator does not write
23961
+ * while any runner node is absent-or-unanswered, so one slice never has two
23962
+ * writers.
23963
+ */
23964
+ motionSliceWriter: literal("orchestrator").optional()
23890
23965
  });
23891
23966
  /**
23892
23967
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -26582,7 +26657,7 @@ var ClipPlaybackSchema = object({
26582
26657
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26583
26658
  * time the camera is awake for its own reasons — is deliberately not a member:
26584
26659
  * it was proposed, it is free on the battery, and the operator declined it on
26585
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
26660
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26586
26661
  * D558 "Considered and not taken").
26587
26662
  */
26588
26663
  var ClipWakeSchema = _enum(["authorised"]);
@@ -26843,7 +26918,7 @@ DeviceType.Camera, method(object({
26843
26918
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
26844
26919
  * assumed the answer was a fan-out over everything a camera has. The
26845
26920
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
26846
- * solo"* — so the list is asked of one provider at a time and there is
26921
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
26847
26922
  * nothing to filter out of it.
26848
26923
  */
26849
26924
  provider: string().min(1)
@@ -28787,6 +28862,17 @@ object({
28787
28862
  deviceId: number(),
28788
28863
  status: DeviceStatusSchema
28789
28864
  });
28865
+ object({
28866
+ /** Ms epoch of the last press. null = never observed since this provider started. */
28867
+ lastPressedAt: number().nullable(),
28868
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
28869
+ pressCountSinceStart: number()
28870
+ });
28871
+ object({
28872
+ deviceId: number(),
28873
+ timestamp: number()
28874
+ });
28875
+ DeviceType.Button, DeviceType.Camera;
28790
28876
  /**
28791
28877
  * Enum-state sensor — a string value picked from a finite option set.
28792
28878
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30431,9 +30517,9 @@ object({
30431
30517
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30432
30518
  lastDetectedAt: number().nullable(),
30433
30519
  /**
30434
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30435
- * `null` while false and on every `Sensor` device (D475) — see that
30436
- * constant's doc for the one-authority rule.
30520
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30521
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30522
+ * device (D475) — see that constant's doc for the one-authority rule.
30437
30523
  *
30438
30524
  * ## Reading this field still arms nothing
30439
30525
  *
@@ -30451,38 +30537,41 @@ object({
30451
30537
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30452
30538
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30453
30539
  *
30454
- * ## Who writes it
30455
- *
30456
- * - **Cameras** — the runner's phase machine, `active → watching` on
30457
- * `cooldown_expired`, which then writes this slice with
30458
- * `detected: false` (`handlePhaseChanged` in
30459
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30460
- * matters most for the sources that only ever push a rising one:
30461
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30462
- * a false.
30463
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30464
- * provider pushes the false itself, from the upstream system's own
30465
- * state change. No phase machine is involved.
30466
- *
30467
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30468
- *
30469
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30470
- * phase machine is the sole writer for a camera. It is not.
30471
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30472
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30473
- * Hikvision's comment says why: it read THIS docblock, agreed the
30474
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30475
- * two authorities can disagree about one slice, which this repo
30476
- * forbids, and the doc said otherwise — which is worse than saying
30477
- * nothing, because it reads as verification.
30478
- *
30479
- * This predates D475 and is not fixed there: the fix touches every
30480
- * camera provider. Recorded in D475's Consequences. Do not restore the
30481
- * "sole writer" wording without also removing the other writers.
30540
+ * ## Who writes it (D696)
30541
+ *
30542
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30543
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30544
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30545
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30546
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30547
+ * device activity, as the camera's `motionSources` counts them. A source
30548
+ * that never sends a clear falls one window after its last `true`.
30549
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30550
+ * writes this slice (not even a boot seed), and neither does the runner's
30551
+ * phase machine: its phase is "a session is attached" for every on-motion
30552
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30553
+ * A provider's `isDetected` answers from the hub's slice.
30554
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30555
+ * imports) — the provider pushes both edges itself, from the upstream
30556
+ * system's own state change. That is the sensor's own fact.
30557
+ *
30558
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30559
+ * `motion` slice; each sensor writer is a named exemption.
30560
+ *
30561
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30562
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30563
+ * it, every provider seeded it at boot, and every session left it
30564
+ * `detected: true` after its detach.
30482
30565
  */
30483
30566
  autoClearAfterMs: number().nullable()
30484
30567
  });
30485
- object({
30568
+ /**
30569
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30570
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30571
+ * — both the cap event surface and the bus payload type alias to this
30572
+ * schema.
30573
+ */
30574
+ var MotionOnMotionChangedDataSchema = object({
30486
30575
  deviceId: number(),
30487
30576
  detected: boolean(),
30488
30577
  timestamp: number(),
@@ -32947,7 +33036,7 @@ var ReadGopBytesResultSchema = object({
32947
33036
  /**
32948
33037
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
32949
33038
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
32950
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
33039
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
32951
33040
  * a replay needs several seconds of native pixels, not one frame.
32952
33041
  *
32953
33042
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -35563,6 +35652,7 @@ DeviceType.Camera, method(object({
35563
35652
  detection: array(ZoneRuleSchema).readonly(),
35564
35653
  package: array(ZoneRuleSchema).readonly()
35565
35654
  });
35655
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
35566
35656
  /**
35567
35657
  * Accessory device helpers — shared across drivers.
35568
35658
  *
@@ -40541,6 +40631,12 @@ Object.freeze({
40541
40631
  addonId: null,
40542
40632
  access: "view"
40543
40633
  },
40634
+ "notificationRules.getAlarmSensors": {
40635
+ capName: "notification-rules",
40636
+ capScope: "system",
40637
+ addonId: null,
40638
+ access: "view"
40639
+ },
40544
40640
  "notificationRules.getClipRetentionAsks": {
40545
40641
  capName: "notification-rules",
40546
40642
  capScope: "system",
@@ -44119,6 +44215,11 @@ Object.freeze({
44119
44215
  }
44120
44216
  });
44121
44217
  Object.freeze({
44218
+ "accessories.getStatus": [{
44219
+ name: "deviceId",
44220
+ form: "single",
44221
+ optional: false
44222
+ }],
44122
44223
  "accessories.setChildHidden": [{
44123
44224
  name: "childDeviceId",
44124
44225
  form: "single",
@@ -44138,6 +44239,11 @@ Object.freeze({
44138
44239
  form: "single",
44139
44240
  optional: false
44140
44241
  }],
44242
+ "airQualitySensor.getStatus": [{
44243
+ name: "deviceId",
44244
+ form: "single",
44245
+ optional: false
44246
+ }],
44141
44247
  "alarmPanel.arm": [{
44142
44248
  name: "deviceId",
44143
44249
  form: "single",
@@ -44148,11 +44254,36 @@ Object.freeze({
44148
44254
  form: "single",
44149
44255
  optional: false
44150
44256
  }],
44257
+ "alarmPanel.getStatus": [{
44258
+ name: "deviceId",
44259
+ form: "single",
44260
+ optional: false
44261
+ }],
44151
44262
  "alarmPanel.trigger": [{
44152
44263
  name: "deviceId",
44153
44264
  form: "single",
44154
44265
  optional: false
44155
44266
  }],
44267
+ "ambientLightSensor.getStatus": [{
44268
+ name: "deviceId",
44269
+ form: "single",
44270
+ optional: false
44271
+ }],
44272
+ "audioAnalysis.applyDeviceSettingsPatch": [{
44273
+ name: "deviceId",
44274
+ form: "single",
44275
+ optional: false
44276
+ }],
44277
+ "audioAnalysis.getDeviceLiveContribution": [{
44278
+ name: "deviceId",
44279
+ form: "single",
44280
+ optional: false
44281
+ }],
44282
+ "audioAnalysis.getDeviceSettingsContribution": [{
44283
+ name: "deviceId",
44284
+ form: "single",
44285
+ optional: false
44286
+ }],
44156
44287
  "audioAnalysis.resolveDeviceSettings": [{
44157
44288
  name: "deviceId",
44158
44289
  form: "single",
@@ -44193,16 +44324,36 @@ Object.freeze({
44193
44324
  form: "single",
44194
44325
  optional: false
44195
44326
  }],
44327
+ "automationControl.getStatus": [{
44328
+ name: "deviceId",
44329
+ form: "single",
44330
+ optional: false
44331
+ }],
44196
44332
  "automationControl.trigger": [{
44197
44333
  name: "deviceId",
44198
44334
  form: "single",
44199
44335
  optional: false
44200
44336
  }],
44337
+ "battery.getStatus": [{
44338
+ name: "deviceId",
44339
+ form: "single",
44340
+ optional: false
44341
+ }],
44201
44342
  "battery.wakeForStream": [{
44202
44343
  name: "deviceId",
44203
44344
  form: "single",
44204
44345
  optional: false
44205
44346
  }],
44347
+ "binary.getStatus": [{
44348
+ name: "deviceId",
44349
+ form: "single",
44350
+ optional: false
44351
+ }],
44352
+ "brightness.getStatus": [{
44353
+ name: "deviceId",
44354
+ form: "single",
44355
+ optional: false
44356
+ }],
44206
44357
  "brightness.setBrightness": [{
44207
44358
  name: "deviceId",
44208
44359
  form: "single",
@@ -44218,6 +44369,26 @@ Object.freeze({
44218
44369
  form: "single",
44219
44370
  optional: false
44220
44371
  }],
44372
+ "cameraCredentials.getStatus": [{
44373
+ name: "deviceId",
44374
+ form: "single",
44375
+ optional: false
44376
+ }],
44377
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
44378
+ name: "deviceId",
44379
+ form: "single",
44380
+ optional: false
44381
+ }],
44382
+ "cameraGridLayout.getDeviceLiveContribution": [{
44383
+ name: "deviceId",
44384
+ form: "single",
44385
+ optional: false
44386
+ }],
44387
+ "cameraGridLayout.getDeviceSettingsContribution": [{
44388
+ name: "deviceId",
44389
+ form: "single",
44390
+ optional: false
44391
+ }],
44221
44392
  "cameraGridLayout.getLayout": [{
44222
44393
  name: "deviceId",
44223
44394
  form: "single",
@@ -44233,6 +44404,21 @@ Object.freeze({
44233
44404
  form: "single",
44234
44405
  optional: false
44235
44406
  }],
44407
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
44408
+ name: "deviceId",
44409
+ form: "single",
44410
+ optional: false
44411
+ }],
44412
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
44413
+ name: "deviceId",
44414
+ form: "single",
44415
+ optional: false
44416
+ }],
44417
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
44418
+ name: "deviceId",
44419
+ form: "single",
44420
+ optional: false
44421
+ }],
44236
44422
  "cameraStreams.getBrokerStreams": [{
44237
44423
  name: "deviceId",
44238
44424
  form: "single",
@@ -44258,6 +44444,16 @@ Object.freeze({
44258
44444
  form: "single",
44259
44445
  optional: false
44260
44446
  }],
44447
+ "carbonMonoxide.getStatus": [{
44448
+ name: "deviceId",
44449
+ form: "single",
44450
+ optional: false
44451
+ }],
44452
+ "climateControl.getStatus": [{
44453
+ name: "deviceId",
44454
+ form: "single",
44455
+ optional: false
44456
+ }],
44261
44457
  "climateControl.setFanMode": [{
44262
44458
  name: "deviceId",
44263
44459
  form: "single",
@@ -44298,16 +44494,41 @@ Object.freeze({
44298
44494
  form: "single",
44299
44495
  optional: false
44300
44496
  }],
44497
+ "color.getStatus": [{
44498
+ name: "deviceId",
44499
+ form: "single",
44500
+ optional: false
44501
+ }],
44301
44502
  "color.setColor": [{
44302
44503
  name: "deviceId",
44303
44504
  form: "single",
44304
44505
  optional: false
44305
44506
  }],
44507
+ "connectivity.getStatus": [{
44508
+ name: "deviceId",
44509
+ form: "single",
44510
+ optional: false
44511
+ }],
44512
+ "consumables.getStatus": [{
44513
+ name: "deviceId",
44514
+ form: "single",
44515
+ optional: false
44516
+ }],
44306
44517
  "consumables.reset": [{
44307
44518
  name: "deviceId",
44308
44519
  form: "single",
44309
44520
  optional: false
44310
44521
  }],
44522
+ "contact.getStatus": [{
44523
+ name: "deviceId",
44524
+ form: "single",
44525
+ optional: false
44526
+ }],
44527
+ "control.getStatus": [{
44528
+ name: "deviceId",
44529
+ form: "single",
44530
+ optional: false
44531
+ }],
44311
44532
  "control.setValue": [{
44312
44533
  name: "deviceId",
44313
44534
  form: "single",
@@ -44318,6 +44539,11 @@ Object.freeze({
44318
44539
  form: "single",
44319
44540
  optional: false
44320
44541
  }],
44542
+ "cover.getStatus": [{
44543
+ name: "deviceId",
44544
+ form: "single",
44545
+ optional: false
44546
+ }],
44321
44547
  "cover.open": [{
44322
44548
  name: "deviceId",
44323
44549
  form: "single",
@@ -44343,6 +44569,11 @@ Object.freeze({
44343
44569
  form: "single",
44344
44570
  optional: false
44345
44571
  }],
44572
+ "dayNight.getStatus": [{
44573
+ name: "deviceId",
44574
+ form: "single",
44575
+ optional: false
44576
+ }],
44346
44577
  "dayNight.setSettings": [{
44347
44578
  name: "deviceId",
44348
44579
  form: "single",
@@ -44353,11 +44584,31 @@ Object.freeze({
44353
44584
  form: "single",
44354
44585
  optional: true
44355
44586
  }],
44587
+ "detectionPipeline.applyDeviceSettingsPatch": [{
44588
+ name: "deviceId",
44589
+ form: "single",
44590
+ optional: false
44591
+ }],
44592
+ "detectionPipeline.getDeviceLiveContribution": [{
44593
+ name: "deviceId",
44594
+ form: "single",
44595
+ optional: false
44596
+ }],
44597
+ "detectionPipeline.getDeviceSettingsContribution": [{
44598
+ name: "deviceId",
44599
+ form: "single",
44600
+ optional: false
44601
+ }],
44356
44602
  "deviceAdminLink.getAdminLink": [{
44357
44603
  name: "deviceId",
44358
44604
  form: "single",
44359
44605
  optional: false
44360
44606
  }],
44607
+ "deviceAdoption.getStatus": [{
44608
+ name: "deviceId",
44609
+ form: "single",
44610
+ optional: false
44611
+ }],
44361
44612
  "deviceAdoption.release": [{
44362
44613
  name: "camDeviceId",
44363
44614
  form: "single",
@@ -44373,6 +44624,11 @@ Object.freeze({
44373
44624
  form: "single",
44374
44625
  optional: false
44375
44626
  }],
44627
+ "deviceDiscovery.getStatus": [{
44628
+ name: "deviceId",
44629
+ form: "single",
44630
+ optional: false
44631
+ }],
44376
44632
  "deviceDiscovery.listDiscovered": [{
44377
44633
  name: "deviceId",
44378
44634
  form: "single",
@@ -44392,6 +44648,21 @@ Object.freeze({
44392
44648
  form: "single",
44393
44649
  optional: false
44394
44650
  }],
44651
+ "deviceExport.applyDeviceSettingsPatch": [{
44652
+ name: "deviceId",
44653
+ form: "single",
44654
+ optional: false
44655
+ }],
44656
+ "deviceExport.getDeviceLiveContribution": [{
44657
+ name: "deviceId",
44658
+ form: "single",
44659
+ optional: false
44660
+ }],
44661
+ "deviceExport.getDeviceSettingsContribution": [{
44662
+ name: "deviceId",
44663
+ form: "single",
44664
+ optional: false
44665
+ }],
44395
44666
  "deviceExtension.listCandidates": [{
44396
44667
  name: "deviceId",
44397
44668
  form: "single",
@@ -44740,6 +45011,26 @@ Object.freeze({
44740
45011
  form: "single",
44741
45012
  optional: false
44742
45013
  }],
45014
+ "deviceStatus.getStatus": [{
45015
+ name: "deviceId",
45016
+ form: "single",
45017
+ optional: false
45018
+ }],
45019
+ "doorbell.getStatus": [{
45020
+ name: "deviceId",
45021
+ form: "single",
45022
+ optional: false
45023
+ }],
45024
+ "enumSensor.getStatus": [{
45025
+ name: "deviceId",
45026
+ form: "single",
45027
+ optional: false
45028
+ }],
45029
+ "eventEmitter.getStatus": [{
45030
+ name: "deviceId",
45031
+ form: "single",
45032
+ optional: false
45033
+ }],
44743
45034
  "faceGallery.getFaceByTrack": [{
44744
45035
  name: "deviceId",
44745
45036
  form: "single",
@@ -44754,6 +45045,11 @@ Object.freeze({
44754
45045
  form: "array",
44755
45046
  optional: true
44756
45047
  }],
45048
+ "fanControl.getStatus": [{
45049
+ name: "deviceId",
45050
+ form: "single",
45051
+ optional: false
45052
+ }],
44757
45053
  "fanControl.setDirection": [{
44758
45054
  name: "deviceId",
44759
45055
  form: "single",
@@ -44774,6 +45070,26 @@ Object.freeze({
44774
45070
  form: "single",
44775
45071
  optional: false
44776
45072
  }],
45073
+ "featureProbe.getStatus": [{
45074
+ name: "deviceId",
45075
+ form: "single",
45076
+ optional: false
45077
+ }],
45078
+ "flood.getStatus": [{
45079
+ name: "deviceId",
45080
+ form: "single",
45081
+ optional: false
45082
+ }],
45083
+ "gas.getStatus": [{
45084
+ name: "deviceId",
45085
+ form: "single",
45086
+ optional: false
45087
+ }],
45088
+ "humidifier.getStatus": [{
45089
+ name: "deviceId",
45090
+ form: "single",
45091
+ optional: false
45092
+ }],
44777
45093
  "humidifier.setMode": [{
44778
45094
  name: "deviceId",
44779
45095
  form: "single",
@@ -44789,11 +45105,26 @@ Object.freeze({
44789
45105
  form: "single",
44790
45106
  optional: false
44791
45107
  }],
45108
+ "humiditySensor.getStatus": [{
45109
+ name: "deviceId",
45110
+ form: "single",
45111
+ optional: false
45112
+ }],
45113
+ "image.getStatus": [{
45114
+ name: "deviceId",
45115
+ form: "single",
45116
+ optional: false
45117
+ }],
44792
45118
  "imageSettings.getOptions": [{
44793
45119
  name: "deviceId",
44794
45120
  form: "single",
44795
45121
  optional: false
44796
45122
  }],
45123
+ "imageSettings.getStatus": [{
45124
+ name: "deviceId",
45125
+ form: "single",
45126
+ optional: false
45127
+ }],
44797
45128
  "imageSettings.setSettings": [{
44798
45129
  name: "deviceId",
44799
45130
  form: "single",
@@ -44804,6 +45135,11 @@ Object.freeze({
44804
45135
  form: "single",
44805
45136
  optional: false
44806
45137
  }],
45138
+ "intercom.getStatus": [{
45139
+ name: "deviceId",
45140
+ form: "single",
45141
+ optional: false
45142
+ }],
44807
45143
  "intercom.handleAnswer": [{
44808
45144
  name: "deviceId",
44809
45145
  form: "single",
@@ -44834,6 +45170,11 @@ Object.freeze({
44834
45170
  form: "single",
44835
45171
  optional: false
44836
45172
  }],
45173
+ "lawnMowerControl.getStatus": [{
45174
+ name: "deviceId",
45175
+ form: "single",
45176
+ optional: false
45177
+ }],
44837
45178
  "lawnMowerControl.pause": [{
44838
45179
  name: "deviceId",
44839
45180
  form: "single",
@@ -44844,6 +45185,11 @@ Object.freeze({
44844
45185
  form: "single",
44845
45186
  optional: false
44846
45187
  }],
45188
+ "lockControl.getStatus": [{
45189
+ name: "deviceId",
45190
+ form: "single",
45191
+ optional: false
45192
+ }],
44847
45193
  "lockControl.lock": [{
44848
45194
  name: "deviceId",
44849
45195
  form: "single",
@@ -44859,6 +45205,11 @@ Object.freeze({
44859
45205
  form: "single",
44860
45206
  optional: false
44861
45207
  }],
45208
+ "mediaPlayer.getStatus": [{
45209
+ name: "deviceId",
45210
+ form: "single",
45211
+ optional: false
45212
+ }],
44862
45213
  "mediaPlayer.next": [{
44863
45214
  name: "deviceId",
44864
45215
  form: "single",
@@ -44919,6 +45270,11 @@ Object.freeze({
44919
45270
  form: "single",
44920
45271
  optional: false
44921
45272
  }],
45273
+ "motion.getStatus": [{
45274
+ name: "deviceId",
45275
+ form: "single",
45276
+ optional: false
45277
+ }],
44922
45278
  "motion.isDetected": [{
44923
45279
  name: "deviceId",
44924
45280
  form: "single",
@@ -44929,11 +45285,31 @@ Object.freeze({
44929
45285
  form: "single",
44930
45286
  optional: false
44931
45287
  }],
45288
+ "motionDetection.applyDeviceSettingsPatch": [{
45289
+ name: "deviceId",
45290
+ form: "single",
45291
+ optional: false
45292
+ }],
45293
+ "motionDetection.getDeviceLiveContribution": [{
45294
+ name: "deviceId",
45295
+ form: "single",
45296
+ optional: false
45297
+ }],
45298
+ "motionDetection.getDeviceSettingsContribution": [{
45299
+ name: "deviceId",
45300
+ form: "single",
45301
+ optional: false
45302
+ }],
44932
45303
  "motionDetection.removeCamera": [{
44933
45304
  name: "deviceId",
44934
45305
  form: "single",
44935
45306
  optional: false
44936
45307
  }],
45308
+ "motionTrigger.getStatus": [{
45309
+ name: "deviceId",
45310
+ form: "single",
45311
+ optional: false
45312
+ }],
44937
45313
  "motionTrigger.setMotionTrigger": [{
44938
45314
  name: "deviceId",
44939
45315
  form: "single",
@@ -44944,6 +45320,11 @@ Object.freeze({
44944
45320
  form: "single",
44945
45321
  optional: false
44946
45322
  }],
45323
+ "motionZones.getStatus": [{
45324
+ name: "deviceId",
45325
+ form: "single",
45326
+ optional: false
45327
+ }],
44947
45328
  "motionZones.setZone": [{
44948
45329
  name: "deviceId",
44949
45330
  form: "single",
@@ -44954,6 +45335,11 @@ Object.freeze({
44954
45335
  form: "single",
44955
45336
  optional: false
44956
45337
  }],
45338
+ "nativeObjectDetection.getStatus": [{
45339
+ name: "deviceId",
45340
+ form: "single",
45341
+ optional: false
45342
+ }],
44957
45343
  "nativeObjectDetection.setEnabled": [{
44958
45344
  name: "deviceId",
44959
45345
  form: "single",
@@ -44969,6 +45355,11 @@ Object.freeze({
44969
45355
  form: "single",
44970
45356
  optional: false
44971
45357
  }],
45358
+ "navigation.getStatus": [{
45359
+ name: "deviceId",
45360
+ form: "single",
45361
+ optional: false
45362
+ }],
44972
45363
  "navigation.goToPoint": [{
44973
45364
  name: "deviceId",
44974
45365
  form: "single",
@@ -45014,6 +45405,11 @@ Object.freeze({
45014
45405
  form: "single",
45015
45406
  optional: false
45016
45407
  }],
45408
+ "networkLink.getStatus": [{
45409
+ name: "deviceId",
45410
+ form: "single",
45411
+ optional: false
45412
+ }],
45017
45413
  "networkQuality.getDeviceStats": [{
45018
45414
  name: "deviceId",
45019
45415
  form: "single",
@@ -45024,6 +45420,11 @@ Object.freeze({
45024
45420
  form: "single",
45025
45421
  optional: false
45026
45422
  }],
45423
+ "notificationRules.getAlarmSensors": [{
45424
+ name: "deviceIds",
45425
+ form: "array",
45426
+ optional: false
45427
+ }],
45027
45428
  "notificationRules.setDeviceMuted": [{
45028
45429
  name: "deviceId",
45029
45430
  form: "single",
@@ -45034,11 +45435,26 @@ Object.freeze({
45034
45435
  form: "single",
45035
45436
  optional: false
45036
45437
  }],
45438
+ "notifier.getStatus": [{
45439
+ name: "deviceId",
45440
+ form: "single",
45441
+ optional: false
45442
+ }],
45037
45443
  "notifier.send": [{
45038
45444
  name: "deviceId",
45039
45445
  form: "single",
45040
45446
  optional: false
45041
45447
  }],
45448
+ "numericSensor.getStatus": [{
45449
+ name: "deviceId",
45450
+ form: "single",
45451
+ optional: false
45452
+ }],
45453
+ "osd.getStatus": [{
45454
+ name: "deviceId",
45455
+ form: "single",
45456
+ optional: false
45457
+ }],
45042
45458
  "osd.setOverlay": [{
45043
45459
  name: "deviceId",
45044
45460
  form: "single",
@@ -45098,6 +45514,11 @@ Object.freeze({
45098
45514
  form: "single",
45099
45515
  optional: false
45100
45516
  }],
45517
+ "petFeeder.getStatus": [{
45518
+ name: "deviceId",
45519
+ form: "single",
45520
+ optional: false
45521
+ }],
45101
45522
  "petFeeder.markFoodReplenished": [{
45102
45523
  name: "deviceId",
45103
45524
  form: "single",
@@ -45133,6 +45554,11 @@ Object.freeze({
45133
45554
  form: "single",
45134
45555
  optional: false
45135
45556
  }],
45557
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
45558
+ name: "deviceId",
45559
+ form: "single",
45560
+ optional: false
45561
+ }],
45136
45562
  "pipelineAnalytics.clearTrackRelation": [{
45137
45563
  name: "deviceId",
45138
45564
  form: "single",
@@ -45173,6 +45599,16 @@ Object.freeze({
45173
45599
  form: "single",
45174
45600
  optional: false
45175
45601
  }],
45602
+ "pipelineAnalytics.getDeviceLiveContribution": [{
45603
+ name: "deviceId",
45604
+ form: "single",
45605
+ optional: false
45606
+ }],
45607
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
45608
+ name: "deviceId",
45609
+ form: "single",
45610
+ optional: false
45611
+ }],
45176
45612
  "pipelineAnalytics.getEventDensity": [{
45177
45613
  name: "deviceId",
45178
45614
  form: "single",
@@ -45388,6 +45824,11 @@ Object.freeze({
45388
45824
  form: "single",
45389
45825
  optional: true
45390
45826
  }],
45827
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
45828
+ name: "deviceId",
45829
+ form: "single",
45830
+ optional: false
45831
+ }],
45391
45832
  "pipelineOrchestrator.assignAudio": [{
45392
45833
  name: "deviceId",
45393
45834
  form: "single",
@@ -45433,6 +45874,16 @@ Object.freeze({
45433
45874
  form: "single",
45434
45875
  optional: false
45435
45876
  }],
45877
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
45878
+ name: "deviceId",
45879
+ form: "single",
45880
+ optional: false
45881
+ }],
45882
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
45883
+ name: "deviceId",
45884
+ form: "single",
45885
+ optional: false
45886
+ }],
45436
45887
  "pipelineOrchestrator.getPipelineAssignment": [{
45437
45888
  name: "deviceId",
45438
45889
  form: "single",
@@ -45553,11 +46004,31 @@ Object.freeze({
45553
46004
  form: "single",
45554
46005
  optional: true
45555
46006
  }],
46007
+ "powerMeter.getStatus": [{
46008
+ name: "deviceId",
46009
+ form: "single",
46010
+ optional: false
46011
+ }],
46012
+ "presence.getStatus": [{
46013
+ name: "deviceId",
46014
+ form: "single",
46015
+ optional: false
46016
+ }],
46017
+ "pressureSensor.getStatus": [{
46018
+ name: "deviceId",
46019
+ form: "single",
46020
+ optional: false
46021
+ }],
45556
46022
  "privacyMask.getOptions": [{
45557
46023
  name: "deviceId",
45558
46024
  form: "single",
45559
46025
  optional: false
45560
46026
  }],
46027
+ "privacyMask.getStatus": [{
46028
+ name: "deviceId",
46029
+ form: "single",
46030
+ optional: false
46031
+ }],
45561
46032
  "privacyMask.setAudioEnabled": [{
45562
46033
  name: "deviceId",
45563
46034
  form: "single",
@@ -45608,6 +46079,11 @@ Object.freeze({
45608
46079
  form: "single",
45609
46080
  optional: false
45610
46081
  }],
46082
+ "ptz.getStatus": [{
46083
+ name: "deviceId",
46084
+ form: "single",
46085
+ optional: false
46086
+ }],
45611
46087
  "ptz.goHome": [{
45612
46088
  name: "deviceId",
45613
46089
  form: "single",
@@ -45698,6 +46174,11 @@ Object.freeze({
45698
46174
  form: "single",
45699
46175
  optional: false
45700
46176
  }],
46177
+ "recordingArchive.applyDeviceSettingsPatch": [{
46178
+ name: "deviceId",
46179
+ form: "single",
46180
+ optional: false
46181
+ }],
45701
46182
  "recordingArchive.deleteFootprint": [{
45702
46183
  name: "deviceId",
45703
46184
  form: "single",
@@ -45718,6 +46199,21 @@ Object.freeze({
45718
46199
  form: "single",
45719
46200
  optional: false
45720
46201
  }],
46202
+ "recordingArchive.getDeviceLiveContribution": [{
46203
+ name: "deviceId",
46204
+ form: "single",
46205
+ optional: false
46206
+ }],
46207
+ "recordingArchive.getDeviceSettingsContribution": [{
46208
+ name: "deviceId",
46209
+ form: "single",
46210
+ optional: false
46211
+ }],
46212
+ "recordingArchive.getStatus": [{
46213
+ name: "deviceId",
46214
+ form: "single",
46215
+ optional: false
46216
+ }],
45721
46217
  "recordingArchive.listOpsLog": [{
45722
46218
  name: "deviceId",
45723
46219
  form: "single",
@@ -45803,6 +46299,11 @@ Object.freeze({
45803
46299
  form: "single",
45804
46300
  optional: false
45805
46301
  }],
46302
+ "recordingOnboard.getStatus": [{
46303
+ name: "deviceId",
46304
+ form: "single",
46305
+ optional: false
46306
+ }],
45806
46307
  "recordingOnboard.setSettings": [{
45807
46308
  name: "deviceId",
45808
46309
  form: "single",
@@ -45833,6 +46334,11 @@ Object.freeze({
45833
46334
  form: "single",
45834
46335
  optional: false
45835
46336
  }],
46337
+ "sceneMonitor.getStatus": [{
46338
+ name: "deviceId",
46339
+ form: "single",
46340
+ optional: false
46341
+ }],
45836
46342
  "sceneMonitor.listScenes": [{
45837
46343
  name: "deviceId",
45838
46344
  form: "single",
@@ -45858,6 +46364,11 @@ Object.freeze({
45858
46364
  form: "single",
45859
46365
  optional: false
45860
46366
  }],
46367
+ "scriptRunner.getStatus": [{
46368
+ name: "deviceId",
46369
+ form: "single",
46370
+ optional: false
46371
+ }],
45861
46372
  "scriptRunner.run": [{
45862
46373
  name: "deviceId",
45863
46374
  form: "single",
@@ -45868,11 +46379,31 @@ Object.freeze({
45868
46379
  form: "single",
45869
46380
  optional: false
45870
46381
  }],
46382
+ "smoke.getStatus": [{
46383
+ name: "deviceId",
46384
+ form: "single",
46385
+ optional: false
46386
+ }],
46387
+ "snapshot.applyDeviceSettingsPatch": [{
46388
+ name: "deviceId",
46389
+ form: "single",
46390
+ optional: false
46391
+ }],
45871
46392
  "snapshot.getDebugState": [{
45872
46393
  name: "deviceId",
45873
46394
  form: "single",
45874
46395
  optional: false
45875
46396
  }],
46397
+ "snapshot.getDeviceLiveContribution": [{
46398
+ name: "deviceId",
46399
+ form: "single",
46400
+ optional: false
46401
+ }],
46402
+ "snapshot.getDeviceSettingsContribution": [{
46403
+ name: "deviceId",
46404
+ form: "single",
46405
+ optional: false
46406
+ }],
45876
46407
  "snapshot.getSnapshot": [{
45877
46408
  name: "deviceId",
45878
46409
  form: "single",
@@ -45889,6 +46420,11 @@ Object.freeze({
45889
46420
  form: "array",
45890
46421
  optional: false
45891
46422
  }],
46423
+ "snapshot.getStatus": [{
46424
+ name: "deviceId",
46425
+ form: "single",
46426
+ optional: false
46427
+ }],
45892
46428
  "snapshot.invalidateCache": [{
45893
46429
  name: "deviceId",
45894
46430
  form: "single",
@@ -45899,6 +46435,11 @@ Object.freeze({
45899
46435
  form: "single",
45900
46436
  optional: false
45901
46437
  }],
46438
+ "streamBroker.applyDeviceSettingsPatch": [{
46439
+ name: "deviceId",
46440
+ form: "single",
46441
+ optional: false
46442
+ }],
45902
46443
  "streamBroker.assignProfile": [{
45903
46444
  name: "deviceId",
45904
46445
  form: "single",
@@ -45914,6 +46455,16 @@ Object.freeze({
45914
46455
  form: "single",
45915
46456
  optional: false
45916
46457
  }],
46458
+ "streamBroker.getDeviceLiveContribution": [{
46459
+ name: "deviceId",
46460
+ form: "single",
46461
+ optional: false
46462
+ }],
46463
+ "streamBroker.getDeviceSettingsContribution": [{
46464
+ name: "deviceId",
46465
+ form: "single",
46466
+ optional: false
46467
+ }],
45917
46468
  "streamBroker.getStreamWithCodec": [{
45918
46469
  name: "deviceId",
45919
46470
  form: "single",
@@ -45974,16 +46525,46 @@ Object.freeze({
45974
46525
  form: "single",
45975
46526
  optional: false
45976
46527
  }],
46528
+ "streamParams.getStatus": [{
46529
+ name: "deviceId",
46530
+ form: "single",
46531
+ optional: false
46532
+ }],
45977
46533
  "streamParams.setProfile": [{
45978
46534
  name: "deviceId",
45979
46535
  form: "single",
45980
46536
  optional: false
45981
46537
  }],
46538
+ "switch.getStatus": [{
46539
+ name: "deviceId",
46540
+ form: "single",
46541
+ optional: false
46542
+ }],
45982
46543
  "switch.setState": [{
45983
46544
  name: "deviceId",
45984
46545
  form: "single",
45985
46546
  optional: false
45986
46547
  }],
46548
+ "tamper.getStatus": [{
46549
+ name: "deviceId",
46550
+ form: "single",
46551
+ optional: false
46552
+ }],
46553
+ "temperatureSensor.getStatus": [{
46554
+ name: "deviceId",
46555
+ form: "single",
46556
+ optional: false
46557
+ }],
46558
+ "update.getStatus": [{
46559
+ name: "deviceId",
46560
+ form: "single",
46561
+ optional: false
46562
+ }],
46563
+ "vacuumControl.getStatus": [{
46564
+ name: "deviceId",
46565
+ form: "single",
46566
+ optional: false
46567
+ }],
45987
46568
  "vacuumControl.locate": [{
45988
46569
  name: "deviceId",
45989
46570
  form: "single",
@@ -46019,6 +46600,11 @@ Object.freeze({
46019
46600
  form: "single",
46020
46601
  optional: false
46021
46602
  }],
46603
+ "valve.getStatus": [{
46604
+ name: "deviceId",
46605
+ form: "single",
46606
+ optional: false
46607
+ }],
46022
46608
  "valve.open": [{
46023
46609
  name: "deviceId",
46024
46610
  form: "single",
@@ -46034,6 +46620,11 @@ Object.freeze({
46034
46620
  form: "single",
46035
46621
  optional: false
46036
46622
  }],
46623
+ "vibration.getStatus": [{
46624
+ name: "deviceId",
46625
+ form: "single",
46626
+ optional: false
46627
+ }],
46037
46628
  "videoclips.dialClipStream": [{
46038
46629
  name: "deviceId",
46039
46630
  form: "single",
@@ -46069,6 +46660,11 @@ Object.freeze({
46069
46660
  form: "single",
46070
46661
  optional: false
46071
46662
  }],
46663
+ "waterHeater.getStatus": [{
46664
+ name: "deviceId",
46665
+ form: "single",
46666
+ optional: false
46667
+ }],
46072
46668
  "waterHeater.setAway": [{
46073
46669
  name: "deviceId",
46074
46670
  form: "single",
@@ -46084,6 +46680,11 @@ Object.freeze({
46084
46680
  form: "single",
46085
46681
  optional: false
46086
46682
  }],
46683
+ "weather.getStatus": [{
46684
+ name: "deviceId",
46685
+ form: "single",
46686
+ optional: false
46687
+ }],
46087
46688
  "webrtcSession.addIceCandidate": [{
46088
46689
  name: "deviceId",
46089
46690
  form: "single",
@@ -46821,7 +47422,7 @@ var PreviewTextField = string().max(200);
46821
47422
  * Whether the notification's preview is a STILL or a short animation.
46822
47423
  *
46823
47424
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
46824
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
47425
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
46825
47426
  * night reads better as three seconds of motion than as one frame of it. Both
46826
47427
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
46827
47428
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -46959,6 +47560,7 @@ TimelapseRuleInputSchema.extend({
46959
47560
  createdAt: number(),
46960
47561
  updatedAt: number()
46961
47562
  });
47563
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
46962
47564
  object({
46963
47565
  /**
46964
47566
  * Fraction of the box's own size added on EACH side before cutting.