@camstack/addon-notifiers 1.2.122 → 1.2.123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +836 -234
  2. package/dist/addon.mjs +836 -234
  3. package/package.json +1 -3
package/dist/addon.js CHANGED
@@ -29,7 +29,7 @@ let node_fs = require("node:fs");
29
29
  let node_path = require("node:path");
30
30
  node_path = __toESM(node_path);
31
31
  let node_url = require("node:url");
32
- //#region ../types/dist/event-category-C5xZWqz6.mjs
32
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
33
33
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
34
34
  EventCategory["SystemBoot"] = "system.boot";
35
35
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -562,6 +562,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
562
562
  */
563
563
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
564
564
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
565
+ /**
566
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
567
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
568
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
569
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
570
+ * for a camera with no attached sensor). Providers must not emit
571
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
572
+ * keeps it so (camera-grid is a named, deferred exception).
573
+ */
565
574
  EventCategory["ProviderMotion"] = "provider.motion";
566
575
  EventCategory["ProviderDetection"] = "provider.detection";
567
576
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5400,7 +5409,7 @@ var ZodIssueCode = {
5400
5409
  var ZodFirstPartyTypeKind;
5401
5410
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5402
5411
  //#endregion
5403
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5412
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5404
5413
  /**
5405
5414
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5406
5415
  * window to float samples (D455).
@@ -7451,7 +7460,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
7451
7460
  return DeviceRole;
7452
7461
  }({});
7453
7462
  //#endregion
7454
- //#region ../types/dist/composition-Dq42XWp0.mjs
7463
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7455
7464
  /**
7456
7465
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7457
7466
  *
@@ -7505,21 +7514,20 @@ function portable(target) {
7505
7514
  }
7506
7515
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7507
7516
  /**
7508
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7509
- * valutatore puro del suo FRONTE.
7517
+ * THE table "which boolean of this device type counts as HIGH", and the
7518
+ * pure evaluator of its EDGE.
7510
7519
  *
7511
- * Viveva dentro il builtin virtual-doorbell
7512
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7513
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7514
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7515
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7516
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7517
- * SPOSTA qui e il doorbell la ri-esporta.
7520
+ * ONE table for everyone who asks "is this device HIGH?": the
7521
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7522
+ * sensor markers, the orchestrator's camera-sensors bridge and
7523
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7524
+ * A second copy would diverge at the first cap added, and the symptom — "the
7525
+ * sensor rings the doorbell but does not record" — is exactly D62.
7518
7526
  *
7519
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7520
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7521
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7522
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7527
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7528
+ * two must not be unified: that one answers "what is the state WORD for a
7529
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7530
+ * boolean's EDGE counts". Deliberately different vocabularies.
7523
7531
  */
7524
7532
  /**
7525
7533
  * Known binary / switch source caps → the boolean slice field whose
@@ -7540,6 +7548,8 @@ var SOURCE_CAP_ACTIVE_FIELD = {
7540
7548
  vibration: "detected",
7541
7549
  tamper: "tampered"
7542
7550
  };
7551
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7552
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7543
7553
  /**
7544
7554
  * Error types for the safe expression engine. Two distinct classes so callers
7545
7555
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -9243,22 +9253,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9243
9253
  "package"
9244
9254
  ];
9245
9255
  /**
9246
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9247
- * un operatore può selezionare.
9256
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9257
+ * an operator can select.
9248
9258
  *
9249
- * Sono le tre offerte dallo step `object-detection`
9259
+ * They are the three offered by the `object-detection` step
9250
9260
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9251
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9252
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9253
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9254
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9261
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9262
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9263
+ * `package-detection`, whose enablement is driven by zones, and offering the
9264
+ * same word twice has already made an operator turn on the COCO proxy
9255
9265
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9256
9266
  *
9257
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9258
- * dello step e una seconda volta come union `FirstLevelMacro`
9259
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9260
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9261
- * successiva.
9267
+ * ONE list. Before today the same three were hand-written in the step's offer
9268
+ * and a second time as the union `FirstLevelMacro`
9269
+ * (`types/detection.ts`); a third copy for the recording trigger
9270
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9271
+ * invisible.
9262
9272
  */
9263
9273
  var FIRST_LEVEL_MACRO_CLASSES = [
9264
9274
  "person",
@@ -9323,17 +9333,17 @@ var RecordingStorageModeSchema = _enum([
9323
9333
  "on-device-decision"
9324
9334
  ]);
9325
9335
  /**
9326
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9327
- * tre offerte dallo step `object-detection`, da UNA lista
9336
+ * The macro classes that can open a recording window — the same
9337
+ * three offered by the `object-detection` step, from ONE list
9328
9338
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9329
9339
  */
9330
9340
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9331
9341
  /**
9332
- * True quando `values` non ripete un elemento.
9342
+ * True when `values` does not repeat an element.
9333
9343
  *
9334
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9335
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9336
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9344
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9345
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9346
+ * would count its windows twice in `segmentMissedByMs`.
9337
9347
  */
9338
9348
  var noDuplicates = (values) => new Set(values).size === values.length;
9339
9349
  /** Which detectors trigger an `events`-mode band. */
@@ -9341,34 +9351,34 @@ var RecordingTriggersSchema = object({
9341
9351
  motion: boolean().optional(),
9342
9352
  audioThresholdDbfs: number().optional(),
9343
9353
  /**
9344
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9345
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9346
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9347
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9354
+ * The macro classes whose detection opens a window. ABSENT = the source
9355
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9356
+ * trigger on, no class" is the same shape "enabled and records
9357
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9348
9358
  *
9349
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9350
- * quelle che hanno attraversato `enabledMacroClasses`, i
9351
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9352
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9353
- * finestre — vedi `recorder/object-trigger.ts`.
9359
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9360
+ * those that went through `enabledMacroClasses`, the
9361
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9362
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9363
+ * windows — see `recorder/object-trigger.ts`.
9354
9364
  */
9355
9365
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9356
9366
  /**
9357
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9358
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9367
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9368
+ * is not listened to; an empty array is rejected for the same reason as
9359
9369
  * `objectClasses`.
9360
9370
  *
9361
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9362
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9363
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9364
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9365
- * device (D12) — mai un elenco globale di cap.
9371
+ * These are SOURCE device ids, not cameras: the band names them, so the
9372
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9373
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9374
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9375
+ * device (D12) — never a global list of caps.
9366
9376
  *
9367
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9368
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9369
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9370
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9371
- * registrare.
9377
+ * What "high" means depends on the device TYPE and is not decided here:
9378
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9379
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9380
+ * the level: a contact found already open at runner restart does not
9381
+ * record.
9372
9382
  */
9373
9383
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9374
9384
  });
@@ -10445,7 +10455,7 @@ var DecoderSessionConfigSchema = object({
10445
10455
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10446
10456
  * thing: the logging settings document on the `system` cap. Two authorities
10447
10457
  * over the values is the exact defect
10448
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10458
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10449
10459
  * remove; re-introducing it from the cure side would be grotesque.
10450
10460
  *
10451
10461
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -12175,7 +12185,7 @@ var BrokerStatusEnum = _enum([
12175
12185
  "unreachable",
12176
12186
  "error"
12177
12187
  ]);
12178
- var BrokerInfoSchema$1 = object({
12188
+ var BrokerInfoSchema = object({
12179
12189
  /** Stable broker id. Persisted; survives addon restarts. */
12180
12190
  id: string(),
12181
12191
  /** Addon id of the provider that OWNS this broker.
@@ -12308,7 +12318,7 @@ var GetStateInputSchema = object({
12308
12318
  * HA: entity_id (returns the cached entity state). */
12309
12319
  key: string()
12310
12320
  });
12311
- method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12321
+ method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12312
12322
  kind: "mutation",
12313
12323
  auth: "admin"
12314
12324
  }), method(RemoveInputSchema, _void(), {
@@ -13917,7 +13927,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13917
13927
  *
13918
13928
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13919
13929
  * was wrong — corrected 2026-08-26 after the hop map
13920
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13930
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13921
13931
  * There is **no Zod parse at all** between the door and the engine: the
13922
13932
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13923
13933
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14673,7 +14683,17 @@ var SiteZoneSchema = object({
14673
14683
  var ProviderStatusSchema = object({
14674
14684
  connected: boolean(),
14675
14685
  deviceCount: number(),
14676
- error: string().optional()
14686
+ error: string().optional(),
14687
+ /**
14688
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14689
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14690
+ * which every provider bundles, so only a provider BUILT from D696-amended
14691
+ * code says it. ABSENT means an older provider that may still write it (raw
14692
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14693
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14694
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14695
+ */
14696
+ motionSliceWriter: literal("orchestrator").optional()
14677
14697
  });
14678
14698
  object({
14679
14699
  externalId: string(),
@@ -15471,28 +15491,10 @@ method(object({
15471
15491
  deviceIds: array(number()).readonly(),
15472
15492
  caps: array(string()).readonly().optional()
15473
15493
  }), record(string(), record(string(), unknown().nullable())));
15474
- object({
15475
- /** Ms epoch of the last press. null = never observed since this provider started. */
15476
- lastPressedAt: number().nullable(),
15477
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15478
- pressCountSinceStart: number()
15479
- });
15480
- object({
15481
- deviceId: number(),
15482
- timestamp: number()
15483
- });
15484
- /**
15485
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15486
- * virtual-doorbell trigger devices, exactly what the wrapper's
15487
- * `doorbellSources` persists (as `{ deviceId }` records).
15488
- */
15489
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15494
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15490
15495
  DeviceType.Camera;
15491
- DeviceType.Button, DeviceType.Camera;
15492
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15493
- DeviceType.Camera, DeviceType.Camera;
15494
15496
  /** A slot's editable value, discriminated on `slot`. */
15495
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15497
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15496
15498
  DeviceType.Camera;
15497
15499
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15498
15500
  state: literal("unreachable"),
@@ -15529,10 +15531,11 @@ var ExtensionSlotSourceSchema = object({
15529
15531
  });
15530
15532
  /**
15531
15533
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15532
- * reported read-only, named, never written through this contract (D62). The
15533
- * virtual doorbell's sources ring only while the wrapper is bound to the
15534
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15535
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15534
+ * reported read-only, named, never written through this contract (D62) — e.g.
15535
+ * a slot whose sources act only while a wrapper is bound to the host, a
15536
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15537
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15538
+ * not be read; it is not "inactive" (D315).
15536
15539
  */
15537
15540
  var ExtensionSlotActivationSchema = object({
15538
15541
  state: _enum([
@@ -15548,7 +15551,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15548
15551
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15549
15552
  slotRef: string(),
15550
15553
  slot: ExtensionSlotIdSchema,
15551
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15554
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15552
15555
  authority: string(),
15553
15556
  activation: ExtensionSlotActivationSchema.nullable(),
15554
15557
  value: ExtensionSlotValueSchema,
@@ -15826,7 +15829,7 @@ method(object({
15826
15829
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15827
15830
  * ends in a log line, and a log line is the thing the operator asked to stop
15828
15831
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15829
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15832
+ * 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
15830
15833
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15831
15834
  * media blackout were both diagnosed. The counters stay; this is where they can
15832
15835
  * be READ.
@@ -17202,7 +17205,7 @@ var BrokerStatusSchema$1 = _enum([
17202
17205
  "unreachable",
17203
17206
  "tls-error"
17204
17207
  ]);
17205
- var BrokerInfoSchema = object({
17208
+ var BrokerInfoSchema$1 = object({
17206
17209
  id: string(),
17207
17210
  name: string(),
17208
17211
  url: string(),
@@ -17265,7 +17268,7 @@ var StatusSchema = object({
17265
17268
  brokerCount: number(),
17266
17269
  embeddedRunning: boolean()
17267
17270
  });
17268
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17271
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17269
17272
  kind: "mutation",
17270
17273
  auth: "admin"
17271
17274
  }), method(IdInputSchema, _void(), {
@@ -19455,78 +19458,16 @@ var NcRuleInputSchema = object({
19455
19458
  */
19456
19459
  confirm: NcConfirmSchema.optional(),
19457
19460
  /**
19458
- * WAIT for face/plate recognition before saying anything.
19459
- *
19460
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19461
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19462
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19463
- * measured on this hub — and an `immediate` rule enqueues on the first object
19464
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19465
- * immediate path, and no amount of media re-resolution fixes a sentence.
19466
- *
19467
- * Only two honest answers exist, and this flag picks between them. It has
19468
- * effect ONLY on a rule that declares a recognition scope
19469
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19470
- * other rule there is nothing to wait for and the flag is inert.
19471
- *
19472
- * | value | what happens |
19473
- * | --- | --- |
19474
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19475
- * | 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) |
19476
- *
19477
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19478
- * on the addon cap path, and absent has to keep meaning exactly what every
19479
- * rule authored before this field meant.
19480
- *
19481
- * The cost of `true` is stated here because the editor states it too: a rule
19482
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19483
- * tests every zone the track visited and a `crossing` condition can no longer
19484
- * be satisfied, because a closed track carries no crossing.
19485
- */
19486
- waitForEnhancement: boolean().optional(),
19487
- /**
19488
- * GROUP a burst of subjects into ONE notification that grows.
19489
- *
19490
- * Seconds of quiet after the last matching subject before the burst is
19491
- * considered over. While it is open, the first subject enqueues immediately —
19492
- * **exactly as today, with no added latency** — and every real growth (a new
19493
- * subject, or a name confirmed on one already in it) REPLACES that
19494
- * notification with an updated one naming everybody. The push carries the
19495
- * group's own coalescing tag, so the phone replaces rather than stacks.
19496
- *
19497
- * `0` / absent = off, and off is today's behaviour byte for byte.
19498
- *
19499
- * ### Why an idle cutoff and not a window
19500
- *
19501
- * The measured seven-person arrival on device 590 spans 110 s with every
19502
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19503
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19504
- * 30 s is a widely shipped value for the same decision.
19505
- *
19506
- * ### What it replaces
19507
- *
19508
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19509
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19510
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19511
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19512
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19513
- * budget over GROUPS — which is what it always meant — and a growth is never
19514
- * throttled by the window its own first member spent.
19515
- *
19516
- * ### Interaction with {@link waitForEnhancement}
19517
- *
19518
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19519
- * TRACK CLOSE, so with both set the group is opened by the first member to
19520
- * CLOSE — already carrying its name — and grows as later members close. That
19521
- * is later, and complete. With grouping alone the group opens on the first
19522
- * object event and picks up names as they are confirmed, through the growth
19523
- * path. Neither combination fires twice for one subject.
19524
- *
19525
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19526
- * on the addon cap path, so absent must keep meaning what it meant before this
19527
- * field existed.
19461
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19462
+ * seconds; the rule sends ONE message when the window closes — early, once every
19463
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19464
+ * the rule matched, with one mosaic tile per subject.
19465
+ * Absent = plain immediate (one message per track, at the first match).
19466
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19467
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19468
+ * path does not run Zod defaults.
19528
19469
  */
19529
- groupIdleSec: number().int().min(0).max(600).optional()
19470
+ groupingSec: number().int().min(5).max(60).optional()
19530
19471
  });
19531
19472
  /**
19532
19473
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19574,9 +19515,10 @@ var NcRuleClearableKeySchema = _enum([
19574
19515
  "targetUsers",
19575
19516
  "snoozeOptions",
19576
19517
  "snoozeAllowGlobal",
19577
- "waitForEnhancement",
19578
19518
  "actions",
19579
19519
  "confirm",
19520
+ "groupingSec",
19521
+ "waitForEnhancement",
19580
19522
  "groupIdleSec"
19581
19523
  ]);
19582
19524
  var NcRuleUpdateInputSchema = object({
@@ -19598,9 +19540,25 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19598
19540
  */
19599
19541
  disabledTargetIds: array(string()).default([])
19600
19542
  });
19543
+ /**
19544
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19545
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19546
+ * job is not an open state, and the alarm stays instant.
19547
+ */
19548
+ var NcStateSummarySectionIdSchema = _enum([
19549
+ "device-state",
19550
+ "streams",
19551
+ "battery",
19552
+ "consumables",
19553
+ "tanks",
19554
+ "detection-blind",
19555
+ "nodes",
19556
+ "addons",
19557
+ "updates"
19558
+ ]);
19601
19559
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19602
19560
  var NcStateSummaryPreviewSectionSchema = object({
19603
- id: string(),
19561
+ id: NcStateSummarySectionIdSchema,
19604
19562
  status: _enum(["read", "unavailable"]),
19605
19563
  items: number().int().nonnegative(),
19606
19564
  reason: string().optional()
@@ -19610,7 +19568,7 @@ var NcStateSummaryPreviewSectionSchema = object({
19610
19568
  * a `rule` split (no `section`), one section's on a `section` split.
19611
19569
  */
19612
19570
  var NcStateSummaryPreviewRowSchema = object({
19613
- section: string().optional(),
19571
+ section: NcStateSummarySectionIdSchema.optional(),
19614
19572
  /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19615
19573
  title: string(),
19616
19574
  text: string(),
@@ -20047,6 +20005,29 @@ object({
20047
20005
  firstAt: number(),
20048
20006
  lastAt: number()
20049
20007
  });
20008
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
20009
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
20010
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
20011
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
20012
+ var NcAlarmModeDelaysListSchema = array(object({
20013
+ mode: AlarmArmModeSchema,
20014
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
20015
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
20016
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
20017
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20018
+ var NcAlarmModeDelaysEditListSchema = array(object({
20019
+ mode: AlarmArmModeSchema,
20020
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20021
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20022
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
20023
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20024
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20025
+ var NcAlarmNonBlockingEditSchema = object({
20026
+ mode: AlarmArmModeSchema,
20027
+ deviceId: number().int().positive(),
20028
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20029
+ nonBlocking: boolean()
20030
+ });
20050
20031
  /**
20051
20032
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20052
20033
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -20055,10 +20036,10 @@ object({
20055
20036
  var NcAlarmNonBlockingListSchema = array(object({
20056
20037
  mode: AlarmArmModeSchema,
20057
20038
  deviceIds: array(number().int()).max(200)
20058
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
20039
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20059
20040
  /**
20060
- * The three durations the panel's state machine runs on, who hears about its
20061
- * transitions, and which openings each mode tolerates.
20041
+ * The three durations — global, and per mode since D697 — the panel's state
20042
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20062
20043
  *
20063
20044
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20064
20045
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20068,15 +20049,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20068
20049
  */
20069
20050
  var NcAlarmSettingsSchema = object({
20070
20051
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20071
- exitDelaySec: number().int().min(0).max(600),
20052
+ exitDelaySec: NcAlarmDelaySecSchema,
20072
20053
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20073
- entryDelaySec: number().int().min(0).max(600),
20054
+ entryDelaySec: NcAlarmDelaySecSchema,
20074
20055
  /**
20075
20056
  * How long `triggered` lasts before the panel re-arms itself.
20076
20057
  * **0 = until an operator disarms it** — the behaviour before this field
20077
20058
  * existed, and therefore what an untouched install keeps doing.
20078
20059
  */
20079
- triggeredDurationSec: number().int().min(0).max(3600),
20060
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20080
20061
  /**
20081
20062
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20082
20063
  * remove after one release. Optional so an older admin's patch (which still
@@ -20099,17 +20080,30 @@ var NcAlarmSettingsSchema = object({
20099
20080
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20100
20081
  * parses as "every opening blocks" — the behaviour it was written under.
20101
20082
  */
20102
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20083
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20084
+ /**
20085
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20086
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20087
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20088
+ */
20089
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20103
20090
  });
20104
20091
  /**
20105
20092
  * Every field optional — a tab edits one control at a time.
20106
20093
  *
20107
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20108
- * remove an inner `.default()`, so a patch that never named the field would
20109
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20110
- * (the same trap {@link NcRulePatchSchema} documents).
20094
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20095
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20096
+ * named the field would parse to `[]` and wipe every mode's list on an
20097
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20098
+ * `modeDelays` means EDITS, not the stored overrides.
20111
20099
  */
20112
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20100
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20101
+ nonBlocking: NcAlarmNonBlockingListSchema,
20102
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20103
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20104
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20105
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20106
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20113
20107
  /**
20114
20108
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20115
20109
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20209,6 +20203,28 @@ var NcAlarmConfigSchema = object({
20209
20203
  */
20210
20204
  live: NcAlarmLiveSchema.optional()
20211
20205
  });
20206
+ var NcAlarmSensorReadingSchema = _enum([
20207
+ "open",
20208
+ "closed",
20209
+ "unknown",
20210
+ "not-opening"
20211
+ ]);
20212
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20213
+ var NcAlarmSensorStateSchema = object({
20214
+ deviceId: number().int().positive(),
20215
+ reading: NcAlarmSensorReadingSchema,
20216
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20217
+ state: string().nullable(),
20218
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20219
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20220
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20221
+ online: boolean().nullable()
20222
+ });
20223
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20224
+ var NcAlarmSensorsSchema = object({
20225
+ sensors: array(NcAlarmSensorStateSchema),
20226
+ readAt: number().int().nonnegative()
20227
+ });
20212
20228
  /**
20213
20229
  * ONE rule's demand on ONE camera's clip ring.
20214
20230
  *
@@ -20299,7 +20315,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20299
20315
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20300
20316
  kind: "mutation",
20301
20317
  auth: "admin"
20302
- });
20318
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20303
20319
  /**
20304
20320
  * Zod schemas for persisted record types.
20305
20321
  *
@@ -20646,12 +20662,26 @@ var CameraSensorRefSchema = object({
20646
20662
  function cameraSensorRefKey(ref) {
20647
20663
  return `${ref.addonId}/${ref.stableId}`;
20648
20664
  }
20649
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20650
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20651
- var CameraSensorsConfigSchema = object({
20652
- mode: CameraSensorsModeSchema,
20653
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20654
- }).superRefine((value, ctx) => {
20665
+ /**
20666
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20667
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20668
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20669
+ * camera's own motion counts only while a confirm sensor is active — with or
20670
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20671
+ */
20672
+ var CameraSensorModeSchema = _enum([
20673
+ "in-addition",
20674
+ "confirm",
20675
+ "replace"
20676
+ ]);
20677
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20678
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20679
+ /**
20680
+ * A camera's attached sensors. A CS-1 row carried an envelope
20681
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20682
+ * what the write stores (D672 rule 3), so it is never written again.
20683
+ */
20684
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20655
20685
  const seen = /* @__PURE__ */ new Set();
20656
20686
  value.sensors.forEach((ref, index) => {
20657
20687
  const key = cameraSensorRefKey(ref);
@@ -20663,39 +20693,41 @@ var CameraSensorsConfigSchema = object({
20663
20693
  seen.add(key);
20664
20694
  });
20665
20695
  });
20666
- var RefFields = {
20696
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20697
+ var AttachmentFields = {
20667
20698
  addonId: string(),
20668
- stableId: string()
20699
+ stableId: string(),
20700
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20669
20701
  };
20670
20702
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20671
20703
  object({
20672
- ...RefFields,
20704
+ ...AttachmentFields,
20673
20705
  state: literal("ready"),
20674
20706
  deviceId: number(),
20675
20707
  name: string(),
20676
20708
  caps: array(string()).readonly()
20677
20709
  }),
20678
20710
  object({
20679
- ...RefFields,
20711
+ ...AttachmentFields,
20680
20712
  state: literal("offline"),
20681
20713
  deviceId: number(),
20682
20714
  name: string(),
20683
20715
  reason: _enum(["offline", "disabled"])
20684
20716
  }),
20685
20717
  object({
20686
- ...RefFields,
20718
+ ...AttachmentFields,
20687
20719
  state: literal("missing"),
20688
20720
  reason: string()
20689
20721
  }),
20690
20722
  object({
20691
- ...RefFields,
20723
+ ...AttachmentFields,
20692
20724
  state: literal("unsupported"),
20693
20725
  deviceId: number(),
20694
20726
  name: string(),
20695
20727
  reason: string()
20696
20728
  }),
20697
20729
  object({
20698
- ...RefFields,
20730
+ ...AttachmentFields,
20699
20731
  state: literal("unreadable"),
20700
20732
  reason: string()
20701
20733
  })
@@ -20708,17 +20740,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20708
20740
  * say.
20709
20741
  */
20710
20742
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20743
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20744
+ var CameraOwnMotionSchema = _enum([
20745
+ "always",
20746
+ "when-confirmed",
20747
+ "never"
20748
+ ]);
20749
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20750
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20751
+ /**
20752
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20753
+ * never the effective value. The camera's `motion` slice is the effective one,
20754
+ * so a view of the camera's own row reads this.
20755
+ * - `atMs`: when the bridge received the camera's last own edge.
20756
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20757
+ * AND it is younger than the window the bridge counts the camera's own motion
20758
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20759
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20760
+ * within the window; a status read is a snapshot, so a live view re-asks.
20761
+ * Absent = this orchestrator has not received the camera's own motion since it
20762
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20763
+ */
20764
+ var CameraRawLevelSchema = object({
20765
+ detected: boolean(),
20766
+ atMs: number()
20767
+ });
20711
20768
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20712
- object({ state: literal("not-configured") }),
20769
+ object({
20770
+ state: literal("not-configured"),
20771
+ cameraLevel: CameraRawLevelSchema.optional()
20772
+ }),
20713
20773
  object({
20714
20774
  state: literal("unreadable"),
20715
- reason: string()
20775
+ reason: string(),
20776
+ cameraLevel: CameraRawLevelSchema.optional()
20716
20777
  }),
20717
20778
  object({
20718
20779
  state: literal("configured"),
20719
- mode: CameraSensorsModeSchema,
20780
+ cameraLevel: CameraRawLevelSchema.optional(),
20720
20781
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20721
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20782
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20783
+ cameraMotion: CameraOwnMotionSchema.optional(),
20784
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20785
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20722
20786
  })
20723
20787
  ]);
20724
20788
  Object.freeze({
@@ -21403,14 +21467,14 @@ var TrackSchema = object({
21403
21467
  * `derive-returned-null tile=standin`,
21404
21468
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21405
21469
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21406
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21470
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21407
21471
  * tile is a face/plate stand-in, a raster crop, or an icon.
21408
21472
  *
21409
21473
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21410
21474
  * the TILE ranked below native, so a track whose thumbnail landed natively
21411
21475
  * while its key frame missed carried no reason at all — three of the four
21412
21476
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21413
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21477
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21414
21478
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21415
21479
  * lastFrame copy source`), so its absence is now on this line whatever the
21416
21480
  * tile ended up being.
@@ -21497,7 +21561,7 @@ var MotionEventSchema = object({
21497
21561
  /**
21498
21562
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21499
21563
  * first entry is always `0`) of every genuine off→on transition the
21500
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21564
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21501
21565
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21502
21566
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21503
21567
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -22021,6 +22085,7 @@ var KeyEventSchema = object({
22021
22085
  bestEventId: string(),
22022
22086
  /** Track lifetime in ms (lastSeen - firstSeen). */
22023
22087
  windowMs: number().optional(),
22088
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
22024
22089
  ...TrackFlagFields,
22025
22090
  ...TrackRetrainFields
22026
22091
  });
@@ -23909,8 +23974,10 @@ var RunnerCameraConfigSchema = object({
23909
23974
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23910
23975
  * analyzer providers) running on decoded frames in this runner.
23911
23976
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
23912
- * analytics). The provider emits ProviderMotion
23913
- * events; the orchestrator forwards to `reportMotion`.
23977
+ * analytics). The provider emits its raw push as `ProviderMotion`
23978
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
23979
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
23980
+ * `reportMotion`.
23914
23981
  */
23915
23982
  motionSources: MotionSourcesSchema.default(["analyzer"]),
23916
23983
  /**
@@ -24087,7 +24154,15 @@ var RunnerLocalLoadSchema = object({
24087
24154
  hasGpu: boolean(),
24088
24155
  inferenceBackend: string().optional(),
24089
24156
  cpuCores: number().optional()
24090
- })
24157
+ }),
24158
+ /**
24159
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24160
+ * runner never writes it. ABSENT = a runner older than D696, which still
24161
+ * writes the slice from its phase machine; the orchestrator does not write
24162
+ * while any runner node is absent-or-unanswered, so one slice never has two
24163
+ * writers.
24164
+ */
24165
+ motionSliceWriter: literal("orchestrator").optional()
24091
24166
  });
24092
24167
  /**
24093
24168
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -26723,7 +26798,7 @@ var ClipPlaybackSchema = object({
26723
26798
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26724
26799
  * time the camera is awake for its own reasons — is deliberately not a member:
26725
26800
  * it was proposed, it is free on the battery, and the operator declined it on
26726
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
26801
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26727
26802
  * D558 "Considered and not taken").
26728
26803
  */
26729
26804
  var ClipWakeSchema = _enum(["authorised"]);
@@ -26984,7 +27059,7 @@ DeviceType.Camera, method(object({
26984
27059
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
26985
27060
  * assumed the answer was a fan-out over everything a camera has. The
26986
27061
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
26987
- * solo"* — so the list is asked of one provider at a time and there is
27062
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
26988
27063
  * nothing to filter out of it.
26989
27064
  */
26990
27065
  provider: string().min(1)
@@ -28928,6 +29003,17 @@ object({
28928
29003
  deviceId: number(),
28929
29004
  status: DeviceStatusSchema
28930
29005
  });
29006
+ object({
29007
+ /** Ms epoch of the last press. null = never observed since this provider started. */
29008
+ lastPressedAt: number().nullable(),
29009
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
29010
+ pressCountSinceStart: number()
29011
+ });
29012
+ object({
29013
+ deviceId: number(),
29014
+ timestamp: number()
29015
+ });
29016
+ DeviceType.Button, DeviceType.Camera;
28931
29017
  /**
28932
29018
  * Enum-state sensor — a string value picked from a finite option set.
28933
29019
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30572,9 +30658,9 @@ object({
30572
30658
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30573
30659
  lastDetectedAt: number().nullable(),
30574
30660
  /**
30575
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30576
- * `null` while false and on every `Sensor` device (D475) — see that
30577
- * constant's doc for the one-authority rule.
30661
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30662
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30663
+ * device (D475) — see that constant's doc for the one-authority rule.
30578
30664
  *
30579
30665
  * ## Reading this field still arms nothing
30580
30666
  *
@@ -30592,38 +30678,41 @@ object({
30592
30678
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30593
30679
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30594
30680
  *
30595
- * ## Who writes it
30596
- *
30597
- * - **Cameras** — the runner's phase machine, `active → watching` on
30598
- * `cooldown_expired`, which then writes this slice with
30599
- * `detected: false` (`handlePhaseChanged` in
30600
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30601
- * matters most for the sources that only ever push a rising one:
30602
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30603
- * a false.
30604
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30605
- * provider pushes the false itself, from the upstream system's own
30606
- * state change. No phase machine is involved.
30607
- *
30608
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30609
- *
30610
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30611
- * phase machine is the sole writer for a camera. It is not.
30612
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30613
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30614
- * Hikvision's comment says why: it read THIS docblock, agreed the
30615
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30616
- * two authorities can disagree about one slice, which this repo
30617
- * forbids, and the doc said otherwise — which is worse than saying
30618
- * nothing, because it reads as verification.
30619
- *
30620
- * This predates D475 and is not fixed there: the fix touches every
30621
- * camera provider. Recorded in D475's Consequences. Do not restore the
30622
- * "sole writer" wording without also removing the other writers.
30681
+ * ## Who writes it (D696)
30682
+ *
30683
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30684
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30685
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30686
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30687
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30688
+ * device activity, as the camera's `motionSources` counts them. A source
30689
+ * that never sends a clear falls one window after its last `true`.
30690
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30691
+ * writes this slice (not even a boot seed), and neither does the runner's
30692
+ * phase machine: its phase is "a session is attached" for every on-motion
30693
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30694
+ * A provider's `isDetected` answers from the hub's slice.
30695
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30696
+ * imports) — the provider pushes both edges itself, from the upstream
30697
+ * system's own state change. That is the sensor's own fact.
30698
+ *
30699
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30700
+ * `motion` slice; each sensor writer is a named exemption.
30701
+ *
30702
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30703
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30704
+ * it, every provider seeded it at boot, and every session left it
30705
+ * `detected: true` after its detach.
30623
30706
  */
30624
30707
  autoClearAfterMs: number().nullable()
30625
30708
  });
30626
- object({
30709
+ /**
30710
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30711
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30712
+ * — both the cap event surface and the bus payload type alias to this
30713
+ * schema.
30714
+ */
30715
+ var MotionOnMotionChangedDataSchema = object({
30627
30716
  deviceId: number(),
30628
30717
  detected: boolean(),
30629
30718
  timestamp: number(),
@@ -33088,7 +33177,7 @@ var ReadGopBytesResultSchema = object({
33088
33177
  /**
33089
33178
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
33090
33179
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
33091
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
33180
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
33092
33181
  * a replay needs several seconds of native pixels, not one frame.
33093
33182
  *
33094
33183
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -35704,6 +35793,7 @@ DeviceType.Camera, method(object({
35704
35793
  detection: array(ZoneRuleSchema).readonly(),
35705
35794
  package: array(ZoneRuleSchema).readonly()
35706
35795
  });
35796
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
35707
35797
  /**
35708
35798
  * Accessory device helpers — shared across drivers.
35709
35799
  *
@@ -40682,6 +40772,12 @@ Object.freeze({
40682
40772
  addonId: null,
40683
40773
  access: "view"
40684
40774
  },
40775
+ "notificationRules.getAlarmSensors": {
40776
+ capName: "notification-rules",
40777
+ capScope: "system",
40778
+ addonId: null,
40779
+ access: "view"
40780
+ },
40685
40781
  "notificationRules.getClipRetentionAsks": {
40686
40782
  capName: "notification-rules",
40687
40783
  capScope: "system",
@@ -44260,6 +44356,11 @@ Object.freeze({
44260
44356
  }
44261
44357
  });
44262
44358
  Object.freeze({
44359
+ "accessories.getStatus": [{
44360
+ name: "deviceId",
44361
+ form: "single",
44362
+ optional: false
44363
+ }],
44263
44364
  "accessories.setChildHidden": [{
44264
44365
  name: "childDeviceId",
44265
44366
  form: "single",
@@ -44279,6 +44380,11 @@ Object.freeze({
44279
44380
  form: "single",
44280
44381
  optional: false
44281
44382
  }],
44383
+ "airQualitySensor.getStatus": [{
44384
+ name: "deviceId",
44385
+ form: "single",
44386
+ optional: false
44387
+ }],
44282
44388
  "alarmPanel.arm": [{
44283
44389
  name: "deviceId",
44284
44390
  form: "single",
@@ -44289,11 +44395,36 @@ Object.freeze({
44289
44395
  form: "single",
44290
44396
  optional: false
44291
44397
  }],
44398
+ "alarmPanel.getStatus": [{
44399
+ name: "deviceId",
44400
+ form: "single",
44401
+ optional: false
44402
+ }],
44292
44403
  "alarmPanel.trigger": [{
44293
44404
  name: "deviceId",
44294
44405
  form: "single",
44295
44406
  optional: false
44296
44407
  }],
44408
+ "ambientLightSensor.getStatus": [{
44409
+ name: "deviceId",
44410
+ form: "single",
44411
+ optional: false
44412
+ }],
44413
+ "audioAnalysis.applyDeviceSettingsPatch": [{
44414
+ name: "deviceId",
44415
+ form: "single",
44416
+ optional: false
44417
+ }],
44418
+ "audioAnalysis.getDeviceLiveContribution": [{
44419
+ name: "deviceId",
44420
+ form: "single",
44421
+ optional: false
44422
+ }],
44423
+ "audioAnalysis.getDeviceSettingsContribution": [{
44424
+ name: "deviceId",
44425
+ form: "single",
44426
+ optional: false
44427
+ }],
44297
44428
  "audioAnalysis.resolveDeviceSettings": [{
44298
44429
  name: "deviceId",
44299
44430
  form: "single",
@@ -44334,16 +44465,36 @@ Object.freeze({
44334
44465
  form: "single",
44335
44466
  optional: false
44336
44467
  }],
44468
+ "automationControl.getStatus": [{
44469
+ name: "deviceId",
44470
+ form: "single",
44471
+ optional: false
44472
+ }],
44337
44473
  "automationControl.trigger": [{
44338
44474
  name: "deviceId",
44339
44475
  form: "single",
44340
44476
  optional: false
44341
44477
  }],
44478
+ "battery.getStatus": [{
44479
+ name: "deviceId",
44480
+ form: "single",
44481
+ optional: false
44482
+ }],
44342
44483
  "battery.wakeForStream": [{
44343
44484
  name: "deviceId",
44344
44485
  form: "single",
44345
44486
  optional: false
44346
44487
  }],
44488
+ "binary.getStatus": [{
44489
+ name: "deviceId",
44490
+ form: "single",
44491
+ optional: false
44492
+ }],
44493
+ "brightness.getStatus": [{
44494
+ name: "deviceId",
44495
+ form: "single",
44496
+ optional: false
44497
+ }],
44347
44498
  "brightness.setBrightness": [{
44348
44499
  name: "deviceId",
44349
44500
  form: "single",
@@ -44359,6 +44510,26 @@ Object.freeze({
44359
44510
  form: "single",
44360
44511
  optional: false
44361
44512
  }],
44513
+ "cameraCredentials.getStatus": [{
44514
+ name: "deviceId",
44515
+ form: "single",
44516
+ optional: false
44517
+ }],
44518
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
44519
+ name: "deviceId",
44520
+ form: "single",
44521
+ optional: false
44522
+ }],
44523
+ "cameraGridLayout.getDeviceLiveContribution": [{
44524
+ name: "deviceId",
44525
+ form: "single",
44526
+ optional: false
44527
+ }],
44528
+ "cameraGridLayout.getDeviceSettingsContribution": [{
44529
+ name: "deviceId",
44530
+ form: "single",
44531
+ optional: false
44532
+ }],
44362
44533
  "cameraGridLayout.getLayout": [{
44363
44534
  name: "deviceId",
44364
44535
  form: "single",
@@ -44374,6 +44545,21 @@ Object.freeze({
44374
44545
  form: "single",
44375
44546
  optional: false
44376
44547
  }],
44548
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
44549
+ name: "deviceId",
44550
+ form: "single",
44551
+ optional: false
44552
+ }],
44553
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
44554
+ name: "deviceId",
44555
+ form: "single",
44556
+ optional: false
44557
+ }],
44558
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
44559
+ name: "deviceId",
44560
+ form: "single",
44561
+ optional: false
44562
+ }],
44377
44563
  "cameraStreams.getBrokerStreams": [{
44378
44564
  name: "deviceId",
44379
44565
  form: "single",
@@ -44399,6 +44585,16 @@ Object.freeze({
44399
44585
  form: "single",
44400
44586
  optional: false
44401
44587
  }],
44588
+ "carbonMonoxide.getStatus": [{
44589
+ name: "deviceId",
44590
+ form: "single",
44591
+ optional: false
44592
+ }],
44593
+ "climateControl.getStatus": [{
44594
+ name: "deviceId",
44595
+ form: "single",
44596
+ optional: false
44597
+ }],
44402
44598
  "climateControl.setFanMode": [{
44403
44599
  name: "deviceId",
44404
44600
  form: "single",
@@ -44439,16 +44635,41 @@ Object.freeze({
44439
44635
  form: "single",
44440
44636
  optional: false
44441
44637
  }],
44638
+ "color.getStatus": [{
44639
+ name: "deviceId",
44640
+ form: "single",
44641
+ optional: false
44642
+ }],
44442
44643
  "color.setColor": [{
44443
44644
  name: "deviceId",
44444
44645
  form: "single",
44445
44646
  optional: false
44446
44647
  }],
44648
+ "connectivity.getStatus": [{
44649
+ name: "deviceId",
44650
+ form: "single",
44651
+ optional: false
44652
+ }],
44653
+ "consumables.getStatus": [{
44654
+ name: "deviceId",
44655
+ form: "single",
44656
+ optional: false
44657
+ }],
44447
44658
  "consumables.reset": [{
44448
44659
  name: "deviceId",
44449
44660
  form: "single",
44450
44661
  optional: false
44451
44662
  }],
44663
+ "contact.getStatus": [{
44664
+ name: "deviceId",
44665
+ form: "single",
44666
+ optional: false
44667
+ }],
44668
+ "control.getStatus": [{
44669
+ name: "deviceId",
44670
+ form: "single",
44671
+ optional: false
44672
+ }],
44452
44673
  "control.setValue": [{
44453
44674
  name: "deviceId",
44454
44675
  form: "single",
@@ -44459,6 +44680,11 @@ Object.freeze({
44459
44680
  form: "single",
44460
44681
  optional: false
44461
44682
  }],
44683
+ "cover.getStatus": [{
44684
+ name: "deviceId",
44685
+ form: "single",
44686
+ optional: false
44687
+ }],
44462
44688
  "cover.open": [{
44463
44689
  name: "deviceId",
44464
44690
  form: "single",
@@ -44484,6 +44710,11 @@ Object.freeze({
44484
44710
  form: "single",
44485
44711
  optional: false
44486
44712
  }],
44713
+ "dayNight.getStatus": [{
44714
+ name: "deviceId",
44715
+ form: "single",
44716
+ optional: false
44717
+ }],
44487
44718
  "dayNight.setSettings": [{
44488
44719
  name: "deviceId",
44489
44720
  form: "single",
@@ -44494,11 +44725,31 @@ Object.freeze({
44494
44725
  form: "single",
44495
44726
  optional: true
44496
44727
  }],
44728
+ "detectionPipeline.applyDeviceSettingsPatch": [{
44729
+ name: "deviceId",
44730
+ form: "single",
44731
+ optional: false
44732
+ }],
44733
+ "detectionPipeline.getDeviceLiveContribution": [{
44734
+ name: "deviceId",
44735
+ form: "single",
44736
+ optional: false
44737
+ }],
44738
+ "detectionPipeline.getDeviceSettingsContribution": [{
44739
+ name: "deviceId",
44740
+ form: "single",
44741
+ optional: false
44742
+ }],
44497
44743
  "deviceAdminLink.getAdminLink": [{
44498
44744
  name: "deviceId",
44499
44745
  form: "single",
44500
44746
  optional: false
44501
44747
  }],
44748
+ "deviceAdoption.getStatus": [{
44749
+ name: "deviceId",
44750
+ form: "single",
44751
+ optional: false
44752
+ }],
44502
44753
  "deviceAdoption.release": [{
44503
44754
  name: "camDeviceId",
44504
44755
  form: "single",
@@ -44514,6 +44765,11 @@ Object.freeze({
44514
44765
  form: "single",
44515
44766
  optional: false
44516
44767
  }],
44768
+ "deviceDiscovery.getStatus": [{
44769
+ name: "deviceId",
44770
+ form: "single",
44771
+ optional: false
44772
+ }],
44517
44773
  "deviceDiscovery.listDiscovered": [{
44518
44774
  name: "deviceId",
44519
44775
  form: "single",
@@ -44533,6 +44789,21 @@ Object.freeze({
44533
44789
  form: "single",
44534
44790
  optional: false
44535
44791
  }],
44792
+ "deviceExport.applyDeviceSettingsPatch": [{
44793
+ name: "deviceId",
44794
+ form: "single",
44795
+ optional: false
44796
+ }],
44797
+ "deviceExport.getDeviceLiveContribution": [{
44798
+ name: "deviceId",
44799
+ form: "single",
44800
+ optional: false
44801
+ }],
44802
+ "deviceExport.getDeviceSettingsContribution": [{
44803
+ name: "deviceId",
44804
+ form: "single",
44805
+ optional: false
44806
+ }],
44536
44807
  "deviceExtension.listCandidates": [{
44537
44808
  name: "deviceId",
44538
44809
  form: "single",
@@ -44881,6 +45152,26 @@ Object.freeze({
44881
45152
  form: "single",
44882
45153
  optional: false
44883
45154
  }],
45155
+ "deviceStatus.getStatus": [{
45156
+ name: "deviceId",
45157
+ form: "single",
45158
+ optional: false
45159
+ }],
45160
+ "doorbell.getStatus": [{
45161
+ name: "deviceId",
45162
+ form: "single",
45163
+ optional: false
45164
+ }],
45165
+ "enumSensor.getStatus": [{
45166
+ name: "deviceId",
45167
+ form: "single",
45168
+ optional: false
45169
+ }],
45170
+ "eventEmitter.getStatus": [{
45171
+ name: "deviceId",
45172
+ form: "single",
45173
+ optional: false
45174
+ }],
44884
45175
  "faceGallery.getFaceByTrack": [{
44885
45176
  name: "deviceId",
44886
45177
  form: "single",
@@ -44895,6 +45186,11 @@ Object.freeze({
44895
45186
  form: "array",
44896
45187
  optional: true
44897
45188
  }],
45189
+ "fanControl.getStatus": [{
45190
+ name: "deviceId",
45191
+ form: "single",
45192
+ optional: false
45193
+ }],
44898
45194
  "fanControl.setDirection": [{
44899
45195
  name: "deviceId",
44900
45196
  form: "single",
@@ -44915,6 +45211,26 @@ Object.freeze({
44915
45211
  form: "single",
44916
45212
  optional: false
44917
45213
  }],
45214
+ "featureProbe.getStatus": [{
45215
+ name: "deviceId",
45216
+ form: "single",
45217
+ optional: false
45218
+ }],
45219
+ "flood.getStatus": [{
45220
+ name: "deviceId",
45221
+ form: "single",
45222
+ optional: false
45223
+ }],
45224
+ "gas.getStatus": [{
45225
+ name: "deviceId",
45226
+ form: "single",
45227
+ optional: false
45228
+ }],
45229
+ "humidifier.getStatus": [{
45230
+ name: "deviceId",
45231
+ form: "single",
45232
+ optional: false
45233
+ }],
44918
45234
  "humidifier.setMode": [{
44919
45235
  name: "deviceId",
44920
45236
  form: "single",
@@ -44930,11 +45246,26 @@ Object.freeze({
44930
45246
  form: "single",
44931
45247
  optional: false
44932
45248
  }],
45249
+ "humiditySensor.getStatus": [{
45250
+ name: "deviceId",
45251
+ form: "single",
45252
+ optional: false
45253
+ }],
45254
+ "image.getStatus": [{
45255
+ name: "deviceId",
45256
+ form: "single",
45257
+ optional: false
45258
+ }],
44933
45259
  "imageSettings.getOptions": [{
44934
45260
  name: "deviceId",
44935
45261
  form: "single",
44936
45262
  optional: false
44937
45263
  }],
45264
+ "imageSettings.getStatus": [{
45265
+ name: "deviceId",
45266
+ form: "single",
45267
+ optional: false
45268
+ }],
44938
45269
  "imageSettings.setSettings": [{
44939
45270
  name: "deviceId",
44940
45271
  form: "single",
@@ -44945,6 +45276,11 @@ Object.freeze({
44945
45276
  form: "single",
44946
45277
  optional: false
44947
45278
  }],
45279
+ "intercom.getStatus": [{
45280
+ name: "deviceId",
45281
+ form: "single",
45282
+ optional: false
45283
+ }],
44948
45284
  "intercom.handleAnswer": [{
44949
45285
  name: "deviceId",
44950
45286
  form: "single",
@@ -44975,6 +45311,11 @@ Object.freeze({
44975
45311
  form: "single",
44976
45312
  optional: false
44977
45313
  }],
45314
+ "lawnMowerControl.getStatus": [{
45315
+ name: "deviceId",
45316
+ form: "single",
45317
+ optional: false
45318
+ }],
44978
45319
  "lawnMowerControl.pause": [{
44979
45320
  name: "deviceId",
44980
45321
  form: "single",
@@ -44985,6 +45326,11 @@ Object.freeze({
44985
45326
  form: "single",
44986
45327
  optional: false
44987
45328
  }],
45329
+ "lockControl.getStatus": [{
45330
+ name: "deviceId",
45331
+ form: "single",
45332
+ optional: false
45333
+ }],
44988
45334
  "lockControl.lock": [{
44989
45335
  name: "deviceId",
44990
45336
  form: "single",
@@ -45000,6 +45346,11 @@ Object.freeze({
45000
45346
  form: "single",
45001
45347
  optional: false
45002
45348
  }],
45349
+ "mediaPlayer.getStatus": [{
45350
+ name: "deviceId",
45351
+ form: "single",
45352
+ optional: false
45353
+ }],
45003
45354
  "mediaPlayer.next": [{
45004
45355
  name: "deviceId",
45005
45356
  form: "single",
@@ -45060,6 +45411,11 @@ Object.freeze({
45060
45411
  form: "single",
45061
45412
  optional: false
45062
45413
  }],
45414
+ "motion.getStatus": [{
45415
+ name: "deviceId",
45416
+ form: "single",
45417
+ optional: false
45418
+ }],
45063
45419
  "motion.isDetected": [{
45064
45420
  name: "deviceId",
45065
45421
  form: "single",
@@ -45070,11 +45426,31 @@ Object.freeze({
45070
45426
  form: "single",
45071
45427
  optional: false
45072
45428
  }],
45429
+ "motionDetection.applyDeviceSettingsPatch": [{
45430
+ name: "deviceId",
45431
+ form: "single",
45432
+ optional: false
45433
+ }],
45434
+ "motionDetection.getDeviceLiveContribution": [{
45435
+ name: "deviceId",
45436
+ form: "single",
45437
+ optional: false
45438
+ }],
45439
+ "motionDetection.getDeviceSettingsContribution": [{
45440
+ name: "deviceId",
45441
+ form: "single",
45442
+ optional: false
45443
+ }],
45073
45444
  "motionDetection.removeCamera": [{
45074
45445
  name: "deviceId",
45075
45446
  form: "single",
45076
45447
  optional: false
45077
45448
  }],
45449
+ "motionTrigger.getStatus": [{
45450
+ name: "deviceId",
45451
+ form: "single",
45452
+ optional: false
45453
+ }],
45078
45454
  "motionTrigger.setMotionTrigger": [{
45079
45455
  name: "deviceId",
45080
45456
  form: "single",
@@ -45085,6 +45461,11 @@ Object.freeze({
45085
45461
  form: "single",
45086
45462
  optional: false
45087
45463
  }],
45464
+ "motionZones.getStatus": [{
45465
+ name: "deviceId",
45466
+ form: "single",
45467
+ optional: false
45468
+ }],
45088
45469
  "motionZones.setZone": [{
45089
45470
  name: "deviceId",
45090
45471
  form: "single",
@@ -45095,6 +45476,11 @@ Object.freeze({
45095
45476
  form: "single",
45096
45477
  optional: false
45097
45478
  }],
45479
+ "nativeObjectDetection.getStatus": [{
45480
+ name: "deviceId",
45481
+ form: "single",
45482
+ optional: false
45483
+ }],
45098
45484
  "nativeObjectDetection.setEnabled": [{
45099
45485
  name: "deviceId",
45100
45486
  form: "single",
@@ -45110,6 +45496,11 @@ Object.freeze({
45110
45496
  form: "single",
45111
45497
  optional: false
45112
45498
  }],
45499
+ "navigation.getStatus": [{
45500
+ name: "deviceId",
45501
+ form: "single",
45502
+ optional: false
45503
+ }],
45113
45504
  "navigation.goToPoint": [{
45114
45505
  name: "deviceId",
45115
45506
  form: "single",
@@ -45155,6 +45546,11 @@ Object.freeze({
45155
45546
  form: "single",
45156
45547
  optional: false
45157
45548
  }],
45549
+ "networkLink.getStatus": [{
45550
+ name: "deviceId",
45551
+ form: "single",
45552
+ optional: false
45553
+ }],
45158
45554
  "networkQuality.getDeviceStats": [{
45159
45555
  name: "deviceId",
45160
45556
  form: "single",
@@ -45165,6 +45561,11 @@ Object.freeze({
45165
45561
  form: "single",
45166
45562
  optional: false
45167
45563
  }],
45564
+ "notificationRules.getAlarmSensors": [{
45565
+ name: "deviceIds",
45566
+ form: "array",
45567
+ optional: false
45568
+ }],
45168
45569
  "notificationRules.setDeviceMuted": [{
45169
45570
  name: "deviceId",
45170
45571
  form: "single",
@@ -45175,11 +45576,26 @@ Object.freeze({
45175
45576
  form: "single",
45176
45577
  optional: false
45177
45578
  }],
45579
+ "notifier.getStatus": [{
45580
+ name: "deviceId",
45581
+ form: "single",
45582
+ optional: false
45583
+ }],
45178
45584
  "notifier.send": [{
45179
45585
  name: "deviceId",
45180
45586
  form: "single",
45181
45587
  optional: false
45182
45588
  }],
45589
+ "numericSensor.getStatus": [{
45590
+ name: "deviceId",
45591
+ form: "single",
45592
+ optional: false
45593
+ }],
45594
+ "osd.getStatus": [{
45595
+ name: "deviceId",
45596
+ form: "single",
45597
+ optional: false
45598
+ }],
45183
45599
  "osd.setOverlay": [{
45184
45600
  name: "deviceId",
45185
45601
  form: "single",
@@ -45239,6 +45655,11 @@ Object.freeze({
45239
45655
  form: "single",
45240
45656
  optional: false
45241
45657
  }],
45658
+ "petFeeder.getStatus": [{
45659
+ name: "deviceId",
45660
+ form: "single",
45661
+ optional: false
45662
+ }],
45242
45663
  "petFeeder.markFoodReplenished": [{
45243
45664
  name: "deviceId",
45244
45665
  form: "single",
@@ -45274,6 +45695,11 @@ Object.freeze({
45274
45695
  form: "single",
45275
45696
  optional: false
45276
45697
  }],
45698
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
45699
+ name: "deviceId",
45700
+ form: "single",
45701
+ optional: false
45702
+ }],
45277
45703
  "pipelineAnalytics.clearTrackRelation": [{
45278
45704
  name: "deviceId",
45279
45705
  form: "single",
@@ -45314,6 +45740,16 @@ Object.freeze({
45314
45740
  form: "single",
45315
45741
  optional: false
45316
45742
  }],
45743
+ "pipelineAnalytics.getDeviceLiveContribution": [{
45744
+ name: "deviceId",
45745
+ form: "single",
45746
+ optional: false
45747
+ }],
45748
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
45749
+ name: "deviceId",
45750
+ form: "single",
45751
+ optional: false
45752
+ }],
45317
45753
  "pipelineAnalytics.getEventDensity": [{
45318
45754
  name: "deviceId",
45319
45755
  form: "single",
@@ -45529,6 +45965,11 @@ Object.freeze({
45529
45965
  form: "single",
45530
45966
  optional: true
45531
45967
  }],
45968
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
45969
+ name: "deviceId",
45970
+ form: "single",
45971
+ optional: false
45972
+ }],
45532
45973
  "pipelineOrchestrator.assignAudio": [{
45533
45974
  name: "deviceId",
45534
45975
  form: "single",
@@ -45574,6 +46015,16 @@ Object.freeze({
45574
46015
  form: "single",
45575
46016
  optional: false
45576
46017
  }],
46018
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
46019
+ name: "deviceId",
46020
+ form: "single",
46021
+ optional: false
46022
+ }],
46023
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
46024
+ name: "deviceId",
46025
+ form: "single",
46026
+ optional: false
46027
+ }],
45577
46028
  "pipelineOrchestrator.getPipelineAssignment": [{
45578
46029
  name: "deviceId",
45579
46030
  form: "single",
@@ -45694,11 +46145,31 @@ Object.freeze({
45694
46145
  form: "single",
45695
46146
  optional: true
45696
46147
  }],
46148
+ "powerMeter.getStatus": [{
46149
+ name: "deviceId",
46150
+ form: "single",
46151
+ optional: false
46152
+ }],
46153
+ "presence.getStatus": [{
46154
+ name: "deviceId",
46155
+ form: "single",
46156
+ optional: false
46157
+ }],
46158
+ "pressureSensor.getStatus": [{
46159
+ name: "deviceId",
46160
+ form: "single",
46161
+ optional: false
46162
+ }],
45697
46163
  "privacyMask.getOptions": [{
45698
46164
  name: "deviceId",
45699
46165
  form: "single",
45700
46166
  optional: false
45701
46167
  }],
46168
+ "privacyMask.getStatus": [{
46169
+ name: "deviceId",
46170
+ form: "single",
46171
+ optional: false
46172
+ }],
45702
46173
  "privacyMask.setAudioEnabled": [{
45703
46174
  name: "deviceId",
45704
46175
  form: "single",
@@ -45749,6 +46220,11 @@ Object.freeze({
45749
46220
  form: "single",
45750
46221
  optional: false
45751
46222
  }],
46223
+ "ptz.getStatus": [{
46224
+ name: "deviceId",
46225
+ form: "single",
46226
+ optional: false
46227
+ }],
45752
46228
  "ptz.goHome": [{
45753
46229
  name: "deviceId",
45754
46230
  form: "single",
@@ -45839,6 +46315,11 @@ Object.freeze({
45839
46315
  form: "single",
45840
46316
  optional: false
45841
46317
  }],
46318
+ "recordingArchive.applyDeviceSettingsPatch": [{
46319
+ name: "deviceId",
46320
+ form: "single",
46321
+ optional: false
46322
+ }],
45842
46323
  "recordingArchive.deleteFootprint": [{
45843
46324
  name: "deviceId",
45844
46325
  form: "single",
@@ -45859,6 +46340,21 @@ Object.freeze({
45859
46340
  form: "single",
45860
46341
  optional: false
45861
46342
  }],
46343
+ "recordingArchive.getDeviceLiveContribution": [{
46344
+ name: "deviceId",
46345
+ form: "single",
46346
+ optional: false
46347
+ }],
46348
+ "recordingArchive.getDeviceSettingsContribution": [{
46349
+ name: "deviceId",
46350
+ form: "single",
46351
+ optional: false
46352
+ }],
46353
+ "recordingArchive.getStatus": [{
46354
+ name: "deviceId",
46355
+ form: "single",
46356
+ optional: false
46357
+ }],
45862
46358
  "recordingArchive.listOpsLog": [{
45863
46359
  name: "deviceId",
45864
46360
  form: "single",
@@ -45944,6 +46440,11 @@ Object.freeze({
45944
46440
  form: "single",
45945
46441
  optional: false
45946
46442
  }],
46443
+ "recordingOnboard.getStatus": [{
46444
+ name: "deviceId",
46445
+ form: "single",
46446
+ optional: false
46447
+ }],
45947
46448
  "recordingOnboard.setSettings": [{
45948
46449
  name: "deviceId",
45949
46450
  form: "single",
@@ -45974,6 +46475,11 @@ Object.freeze({
45974
46475
  form: "single",
45975
46476
  optional: false
45976
46477
  }],
46478
+ "sceneMonitor.getStatus": [{
46479
+ name: "deviceId",
46480
+ form: "single",
46481
+ optional: false
46482
+ }],
45977
46483
  "sceneMonitor.listScenes": [{
45978
46484
  name: "deviceId",
45979
46485
  form: "single",
@@ -45999,6 +46505,11 @@ Object.freeze({
45999
46505
  form: "single",
46000
46506
  optional: false
46001
46507
  }],
46508
+ "scriptRunner.getStatus": [{
46509
+ name: "deviceId",
46510
+ form: "single",
46511
+ optional: false
46512
+ }],
46002
46513
  "scriptRunner.run": [{
46003
46514
  name: "deviceId",
46004
46515
  form: "single",
@@ -46009,11 +46520,31 @@ Object.freeze({
46009
46520
  form: "single",
46010
46521
  optional: false
46011
46522
  }],
46523
+ "smoke.getStatus": [{
46524
+ name: "deviceId",
46525
+ form: "single",
46526
+ optional: false
46527
+ }],
46528
+ "snapshot.applyDeviceSettingsPatch": [{
46529
+ name: "deviceId",
46530
+ form: "single",
46531
+ optional: false
46532
+ }],
46012
46533
  "snapshot.getDebugState": [{
46013
46534
  name: "deviceId",
46014
46535
  form: "single",
46015
46536
  optional: false
46016
46537
  }],
46538
+ "snapshot.getDeviceLiveContribution": [{
46539
+ name: "deviceId",
46540
+ form: "single",
46541
+ optional: false
46542
+ }],
46543
+ "snapshot.getDeviceSettingsContribution": [{
46544
+ name: "deviceId",
46545
+ form: "single",
46546
+ optional: false
46547
+ }],
46017
46548
  "snapshot.getSnapshot": [{
46018
46549
  name: "deviceId",
46019
46550
  form: "single",
@@ -46030,6 +46561,11 @@ Object.freeze({
46030
46561
  form: "array",
46031
46562
  optional: false
46032
46563
  }],
46564
+ "snapshot.getStatus": [{
46565
+ name: "deviceId",
46566
+ form: "single",
46567
+ optional: false
46568
+ }],
46033
46569
  "snapshot.invalidateCache": [{
46034
46570
  name: "deviceId",
46035
46571
  form: "single",
@@ -46040,6 +46576,11 @@ Object.freeze({
46040
46576
  form: "single",
46041
46577
  optional: false
46042
46578
  }],
46579
+ "streamBroker.applyDeviceSettingsPatch": [{
46580
+ name: "deviceId",
46581
+ form: "single",
46582
+ optional: false
46583
+ }],
46043
46584
  "streamBroker.assignProfile": [{
46044
46585
  name: "deviceId",
46045
46586
  form: "single",
@@ -46055,6 +46596,16 @@ Object.freeze({
46055
46596
  form: "single",
46056
46597
  optional: false
46057
46598
  }],
46599
+ "streamBroker.getDeviceLiveContribution": [{
46600
+ name: "deviceId",
46601
+ form: "single",
46602
+ optional: false
46603
+ }],
46604
+ "streamBroker.getDeviceSettingsContribution": [{
46605
+ name: "deviceId",
46606
+ form: "single",
46607
+ optional: false
46608
+ }],
46058
46609
  "streamBroker.getStreamWithCodec": [{
46059
46610
  name: "deviceId",
46060
46611
  form: "single",
@@ -46115,16 +46666,46 @@ Object.freeze({
46115
46666
  form: "single",
46116
46667
  optional: false
46117
46668
  }],
46669
+ "streamParams.getStatus": [{
46670
+ name: "deviceId",
46671
+ form: "single",
46672
+ optional: false
46673
+ }],
46118
46674
  "streamParams.setProfile": [{
46119
46675
  name: "deviceId",
46120
46676
  form: "single",
46121
46677
  optional: false
46122
46678
  }],
46679
+ "switch.getStatus": [{
46680
+ name: "deviceId",
46681
+ form: "single",
46682
+ optional: false
46683
+ }],
46123
46684
  "switch.setState": [{
46124
46685
  name: "deviceId",
46125
46686
  form: "single",
46126
46687
  optional: false
46127
46688
  }],
46689
+ "tamper.getStatus": [{
46690
+ name: "deviceId",
46691
+ form: "single",
46692
+ optional: false
46693
+ }],
46694
+ "temperatureSensor.getStatus": [{
46695
+ name: "deviceId",
46696
+ form: "single",
46697
+ optional: false
46698
+ }],
46699
+ "update.getStatus": [{
46700
+ name: "deviceId",
46701
+ form: "single",
46702
+ optional: false
46703
+ }],
46704
+ "vacuumControl.getStatus": [{
46705
+ name: "deviceId",
46706
+ form: "single",
46707
+ optional: false
46708
+ }],
46128
46709
  "vacuumControl.locate": [{
46129
46710
  name: "deviceId",
46130
46711
  form: "single",
@@ -46160,6 +46741,11 @@ Object.freeze({
46160
46741
  form: "single",
46161
46742
  optional: false
46162
46743
  }],
46744
+ "valve.getStatus": [{
46745
+ name: "deviceId",
46746
+ form: "single",
46747
+ optional: false
46748
+ }],
46163
46749
  "valve.open": [{
46164
46750
  name: "deviceId",
46165
46751
  form: "single",
@@ -46175,6 +46761,11 @@ Object.freeze({
46175
46761
  form: "single",
46176
46762
  optional: false
46177
46763
  }],
46764
+ "vibration.getStatus": [{
46765
+ name: "deviceId",
46766
+ form: "single",
46767
+ optional: false
46768
+ }],
46178
46769
  "videoclips.dialClipStream": [{
46179
46770
  name: "deviceId",
46180
46771
  form: "single",
@@ -46210,6 +46801,11 @@ Object.freeze({
46210
46801
  form: "single",
46211
46802
  optional: false
46212
46803
  }],
46804
+ "waterHeater.getStatus": [{
46805
+ name: "deviceId",
46806
+ form: "single",
46807
+ optional: false
46808
+ }],
46213
46809
  "waterHeater.setAway": [{
46214
46810
  name: "deviceId",
46215
46811
  form: "single",
@@ -46225,6 +46821,11 @@ Object.freeze({
46225
46821
  form: "single",
46226
46822
  optional: false
46227
46823
  }],
46824
+ "weather.getStatus": [{
46825
+ name: "deviceId",
46826
+ form: "single",
46827
+ optional: false
46828
+ }],
46228
46829
  "webrtcSession.addIceCandidate": [{
46229
46830
  name: "deviceId",
46230
46831
  form: "single",
@@ -47169,7 +47770,7 @@ var PreviewTextField = string().max(200);
47169
47770
  * Whether the notification's preview is a STILL or a short animation.
47170
47771
  *
47171
47772
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
47172
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
47773
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
47173
47774
  * night reads better as three seconds of motion than as one frame of it. Both
47174
47775
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
47175
47776
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -47307,6 +47908,7 @@ TimelapseRuleInputSchema.extend({
47307
47908
  createdAt: number(),
47308
47909
  updatedAt: number()
47309
47910
  });
47911
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47310
47912
  object({
47311
47913
  /**
47312
47914
  * Fraction of the box's own size added on EACH side before cutting.