@camstack/addon-export-google 0.1.80 → 0.1.81

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.
@@ -73,7 +73,7 @@ function createApiDeviceGateway(deps) {
73
73
  };
74
74
  }
75
75
  //#endregion
76
- //#region ../types/dist/event-category-C5xZWqz6.mjs
76
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
77
77
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
78
78
  EventCategory["SystemBoot"] = "system.boot";
79
79
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -606,6 +606,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
606
606
  */
607
607
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
608
608
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
609
+ /**
610
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
611
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
612
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
613
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
614
+ * for a camera with no attached sensor). Providers must not emit
615
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
616
+ * keeps it so (camera-grid is a named, deferred exception).
617
+ */
609
618
  EventCategory["ProviderMotion"] = "provider.motion";
610
619
  EventCategory["ProviderDetection"] = "provider.detection";
611
620
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5444,7 +5453,7 @@ var ZodIssueCode = {
5444
5453
  var ZodFirstPartyTypeKind;
5445
5454
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5446
5455
  //#endregion
5447
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5456
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5448
5457
  /**
5449
5458
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5450
5459
  * window to float samples (D455).
@@ -7505,7 +7514,7 @@ function errMsg(err) {
7505
7514
  return String(err);
7506
7515
  }
7507
7516
  //#endregion
7508
- //#region ../types/dist/composition-Dq42XWp0.mjs
7517
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7509
7518
  /**
7510
7519
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7511
7520
  *
@@ -7559,21 +7568,20 @@ function portable(target) {
7559
7568
  }
7560
7569
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7561
7570
  /**
7562
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7563
- * valutatore puro del suo FRONTE.
7571
+ * THE table "which boolean of this device type counts as HIGH", and the
7572
+ * pure evaluator of its EDGE.
7564
7573
  *
7565
- * Viveva dentro il builtin virtual-doorbell
7566
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7567
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7568
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7569
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7570
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7571
- * SPOSTA qui e il doorbell la ri-esporta.
7574
+ * ONE table for everyone who asks "is this device HIGH?": the
7575
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7576
+ * sensor markers, the orchestrator's camera-sensors bridge and
7577
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7578
+ * A second copy would diverge at the first cap added, and the symptom — "the
7579
+ * sensor rings the doorbell but does not record" — is exactly D62.
7572
7580
  *
7573
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7574
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7575
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7576
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7581
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7582
+ * two must not be unified: that one answers "what is the state WORD for a
7583
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7584
+ * boolean's EDGE counts". Deliberately different vocabularies.
7577
7585
  */
7578
7586
  /**
7579
7587
  * Known binary / switch source caps → the boolean slice field whose
@@ -7594,6 +7602,8 @@ var SOURCE_CAP_ACTIVE_FIELD = {
7594
7602
  vibration: "detected",
7595
7603
  tamper: "tampered"
7596
7604
  };
7605
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7606
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7597
7607
  /**
7598
7608
  * Error types for the safe expression engine. Two distinct classes so callers
7599
7609
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -9297,22 +9307,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9297
9307
  "package"
9298
9308
  ];
9299
9309
  /**
9300
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9301
- * un operatore può selezionare.
9310
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9311
+ * an operator can select.
9302
9312
  *
9303
- * Sono le tre offerte dallo step `object-detection`
9313
+ * They are the three offered by the `object-detection` step
9304
9314
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9305
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9306
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9307
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9308
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9315
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9316
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9317
+ * `package-detection`, whose enablement is driven by zones, and offering the
9318
+ * same word twice has already made an operator turn on the COCO proxy
9309
9319
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9310
9320
  *
9311
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9312
- * dello step e una seconda volta come union `FirstLevelMacro`
9313
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9314
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9315
- * successiva.
9321
+ * ONE list. Before today the same three were hand-written in the step's offer
9322
+ * and a second time as the union `FirstLevelMacro`
9323
+ * (`types/detection.ts`); a third copy for the recording trigger
9324
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9325
+ * invisible.
9316
9326
  */
9317
9327
  var FIRST_LEVEL_MACRO_CLASSES = [
9318
9328
  "person",
@@ -9377,17 +9387,17 @@ var RecordingStorageModeSchema = _enum([
9377
9387
  "on-device-decision"
9378
9388
  ]);
9379
9389
  /**
9380
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9381
- * tre offerte dallo step `object-detection`, da UNA lista
9390
+ * The macro classes that can open a recording window — the same
9391
+ * three offered by the `object-detection` step, from ONE list
9382
9392
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9383
9393
  */
9384
9394
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9385
9395
  /**
9386
- * True quando `values` non ripete un elemento.
9396
+ * True when `values` does not repeat an element.
9387
9397
  *
9388
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9389
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9390
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9398
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9399
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9400
+ * would count its windows twice in `segmentMissedByMs`.
9391
9401
  */
9392
9402
  var noDuplicates = (values) => new Set(values).size === values.length;
9393
9403
  /** Which detectors trigger an `events`-mode band. */
@@ -9395,34 +9405,34 @@ var RecordingTriggersSchema = object({
9395
9405
  motion: boolean().optional(),
9396
9406
  audioThresholdDbfs: number().optional(),
9397
9407
  /**
9398
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9399
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9400
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9401
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9408
+ * The macro classes whose detection opens a window. ABSENT = the source
9409
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9410
+ * trigger on, no class" is the same shape "enabled and records
9411
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9402
9412
  *
9403
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9404
- * quelle che hanno attraversato `enabledMacroClasses`, i
9405
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9406
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9407
- * finestre — vedi `recorder/object-trigger.ts`.
9413
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9414
+ * those that went through `enabledMacroClasses`, the
9415
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9416
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9417
+ * windows — see `recorder/object-trigger.ts`.
9408
9418
  */
9409
9419
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9410
9420
  /**
9411
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9412
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9421
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9422
+ * is not listened to; an empty array is rejected for the same reason as
9413
9423
  * `objectClasses`.
9414
9424
  *
9415
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9416
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9417
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9418
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9419
- * device (D12) — mai un elenco globale di cap.
9425
+ * These are SOURCE device ids, not cameras: the band names them, so the
9426
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9427
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9428
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9429
+ * device (D12) — never a global list of caps.
9420
9430
  *
9421
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9422
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9423
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9424
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9425
- * registrare.
9431
+ * What "high" means depends on the device TYPE and is not decided here:
9432
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9433
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9434
+ * the level: a contact found already open at runner restart does not
9435
+ * record.
9426
9436
  */
9427
9437
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9428
9438
  });
@@ -10499,7 +10509,7 @@ var DecoderSessionConfigSchema = object({
10499
10509
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10500
10510
  * thing: the logging settings document on the `system` cap. Two authorities
10501
10511
  * over the values is the exact defect
10502
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10512
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10503
10513
  * remove; re-introducing it from the cure side would be grotesque.
10504
10514
  *
10505
10515
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -12236,7 +12246,7 @@ var BrokerStatusEnum = _enum([
12236
12246
  "unreachable",
12237
12247
  "error"
12238
12248
  ]);
12239
- var BrokerInfoSchema$1 = object({
12249
+ var BrokerInfoSchema = object({
12240
12250
  /** Stable broker id. Persisted; survives addon restarts. */
12241
12251
  id: string(),
12242
12252
  /** Addon id of the provider that OWNS this broker.
@@ -12369,7 +12379,7 @@ var GetStateInputSchema = object({
12369
12379
  * HA: entity_id (returns the cached entity state). */
12370
12380
  key: string()
12371
12381
  });
12372
- method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12382
+ method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12373
12383
  kind: "mutation",
12374
12384
  auth: "admin"
12375
12385
  }), method(RemoveInputSchema, _void(), {
@@ -13978,7 +13988,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13978
13988
  *
13979
13989
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13980
13990
  * was wrong — corrected 2026-08-26 after the hop map
13981
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13991
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13982
13992
  * There is **no Zod parse at all** between the door and the engine: the
13983
13993
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13984
13994
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14759,7 +14769,17 @@ var SiteZoneSchema = object({
14759
14769
  var ProviderStatusSchema = object({
14760
14770
  connected: boolean(),
14761
14771
  deviceCount: number(),
14762
- error: string().optional()
14772
+ error: string().optional(),
14773
+ /**
14774
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14775
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14776
+ * which every provider bundles, so only a provider BUILT from D696-amended
14777
+ * code says it. ABSENT means an older provider that may still write it (raw
14778
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14779
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14780
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14781
+ */
14782
+ motionSliceWriter: literal("orchestrator").optional()
14763
14783
  });
14764
14784
  object({
14765
14785
  externalId: string(),
@@ -15557,28 +15577,10 @@ method(object({
15557
15577
  deviceIds: array(number()).readonly(),
15558
15578
  caps: array(string()).readonly().optional()
15559
15579
  }), record(string(), record(string(), unknown().nullable())));
15560
- object({
15561
- /** Ms epoch of the last press. null = never observed since this provider started. */
15562
- lastPressedAt: number().nullable(),
15563
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15564
- pressCountSinceStart: number()
15565
- });
15566
- object({
15567
- deviceId: number(),
15568
- timestamp: number()
15569
- });
15570
- /**
15571
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15572
- * virtual-doorbell trigger devices, exactly what the wrapper's
15573
- * `doorbellSources` persists (as `{ deviceId }` records).
15574
- */
15575
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15580
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15576
15581
  DeviceType.Camera;
15577
- DeviceType.Button, DeviceType.Camera;
15578
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15579
- DeviceType.Camera, DeviceType.Camera;
15580
15582
  /** A slot's editable value, discriminated on `slot`. */
15581
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15583
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15582
15584
  DeviceType.Camera;
15583
15585
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15584
15586
  state: literal("unreachable"),
@@ -15615,10 +15617,11 @@ var ExtensionSlotSourceSchema = object({
15615
15617
  });
15616
15618
  /**
15617
15619
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15618
- * reported read-only, named, never written through this contract (D62). The
15619
- * virtual doorbell's sources ring only while the wrapper is bound to the
15620
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15621
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15620
+ * reported read-only, named, never written through this contract (D62) — e.g.
15621
+ * a slot whose sources act only while a wrapper is bound to the host, a
15622
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15623
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15624
+ * not be read; it is not "inactive" (D315).
15622
15625
  */
15623
15626
  var ExtensionSlotActivationSchema = object({
15624
15627
  state: _enum([
@@ -15634,7 +15637,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15634
15637
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15635
15638
  slotRef: string(),
15636
15639
  slot: ExtensionSlotIdSchema,
15637
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15640
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15638
15641
  authority: string(),
15639
15642
  activation: ExtensionSlotActivationSchema.nullable(),
15640
15643
  value: ExtensionSlotValueSchema,
@@ -15912,7 +15915,7 @@ method(object({
15912
15915
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15913
15916
  * ends in a log line, and a log line is the thing the operator asked to stop
15914
15917
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15915
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15918
+ * 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
15916
15919
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15917
15920
  * media blackout were both diagnosed. The counters stay; this is where they can
15918
15921
  * be READ.
@@ -17288,7 +17291,7 @@ var BrokerStatusSchema$1 = _enum([
17288
17291
  "unreachable",
17289
17292
  "tls-error"
17290
17293
  ]);
17291
- var BrokerInfoSchema = object({
17294
+ var BrokerInfoSchema$1 = object({
17292
17295
  id: string(),
17293
17296
  name: string(),
17294
17297
  url: string(),
@@ -17351,7 +17354,7 @@ var StatusSchema = object({
17351
17354
  brokerCount: number(),
17352
17355
  embeddedRunning: boolean()
17353
17356
  });
17354
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17357
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17355
17358
  kind: "mutation",
17356
17359
  auth: "admin"
17357
17360
  }), method(IdInputSchema, _void(), {
@@ -19527,78 +19530,16 @@ var NcRuleInputSchema = object({
19527
19530
  */
19528
19531
  confirm: NcConfirmSchema.optional(),
19529
19532
  /**
19530
- * WAIT for face/plate recognition before saying anything.
19531
- *
19532
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19533
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19534
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19535
- * measured on this hub — and an `immediate` rule enqueues on the first object
19536
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19537
- * immediate path, and no amount of media re-resolution fixes a sentence.
19538
- *
19539
- * Only two honest answers exist, and this flag picks between them. It has
19540
- * effect ONLY on a rule that declares a recognition scope
19541
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19542
- * other rule there is nothing to wait for and the flag is inert.
19543
- *
19544
- * | value | what happens |
19545
- * | --- | --- |
19546
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19547
- * | 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) |
19548
- *
19549
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19550
- * on the addon cap path, and absent has to keep meaning exactly what every
19551
- * rule authored before this field meant.
19552
- *
19553
- * The cost of `true` is stated here because the editor states it too: a rule
19554
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19555
- * tests every zone the track visited and a `crossing` condition can no longer
19556
- * be satisfied, because a closed track carries no crossing.
19557
- */
19558
- waitForEnhancement: boolean().optional(),
19559
- /**
19560
- * GROUP a burst of subjects into ONE notification that grows.
19561
- *
19562
- * Seconds of quiet after the last matching subject before the burst is
19563
- * considered over. While it is open, the first subject enqueues immediately —
19564
- * **exactly as today, with no added latency** — and every real growth (a new
19565
- * subject, or a name confirmed on one already in it) REPLACES that
19566
- * notification with an updated one naming everybody. The push carries the
19567
- * group's own coalescing tag, so the phone replaces rather than stacks.
19568
- *
19569
- * `0` / absent = off, and off is today's behaviour byte for byte.
19570
- *
19571
- * ### Why an idle cutoff and not a window
19572
- *
19573
- * The measured seven-person arrival on device 590 spans 110 s with every
19574
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19575
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19576
- * 30 s is a widely shipped value for the same decision.
19577
- *
19578
- * ### What it replaces
19579
- *
19580
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19581
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19582
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19583
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19584
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19585
- * budget over GROUPS — which is what it always meant — and a growth is never
19586
- * throttled by the window its own first member spent.
19587
- *
19588
- * ### Interaction with {@link waitForEnhancement}
19589
- *
19590
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19591
- * TRACK CLOSE, so with both set the group is opened by the first member to
19592
- * CLOSE — already carrying its name — and grows as later members close. That
19593
- * is later, and complete. With grouping alone the group opens on the first
19594
- * object event and picks up names as they are confirmed, through the growth
19595
- * path. Neither combination fires twice for one subject.
19596
- *
19597
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19598
- * on the addon cap path, so absent must keep meaning what it meant before this
19599
- * field existed.
19533
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19534
+ * seconds; the rule sends ONE message when the window closes — early, once every
19535
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19536
+ * the rule matched, with one mosaic tile per subject.
19537
+ * Absent = plain immediate (one message per track, at the first match).
19538
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19539
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19540
+ * path does not run Zod defaults.
19600
19541
  */
19601
- groupIdleSec: number().int().min(0).max(600).optional()
19542
+ groupingSec: number().int().min(5).max(60).optional()
19602
19543
  });
19603
19544
  /**
19604
19545
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19646,9 +19587,10 @@ var NcRuleClearableKeySchema = _enum([
19646
19587
  "targetUsers",
19647
19588
  "snoozeOptions",
19648
19589
  "snoozeAllowGlobal",
19649
- "waitForEnhancement",
19650
19590
  "actions",
19651
19591
  "confirm",
19592
+ "groupingSec",
19593
+ "waitForEnhancement",
19652
19594
  "groupIdleSec"
19653
19595
  ]);
19654
19596
  var NcRuleUpdateInputSchema = object({
@@ -19670,9 +19612,25 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19670
19612
  */
19671
19613
  disabledTargetIds: array(string()).default([])
19672
19614
  });
19615
+ /**
19616
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19617
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19618
+ * job is not an open state, and the alarm stays instant.
19619
+ */
19620
+ var NcStateSummarySectionIdSchema = _enum([
19621
+ "device-state",
19622
+ "streams",
19623
+ "battery",
19624
+ "consumables",
19625
+ "tanks",
19626
+ "detection-blind",
19627
+ "nodes",
19628
+ "addons",
19629
+ "updates"
19630
+ ]);
19673
19631
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19674
19632
  var NcStateSummaryPreviewSectionSchema = object({
19675
- id: string(),
19633
+ id: NcStateSummarySectionIdSchema,
19676
19634
  status: _enum(["read", "unavailable"]),
19677
19635
  items: number().int().nonnegative(),
19678
19636
  reason: string().optional()
@@ -19682,7 +19640,7 @@ var NcStateSummaryPreviewSectionSchema = object({
19682
19640
  * a `rule` split (no `section`), one section's on a `section` split.
19683
19641
  */
19684
19642
  var NcStateSummaryPreviewRowSchema = object({
19685
- section: string().optional(),
19643
+ section: NcStateSummarySectionIdSchema.optional(),
19686
19644
  /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19687
19645
  title: string(),
19688
19646
  text: string(),
@@ -20119,6 +20077,29 @@ object({
20119
20077
  firstAt: number(),
20120
20078
  lastAt: number()
20121
20079
  });
20080
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
20081
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
20082
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
20083
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
20084
+ var NcAlarmModeDelaysListSchema = array(object({
20085
+ mode: AlarmArmModeSchema,
20086
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
20087
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
20088
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
20089
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20090
+ var NcAlarmModeDelaysEditListSchema = array(object({
20091
+ mode: AlarmArmModeSchema,
20092
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20093
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20094
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
20095
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20096
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20097
+ var NcAlarmNonBlockingEditSchema = object({
20098
+ mode: AlarmArmModeSchema,
20099
+ deviceId: number().int().positive(),
20100
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20101
+ nonBlocking: boolean()
20102
+ });
20122
20103
  /**
20123
20104
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20124
20105
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -20127,10 +20108,10 @@ object({
20127
20108
  var NcAlarmNonBlockingListSchema = array(object({
20128
20109
  mode: AlarmArmModeSchema,
20129
20110
  deviceIds: array(number().int()).max(200)
20130
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
20111
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20131
20112
  /**
20132
- * The three durations the panel's state machine runs on, who hears about its
20133
- * transitions, and which openings each mode tolerates.
20113
+ * The three durations — global, and per mode since D697 — the panel's state
20114
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20134
20115
  *
20135
20116
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20136
20117
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20140,15 +20121,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20140
20121
  */
20141
20122
  var NcAlarmSettingsSchema = object({
20142
20123
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20143
- exitDelaySec: number().int().min(0).max(600),
20124
+ exitDelaySec: NcAlarmDelaySecSchema,
20144
20125
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20145
- entryDelaySec: number().int().min(0).max(600),
20126
+ entryDelaySec: NcAlarmDelaySecSchema,
20146
20127
  /**
20147
20128
  * How long `triggered` lasts before the panel re-arms itself.
20148
20129
  * **0 = until an operator disarms it** — the behaviour before this field
20149
20130
  * existed, and therefore what an untouched install keeps doing.
20150
20131
  */
20151
- triggeredDurationSec: number().int().min(0).max(3600),
20132
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20152
20133
  /**
20153
20134
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20154
20135
  * remove after one release. Optional so an older admin's patch (which still
@@ -20171,17 +20152,30 @@ var NcAlarmSettingsSchema = object({
20171
20152
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20172
20153
  * parses as "every opening blocks" — the behaviour it was written under.
20173
20154
  */
20174
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20155
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20156
+ /**
20157
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20158
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20159
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20160
+ */
20161
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20175
20162
  });
20176
20163
  /**
20177
20164
  * Every field optional — a tab edits one control at a time.
20178
20165
  *
20179
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20180
- * remove an inner `.default()`, so a patch that never named the field would
20181
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20182
- * (the same trap {@link NcRulePatchSchema} documents).
20166
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20167
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20168
+ * named the field would parse to `[]` and wipe every mode's list on an
20169
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20170
+ * `modeDelays` means EDITS, not the stored overrides.
20183
20171
  */
20184
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20172
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20173
+ nonBlocking: NcAlarmNonBlockingListSchema,
20174
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20175
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20176
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20177
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20178
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20185
20179
  /**
20186
20180
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20187
20181
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20281,6 +20275,28 @@ var NcAlarmConfigSchema = object({
20281
20275
  */
20282
20276
  live: NcAlarmLiveSchema.optional()
20283
20277
  });
20278
+ var NcAlarmSensorReadingSchema = _enum([
20279
+ "open",
20280
+ "closed",
20281
+ "unknown",
20282
+ "not-opening"
20283
+ ]);
20284
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20285
+ var NcAlarmSensorStateSchema = object({
20286
+ deviceId: number().int().positive(),
20287
+ reading: NcAlarmSensorReadingSchema,
20288
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20289
+ state: string().nullable(),
20290
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20291
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20292
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20293
+ online: boolean().nullable()
20294
+ });
20295
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20296
+ var NcAlarmSensorsSchema = object({
20297
+ sensors: array(NcAlarmSensorStateSchema),
20298
+ readAt: number().int().nonnegative()
20299
+ });
20284
20300
  /**
20285
20301
  * ONE rule's demand on ONE camera's clip ring.
20286
20302
  *
@@ -20371,7 +20387,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20371
20387
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20372
20388
  kind: "mutation",
20373
20389
  auth: "admin"
20374
- });
20390
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20375
20391
  /**
20376
20392
  * Zod schemas for persisted record types.
20377
20393
  *
@@ -20734,12 +20750,26 @@ var CameraSensorRefSchema = object({
20734
20750
  function cameraSensorRefKey(ref) {
20735
20751
  return `${ref.addonId}/${ref.stableId}`;
20736
20752
  }
20737
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20738
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20739
- var CameraSensorsConfigSchema = object({
20740
- mode: CameraSensorsModeSchema,
20741
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20742
- }).superRefine((value, ctx) => {
20753
+ /**
20754
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20755
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20756
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20757
+ * camera's own motion counts only while a confirm sensor is active — with or
20758
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20759
+ */
20760
+ var CameraSensorModeSchema = _enum([
20761
+ "in-addition",
20762
+ "confirm",
20763
+ "replace"
20764
+ ]);
20765
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20766
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20767
+ /**
20768
+ * A camera's attached sensors. A CS-1 row carried an envelope
20769
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20770
+ * what the write stores (D672 rule 3), so it is never written again.
20771
+ */
20772
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20743
20773
  const seen = /* @__PURE__ */ new Set();
20744
20774
  value.sensors.forEach((ref, index) => {
20745
20775
  const key = cameraSensorRefKey(ref);
@@ -20751,39 +20781,41 @@ var CameraSensorsConfigSchema = object({
20751
20781
  seen.add(key);
20752
20782
  });
20753
20783
  });
20754
- var RefFields = {
20784
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20785
+ var AttachmentFields = {
20755
20786
  addonId: string(),
20756
- stableId: string()
20787
+ stableId: string(),
20788
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20757
20789
  };
20758
20790
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20759
20791
  object({
20760
- ...RefFields,
20792
+ ...AttachmentFields,
20761
20793
  state: literal("ready"),
20762
20794
  deviceId: number(),
20763
20795
  name: string(),
20764
20796
  caps: array(string()).readonly()
20765
20797
  }),
20766
20798
  object({
20767
- ...RefFields,
20799
+ ...AttachmentFields,
20768
20800
  state: literal("offline"),
20769
20801
  deviceId: number(),
20770
20802
  name: string(),
20771
20803
  reason: _enum(["offline", "disabled"])
20772
20804
  }),
20773
20805
  object({
20774
- ...RefFields,
20806
+ ...AttachmentFields,
20775
20807
  state: literal("missing"),
20776
20808
  reason: string()
20777
20809
  }),
20778
20810
  object({
20779
- ...RefFields,
20811
+ ...AttachmentFields,
20780
20812
  state: literal("unsupported"),
20781
20813
  deviceId: number(),
20782
20814
  name: string(),
20783
20815
  reason: string()
20784
20816
  }),
20785
20817
  object({
20786
- ...RefFields,
20818
+ ...AttachmentFields,
20787
20819
  state: literal("unreadable"),
20788
20820
  reason: string()
20789
20821
  })
@@ -20796,17 +20828,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20796
20828
  * say.
20797
20829
  */
20798
20830
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20831
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20832
+ var CameraOwnMotionSchema = _enum([
20833
+ "always",
20834
+ "when-confirmed",
20835
+ "never"
20836
+ ]);
20837
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20838
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20839
+ /**
20840
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20841
+ * never the effective value. The camera's `motion` slice is the effective one,
20842
+ * so a view of the camera's own row reads this.
20843
+ * - `atMs`: when the bridge received the camera's last own edge.
20844
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20845
+ * AND it is younger than the window the bridge counts the camera's own motion
20846
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20847
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20848
+ * within the window; a status read is a snapshot, so a live view re-asks.
20849
+ * Absent = this orchestrator has not received the camera's own motion since it
20850
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20851
+ */
20852
+ var CameraRawLevelSchema = object({
20853
+ detected: boolean(),
20854
+ atMs: number()
20855
+ });
20799
20856
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20800
- object({ state: literal("not-configured") }),
20857
+ object({
20858
+ state: literal("not-configured"),
20859
+ cameraLevel: CameraRawLevelSchema.optional()
20860
+ }),
20801
20861
  object({
20802
20862
  state: literal("unreadable"),
20803
- reason: string()
20863
+ reason: string(),
20864
+ cameraLevel: CameraRawLevelSchema.optional()
20804
20865
  }),
20805
20866
  object({
20806
20867
  state: literal("configured"),
20807
- mode: CameraSensorsModeSchema,
20868
+ cameraLevel: CameraRawLevelSchema.optional(),
20808
20869
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20809
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20870
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20871
+ cameraMotion: CameraOwnMotionSchema.optional(),
20872
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20873
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20810
20874
  })
20811
20875
  ]);
20812
20876
  Object.freeze({
@@ -21491,14 +21555,14 @@ var TrackSchema = object({
21491
21555
  * `derive-returned-null tile=standin`,
21492
21556
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21493
21557
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21494
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21558
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21495
21559
  * tile is a face/plate stand-in, a raster crop, or an icon.
21496
21560
  *
21497
21561
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21498
21562
  * the TILE ranked below native, so a track whose thumbnail landed natively
21499
21563
  * while its key frame missed carried no reason at all — three of the four
21500
21564
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21501
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21565
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21502
21566
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21503
21567
  * lastFrame copy source`), so its absence is now on this line whatever the
21504
21568
  * tile ended up being.
@@ -21585,7 +21649,7 @@ var MotionEventSchema = object({
21585
21649
  /**
21586
21650
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21587
21651
  * first entry is always `0`) of every genuine off→on transition the
21588
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21652
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21589
21653
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21590
21654
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21591
21655
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -22109,6 +22173,7 @@ var KeyEventSchema = object({
22109
22173
  bestEventId: string(),
22110
22174
  /** Track lifetime in ms (lastSeen - firstSeen). */
22111
22175
  windowMs: number().optional(),
22176
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
22112
22177
  ...TrackFlagFields,
22113
22178
  ...TrackRetrainFields
22114
22179
  });
@@ -23997,8 +24062,10 @@ var RunnerCameraConfigSchema = object({
23997
24062
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23998
24063
  * analyzer providers) running on decoded frames in this runner.
23999
24064
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
24000
- * analytics). The provider emits ProviderMotion
24001
- * events; the orchestrator forwards to `reportMotion`.
24065
+ * analytics). The provider emits its raw push as `ProviderMotion`
24066
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
24067
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
24068
+ * `reportMotion`.
24002
24069
  */
24003
24070
  motionSources: MotionSourcesSchema.default(["analyzer"]),
24004
24071
  /**
@@ -24175,7 +24242,15 @@ var RunnerLocalLoadSchema = object({
24175
24242
  hasGpu: boolean(),
24176
24243
  inferenceBackend: string().optional(),
24177
24244
  cpuCores: number().optional()
24178
- })
24245
+ }),
24246
+ /**
24247
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24248
+ * runner never writes it. ABSENT = a runner older than D696, which still
24249
+ * writes the slice from its phase machine; the orchestrator does not write
24250
+ * while any runner node is absent-or-unanswered, so one slice never has two
24251
+ * writers.
24252
+ */
24253
+ motionSliceWriter: literal("orchestrator").optional()
24179
24254
  });
24180
24255
  /**
24181
24256
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -26811,7 +26886,7 @@ var ClipPlaybackSchema = object({
26811
26886
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26812
26887
  * time the camera is awake for its own reasons — is deliberately not a member:
26813
26888
  * it was proposed, it is free on the battery, and the operator declined it on
26814
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
26889
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26815
26890
  * D558 "Considered and not taken").
26816
26891
  */
26817
26892
  var ClipWakeSchema = _enum(["authorised"]);
@@ -27072,7 +27147,7 @@ DeviceType.Camera, method(object({
27072
27147
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
27073
27148
  * assumed the answer was a fan-out over everything a camera has. The
27074
27149
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
27075
- * solo"* — so the list is asked of one provider at a time and there is
27150
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
27076
27151
  * nothing to filter out of it.
27077
27152
  */
27078
27153
  provider: string().min(1)
@@ -29028,6 +29103,17 @@ object({
29028
29103
  deviceId: number(),
29029
29104
  status: DeviceStatusSchema
29030
29105
  });
29106
+ object({
29107
+ /** Ms epoch of the last press. null = never observed since this provider started. */
29108
+ lastPressedAt: number().nullable(),
29109
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
29110
+ pressCountSinceStart: number()
29111
+ });
29112
+ object({
29113
+ deviceId: number(),
29114
+ timestamp: number()
29115
+ });
29116
+ DeviceType.Button, DeviceType.Camera;
29031
29117
  /**
29032
29118
  * Enum-state sensor — a string value picked from a finite option set.
29033
29119
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30672,9 +30758,9 @@ object({
30672
30758
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30673
30759
  lastDetectedAt: number().nullable(),
30674
30760
  /**
30675
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30676
- * `null` while false and on every `Sensor` device (D475) — see that
30677
- * constant's doc for the one-authority rule.
30761
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30762
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30763
+ * device (D475) — see that constant's doc for the one-authority rule.
30678
30764
  *
30679
30765
  * ## Reading this field still arms nothing
30680
30766
  *
@@ -30692,38 +30778,41 @@ object({
30692
30778
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30693
30779
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30694
30780
  *
30695
- * ## Who writes it
30696
- *
30697
- * - **Cameras** — the runner's phase machine, `active → watching` on
30698
- * `cooldown_expired`, which then writes this slice with
30699
- * `detected: false` (`handlePhaseChanged` in
30700
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30701
- * matters most for the sources that only ever push a rising one:
30702
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30703
- * a false.
30704
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30705
- * provider pushes the false itself, from the upstream system's own
30706
- * state change. No phase machine is involved.
30707
- *
30708
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30709
- *
30710
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30711
- * phase machine is the sole writer for a camera. It is not.
30712
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30713
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30714
- * Hikvision's comment says why: it read THIS docblock, agreed the
30715
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30716
- * two authorities can disagree about one slice, which this repo
30717
- * forbids, and the doc said otherwise — which is worse than saying
30718
- * nothing, because it reads as verification.
30719
- *
30720
- * This predates D475 and is not fixed there: the fix touches every
30721
- * camera provider. Recorded in D475's Consequences. Do not restore the
30722
- * "sole writer" wording without also removing the other writers.
30781
+ * ## Who writes it (D696)
30782
+ *
30783
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30784
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30785
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30786
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30787
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30788
+ * device activity, as the camera's `motionSources` counts them. A source
30789
+ * that never sends a clear falls one window after its last `true`.
30790
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30791
+ * writes this slice (not even a boot seed), and neither does the runner's
30792
+ * phase machine: its phase is "a session is attached" for every on-motion
30793
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30794
+ * A provider's `isDetected` answers from the hub's slice.
30795
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30796
+ * imports) — the provider pushes both edges itself, from the upstream
30797
+ * system's own state change. That is the sensor's own fact.
30798
+ *
30799
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30800
+ * `motion` slice; each sensor writer is a named exemption.
30801
+ *
30802
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30803
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30804
+ * it, every provider seeded it at boot, and every session left it
30805
+ * `detected: true` after its detach.
30723
30806
  */
30724
30807
  autoClearAfterMs: number().nullable()
30725
30808
  });
30726
- object({
30809
+ /**
30810
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30811
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30812
+ * — both the cap event surface and the bus payload type alias to this
30813
+ * schema.
30814
+ */
30815
+ var MotionOnMotionChangedDataSchema = object({
30727
30816
  deviceId: number(),
30728
30817
  detected: boolean(),
30729
30818
  timestamp: number(),
@@ -33188,7 +33277,7 @@ var ReadGopBytesResultSchema = object({
33188
33277
  /**
33189
33278
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
33190
33279
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
33191
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
33280
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
33192
33281
  * a replay needs several seconds of native pixels, not one frame.
33193
33282
  *
33194
33283
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -35811,6 +35900,7 @@ DeviceType.Camera, method(object({
35811
35900
  detection: array(ZoneRuleSchema).readonly(),
35812
35901
  package: array(ZoneRuleSchema).readonly()
35813
35902
  });
35903
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
35814
35904
  /**
35815
35905
  * Accessory device helpers — shared across drivers.
35816
35906
  *
@@ -40789,6 +40879,12 @@ Object.freeze({
40789
40879
  addonId: null,
40790
40880
  access: "view"
40791
40881
  },
40882
+ "notificationRules.getAlarmSensors": {
40883
+ capName: "notification-rules",
40884
+ capScope: "system",
40885
+ addonId: null,
40886
+ access: "view"
40887
+ },
40792
40888
  "notificationRules.getClipRetentionAsks": {
40793
40889
  capName: "notification-rules",
40794
40890
  capScope: "system",
@@ -44367,6 +44463,11 @@ Object.freeze({
44367
44463
  }
44368
44464
  });
44369
44465
  Object.freeze({
44466
+ "accessories.getStatus": [{
44467
+ name: "deviceId",
44468
+ form: "single",
44469
+ optional: false
44470
+ }],
44370
44471
  "accessories.setChildHidden": [{
44371
44472
  name: "childDeviceId",
44372
44473
  form: "single",
@@ -44386,6 +44487,11 @@ Object.freeze({
44386
44487
  form: "single",
44387
44488
  optional: false
44388
44489
  }],
44490
+ "airQualitySensor.getStatus": [{
44491
+ name: "deviceId",
44492
+ form: "single",
44493
+ optional: false
44494
+ }],
44389
44495
  "alarmPanel.arm": [{
44390
44496
  name: "deviceId",
44391
44497
  form: "single",
@@ -44396,11 +44502,36 @@ Object.freeze({
44396
44502
  form: "single",
44397
44503
  optional: false
44398
44504
  }],
44505
+ "alarmPanel.getStatus": [{
44506
+ name: "deviceId",
44507
+ form: "single",
44508
+ optional: false
44509
+ }],
44399
44510
  "alarmPanel.trigger": [{
44400
44511
  name: "deviceId",
44401
44512
  form: "single",
44402
44513
  optional: false
44403
44514
  }],
44515
+ "ambientLightSensor.getStatus": [{
44516
+ name: "deviceId",
44517
+ form: "single",
44518
+ optional: false
44519
+ }],
44520
+ "audioAnalysis.applyDeviceSettingsPatch": [{
44521
+ name: "deviceId",
44522
+ form: "single",
44523
+ optional: false
44524
+ }],
44525
+ "audioAnalysis.getDeviceLiveContribution": [{
44526
+ name: "deviceId",
44527
+ form: "single",
44528
+ optional: false
44529
+ }],
44530
+ "audioAnalysis.getDeviceSettingsContribution": [{
44531
+ name: "deviceId",
44532
+ form: "single",
44533
+ optional: false
44534
+ }],
44404
44535
  "audioAnalysis.resolveDeviceSettings": [{
44405
44536
  name: "deviceId",
44406
44537
  form: "single",
@@ -44441,16 +44572,36 @@ Object.freeze({
44441
44572
  form: "single",
44442
44573
  optional: false
44443
44574
  }],
44575
+ "automationControl.getStatus": [{
44576
+ name: "deviceId",
44577
+ form: "single",
44578
+ optional: false
44579
+ }],
44444
44580
  "automationControl.trigger": [{
44445
44581
  name: "deviceId",
44446
44582
  form: "single",
44447
44583
  optional: false
44448
44584
  }],
44585
+ "battery.getStatus": [{
44586
+ name: "deviceId",
44587
+ form: "single",
44588
+ optional: false
44589
+ }],
44449
44590
  "battery.wakeForStream": [{
44450
44591
  name: "deviceId",
44451
44592
  form: "single",
44452
44593
  optional: false
44453
44594
  }],
44595
+ "binary.getStatus": [{
44596
+ name: "deviceId",
44597
+ form: "single",
44598
+ optional: false
44599
+ }],
44600
+ "brightness.getStatus": [{
44601
+ name: "deviceId",
44602
+ form: "single",
44603
+ optional: false
44604
+ }],
44454
44605
  "brightness.setBrightness": [{
44455
44606
  name: "deviceId",
44456
44607
  form: "single",
@@ -44466,6 +44617,26 @@ Object.freeze({
44466
44617
  form: "single",
44467
44618
  optional: false
44468
44619
  }],
44620
+ "cameraCredentials.getStatus": [{
44621
+ name: "deviceId",
44622
+ form: "single",
44623
+ optional: false
44624
+ }],
44625
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
44626
+ name: "deviceId",
44627
+ form: "single",
44628
+ optional: false
44629
+ }],
44630
+ "cameraGridLayout.getDeviceLiveContribution": [{
44631
+ name: "deviceId",
44632
+ form: "single",
44633
+ optional: false
44634
+ }],
44635
+ "cameraGridLayout.getDeviceSettingsContribution": [{
44636
+ name: "deviceId",
44637
+ form: "single",
44638
+ optional: false
44639
+ }],
44469
44640
  "cameraGridLayout.getLayout": [{
44470
44641
  name: "deviceId",
44471
44642
  form: "single",
@@ -44481,6 +44652,21 @@ Object.freeze({
44481
44652
  form: "single",
44482
44653
  optional: false
44483
44654
  }],
44655
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
44656
+ name: "deviceId",
44657
+ form: "single",
44658
+ optional: false
44659
+ }],
44660
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
44661
+ name: "deviceId",
44662
+ form: "single",
44663
+ optional: false
44664
+ }],
44665
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
44666
+ name: "deviceId",
44667
+ form: "single",
44668
+ optional: false
44669
+ }],
44484
44670
  "cameraStreams.getBrokerStreams": [{
44485
44671
  name: "deviceId",
44486
44672
  form: "single",
@@ -44506,6 +44692,16 @@ Object.freeze({
44506
44692
  form: "single",
44507
44693
  optional: false
44508
44694
  }],
44695
+ "carbonMonoxide.getStatus": [{
44696
+ name: "deviceId",
44697
+ form: "single",
44698
+ optional: false
44699
+ }],
44700
+ "climateControl.getStatus": [{
44701
+ name: "deviceId",
44702
+ form: "single",
44703
+ optional: false
44704
+ }],
44509
44705
  "climateControl.setFanMode": [{
44510
44706
  name: "deviceId",
44511
44707
  form: "single",
@@ -44546,16 +44742,41 @@ Object.freeze({
44546
44742
  form: "single",
44547
44743
  optional: false
44548
44744
  }],
44745
+ "color.getStatus": [{
44746
+ name: "deviceId",
44747
+ form: "single",
44748
+ optional: false
44749
+ }],
44549
44750
  "color.setColor": [{
44550
44751
  name: "deviceId",
44551
44752
  form: "single",
44552
44753
  optional: false
44553
44754
  }],
44755
+ "connectivity.getStatus": [{
44756
+ name: "deviceId",
44757
+ form: "single",
44758
+ optional: false
44759
+ }],
44760
+ "consumables.getStatus": [{
44761
+ name: "deviceId",
44762
+ form: "single",
44763
+ optional: false
44764
+ }],
44554
44765
  "consumables.reset": [{
44555
44766
  name: "deviceId",
44556
44767
  form: "single",
44557
44768
  optional: false
44558
44769
  }],
44770
+ "contact.getStatus": [{
44771
+ name: "deviceId",
44772
+ form: "single",
44773
+ optional: false
44774
+ }],
44775
+ "control.getStatus": [{
44776
+ name: "deviceId",
44777
+ form: "single",
44778
+ optional: false
44779
+ }],
44559
44780
  "control.setValue": [{
44560
44781
  name: "deviceId",
44561
44782
  form: "single",
@@ -44566,6 +44787,11 @@ Object.freeze({
44566
44787
  form: "single",
44567
44788
  optional: false
44568
44789
  }],
44790
+ "cover.getStatus": [{
44791
+ name: "deviceId",
44792
+ form: "single",
44793
+ optional: false
44794
+ }],
44569
44795
  "cover.open": [{
44570
44796
  name: "deviceId",
44571
44797
  form: "single",
@@ -44591,6 +44817,11 @@ Object.freeze({
44591
44817
  form: "single",
44592
44818
  optional: false
44593
44819
  }],
44820
+ "dayNight.getStatus": [{
44821
+ name: "deviceId",
44822
+ form: "single",
44823
+ optional: false
44824
+ }],
44594
44825
  "dayNight.setSettings": [{
44595
44826
  name: "deviceId",
44596
44827
  form: "single",
@@ -44601,11 +44832,31 @@ Object.freeze({
44601
44832
  form: "single",
44602
44833
  optional: true
44603
44834
  }],
44835
+ "detectionPipeline.applyDeviceSettingsPatch": [{
44836
+ name: "deviceId",
44837
+ form: "single",
44838
+ optional: false
44839
+ }],
44840
+ "detectionPipeline.getDeviceLiveContribution": [{
44841
+ name: "deviceId",
44842
+ form: "single",
44843
+ optional: false
44844
+ }],
44845
+ "detectionPipeline.getDeviceSettingsContribution": [{
44846
+ name: "deviceId",
44847
+ form: "single",
44848
+ optional: false
44849
+ }],
44604
44850
  "deviceAdminLink.getAdminLink": [{
44605
44851
  name: "deviceId",
44606
44852
  form: "single",
44607
44853
  optional: false
44608
44854
  }],
44855
+ "deviceAdoption.getStatus": [{
44856
+ name: "deviceId",
44857
+ form: "single",
44858
+ optional: false
44859
+ }],
44609
44860
  "deviceAdoption.release": [{
44610
44861
  name: "camDeviceId",
44611
44862
  form: "single",
@@ -44621,6 +44872,11 @@ Object.freeze({
44621
44872
  form: "single",
44622
44873
  optional: false
44623
44874
  }],
44875
+ "deviceDiscovery.getStatus": [{
44876
+ name: "deviceId",
44877
+ form: "single",
44878
+ optional: false
44879
+ }],
44624
44880
  "deviceDiscovery.listDiscovered": [{
44625
44881
  name: "deviceId",
44626
44882
  form: "single",
@@ -44640,6 +44896,21 @@ Object.freeze({
44640
44896
  form: "single",
44641
44897
  optional: false
44642
44898
  }],
44899
+ "deviceExport.applyDeviceSettingsPatch": [{
44900
+ name: "deviceId",
44901
+ form: "single",
44902
+ optional: false
44903
+ }],
44904
+ "deviceExport.getDeviceLiveContribution": [{
44905
+ name: "deviceId",
44906
+ form: "single",
44907
+ optional: false
44908
+ }],
44909
+ "deviceExport.getDeviceSettingsContribution": [{
44910
+ name: "deviceId",
44911
+ form: "single",
44912
+ optional: false
44913
+ }],
44643
44914
  "deviceExtension.listCandidates": [{
44644
44915
  name: "deviceId",
44645
44916
  form: "single",
@@ -44988,6 +45259,26 @@ Object.freeze({
44988
45259
  form: "single",
44989
45260
  optional: false
44990
45261
  }],
45262
+ "deviceStatus.getStatus": [{
45263
+ name: "deviceId",
45264
+ form: "single",
45265
+ optional: false
45266
+ }],
45267
+ "doorbell.getStatus": [{
45268
+ name: "deviceId",
45269
+ form: "single",
45270
+ optional: false
45271
+ }],
45272
+ "enumSensor.getStatus": [{
45273
+ name: "deviceId",
45274
+ form: "single",
45275
+ optional: false
45276
+ }],
45277
+ "eventEmitter.getStatus": [{
45278
+ name: "deviceId",
45279
+ form: "single",
45280
+ optional: false
45281
+ }],
44991
45282
  "faceGallery.getFaceByTrack": [{
44992
45283
  name: "deviceId",
44993
45284
  form: "single",
@@ -45002,6 +45293,11 @@ Object.freeze({
45002
45293
  form: "array",
45003
45294
  optional: true
45004
45295
  }],
45296
+ "fanControl.getStatus": [{
45297
+ name: "deviceId",
45298
+ form: "single",
45299
+ optional: false
45300
+ }],
45005
45301
  "fanControl.setDirection": [{
45006
45302
  name: "deviceId",
45007
45303
  form: "single",
@@ -45022,6 +45318,26 @@ Object.freeze({
45022
45318
  form: "single",
45023
45319
  optional: false
45024
45320
  }],
45321
+ "featureProbe.getStatus": [{
45322
+ name: "deviceId",
45323
+ form: "single",
45324
+ optional: false
45325
+ }],
45326
+ "flood.getStatus": [{
45327
+ name: "deviceId",
45328
+ form: "single",
45329
+ optional: false
45330
+ }],
45331
+ "gas.getStatus": [{
45332
+ name: "deviceId",
45333
+ form: "single",
45334
+ optional: false
45335
+ }],
45336
+ "humidifier.getStatus": [{
45337
+ name: "deviceId",
45338
+ form: "single",
45339
+ optional: false
45340
+ }],
45025
45341
  "humidifier.setMode": [{
45026
45342
  name: "deviceId",
45027
45343
  form: "single",
@@ -45037,11 +45353,26 @@ Object.freeze({
45037
45353
  form: "single",
45038
45354
  optional: false
45039
45355
  }],
45356
+ "humiditySensor.getStatus": [{
45357
+ name: "deviceId",
45358
+ form: "single",
45359
+ optional: false
45360
+ }],
45361
+ "image.getStatus": [{
45362
+ name: "deviceId",
45363
+ form: "single",
45364
+ optional: false
45365
+ }],
45040
45366
  "imageSettings.getOptions": [{
45041
45367
  name: "deviceId",
45042
45368
  form: "single",
45043
45369
  optional: false
45044
45370
  }],
45371
+ "imageSettings.getStatus": [{
45372
+ name: "deviceId",
45373
+ form: "single",
45374
+ optional: false
45375
+ }],
45045
45376
  "imageSettings.setSettings": [{
45046
45377
  name: "deviceId",
45047
45378
  form: "single",
@@ -45052,6 +45383,11 @@ Object.freeze({
45052
45383
  form: "single",
45053
45384
  optional: false
45054
45385
  }],
45386
+ "intercom.getStatus": [{
45387
+ name: "deviceId",
45388
+ form: "single",
45389
+ optional: false
45390
+ }],
45055
45391
  "intercom.handleAnswer": [{
45056
45392
  name: "deviceId",
45057
45393
  form: "single",
@@ -45082,6 +45418,11 @@ Object.freeze({
45082
45418
  form: "single",
45083
45419
  optional: false
45084
45420
  }],
45421
+ "lawnMowerControl.getStatus": [{
45422
+ name: "deviceId",
45423
+ form: "single",
45424
+ optional: false
45425
+ }],
45085
45426
  "lawnMowerControl.pause": [{
45086
45427
  name: "deviceId",
45087
45428
  form: "single",
@@ -45092,6 +45433,11 @@ Object.freeze({
45092
45433
  form: "single",
45093
45434
  optional: false
45094
45435
  }],
45436
+ "lockControl.getStatus": [{
45437
+ name: "deviceId",
45438
+ form: "single",
45439
+ optional: false
45440
+ }],
45095
45441
  "lockControl.lock": [{
45096
45442
  name: "deviceId",
45097
45443
  form: "single",
@@ -45107,6 +45453,11 @@ Object.freeze({
45107
45453
  form: "single",
45108
45454
  optional: false
45109
45455
  }],
45456
+ "mediaPlayer.getStatus": [{
45457
+ name: "deviceId",
45458
+ form: "single",
45459
+ optional: false
45460
+ }],
45110
45461
  "mediaPlayer.next": [{
45111
45462
  name: "deviceId",
45112
45463
  form: "single",
@@ -45167,6 +45518,11 @@ Object.freeze({
45167
45518
  form: "single",
45168
45519
  optional: false
45169
45520
  }],
45521
+ "motion.getStatus": [{
45522
+ name: "deviceId",
45523
+ form: "single",
45524
+ optional: false
45525
+ }],
45170
45526
  "motion.isDetected": [{
45171
45527
  name: "deviceId",
45172
45528
  form: "single",
@@ -45177,11 +45533,31 @@ Object.freeze({
45177
45533
  form: "single",
45178
45534
  optional: false
45179
45535
  }],
45536
+ "motionDetection.applyDeviceSettingsPatch": [{
45537
+ name: "deviceId",
45538
+ form: "single",
45539
+ optional: false
45540
+ }],
45541
+ "motionDetection.getDeviceLiveContribution": [{
45542
+ name: "deviceId",
45543
+ form: "single",
45544
+ optional: false
45545
+ }],
45546
+ "motionDetection.getDeviceSettingsContribution": [{
45547
+ name: "deviceId",
45548
+ form: "single",
45549
+ optional: false
45550
+ }],
45180
45551
  "motionDetection.removeCamera": [{
45181
45552
  name: "deviceId",
45182
45553
  form: "single",
45183
45554
  optional: false
45184
45555
  }],
45556
+ "motionTrigger.getStatus": [{
45557
+ name: "deviceId",
45558
+ form: "single",
45559
+ optional: false
45560
+ }],
45185
45561
  "motionTrigger.setMotionTrigger": [{
45186
45562
  name: "deviceId",
45187
45563
  form: "single",
@@ -45192,6 +45568,11 @@ Object.freeze({
45192
45568
  form: "single",
45193
45569
  optional: false
45194
45570
  }],
45571
+ "motionZones.getStatus": [{
45572
+ name: "deviceId",
45573
+ form: "single",
45574
+ optional: false
45575
+ }],
45195
45576
  "motionZones.setZone": [{
45196
45577
  name: "deviceId",
45197
45578
  form: "single",
@@ -45202,6 +45583,11 @@ Object.freeze({
45202
45583
  form: "single",
45203
45584
  optional: false
45204
45585
  }],
45586
+ "nativeObjectDetection.getStatus": [{
45587
+ name: "deviceId",
45588
+ form: "single",
45589
+ optional: false
45590
+ }],
45205
45591
  "nativeObjectDetection.setEnabled": [{
45206
45592
  name: "deviceId",
45207
45593
  form: "single",
@@ -45217,6 +45603,11 @@ Object.freeze({
45217
45603
  form: "single",
45218
45604
  optional: false
45219
45605
  }],
45606
+ "navigation.getStatus": [{
45607
+ name: "deviceId",
45608
+ form: "single",
45609
+ optional: false
45610
+ }],
45220
45611
  "navigation.goToPoint": [{
45221
45612
  name: "deviceId",
45222
45613
  form: "single",
@@ -45262,6 +45653,11 @@ Object.freeze({
45262
45653
  form: "single",
45263
45654
  optional: false
45264
45655
  }],
45656
+ "networkLink.getStatus": [{
45657
+ name: "deviceId",
45658
+ form: "single",
45659
+ optional: false
45660
+ }],
45265
45661
  "networkQuality.getDeviceStats": [{
45266
45662
  name: "deviceId",
45267
45663
  form: "single",
@@ -45272,6 +45668,11 @@ Object.freeze({
45272
45668
  form: "single",
45273
45669
  optional: false
45274
45670
  }],
45671
+ "notificationRules.getAlarmSensors": [{
45672
+ name: "deviceIds",
45673
+ form: "array",
45674
+ optional: false
45675
+ }],
45275
45676
  "notificationRules.setDeviceMuted": [{
45276
45677
  name: "deviceId",
45277
45678
  form: "single",
@@ -45282,11 +45683,26 @@ Object.freeze({
45282
45683
  form: "single",
45283
45684
  optional: false
45284
45685
  }],
45686
+ "notifier.getStatus": [{
45687
+ name: "deviceId",
45688
+ form: "single",
45689
+ optional: false
45690
+ }],
45285
45691
  "notifier.send": [{
45286
45692
  name: "deviceId",
45287
45693
  form: "single",
45288
45694
  optional: false
45289
45695
  }],
45696
+ "numericSensor.getStatus": [{
45697
+ name: "deviceId",
45698
+ form: "single",
45699
+ optional: false
45700
+ }],
45701
+ "osd.getStatus": [{
45702
+ name: "deviceId",
45703
+ form: "single",
45704
+ optional: false
45705
+ }],
45290
45706
  "osd.setOverlay": [{
45291
45707
  name: "deviceId",
45292
45708
  form: "single",
@@ -45346,6 +45762,11 @@ Object.freeze({
45346
45762
  form: "single",
45347
45763
  optional: false
45348
45764
  }],
45765
+ "petFeeder.getStatus": [{
45766
+ name: "deviceId",
45767
+ form: "single",
45768
+ optional: false
45769
+ }],
45349
45770
  "petFeeder.markFoodReplenished": [{
45350
45771
  name: "deviceId",
45351
45772
  form: "single",
@@ -45381,6 +45802,11 @@ Object.freeze({
45381
45802
  form: "single",
45382
45803
  optional: false
45383
45804
  }],
45805
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
45806
+ name: "deviceId",
45807
+ form: "single",
45808
+ optional: false
45809
+ }],
45384
45810
  "pipelineAnalytics.clearTrackRelation": [{
45385
45811
  name: "deviceId",
45386
45812
  form: "single",
@@ -45421,6 +45847,16 @@ Object.freeze({
45421
45847
  form: "single",
45422
45848
  optional: false
45423
45849
  }],
45850
+ "pipelineAnalytics.getDeviceLiveContribution": [{
45851
+ name: "deviceId",
45852
+ form: "single",
45853
+ optional: false
45854
+ }],
45855
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
45856
+ name: "deviceId",
45857
+ form: "single",
45858
+ optional: false
45859
+ }],
45424
45860
  "pipelineAnalytics.getEventDensity": [{
45425
45861
  name: "deviceId",
45426
45862
  form: "single",
@@ -45636,6 +46072,11 @@ Object.freeze({
45636
46072
  form: "single",
45637
46073
  optional: true
45638
46074
  }],
46075
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
46076
+ name: "deviceId",
46077
+ form: "single",
46078
+ optional: false
46079
+ }],
45639
46080
  "pipelineOrchestrator.assignAudio": [{
45640
46081
  name: "deviceId",
45641
46082
  form: "single",
@@ -45681,6 +46122,16 @@ Object.freeze({
45681
46122
  form: "single",
45682
46123
  optional: false
45683
46124
  }],
46125
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
46126
+ name: "deviceId",
46127
+ form: "single",
46128
+ optional: false
46129
+ }],
46130
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
46131
+ name: "deviceId",
46132
+ form: "single",
46133
+ optional: false
46134
+ }],
45684
46135
  "pipelineOrchestrator.getPipelineAssignment": [{
45685
46136
  name: "deviceId",
45686
46137
  form: "single",
@@ -45801,11 +46252,31 @@ Object.freeze({
45801
46252
  form: "single",
45802
46253
  optional: true
45803
46254
  }],
46255
+ "powerMeter.getStatus": [{
46256
+ name: "deviceId",
46257
+ form: "single",
46258
+ optional: false
46259
+ }],
46260
+ "presence.getStatus": [{
46261
+ name: "deviceId",
46262
+ form: "single",
46263
+ optional: false
46264
+ }],
46265
+ "pressureSensor.getStatus": [{
46266
+ name: "deviceId",
46267
+ form: "single",
46268
+ optional: false
46269
+ }],
45804
46270
  "privacyMask.getOptions": [{
45805
46271
  name: "deviceId",
45806
46272
  form: "single",
45807
46273
  optional: false
45808
46274
  }],
46275
+ "privacyMask.getStatus": [{
46276
+ name: "deviceId",
46277
+ form: "single",
46278
+ optional: false
46279
+ }],
45809
46280
  "privacyMask.setAudioEnabled": [{
45810
46281
  name: "deviceId",
45811
46282
  form: "single",
@@ -45856,6 +46327,11 @@ Object.freeze({
45856
46327
  form: "single",
45857
46328
  optional: false
45858
46329
  }],
46330
+ "ptz.getStatus": [{
46331
+ name: "deviceId",
46332
+ form: "single",
46333
+ optional: false
46334
+ }],
45859
46335
  "ptz.goHome": [{
45860
46336
  name: "deviceId",
45861
46337
  form: "single",
@@ -45946,6 +46422,11 @@ Object.freeze({
45946
46422
  form: "single",
45947
46423
  optional: false
45948
46424
  }],
46425
+ "recordingArchive.applyDeviceSettingsPatch": [{
46426
+ name: "deviceId",
46427
+ form: "single",
46428
+ optional: false
46429
+ }],
45949
46430
  "recordingArchive.deleteFootprint": [{
45950
46431
  name: "deviceId",
45951
46432
  form: "single",
@@ -45966,6 +46447,21 @@ Object.freeze({
45966
46447
  form: "single",
45967
46448
  optional: false
45968
46449
  }],
46450
+ "recordingArchive.getDeviceLiveContribution": [{
46451
+ name: "deviceId",
46452
+ form: "single",
46453
+ optional: false
46454
+ }],
46455
+ "recordingArchive.getDeviceSettingsContribution": [{
46456
+ name: "deviceId",
46457
+ form: "single",
46458
+ optional: false
46459
+ }],
46460
+ "recordingArchive.getStatus": [{
46461
+ name: "deviceId",
46462
+ form: "single",
46463
+ optional: false
46464
+ }],
45969
46465
  "recordingArchive.listOpsLog": [{
45970
46466
  name: "deviceId",
45971
46467
  form: "single",
@@ -46051,6 +46547,11 @@ Object.freeze({
46051
46547
  form: "single",
46052
46548
  optional: false
46053
46549
  }],
46550
+ "recordingOnboard.getStatus": [{
46551
+ name: "deviceId",
46552
+ form: "single",
46553
+ optional: false
46554
+ }],
46054
46555
  "recordingOnboard.setSettings": [{
46055
46556
  name: "deviceId",
46056
46557
  form: "single",
@@ -46081,6 +46582,11 @@ Object.freeze({
46081
46582
  form: "single",
46082
46583
  optional: false
46083
46584
  }],
46585
+ "sceneMonitor.getStatus": [{
46586
+ name: "deviceId",
46587
+ form: "single",
46588
+ optional: false
46589
+ }],
46084
46590
  "sceneMonitor.listScenes": [{
46085
46591
  name: "deviceId",
46086
46592
  form: "single",
@@ -46106,6 +46612,11 @@ Object.freeze({
46106
46612
  form: "single",
46107
46613
  optional: false
46108
46614
  }],
46615
+ "scriptRunner.getStatus": [{
46616
+ name: "deviceId",
46617
+ form: "single",
46618
+ optional: false
46619
+ }],
46109
46620
  "scriptRunner.run": [{
46110
46621
  name: "deviceId",
46111
46622
  form: "single",
@@ -46116,11 +46627,31 @@ Object.freeze({
46116
46627
  form: "single",
46117
46628
  optional: false
46118
46629
  }],
46630
+ "smoke.getStatus": [{
46631
+ name: "deviceId",
46632
+ form: "single",
46633
+ optional: false
46634
+ }],
46635
+ "snapshot.applyDeviceSettingsPatch": [{
46636
+ name: "deviceId",
46637
+ form: "single",
46638
+ optional: false
46639
+ }],
46119
46640
  "snapshot.getDebugState": [{
46120
46641
  name: "deviceId",
46121
46642
  form: "single",
46122
46643
  optional: false
46123
46644
  }],
46645
+ "snapshot.getDeviceLiveContribution": [{
46646
+ name: "deviceId",
46647
+ form: "single",
46648
+ optional: false
46649
+ }],
46650
+ "snapshot.getDeviceSettingsContribution": [{
46651
+ name: "deviceId",
46652
+ form: "single",
46653
+ optional: false
46654
+ }],
46124
46655
  "snapshot.getSnapshot": [{
46125
46656
  name: "deviceId",
46126
46657
  form: "single",
@@ -46137,6 +46668,11 @@ Object.freeze({
46137
46668
  form: "array",
46138
46669
  optional: false
46139
46670
  }],
46671
+ "snapshot.getStatus": [{
46672
+ name: "deviceId",
46673
+ form: "single",
46674
+ optional: false
46675
+ }],
46140
46676
  "snapshot.invalidateCache": [{
46141
46677
  name: "deviceId",
46142
46678
  form: "single",
@@ -46147,6 +46683,11 @@ Object.freeze({
46147
46683
  form: "single",
46148
46684
  optional: false
46149
46685
  }],
46686
+ "streamBroker.applyDeviceSettingsPatch": [{
46687
+ name: "deviceId",
46688
+ form: "single",
46689
+ optional: false
46690
+ }],
46150
46691
  "streamBroker.assignProfile": [{
46151
46692
  name: "deviceId",
46152
46693
  form: "single",
@@ -46162,6 +46703,16 @@ Object.freeze({
46162
46703
  form: "single",
46163
46704
  optional: false
46164
46705
  }],
46706
+ "streamBroker.getDeviceLiveContribution": [{
46707
+ name: "deviceId",
46708
+ form: "single",
46709
+ optional: false
46710
+ }],
46711
+ "streamBroker.getDeviceSettingsContribution": [{
46712
+ name: "deviceId",
46713
+ form: "single",
46714
+ optional: false
46715
+ }],
46165
46716
  "streamBroker.getStreamWithCodec": [{
46166
46717
  name: "deviceId",
46167
46718
  form: "single",
@@ -46222,16 +46773,46 @@ Object.freeze({
46222
46773
  form: "single",
46223
46774
  optional: false
46224
46775
  }],
46776
+ "streamParams.getStatus": [{
46777
+ name: "deviceId",
46778
+ form: "single",
46779
+ optional: false
46780
+ }],
46225
46781
  "streamParams.setProfile": [{
46226
46782
  name: "deviceId",
46227
46783
  form: "single",
46228
46784
  optional: false
46229
46785
  }],
46786
+ "switch.getStatus": [{
46787
+ name: "deviceId",
46788
+ form: "single",
46789
+ optional: false
46790
+ }],
46230
46791
  "switch.setState": [{
46231
46792
  name: "deviceId",
46232
46793
  form: "single",
46233
46794
  optional: false
46234
46795
  }],
46796
+ "tamper.getStatus": [{
46797
+ name: "deviceId",
46798
+ form: "single",
46799
+ optional: false
46800
+ }],
46801
+ "temperatureSensor.getStatus": [{
46802
+ name: "deviceId",
46803
+ form: "single",
46804
+ optional: false
46805
+ }],
46806
+ "update.getStatus": [{
46807
+ name: "deviceId",
46808
+ form: "single",
46809
+ optional: false
46810
+ }],
46811
+ "vacuumControl.getStatus": [{
46812
+ name: "deviceId",
46813
+ form: "single",
46814
+ optional: false
46815
+ }],
46235
46816
  "vacuumControl.locate": [{
46236
46817
  name: "deviceId",
46237
46818
  form: "single",
@@ -46267,6 +46848,11 @@ Object.freeze({
46267
46848
  form: "single",
46268
46849
  optional: false
46269
46850
  }],
46851
+ "valve.getStatus": [{
46852
+ name: "deviceId",
46853
+ form: "single",
46854
+ optional: false
46855
+ }],
46270
46856
  "valve.open": [{
46271
46857
  name: "deviceId",
46272
46858
  form: "single",
@@ -46282,6 +46868,11 @@ Object.freeze({
46282
46868
  form: "single",
46283
46869
  optional: false
46284
46870
  }],
46871
+ "vibration.getStatus": [{
46872
+ name: "deviceId",
46873
+ form: "single",
46874
+ optional: false
46875
+ }],
46285
46876
  "videoclips.dialClipStream": [{
46286
46877
  name: "deviceId",
46287
46878
  form: "single",
@@ -46317,6 +46908,11 @@ Object.freeze({
46317
46908
  form: "single",
46318
46909
  optional: false
46319
46910
  }],
46911
+ "waterHeater.getStatus": [{
46912
+ name: "deviceId",
46913
+ form: "single",
46914
+ optional: false
46915
+ }],
46320
46916
  "waterHeater.setAway": [{
46321
46917
  name: "deviceId",
46322
46918
  form: "single",
@@ -46332,6 +46928,11 @@ Object.freeze({
46332
46928
  form: "single",
46333
46929
  optional: false
46334
46930
  }],
46931
+ "weather.getStatus": [{
46932
+ name: "deviceId",
46933
+ form: "single",
46934
+ optional: false
46935
+ }],
46335
46936
  "webrtcSession.addIceCandidate": [{
46336
46937
  name: "deviceId",
46337
46938
  form: "single",
@@ -47069,7 +47670,7 @@ var PreviewTextField = string().max(200);
47069
47670
  * Whether the notification's preview is a STILL or a short animation.
47070
47671
  *
47071
47672
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
47072
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
47673
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
47073
47674
  * night reads better as three seconds of motion than as one frame of it. Both
47074
47675
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
47075
47676
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -47207,6 +47808,7 @@ TimelapseRuleInputSchema.extend({
47207
47808
  createdAt: number(),
47208
47809
  updatedAt: number()
47209
47810
  });
47811
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47210
47812
  object({
47211
47813
  /**
47212
47814
  * Fraction of the box's own size added on EACH side before cutting.