@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.mjs CHANGED
@@ -2,7 +2,7 @@ import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
2
2
  import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- //#region ../types/dist/event-category-C5xZWqz6.mjs
5
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -535,6 +535,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
535
535
  */
536
536
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
537
537
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
538
+ /**
539
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
540
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
541
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
542
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
543
+ * for a camera with no attached sensor). Providers must not emit
544
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
545
+ * keeps it so (camera-grid is a named, deferred exception).
546
+ */
538
547
  EventCategory["ProviderMotion"] = "provider.motion";
539
548
  EventCategory["ProviderDetection"] = "provider.detection";
540
549
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5373,7 +5382,7 @@ var ZodIssueCode = {
5373
5382
  var ZodFirstPartyTypeKind;
5374
5383
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5375
5384
  //#endregion
5376
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5385
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5377
5386
  /**
5378
5387
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5379
5388
  * window to float samples (D455).
@@ -7424,7 +7433,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
7424
7433
  return DeviceRole;
7425
7434
  }({});
7426
7435
  //#endregion
7427
- //#region ../types/dist/composition-Dq42XWp0.mjs
7436
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7428
7437
  /**
7429
7438
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7430
7439
  *
@@ -7478,21 +7487,20 @@ function portable(target) {
7478
7487
  }
7479
7488
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7480
7489
  /**
7481
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7482
- * valutatore puro del suo FRONTE.
7490
+ * THE table "which boolean of this device type counts as HIGH", and the
7491
+ * pure evaluator of its EDGE.
7483
7492
  *
7484
- * Viveva dentro il builtin virtual-doorbell
7485
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7486
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7487
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7488
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7489
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7490
- * SPOSTA qui e il doorbell la ri-esporta.
7493
+ * ONE table for everyone who asks "is this device HIGH?": the
7494
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7495
+ * sensor markers, the orchestrator's camera-sensors bridge and
7496
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7497
+ * A second copy would diverge at the first cap added, and the symptom — "the
7498
+ * sensor rings the doorbell but does not record" — is exactly D62.
7491
7499
  *
7492
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7493
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7494
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7495
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7500
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7501
+ * two must not be unified: that one answers "what is the state WORD for a
7502
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7503
+ * boolean's EDGE counts". Deliberately different vocabularies.
7496
7504
  */
7497
7505
  /**
7498
7506
  * Known binary / switch source caps → the boolean slice field whose
@@ -7513,6 +7521,8 @@ var SOURCE_CAP_ACTIVE_FIELD = {
7513
7521
  vibration: "detected",
7514
7522
  tamper: "tampered"
7515
7523
  };
7524
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7525
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7516
7526
  /**
7517
7527
  * Error types for the safe expression engine. Two distinct classes so callers
7518
7528
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -9216,22 +9226,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9216
9226
  "package"
9217
9227
  ];
9218
9228
  /**
9219
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9220
- * un operatore può selezionare.
9229
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9230
+ * an operator can select.
9221
9231
  *
9222
- * Sono le tre offerte dallo step `object-detection`
9232
+ * They are the three offered by the `object-detection` step
9223
9233
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9224
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9225
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9226
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9227
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9234
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9235
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9236
+ * `package-detection`, whose enablement is driven by zones, and offering the
9237
+ * same word twice has already made an operator turn on the COCO proxy
9228
9238
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9229
9239
  *
9230
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9231
- * dello step e una seconda volta come union `FirstLevelMacro`
9232
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9233
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9234
- * successiva.
9240
+ * ONE list. Before today the same three were hand-written in the step's offer
9241
+ * and a second time as the union `FirstLevelMacro`
9242
+ * (`types/detection.ts`); a third copy for the recording trigger
9243
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9244
+ * invisible.
9235
9245
  */
9236
9246
  var FIRST_LEVEL_MACRO_CLASSES = [
9237
9247
  "person",
@@ -9296,17 +9306,17 @@ var RecordingStorageModeSchema = _enum([
9296
9306
  "on-device-decision"
9297
9307
  ]);
9298
9308
  /**
9299
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9300
- * tre offerte dallo step `object-detection`, da UNA lista
9309
+ * The macro classes that can open a recording window — the same
9310
+ * three offered by the `object-detection` step, from ONE list
9301
9311
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9302
9312
  */
9303
9313
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9304
9314
  /**
9305
- * True quando `values` non ripete un elemento.
9315
+ * True when `values` does not repeat an element.
9306
9316
  *
9307
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9308
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9309
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9317
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9318
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9319
+ * would count its windows twice in `segmentMissedByMs`.
9310
9320
  */
9311
9321
  var noDuplicates = (values) => new Set(values).size === values.length;
9312
9322
  /** Which detectors trigger an `events`-mode band. */
@@ -9314,34 +9324,34 @@ var RecordingTriggersSchema = object({
9314
9324
  motion: boolean().optional(),
9315
9325
  audioThresholdDbfs: number().optional(),
9316
9326
  /**
9317
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9318
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9319
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9320
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9327
+ * The macro classes whose detection opens a window. ABSENT = the source
9328
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9329
+ * trigger on, no class" is the same shape "enabled and records
9330
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9321
9331
  *
9322
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9323
- * quelle che hanno attraversato `enabledMacroClasses`, i
9324
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9325
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9326
- * finestre — vedi `recorder/object-trigger.ts`.
9332
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9333
+ * those that went through `enabledMacroClasses`, the
9334
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9335
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9336
+ * windows — see `recorder/object-trigger.ts`.
9327
9337
  */
9328
9338
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9329
9339
  /**
9330
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9331
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9340
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9341
+ * is not listened to; an empty array is rejected for the same reason as
9332
9342
  * `objectClasses`.
9333
9343
  *
9334
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9335
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9336
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9337
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9338
- * device (D12) — mai un elenco globale di cap.
9344
+ * These are SOURCE device ids, not cameras: the band names them, so the
9345
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9346
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9347
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9348
+ * device (D12) — never a global list of caps.
9339
9349
  *
9340
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9341
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9342
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9343
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9344
- * registrare.
9350
+ * What "high" means depends on the device TYPE and is not decided here:
9351
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9352
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9353
+ * the level: a contact found already open at runner restart does not
9354
+ * record.
9345
9355
  */
9346
9356
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9347
9357
  });
@@ -10418,7 +10428,7 @@ var DecoderSessionConfigSchema = object({
10418
10428
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10419
10429
  * thing: the logging settings document on the `system` cap. Two authorities
10420
10430
  * over the values is the exact defect
10421
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10431
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10422
10432
  * remove; re-introducing it from the cure side would be grotesque.
10423
10433
  *
10424
10434
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -12148,7 +12158,7 @@ var BrokerStatusEnum = _enum([
12148
12158
  "unreachable",
12149
12159
  "error"
12150
12160
  ]);
12151
- var BrokerInfoSchema$1 = object({
12161
+ var BrokerInfoSchema = object({
12152
12162
  /** Stable broker id. Persisted; survives addon restarts. */
12153
12163
  id: string(),
12154
12164
  /** Addon id of the provider that OWNS this broker.
@@ -12281,7 +12291,7 @@ var GetStateInputSchema = object({
12281
12291
  * HA: entity_id (returns the cached entity state). */
12282
12292
  key: string()
12283
12293
  });
12284
- method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12294
+ method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12285
12295
  kind: "mutation",
12286
12296
  auth: "admin"
12287
12297
  }), method(RemoveInputSchema, _void(), {
@@ -13890,7 +13900,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13890
13900
  *
13891
13901
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13892
13902
  * was wrong — corrected 2026-08-26 after the hop map
13893
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13903
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13894
13904
  * There is **no Zod parse at all** between the door and the engine: the
13895
13905
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13896
13906
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14646,7 +14656,17 @@ var SiteZoneSchema = object({
14646
14656
  var ProviderStatusSchema = object({
14647
14657
  connected: boolean(),
14648
14658
  deviceCount: number(),
14649
- error: string().optional()
14659
+ error: string().optional(),
14660
+ /**
14661
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14662
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14663
+ * which every provider bundles, so only a provider BUILT from D696-amended
14664
+ * code says it. ABSENT means an older provider that may still write it (raw
14665
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14666
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14667
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14668
+ */
14669
+ motionSliceWriter: literal("orchestrator").optional()
14650
14670
  });
14651
14671
  object({
14652
14672
  externalId: string(),
@@ -15444,28 +15464,10 @@ method(object({
15444
15464
  deviceIds: array(number()).readonly(),
15445
15465
  caps: array(string()).readonly().optional()
15446
15466
  }), record(string(), record(string(), unknown().nullable())));
15447
- object({
15448
- /** Ms epoch of the last press. null = never observed since this provider started. */
15449
- lastPressedAt: number().nullable(),
15450
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15451
- pressCountSinceStart: number()
15452
- });
15453
- object({
15454
- deviceId: number(),
15455
- timestamp: number()
15456
- });
15457
- /**
15458
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15459
- * virtual-doorbell trigger devices, exactly what the wrapper's
15460
- * `doorbellSources` persists (as `{ deviceId }` records).
15461
- */
15462
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15467
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15463
15468
  DeviceType.Camera;
15464
- DeviceType.Button, DeviceType.Camera;
15465
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15466
- DeviceType.Camera, DeviceType.Camera;
15467
15469
  /** A slot's editable value, discriminated on `slot`. */
15468
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15470
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15469
15471
  DeviceType.Camera;
15470
15472
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15471
15473
  state: literal("unreachable"),
@@ -15502,10 +15504,11 @@ var ExtensionSlotSourceSchema = object({
15502
15504
  });
15503
15505
  /**
15504
15506
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15505
- * reported read-only, named, never written through this contract (D62). The
15506
- * virtual doorbell's sources ring only while the wrapper is bound to the
15507
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15508
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15507
+ * reported read-only, named, never written through this contract (D62) — e.g.
15508
+ * a slot whose sources act only while a wrapper is bound to the host, a
15509
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15510
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15511
+ * not be read; it is not "inactive" (D315).
15509
15512
  */
15510
15513
  var ExtensionSlotActivationSchema = object({
15511
15514
  state: _enum([
@@ -15521,7 +15524,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15521
15524
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15522
15525
  slotRef: string(),
15523
15526
  slot: ExtensionSlotIdSchema,
15524
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15527
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15525
15528
  authority: string(),
15526
15529
  activation: ExtensionSlotActivationSchema.nullable(),
15527
15530
  value: ExtensionSlotValueSchema,
@@ -15799,7 +15802,7 @@ method(object({
15799
15802
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15800
15803
  * ends in a log line, and a log line is the thing the operator asked to stop
15801
15804
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15802
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15805
+ * 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
15803
15806
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15804
15807
  * media blackout were both diagnosed. The counters stay; this is where they can
15805
15808
  * be READ.
@@ -17175,7 +17178,7 @@ var BrokerStatusSchema$1 = _enum([
17175
17178
  "unreachable",
17176
17179
  "tls-error"
17177
17180
  ]);
17178
- var BrokerInfoSchema = object({
17181
+ var BrokerInfoSchema$1 = object({
17179
17182
  id: string(),
17180
17183
  name: string(),
17181
17184
  url: string(),
@@ -17238,7 +17241,7 @@ var StatusSchema = object({
17238
17241
  brokerCount: number(),
17239
17242
  embeddedRunning: boolean()
17240
17243
  });
17241
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17244
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17242
17245
  kind: "mutation",
17243
17246
  auth: "admin"
17244
17247
  }), method(IdInputSchema, _void(), {
@@ -19428,78 +19431,16 @@ var NcRuleInputSchema = object({
19428
19431
  */
19429
19432
  confirm: NcConfirmSchema.optional(),
19430
19433
  /**
19431
- * WAIT for face/plate recognition before saying anything.
19432
- *
19433
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19434
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19435
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19436
- * measured on this hub — and an `immediate` rule enqueues on the first object
19437
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19438
- * immediate path, and no amount of media re-resolution fixes a sentence.
19439
- *
19440
- * Only two honest answers exist, and this flag picks between them. It has
19441
- * effect ONLY on a rule that declares a recognition scope
19442
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19443
- * other rule there is nothing to wait for and the flag is inert.
19444
- *
19445
- * | value | what happens |
19446
- * | --- | --- |
19447
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19448
- * | 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) |
19449
- *
19450
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19451
- * on the addon cap path, and absent has to keep meaning exactly what every
19452
- * rule authored before this field meant.
19453
- *
19454
- * The cost of `true` is stated here because the editor states it too: a rule
19455
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19456
- * tests every zone the track visited and a `crossing` condition can no longer
19457
- * be satisfied, because a closed track carries no crossing.
19458
- */
19459
- waitForEnhancement: boolean().optional(),
19460
- /**
19461
- * GROUP a burst of subjects into ONE notification that grows.
19462
- *
19463
- * Seconds of quiet after the last matching subject before the burst is
19464
- * considered over. While it is open, the first subject enqueues immediately —
19465
- * **exactly as today, with no added latency** — and every real growth (a new
19466
- * subject, or a name confirmed on one already in it) REPLACES that
19467
- * notification with an updated one naming everybody. The push carries the
19468
- * group's own coalescing tag, so the phone replaces rather than stacks.
19469
- *
19470
- * `0` / absent = off, and off is today's behaviour byte for byte.
19471
- *
19472
- * ### Why an idle cutoff and not a window
19473
- *
19474
- * The measured seven-person arrival on device 590 spans 110 s with every
19475
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19476
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19477
- * 30 s is a widely shipped value for the same decision.
19478
- *
19479
- * ### What it replaces
19480
- *
19481
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19482
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19483
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19484
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19485
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19486
- * budget over GROUPS — which is what it always meant — and a growth is never
19487
- * throttled by the window its own first member spent.
19488
- *
19489
- * ### Interaction with {@link waitForEnhancement}
19490
- *
19491
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19492
- * TRACK CLOSE, so with both set the group is opened by the first member to
19493
- * CLOSE — already carrying its name — and grows as later members close. That
19494
- * is later, and complete. With grouping alone the group opens on the first
19495
- * object event and picks up names as they are confirmed, through the growth
19496
- * path. Neither combination fires twice for one subject.
19497
- *
19498
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19499
- * on the addon cap path, so absent must keep meaning what it meant before this
19500
- * field existed.
19434
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19435
+ * seconds; the rule sends ONE message when the window closes — early, once every
19436
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19437
+ * the rule matched, with one mosaic tile per subject.
19438
+ * Absent = plain immediate (one message per track, at the first match).
19439
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19440
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19441
+ * path does not run Zod defaults.
19501
19442
  */
19502
- groupIdleSec: number().int().min(0).max(600).optional()
19443
+ groupingSec: number().int().min(5).max(60).optional()
19503
19444
  });
19504
19445
  /**
19505
19446
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19547,9 +19488,10 @@ var NcRuleClearableKeySchema = _enum([
19547
19488
  "targetUsers",
19548
19489
  "snoozeOptions",
19549
19490
  "snoozeAllowGlobal",
19550
- "waitForEnhancement",
19551
19491
  "actions",
19552
19492
  "confirm",
19493
+ "groupingSec",
19494
+ "waitForEnhancement",
19553
19495
  "groupIdleSec"
19554
19496
  ]);
19555
19497
  var NcRuleUpdateInputSchema = object({
@@ -19571,9 +19513,25 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19571
19513
  */
19572
19514
  disabledTargetIds: array(string()).default([])
19573
19515
  });
19516
+ /**
19517
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19518
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19519
+ * job is not an open state, and the alarm stays instant.
19520
+ */
19521
+ var NcStateSummarySectionIdSchema = _enum([
19522
+ "device-state",
19523
+ "streams",
19524
+ "battery",
19525
+ "consumables",
19526
+ "tanks",
19527
+ "detection-blind",
19528
+ "nodes",
19529
+ "addons",
19530
+ "updates"
19531
+ ]);
19574
19532
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19575
19533
  var NcStateSummaryPreviewSectionSchema = object({
19576
- id: string(),
19534
+ id: NcStateSummarySectionIdSchema,
19577
19535
  status: _enum(["read", "unavailable"]),
19578
19536
  items: number().int().nonnegative(),
19579
19537
  reason: string().optional()
@@ -19583,7 +19541,7 @@ var NcStateSummaryPreviewSectionSchema = object({
19583
19541
  * a `rule` split (no `section`), one section's on a `section` split.
19584
19542
  */
19585
19543
  var NcStateSummaryPreviewRowSchema = object({
19586
- section: string().optional(),
19544
+ section: NcStateSummarySectionIdSchema.optional(),
19587
19545
  /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19588
19546
  title: string(),
19589
19547
  text: string(),
@@ -20020,6 +19978,29 @@ object({
20020
19978
  firstAt: number(),
20021
19979
  lastAt: number()
20022
19980
  });
19981
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
19982
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
19983
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
19984
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
19985
+ var NcAlarmModeDelaysListSchema = array(object({
19986
+ mode: AlarmArmModeSchema,
19987
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
19988
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
19989
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
19990
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
19991
+ var NcAlarmModeDelaysEditListSchema = array(object({
19992
+ mode: AlarmArmModeSchema,
19993
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19994
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19995
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
19996
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
19997
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
19998
+ var NcAlarmNonBlockingEditSchema = object({
19999
+ mode: AlarmArmModeSchema,
20000
+ deviceId: number().int().positive(),
20001
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20002
+ nonBlocking: boolean()
20003
+ });
20023
20004
  /**
20024
20005
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20025
20006
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -20028,10 +20009,10 @@ object({
20028
20009
  var NcAlarmNonBlockingListSchema = array(object({
20029
20010
  mode: AlarmArmModeSchema,
20030
20011
  deviceIds: array(number().int()).max(200)
20031
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
20012
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20032
20013
  /**
20033
- * The three durations the panel's state machine runs on, who hears about its
20034
- * transitions, and which openings each mode tolerates.
20014
+ * The three durations — global, and per mode since D697 — the panel's state
20015
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20035
20016
  *
20036
20017
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20037
20018
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20041,15 +20022,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20041
20022
  */
20042
20023
  var NcAlarmSettingsSchema = object({
20043
20024
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20044
- exitDelaySec: number().int().min(0).max(600),
20025
+ exitDelaySec: NcAlarmDelaySecSchema,
20045
20026
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20046
- entryDelaySec: number().int().min(0).max(600),
20027
+ entryDelaySec: NcAlarmDelaySecSchema,
20047
20028
  /**
20048
20029
  * How long `triggered` lasts before the panel re-arms itself.
20049
20030
  * **0 = until an operator disarms it** — the behaviour before this field
20050
20031
  * existed, and therefore what an untouched install keeps doing.
20051
20032
  */
20052
- triggeredDurationSec: number().int().min(0).max(3600),
20033
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20053
20034
  /**
20054
20035
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20055
20036
  * remove after one release. Optional so an older admin's patch (which still
@@ -20072,17 +20053,30 @@ var NcAlarmSettingsSchema = object({
20072
20053
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20073
20054
  * parses as "every opening blocks" — the behaviour it was written under.
20074
20055
  */
20075
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20056
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20057
+ /**
20058
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20059
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20060
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20061
+ */
20062
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20076
20063
  });
20077
20064
  /**
20078
20065
  * Every field optional — a tab edits one control at a time.
20079
20066
  *
20080
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20081
- * remove an inner `.default()`, so a patch that never named the field would
20082
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20083
- * (the same trap {@link NcRulePatchSchema} documents).
20067
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20068
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20069
+ * named the field would parse to `[]` and wipe every mode's list on an
20070
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20071
+ * `modeDelays` means EDITS, not the stored overrides.
20084
20072
  */
20085
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20073
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20074
+ nonBlocking: NcAlarmNonBlockingListSchema,
20075
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20076
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20077
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20078
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20079
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20086
20080
  /**
20087
20081
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20088
20082
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20182,6 +20176,28 @@ var NcAlarmConfigSchema = object({
20182
20176
  */
20183
20177
  live: NcAlarmLiveSchema.optional()
20184
20178
  });
20179
+ var NcAlarmSensorReadingSchema = _enum([
20180
+ "open",
20181
+ "closed",
20182
+ "unknown",
20183
+ "not-opening"
20184
+ ]);
20185
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20186
+ var NcAlarmSensorStateSchema = object({
20187
+ deviceId: number().int().positive(),
20188
+ reading: NcAlarmSensorReadingSchema,
20189
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20190
+ state: string().nullable(),
20191
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20192
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20193
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20194
+ online: boolean().nullable()
20195
+ });
20196
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20197
+ var NcAlarmSensorsSchema = object({
20198
+ sensors: array(NcAlarmSensorStateSchema),
20199
+ readAt: number().int().nonnegative()
20200
+ });
20185
20201
  /**
20186
20202
  * ONE rule's demand on ONE camera's clip ring.
20187
20203
  *
@@ -20272,7 +20288,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20272
20288
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20273
20289
  kind: "mutation",
20274
20290
  auth: "admin"
20275
- });
20291
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20276
20292
  /**
20277
20293
  * Zod schemas for persisted record types.
20278
20294
  *
@@ -20619,12 +20635,26 @@ var CameraSensorRefSchema = object({
20619
20635
  function cameraSensorRefKey(ref) {
20620
20636
  return `${ref.addonId}/${ref.stableId}`;
20621
20637
  }
20622
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20623
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20624
- var CameraSensorsConfigSchema = object({
20625
- mode: CameraSensorsModeSchema,
20626
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20627
- }).superRefine((value, ctx) => {
20638
+ /**
20639
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20640
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20641
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20642
+ * camera's own motion counts only while a confirm sensor is active — with or
20643
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20644
+ */
20645
+ var CameraSensorModeSchema = _enum([
20646
+ "in-addition",
20647
+ "confirm",
20648
+ "replace"
20649
+ ]);
20650
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20651
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20652
+ /**
20653
+ * A camera's attached sensors. A CS-1 row carried an envelope
20654
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20655
+ * what the write stores (D672 rule 3), so it is never written again.
20656
+ */
20657
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20628
20658
  const seen = /* @__PURE__ */ new Set();
20629
20659
  value.sensors.forEach((ref, index) => {
20630
20660
  const key = cameraSensorRefKey(ref);
@@ -20636,39 +20666,41 @@ var CameraSensorsConfigSchema = object({
20636
20666
  seen.add(key);
20637
20667
  });
20638
20668
  });
20639
- var RefFields = {
20669
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20670
+ var AttachmentFields = {
20640
20671
  addonId: string(),
20641
- stableId: string()
20672
+ stableId: string(),
20673
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20642
20674
  };
20643
20675
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20644
20676
  object({
20645
- ...RefFields,
20677
+ ...AttachmentFields,
20646
20678
  state: literal("ready"),
20647
20679
  deviceId: number(),
20648
20680
  name: string(),
20649
20681
  caps: array(string()).readonly()
20650
20682
  }),
20651
20683
  object({
20652
- ...RefFields,
20684
+ ...AttachmentFields,
20653
20685
  state: literal("offline"),
20654
20686
  deviceId: number(),
20655
20687
  name: string(),
20656
20688
  reason: _enum(["offline", "disabled"])
20657
20689
  }),
20658
20690
  object({
20659
- ...RefFields,
20691
+ ...AttachmentFields,
20660
20692
  state: literal("missing"),
20661
20693
  reason: string()
20662
20694
  }),
20663
20695
  object({
20664
- ...RefFields,
20696
+ ...AttachmentFields,
20665
20697
  state: literal("unsupported"),
20666
20698
  deviceId: number(),
20667
20699
  name: string(),
20668
20700
  reason: string()
20669
20701
  }),
20670
20702
  object({
20671
- ...RefFields,
20703
+ ...AttachmentFields,
20672
20704
  state: literal("unreadable"),
20673
20705
  reason: string()
20674
20706
  })
@@ -20681,17 +20713,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20681
20713
  * say.
20682
20714
  */
20683
20715
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20716
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20717
+ var CameraOwnMotionSchema = _enum([
20718
+ "always",
20719
+ "when-confirmed",
20720
+ "never"
20721
+ ]);
20722
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20723
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20724
+ /**
20725
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20726
+ * never the effective value. The camera's `motion` slice is the effective one,
20727
+ * so a view of the camera's own row reads this.
20728
+ * - `atMs`: when the bridge received the camera's last own edge.
20729
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20730
+ * AND it is younger than the window the bridge counts the camera's own motion
20731
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20732
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20733
+ * within the window; a status read is a snapshot, so a live view re-asks.
20734
+ * Absent = this orchestrator has not received the camera's own motion since it
20735
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20736
+ */
20737
+ var CameraRawLevelSchema = object({
20738
+ detected: boolean(),
20739
+ atMs: number()
20740
+ });
20684
20741
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20685
- object({ state: literal("not-configured") }),
20742
+ object({
20743
+ state: literal("not-configured"),
20744
+ cameraLevel: CameraRawLevelSchema.optional()
20745
+ }),
20686
20746
  object({
20687
20747
  state: literal("unreadable"),
20688
- reason: string()
20748
+ reason: string(),
20749
+ cameraLevel: CameraRawLevelSchema.optional()
20689
20750
  }),
20690
20751
  object({
20691
20752
  state: literal("configured"),
20692
- mode: CameraSensorsModeSchema,
20753
+ cameraLevel: CameraRawLevelSchema.optional(),
20693
20754
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20694
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20755
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20756
+ cameraMotion: CameraOwnMotionSchema.optional(),
20757
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20758
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20695
20759
  })
20696
20760
  ]);
20697
20761
  Object.freeze({
@@ -21376,14 +21440,14 @@ var TrackSchema = object({
21376
21440
  * `derive-returned-null tile=standin`,
21377
21441
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21378
21442
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21379
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21443
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21380
21444
  * tile is a face/plate stand-in, a raster crop, or an icon.
21381
21445
  *
21382
21446
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21383
21447
  * the TILE ranked below native, so a track whose thumbnail landed natively
21384
21448
  * while its key frame missed carried no reason at all — three of the four
21385
21449
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21386
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21450
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21387
21451
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21388
21452
  * lastFrame copy source`), so its absence is now on this line whatever the
21389
21453
  * tile ended up being.
@@ -21470,7 +21534,7 @@ var MotionEventSchema = object({
21470
21534
  /**
21471
21535
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21472
21536
  * first entry is always `0`) of every genuine off→on transition the
21473
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21537
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21474
21538
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21475
21539
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21476
21540
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -21994,6 +22058,7 @@ var KeyEventSchema = object({
21994
22058
  bestEventId: string(),
21995
22059
  /** Track lifetime in ms (lastSeen - firstSeen). */
21996
22060
  windowMs: number().optional(),
22061
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
21997
22062
  ...TrackFlagFields,
21998
22063
  ...TrackRetrainFields
21999
22064
  });
@@ -23882,8 +23947,10 @@ var RunnerCameraConfigSchema = object({
23882
23947
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23883
23948
  * analyzer providers) running on decoded frames in this runner.
23884
23949
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
23885
- * analytics). The provider emits ProviderMotion
23886
- * events; the orchestrator forwards to `reportMotion`.
23950
+ * analytics). The provider emits its raw push as `ProviderMotion`
23951
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
23952
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
23953
+ * `reportMotion`.
23887
23954
  */
23888
23955
  motionSources: MotionSourcesSchema.default(["analyzer"]),
23889
23956
  /**
@@ -24060,7 +24127,15 @@ var RunnerLocalLoadSchema = object({
24060
24127
  hasGpu: boolean(),
24061
24128
  inferenceBackend: string().optional(),
24062
24129
  cpuCores: number().optional()
24063
- })
24130
+ }),
24131
+ /**
24132
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24133
+ * runner never writes it. ABSENT = a runner older than D696, which still
24134
+ * writes the slice from its phase machine; the orchestrator does not write
24135
+ * while any runner node is absent-or-unanswered, so one slice never has two
24136
+ * writers.
24137
+ */
24138
+ motionSliceWriter: literal("orchestrator").optional()
24064
24139
  });
24065
24140
  /**
24066
24141
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -26696,7 +26771,7 @@ var ClipPlaybackSchema = object({
26696
26771
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26697
26772
  * time the camera is awake for its own reasons — is deliberately not a member:
26698
26773
  * it was proposed, it is free on the battery, and the operator declined it on
26699
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
26774
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26700
26775
  * D558 "Considered and not taken").
26701
26776
  */
26702
26777
  var ClipWakeSchema = _enum(["authorised"]);
@@ -26957,7 +27032,7 @@ DeviceType.Camera, method(object({
26957
27032
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
26958
27033
  * assumed the answer was a fan-out over everything a camera has. The
26959
27034
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
26960
- * solo"* — so the list is asked of one provider at a time and there is
27035
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
26961
27036
  * nothing to filter out of it.
26962
27037
  */
26963
27038
  provider: string().min(1)
@@ -28901,6 +28976,17 @@ object({
28901
28976
  deviceId: number(),
28902
28977
  status: DeviceStatusSchema
28903
28978
  });
28979
+ object({
28980
+ /** Ms epoch of the last press. null = never observed since this provider started. */
28981
+ lastPressedAt: number().nullable(),
28982
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
28983
+ pressCountSinceStart: number()
28984
+ });
28985
+ object({
28986
+ deviceId: number(),
28987
+ timestamp: number()
28988
+ });
28989
+ DeviceType.Button, DeviceType.Camera;
28904
28990
  /**
28905
28991
  * Enum-state sensor — a string value picked from a finite option set.
28906
28992
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30545,9 +30631,9 @@ object({
30545
30631
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30546
30632
  lastDetectedAt: number().nullable(),
30547
30633
  /**
30548
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30549
- * `null` while false and on every `Sensor` device (D475) — see that
30550
- * constant's doc for the one-authority rule.
30634
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30635
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30636
+ * device (D475) — see that constant's doc for the one-authority rule.
30551
30637
  *
30552
30638
  * ## Reading this field still arms nothing
30553
30639
  *
@@ -30565,38 +30651,41 @@ object({
30565
30651
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30566
30652
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30567
30653
  *
30568
- * ## Who writes it
30569
- *
30570
- * - **Cameras** — the runner's phase machine, `active → watching` on
30571
- * `cooldown_expired`, which then writes this slice with
30572
- * `detected: false` (`handlePhaseChanged` in
30573
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30574
- * matters most for the sources that only ever push a rising one:
30575
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30576
- * a false.
30577
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30578
- * provider pushes the false itself, from the upstream system's own
30579
- * state change. No phase machine is involved.
30580
- *
30581
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30582
- *
30583
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30584
- * phase machine is the sole writer for a camera. It is not.
30585
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30586
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30587
- * Hikvision's comment says why: it read THIS docblock, agreed the
30588
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30589
- * two authorities can disagree about one slice, which this repo
30590
- * forbids, and the doc said otherwise — which is worse than saying
30591
- * nothing, because it reads as verification.
30592
- *
30593
- * This predates D475 and is not fixed there: the fix touches every
30594
- * camera provider. Recorded in D475's Consequences. Do not restore the
30595
- * "sole writer" wording without also removing the other writers.
30654
+ * ## Who writes it (D696)
30655
+ *
30656
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30657
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30658
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30659
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30660
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30661
+ * device activity, as the camera's `motionSources` counts them. A source
30662
+ * that never sends a clear falls one window after its last `true`.
30663
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30664
+ * writes this slice (not even a boot seed), and neither does the runner's
30665
+ * phase machine: its phase is "a session is attached" for every on-motion
30666
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30667
+ * A provider's `isDetected` answers from the hub's slice.
30668
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30669
+ * imports) — the provider pushes both edges itself, from the upstream
30670
+ * system's own state change. That is the sensor's own fact.
30671
+ *
30672
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30673
+ * `motion` slice; each sensor writer is a named exemption.
30674
+ *
30675
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30676
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30677
+ * it, every provider seeded it at boot, and every session left it
30678
+ * `detected: true` after its detach.
30596
30679
  */
30597
30680
  autoClearAfterMs: number().nullable()
30598
30681
  });
30599
- object({
30682
+ /**
30683
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30684
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30685
+ * — both the cap event surface and the bus payload type alias to this
30686
+ * schema.
30687
+ */
30688
+ var MotionOnMotionChangedDataSchema = object({
30600
30689
  deviceId: number(),
30601
30690
  detected: boolean(),
30602
30691
  timestamp: number(),
@@ -33061,7 +33150,7 @@ var ReadGopBytesResultSchema = object({
33061
33150
  /**
33062
33151
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
33063
33152
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
33064
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
33153
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
33065
33154
  * a replay needs several seconds of native pixels, not one frame.
33066
33155
  *
33067
33156
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -35677,6 +35766,7 @@ DeviceType.Camera, method(object({
35677
35766
  detection: array(ZoneRuleSchema).readonly(),
35678
35767
  package: array(ZoneRuleSchema).readonly()
35679
35768
  });
35769
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
35680
35770
  /**
35681
35771
  * Accessory device helpers — shared across drivers.
35682
35772
  *
@@ -40655,6 +40745,12 @@ Object.freeze({
40655
40745
  addonId: null,
40656
40746
  access: "view"
40657
40747
  },
40748
+ "notificationRules.getAlarmSensors": {
40749
+ capName: "notification-rules",
40750
+ capScope: "system",
40751
+ addonId: null,
40752
+ access: "view"
40753
+ },
40658
40754
  "notificationRules.getClipRetentionAsks": {
40659
40755
  capName: "notification-rules",
40660
40756
  capScope: "system",
@@ -44233,6 +44329,11 @@ Object.freeze({
44233
44329
  }
44234
44330
  });
44235
44331
  Object.freeze({
44332
+ "accessories.getStatus": [{
44333
+ name: "deviceId",
44334
+ form: "single",
44335
+ optional: false
44336
+ }],
44236
44337
  "accessories.setChildHidden": [{
44237
44338
  name: "childDeviceId",
44238
44339
  form: "single",
@@ -44252,6 +44353,11 @@ Object.freeze({
44252
44353
  form: "single",
44253
44354
  optional: false
44254
44355
  }],
44356
+ "airQualitySensor.getStatus": [{
44357
+ name: "deviceId",
44358
+ form: "single",
44359
+ optional: false
44360
+ }],
44255
44361
  "alarmPanel.arm": [{
44256
44362
  name: "deviceId",
44257
44363
  form: "single",
@@ -44262,11 +44368,36 @@ Object.freeze({
44262
44368
  form: "single",
44263
44369
  optional: false
44264
44370
  }],
44371
+ "alarmPanel.getStatus": [{
44372
+ name: "deviceId",
44373
+ form: "single",
44374
+ optional: false
44375
+ }],
44265
44376
  "alarmPanel.trigger": [{
44266
44377
  name: "deviceId",
44267
44378
  form: "single",
44268
44379
  optional: false
44269
44380
  }],
44381
+ "ambientLightSensor.getStatus": [{
44382
+ name: "deviceId",
44383
+ form: "single",
44384
+ optional: false
44385
+ }],
44386
+ "audioAnalysis.applyDeviceSettingsPatch": [{
44387
+ name: "deviceId",
44388
+ form: "single",
44389
+ optional: false
44390
+ }],
44391
+ "audioAnalysis.getDeviceLiveContribution": [{
44392
+ name: "deviceId",
44393
+ form: "single",
44394
+ optional: false
44395
+ }],
44396
+ "audioAnalysis.getDeviceSettingsContribution": [{
44397
+ name: "deviceId",
44398
+ form: "single",
44399
+ optional: false
44400
+ }],
44270
44401
  "audioAnalysis.resolveDeviceSettings": [{
44271
44402
  name: "deviceId",
44272
44403
  form: "single",
@@ -44307,16 +44438,36 @@ Object.freeze({
44307
44438
  form: "single",
44308
44439
  optional: false
44309
44440
  }],
44441
+ "automationControl.getStatus": [{
44442
+ name: "deviceId",
44443
+ form: "single",
44444
+ optional: false
44445
+ }],
44310
44446
  "automationControl.trigger": [{
44311
44447
  name: "deviceId",
44312
44448
  form: "single",
44313
44449
  optional: false
44314
44450
  }],
44451
+ "battery.getStatus": [{
44452
+ name: "deviceId",
44453
+ form: "single",
44454
+ optional: false
44455
+ }],
44315
44456
  "battery.wakeForStream": [{
44316
44457
  name: "deviceId",
44317
44458
  form: "single",
44318
44459
  optional: false
44319
44460
  }],
44461
+ "binary.getStatus": [{
44462
+ name: "deviceId",
44463
+ form: "single",
44464
+ optional: false
44465
+ }],
44466
+ "brightness.getStatus": [{
44467
+ name: "deviceId",
44468
+ form: "single",
44469
+ optional: false
44470
+ }],
44320
44471
  "brightness.setBrightness": [{
44321
44472
  name: "deviceId",
44322
44473
  form: "single",
@@ -44332,6 +44483,26 @@ Object.freeze({
44332
44483
  form: "single",
44333
44484
  optional: false
44334
44485
  }],
44486
+ "cameraCredentials.getStatus": [{
44487
+ name: "deviceId",
44488
+ form: "single",
44489
+ optional: false
44490
+ }],
44491
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
44492
+ name: "deviceId",
44493
+ form: "single",
44494
+ optional: false
44495
+ }],
44496
+ "cameraGridLayout.getDeviceLiveContribution": [{
44497
+ name: "deviceId",
44498
+ form: "single",
44499
+ optional: false
44500
+ }],
44501
+ "cameraGridLayout.getDeviceSettingsContribution": [{
44502
+ name: "deviceId",
44503
+ form: "single",
44504
+ optional: false
44505
+ }],
44335
44506
  "cameraGridLayout.getLayout": [{
44336
44507
  name: "deviceId",
44337
44508
  form: "single",
@@ -44347,6 +44518,21 @@ Object.freeze({
44347
44518
  form: "single",
44348
44519
  optional: false
44349
44520
  }],
44521
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
44522
+ name: "deviceId",
44523
+ form: "single",
44524
+ optional: false
44525
+ }],
44526
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
44527
+ name: "deviceId",
44528
+ form: "single",
44529
+ optional: false
44530
+ }],
44531
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
44532
+ name: "deviceId",
44533
+ form: "single",
44534
+ optional: false
44535
+ }],
44350
44536
  "cameraStreams.getBrokerStreams": [{
44351
44537
  name: "deviceId",
44352
44538
  form: "single",
@@ -44372,6 +44558,16 @@ Object.freeze({
44372
44558
  form: "single",
44373
44559
  optional: false
44374
44560
  }],
44561
+ "carbonMonoxide.getStatus": [{
44562
+ name: "deviceId",
44563
+ form: "single",
44564
+ optional: false
44565
+ }],
44566
+ "climateControl.getStatus": [{
44567
+ name: "deviceId",
44568
+ form: "single",
44569
+ optional: false
44570
+ }],
44375
44571
  "climateControl.setFanMode": [{
44376
44572
  name: "deviceId",
44377
44573
  form: "single",
@@ -44412,16 +44608,41 @@ Object.freeze({
44412
44608
  form: "single",
44413
44609
  optional: false
44414
44610
  }],
44611
+ "color.getStatus": [{
44612
+ name: "deviceId",
44613
+ form: "single",
44614
+ optional: false
44615
+ }],
44415
44616
  "color.setColor": [{
44416
44617
  name: "deviceId",
44417
44618
  form: "single",
44418
44619
  optional: false
44419
44620
  }],
44621
+ "connectivity.getStatus": [{
44622
+ name: "deviceId",
44623
+ form: "single",
44624
+ optional: false
44625
+ }],
44626
+ "consumables.getStatus": [{
44627
+ name: "deviceId",
44628
+ form: "single",
44629
+ optional: false
44630
+ }],
44420
44631
  "consumables.reset": [{
44421
44632
  name: "deviceId",
44422
44633
  form: "single",
44423
44634
  optional: false
44424
44635
  }],
44636
+ "contact.getStatus": [{
44637
+ name: "deviceId",
44638
+ form: "single",
44639
+ optional: false
44640
+ }],
44641
+ "control.getStatus": [{
44642
+ name: "deviceId",
44643
+ form: "single",
44644
+ optional: false
44645
+ }],
44425
44646
  "control.setValue": [{
44426
44647
  name: "deviceId",
44427
44648
  form: "single",
@@ -44432,6 +44653,11 @@ Object.freeze({
44432
44653
  form: "single",
44433
44654
  optional: false
44434
44655
  }],
44656
+ "cover.getStatus": [{
44657
+ name: "deviceId",
44658
+ form: "single",
44659
+ optional: false
44660
+ }],
44435
44661
  "cover.open": [{
44436
44662
  name: "deviceId",
44437
44663
  form: "single",
@@ -44457,6 +44683,11 @@ Object.freeze({
44457
44683
  form: "single",
44458
44684
  optional: false
44459
44685
  }],
44686
+ "dayNight.getStatus": [{
44687
+ name: "deviceId",
44688
+ form: "single",
44689
+ optional: false
44690
+ }],
44460
44691
  "dayNight.setSettings": [{
44461
44692
  name: "deviceId",
44462
44693
  form: "single",
@@ -44467,11 +44698,31 @@ Object.freeze({
44467
44698
  form: "single",
44468
44699
  optional: true
44469
44700
  }],
44701
+ "detectionPipeline.applyDeviceSettingsPatch": [{
44702
+ name: "deviceId",
44703
+ form: "single",
44704
+ optional: false
44705
+ }],
44706
+ "detectionPipeline.getDeviceLiveContribution": [{
44707
+ name: "deviceId",
44708
+ form: "single",
44709
+ optional: false
44710
+ }],
44711
+ "detectionPipeline.getDeviceSettingsContribution": [{
44712
+ name: "deviceId",
44713
+ form: "single",
44714
+ optional: false
44715
+ }],
44470
44716
  "deviceAdminLink.getAdminLink": [{
44471
44717
  name: "deviceId",
44472
44718
  form: "single",
44473
44719
  optional: false
44474
44720
  }],
44721
+ "deviceAdoption.getStatus": [{
44722
+ name: "deviceId",
44723
+ form: "single",
44724
+ optional: false
44725
+ }],
44475
44726
  "deviceAdoption.release": [{
44476
44727
  name: "camDeviceId",
44477
44728
  form: "single",
@@ -44487,6 +44738,11 @@ Object.freeze({
44487
44738
  form: "single",
44488
44739
  optional: false
44489
44740
  }],
44741
+ "deviceDiscovery.getStatus": [{
44742
+ name: "deviceId",
44743
+ form: "single",
44744
+ optional: false
44745
+ }],
44490
44746
  "deviceDiscovery.listDiscovered": [{
44491
44747
  name: "deviceId",
44492
44748
  form: "single",
@@ -44506,6 +44762,21 @@ Object.freeze({
44506
44762
  form: "single",
44507
44763
  optional: false
44508
44764
  }],
44765
+ "deviceExport.applyDeviceSettingsPatch": [{
44766
+ name: "deviceId",
44767
+ form: "single",
44768
+ optional: false
44769
+ }],
44770
+ "deviceExport.getDeviceLiveContribution": [{
44771
+ name: "deviceId",
44772
+ form: "single",
44773
+ optional: false
44774
+ }],
44775
+ "deviceExport.getDeviceSettingsContribution": [{
44776
+ name: "deviceId",
44777
+ form: "single",
44778
+ optional: false
44779
+ }],
44509
44780
  "deviceExtension.listCandidates": [{
44510
44781
  name: "deviceId",
44511
44782
  form: "single",
@@ -44854,6 +45125,26 @@ Object.freeze({
44854
45125
  form: "single",
44855
45126
  optional: false
44856
45127
  }],
45128
+ "deviceStatus.getStatus": [{
45129
+ name: "deviceId",
45130
+ form: "single",
45131
+ optional: false
45132
+ }],
45133
+ "doorbell.getStatus": [{
45134
+ name: "deviceId",
45135
+ form: "single",
45136
+ optional: false
45137
+ }],
45138
+ "enumSensor.getStatus": [{
45139
+ name: "deviceId",
45140
+ form: "single",
45141
+ optional: false
45142
+ }],
45143
+ "eventEmitter.getStatus": [{
45144
+ name: "deviceId",
45145
+ form: "single",
45146
+ optional: false
45147
+ }],
44857
45148
  "faceGallery.getFaceByTrack": [{
44858
45149
  name: "deviceId",
44859
45150
  form: "single",
@@ -44868,6 +45159,11 @@ Object.freeze({
44868
45159
  form: "array",
44869
45160
  optional: true
44870
45161
  }],
45162
+ "fanControl.getStatus": [{
45163
+ name: "deviceId",
45164
+ form: "single",
45165
+ optional: false
45166
+ }],
44871
45167
  "fanControl.setDirection": [{
44872
45168
  name: "deviceId",
44873
45169
  form: "single",
@@ -44888,6 +45184,26 @@ Object.freeze({
44888
45184
  form: "single",
44889
45185
  optional: false
44890
45186
  }],
45187
+ "featureProbe.getStatus": [{
45188
+ name: "deviceId",
45189
+ form: "single",
45190
+ optional: false
45191
+ }],
45192
+ "flood.getStatus": [{
45193
+ name: "deviceId",
45194
+ form: "single",
45195
+ optional: false
45196
+ }],
45197
+ "gas.getStatus": [{
45198
+ name: "deviceId",
45199
+ form: "single",
45200
+ optional: false
45201
+ }],
45202
+ "humidifier.getStatus": [{
45203
+ name: "deviceId",
45204
+ form: "single",
45205
+ optional: false
45206
+ }],
44891
45207
  "humidifier.setMode": [{
44892
45208
  name: "deviceId",
44893
45209
  form: "single",
@@ -44903,11 +45219,26 @@ Object.freeze({
44903
45219
  form: "single",
44904
45220
  optional: false
44905
45221
  }],
45222
+ "humiditySensor.getStatus": [{
45223
+ name: "deviceId",
45224
+ form: "single",
45225
+ optional: false
45226
+ }],
45227
+ "image.getStatus": [{
45228
+ name: "deviceId",
45229
+ form: "single",
45230
+ optional: false
45231
+ }],
44906
45232
  "imageSettings.getOptions": [{
44907
45233
  name: "deviceId",
44908
45234
  form: "single",
44909
45235
  optional: false
44910
45236
  }],
45237
+ "imageSettings.getStatus": [{
45238
+ name: "deviceId",
45239
+ form: "single",
45240
+ optional: false
45241
+ }],
44911
45242
  "imageSettings.setSettings": [{
44912
45243
  name: "deviceId",
44913
45244
  form: "single",
@@ -44918,6 +45249,11 @@ Object.freeze({
44918
45249
  form: "single",
44919
45250
  optional: false
44920
45251
  }],
45252
+ "intercom.getStatus": [{
45253
+ name: "deviceId",
45254
+ form: "single",
45255
+ optional: false
45256
+ }],
44921
45257
  "intercom.handleAnswer": [{
44922
45258
  name: "deviceId",
44923
45259
  form: "single",
@@ -44948,6 +45284,11 @@ Object.freeze({
44948
45284
  form: "single",
44949
45285
  optional: false
44950
45286
  }],
45287
+ "lawnMowerControl.getStatus": [{
45288
+ name: "deviceId",
45289
+ form: "single",
45290
+ optional: false
45291
+ }],
44951
45292
  "lawnMowerControl.pause": [{
44952
45293
  name: "deviceId",
44953
45294
  form: "single",
@@ -44958,6 +45299,11 @@ Object.freeze({
44958
45299
  form: "single",
44959
45300
  optional: false
44960
45301
  }],
45302
+ "lockControl.getStatus": [{
45303
+ name: "deviceId",
45304
+ form: "single",
45305
+ optional: false
45306
+ }],
44961
45307
  "lockControl.lock": [{
44962
45308
  name: "deviceId",
44963
45309
  form: "single",
@@ -44973,6 +45319,11 @@ Object.freeze({
44973
45319
  form: "single",
44974
45320
  optional: false
44975
45321
  }],
45322
+ "mediaPlayer.getStatus": [{
45323
+ name: "deviceId",
45324
+ form: "single",
45325
+ optional: false
45326
+ }],
44976
45327
  "mediaPlayer.next": [{
44977
45328
  name: "deviceId",
44978
45329
  form: "single",
@@ -45033,6 +45384,11 @@ Object.freeze({
45033
45384
  form: "single",
45034
45385
  optional: false
45035
45386
  }],
45387
+ "motion.getStatus": [{
45388
+ name: "deviceId",
45389
+ form: "single",
45390
+ optional: false
45391
+ }],
45036
45392
  "motion.isDetected": [{
45037
45393
  name: "deviceId",
45038
45394
  form: "single",
@@ -45043,11 +45399,31 @@ Object.freeze({
45043
45399
  form: "single",
45044
45400
  optional: false
45045
45401
  }],
45402
+ "motionDetection.applyDeviceSettingsPatch": [{
45403
+ name: "deviceId",
45404
+ form: "single",
45405
+ optional: false
45406
+ }],
45407
+ "motionDetection.getDeviceLiveContribution": [{
45408
+ name: "deviceId",
45409
+ form: "single",
45410
+ optional: false
45411
+ }],
45412
+ "motionDetection.getDeviceSettingsContribution": [{
45413
+ name: "deviceId",
45414
+ form: "single",
45415
+ optional: false
45416
+ }],
45046
45417
  "motionDetection.removeCamera": [{
45047
45418
  name: "deviceId",
45048
45419
  form: "single",
45049
45420
  optional: false
45050
45421
  }],
45422
+ "motionTrigger.getStatus": [{
45423
+ name: "deviceId",
45424
+ form: "single",
45425
+ optional: false
45426
+ }],
45051
45427
  "motionTrigger.setMotionTrigger": [{
45052
45428
  name: "deviceId",
45053
45429
  form: "single",
@@ -45058,6 +45434,11 @@ Object.freeze({
45058
45434
  form: "single",
45059
45435
  optional: false
45060
45436
  }],
45437
+ "motionZones.getStatus": [{
45438
+ name: "deviceId",
45439
+ form: "single",
45440
+ optional: false
45441
+ }],
45061
45442
  "motionZones.setZone": [{
45062
45443
  name: "deviceId",
45063
45444
  form: "single",
@@ -45068,6 +45449,11 @@ Object.freeze({
45068
45449
  form: "single",
45069
45450
  optional: false
45070
45451
  }],
45452
+ "nativeObjectDetection.getStatus": [{
45453
+ name: "deviceId",
45454
+ form: "single",
45455
+ optional: false
45456
+ }],
45071
45457
  "nativeObjectDetection.setEnabled": [{
45072
45458
  name: "deviceId",
45073
45459
  form: "single",
@@ -45083,6 +45469,11 @@ Object.freeze({
45083
45469
  form: "single",
45084
45470
  optional: false
45085
45471
  }],
45472
+ "navigation.getStatus": [{
45473
+ name: "deviceId",
45474
+ form: "single",
45475
+ optional: false
45476
+ }],
45086
45477
  "navigation.goToPoint": [{
45087
45478
  name: "deviceId",
45088
45479
  form: "single",
@@ -45128,6 +45519,11 @@ Object.freeze({
45128
45519
  form: "single",
45129
45520
  optional: false
45130
45521
  }],
45522
+ "networkLink.getStatus": [{
45523
+ name: "deviceId",
45524
+ form: "single",
45525
+ optional: false
45526
+ }],
45131
45527
  "networkQuality.getDeviceStats": [{
45132
45528
  name: "deviceId",
45133
45529
  form: "single",
@@ -45138,6 +45534,11 @@ Object.freeze({
45138
45534
  form: "single",
45139
45535
  optional: false
45140
45536
  }],
45537
+ "notificationRules.getAlarmSensors": [{
45538
+ name: "deviceIds",
45539
+ form: "array",
45540
+ optional: false
45541
+ }],
45141
45542
  "notificationRules.setDeviceMuted": [{
45142
45543
  name: "deviceId",
45143
45544
  form: "single",
@@ -45148,11 +45549,26 @@ Object.freeze({
45148
45549
  form: "single",
45149
45550
  optional: false
45150
45551
  }],
45552
+ "notifier.getStatus": [{
45553
+ name: "deviceId",
45554
+ form: "single",
45555
+ optional: false
45556
+ }],
45151
45557
  "notifier.send": [{
45152
45558
  name: "deviceId",
45153
45559
  form: "single",
45154
45560
  optional: false
45155
45561
  }],
45562
+ "numericSensor.getStatus": [{
45563
+ name: "deviceId",
45564
+ form: "single",
45565
+ optional: false
45566
+ }],
45567
+ "osd.getStatus": [{
45568
+ name: "deviceId",
45569
+ form: "single",
45570
+ optional: false
45571
+ }],
45156
45572
  "osd.setOverlay": [{
45157
45573
  name: "deviceId",
45158
45574
  form: "single",
@@ -45212,6 +45628,11 @@ Object.freeze({
45212
45628
  form: "single",
45213
45629
  optional: false
45214
45630
  }],
45631
+ "petFeeder.getStatus": [{
45632
+ name: "deviceId",
45633
+ form: "single",
45634
+ optional: false
45635
+ }],
45215
45636
  "petFeeder.markFoodReplenished": [{
45216
45637
  name: "deviceId",
45217
45638
  form: "single",
@@ -45247,6 +45668,11 @@ Object.freeze({
45247
45668
  form: "single",
45248
45669
  optional: false
45249
45670
  }],
45671
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
45672
+ name: "deviceId",
45673
+ form: "single",
45674
+ optional: false
45675
+ }],
45250
45676
  "pipelineAnalytics.clearTrackRelation": [{
45251
45677
  name: "deviceId",
45252
45678
  form: "single",
@@ -45287,6 +45713,16 @@ Object.freeze({
45287
45713
  form: "single",
45288
45714
  optional: false
45289
45715
  }],
45716
+ "pipelineAnalytics.getDeviceLiveContribution": [{
45717
+ name: "deviceId",
45718
+ form: "single",
45719
+ optional: false
45720
+ }],
45721
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
45722
+ name: "deviceId",
45723
+ form: "single",
45724
+ optional: false
45725
+ }],
45290
45726
  "pipelineAnalytics.getEventDensity": [{
45291
45727
  name: "deviceId",
45292
45728
  form: "single",
@@ -45502,6 +45938,11 @@ Object.freeze({
45502
45938
  form: "single",
45503
45939
  optional: true
45504
45940
  }],
45941
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
45942
+ name: "deviceId",
45943
+ form: "single",
45944
+ optional: false
45945
+ }],
45505
45946
  "pipelineOrchestrator.assignAudio": [{
45506
45947
  name: "deviceId",
45507
45948
  form: "single",
@@ -45547,6 +45988,16 @@ Object.freeze({
45547
45988
  form: "single",
45548
45989
  optional: false
45549
45990
  }],
45991
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
45992
+ name: "deviceId",
45993
+ form: "single",
45994
+ optional: false
45995
+ }],
45996
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
45997
+ name: "deviceId",
45998
+ form: "single",
45999
+ optional: false
46000
+ }],
45550
46001
  "pipelineOrchestrator.getPipelineAssignment": [{
45551
46002
  name: "deviceId",
45552
46003
  form: "single",
@@ -45667,11 +46118,31 @@ Object.freeze({
45667
46118
  form: "single",
45668
46119
  optional: true
45669
46120
  }],
46121
+ "powerMeter.getStatus": [{
46122
+ name: "deviceId",
46123
+ form: "single",
46124
+ optional: false
46125
+ }],
46126
+ "presence.getStatus": [{
46127
+ name: "deviceId",
46128
+ form: "single",
46129
+ optional: false
46130
+ }],
46131
+ "pressureSensor.getStatus": [{
46132
+ name: "deviceId",
46133
+ form: "single",
46134
+ optional: false
46135
+ }],
45670
46136
  "privacyMask.getOptions": [{
45671
46137
  name: "deviceId",
45672
46138
  form: "single",
45673
46139
  optional: false
45674
46140
  }],
46141
+ "privacyMask.getStatus": [{
46142
+ name: "deviceId",
46143
+ form: "single",
46144
+ optional: false
46145
+ }],
45675
46146
  "privacyMask.setAudioEnabled": [{
45676
46147
  name: "deviceId",
45677
46148
  form: "single",
@@ -45722,6 +46193,11 @@ Object.freeze({
45722
46193
  form: "single",
45723
46194
  optional: false
45724
46195
  }],
46196
+ "ptz.getStatus": [{
46197
+ name: "deviceId",
46198
+ form: "single",
46199
+ optional: false
46200
+ }],
45725
46201
  "ptz.goHome": [{
45726
46202
  name: "deviceId",
45727
46203
  form: "single",
@@ -45812,6 +46288,11 @@ Object.freeze({
45812
46288
  form: "single",
45813
46289
  optional: false
45814
46290
  }],
46291
+ "recordingArchive.applyDeviceSettingsPatch": [{
46292
+ name: "deviceId",
46293
+ form: "single",
46294
+ optional: false
46295
+ }],
45815
46296
  "recordingArchive.deleteFootprint": [{
45816
46297
  name: "deviceId",
45817
46298
  form: "single",
@@ -45832,6 +46313,21 @@ Object.freeze({
45832
46313
  form: "single",
45833
46314
  optional: false
45834
46315
  }],
46316
+ "recordingArchive.getDeviceLiveContribution": [{
46317
+ name: "deviceId",
46318
+ form: "single",
46319
+ optional: false
46320
+ }],
46321
+ "recordingArchive.getDeviceSettingsContribution": [{
46322
+ name: "deviceId",
46323
+ form: "single",
46324
+ optional: false
46325
+ }],
46326
+ "recordingArchive.getStatus": [{
46327
+ name: "deviceId",
46328
+ form: "single",
46329
+ optional: false
46330
+ }],
45835
46331
  "recordingArchive.listOpsLog": [{
45836
46332
  name: "deviceId",
45837
46333
  form: "single",
@@ -45917,6 +46413,11 @@ Object.freeze({
45917
46413
  form: "single",
45918
46414
  optional: false
45919
46415
  }],
46416
+ "recordingOnboard.getStatus": [{
46417
+ name: "deviceId",
46418
+ form: "single",
46419
+ optional: false
46420
+ }],
45920
46421
  "recordingOnboard.setSettings": [{
45921
46422
  name: "deviceId",
45922
46423
  form: "single",
@@ -45947,6 +46448,11 @@ Object.freeze({
45947
46448
  form: "single",
45948
46449
  optional: false
45949
46450
  }],
46451
+ "sceneMonitor.getStatus": [{
46452
+ name: "deviceId",
46453
+ form: "single",
46454
+ optional: false
46455
+ }],
45950
46456
  "sceneMonitor.listScenes": [{
45951
46457
  name: "deviceId",
45952
46458
  form: "single",
@@ -45972,6 +46478,11 @@ Object.freeze({
45972
46478
  form: "single",
45973
46479
  optional: false
45974
46480
  }],
46481
+ "scriptRunner.getStatus": [{
46482
+ name: "deviceId",
46483
+ form: "single",
46484
+ optional: false
46485
+ }],
45975
46486
  "scriptRunner.run": [{
45976
46487
  name: "deviceId",
45977
46488
  form: "single",
@@ -45982,11 +46493,31 @@ Object.freeze({
45982
46493
  form: "single",
45983
46494
  optional: false
45984
46495
  }],
46496
+ "smoke.getStatus": [{
46497
+ name: "deviceId",
46498
+ form: "single",
46499
+ optional: false
46500
+ }],
46501
+ "snapshot.applyDeviceSettingsPatch": [{
46502
+ name: "deviceId",
46503
+ form: "single",
46504
+ optional: false
46505
+ }],
45985
46506
  "snapshot.getDebugState": [{
45986
46507
  name: "deviceId",
45987
46508
  form: "single",
45988
46509
  optional: false
45989
46510
  }],
46511
+ "snapshot.getDeviceLiveContribution": [{
46512
+ name: "deviceId",
46513
+ form: "single",
46514
+ optional: false
46515
+ }],
46516
+ "snapshot.getDeviceSettingsContribution": [{
46517
+ name: "deviceId",
46518
+ form: "single",
46519
+ optional: false
46520
+ }],
45990
46521
  "snapshot.getSnapshot": [{
45991
46522
  name: "deviceId",
45992
46523
  form: "single",
@@ -46003,6 +46534,11 @@ Object.freeze({
46003
46534
  form: "array",
46004
46535
  optional: false
46005
46536
  }],
46537
+ "snapshot.getStatus": [{
46538
+ name: "deviceId",
46539
+ form: "single",
46540
+ optional: false
46541
+ }],
46006
46542
  "snapshot.invalidateCache": [{
46007
46543
  name: "deviceId",
46008
46544
  form: "single",
@@ -46013,6 +46549,11 @@ Object.freeze({
46013
46549
  form: "single",
46014
46550
  optional: false
46015
46551
  }],
46552
+ "streamBroker.applyDeviceSettingsPatch": [{
46553
+ name: "deviceId",
46554
+ form: "single",
46555
+ optional: false
46556
+ }],
46016
46557
  "streamBroker.assignProfile": [{
46017
46558
  name: "deviceId",
46018
46559
  form: "single",
@@ -46028,6 +46569,16 @@ Object.freeze({
46028
46569
  form: "single",
46029
46570
  optional: false
46030
46571
  }],
46572
+ "streamBroker.getDeviceLiveContribution": [{
46573
+ name: "deviceId",
46574
+ form: "single",
46575
+ optional: false
46576
+ }],
46577
+ "streamBroker.getDeviceSettingsContribution": [{
46578
+ name: "deviceId",
46579
+ form: "single",
46580
+ optional: false
46581
+ }],
46031
46582
  "streamBroker.getStreamWithCodec": [{
46032
46583
  name: "deviceId",
46033
46584
  form: "single",
@@ -46088,16 +46639,46 @@ Object.freeze({
46088
46639
  form: "single",
46089
46640
  optional: false
46090
46641
  }],
46642
+ "streamParams.getStatus": [{
46643
+ name: "deviceId",
46644
+ form: "single",
46645
+ optional: false
46646
+ }],
46091
46647
  "streamParams.setProfile": [{
46092
46648
  name: "deviceId",
46093
46649
  form: "single",
46094
46650
  optional: false
46095
46651
  }],
46652
+ "switch.getStatus": [{
46653
+ name: "deviceId",
46654
+ form: "single",
46655
+ optional: false
46656
+ }],
46096
46657
  "switch.setState": [{
46097
46658
  name: "deviceId",
46098
46659
  form: "single",
46099
46660
  optional: false
46100
46661
  }],
46662
+ "tamper.getStatus": [{
46663
+ name: "deviceId",
46664
+ form: "single",
46665
+ optional: false
46666
+ }],
46667
+ "temperatureSensor.getStatus": [{
46668
+ name: "deviceId",
46669
+ form: "single",
46670
+ optional: false
46671
+ }],
46672
+ "update.getStatus": [{
46673
+ name: "deviceId",
46674
+ form: "single",
46675
+ optional: false
46676
+ }],
46677
+ "vacuumControl.getStatus": [{
46678
+ name: "deviceId",
46679
+ form: "single",
46680
+ optional: false
46681
+ }],
46101
46682
  "vacuumControl.locate": [{
46102
46683
  name: "deviceId",
46103
46684
  form: "single",
@@ -46133,6 +46714,11 @@ Object.freeze({
46133
46714
  form: "single",
46134
46715
  optional: false
46135
46716
  }],
46717
+ "valve.getStatus": [{
46718
+ name: "deviceId",
46719
+ form: "single",
46720
+ optional: false
46721
+ }],
46136
46722
  "valve.open": [{
46137
46723
  name: "deviceId",
46138
46724
  form: "single",
@@ -46148,6 +46734,11 @@ Object.freeze({
46148
46734
  form: "single",
46149
46735
  optional: false
46150
46736
  }],
46737
+ "vibration.getStatus": [{
46738
+ name: "deviceId",
46739
+ form: "single",
46740
+ optional: false
46741
+ }],
46151
46742
  "videoclips.dialClipStream": [{
46152
46743
  name: "deviceId",
46153
46744
  form: "single",
@@ -46183,6 +46774,11 @@ Object.freeze({
46183
46774
  form: "single",
46184
46775
  optional: false
46185
46776
  }],
46777
+ "waterHeater.getStatus": [{
46778
+ name: "deviceId",
46779
+ form: "single",
46780
+ optional: false
46781
+ }],
46186
46782
  "waterHeater.setAway": [{
46187
46783
  name: "deviceId",
46188
46784
  form: "single",
@@ -46198,6 +46794,11 @@ Object.freeze({
46198
46794
  form: "single",
46199
46795
  optional: false
46200
46796
  }],
46797
+ "weather.getStatus": [{
46798
+ name: "deviceId",
46799
+ form: "single",
46800
+ optional: false
46801
+ }],
46201
46802
  "webrtcSession.addIceCandidate": [{
46202
46803
  name: "deviceId",
46203
46804
  form: "single",
@@ -47142,7 +47743,7 @@ var PreviewTextField = string().max(200);
47142
47743
  * Whether the notification's preview is a STILL or a short animation.
47143
47744
  *
47144
47745
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
47145
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
47746
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
47146
47747
  * night reads better as three seconds of motion than as one frame of it. Both
47147
47748
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
47148
47749
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -47280,6 +47881,7 @@ TimelapseRuleInputSchema.extend({
47280
47881
  createdAt: number(),
47281
47882
  updatedAt: number()
47282
47883
  });
47884
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47283
47885
  object({
47284
47886
  /**
47285
47887
  * Fraction of the box's own size added on EACH side before cutting.