@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.
@@ -77,7 +77,7 @@ function createApiDeviceGateway(deps) {
77
77
  };
78
78
  }
79
79
  //#endregion
80
- //#region ../types/dist/event-category-C5xZWqz6.mjs
80
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
81
81
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
82
82
  EventCategory["SystemBoot"] = "system.boot";
83
83
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -610,6 +610,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
610
610
  */
611
611
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
612
612
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
613
+ /**
614
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
615
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
616
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
617
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
618
+ * for a camera with no attached sensor). Providers must not emit
619
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
620
+ * keeps it so (camera-grid is a named, deferred exception).
621
+ */
613
622
  EventCategory["ProviderMotion"] = "provider.motion";
614
623
  EventCategory["ProviderDetection"] = "provider.detection";
615
624
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5448,7 +5457,7 @@ var ZodIssueCode = {
5448
5457
  var ZodFirstPartyTypeKind;
5449
5458
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5450
5459
  //#endregion
5451
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5460
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5452
5461
  /**
5453
5462
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5454
5463
  * window to float samples (D455).
@@ -7509,7 +7518,7 @@ function errMsg(err) {
7509
7518
  return String(err);
7510
7519
  }
7511
7520
  //#endregion
7512
- //#region ../types/dist/composition-Dq42XWp0.mjs
7521
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7513
7522
  /**
7514
7523
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7515
7524
  *
@@ -7563,21 +7572,20 @@ function portable(target) {
7563
7572
  }
7564
7573
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7565
7574
  /**
7566
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7567
- * valutatore puro del suo FRONTE.
7575
+ * THE table "which boolean of this device type counts as HIGH", and the
7576
+ * pure evaluator of its EDGE.
7568
7577
  *
7569
- * Viveva dentro il builtin virtual-doorbell
7570
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7571
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7572
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7573
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7574
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7575
- * SPOSTA qui e il doorbell la ri-esporta.
7578
+ * ONE table for everyone who asks "is this device HIGH?": the
7579
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7580
+ * sensor markers, the orchestrator's camera-sensors bridge and
7581
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7582
+ * A second copy would diverge at the first cap added, and the symptom — "the
7583
+ * sensor rings the doorbell but does not record" — is exactly D62.
7576
7584
  *
7577
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7578
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7579
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7580
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7585
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7586
+ * two must not be unified: that one answers "what is the state WORD for a
7587
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7588
+ * boolean's EDGE counts". Deliberately different vocabularies.
7581
7589
  */
7582
7590
  /**
7583
7591
  * Known binary / switch source caps → the boolean slice field whose
@@ -7598,6 +7606,8 @@ var SOURCE_CAP_ACTIVE_FIELD = {
7598
7606
  vibration: "detected",
7599
7607
  tamper: "tampered"
7600
7608
  };
7609
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7610
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7601
7611
  /**
7602
7612
  * Error types for the safe expression engine. Two distinct classes so callers
7603
7613
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -9301,22 +9311,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9301
9311
  "package"
9302
9312
  ];
9303
9313
  /**
9304
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9305
- * un operatore può selezionare.
9314
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9315
+ * an operator can select.
9306
9316
  *
9307
- * Sono le tre offerte dallo step `object-detection`
9317
+ * They are the three offered by the `object-detection` step
9308
9318
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9309
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9310
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9311
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9312
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9319
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9320
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9321
+ * `package-detection`, whose enablement is driven by zones, and offering the
9322
+ * same word twice has already made an operator turn on the COCO proxy
9313
9323
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9314
9324
  *
9315
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9316
- * dello step e una seconda volta come union `FirstLevelMacro`
9317
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9318
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9319
- * successiva.
9325
+ * ONE list. Before today the same three were hand-written in the step's offer
9326
+ * and a second time as the union `FirstLevelMacro`
9327
+ * (`types/detection.ts`); a third copy for the recording trigger
9328
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9329
+ * invisible.
9320
9330
  */
9321
9331
  var FIRST_LEVEL_MACRO_CLASSES = [
9322
9332
  "person",
@@ -9381,17 +9391,17 @@ var RecordingStorageModeSchema = _enum([
9381
9391
  "on-device-decision"
9382
9392
  ]);
9383
9393
  /**
9384
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9385
- * tre offerte dallo step `object-detection`, da UNA lista
9394
+ * The macro classes that can open a recording window — the same
9395
+ * three offered by the `object-detection` step, from ONE list
9386
9396
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9387
9397
  */
9388
9398
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9389
9399
  /**
9390
- * True quando `values` non ripete un elemento.
9400
+ * True when `values` does not repeat an element.
9391
9401
  *
9392
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9393
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9394
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9402
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9403
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9404
+ * would count its windows twice in `segmentMissedByMs`.
9395
9405
  */
9396
9406
  var noDuplicates = (values) => new Set(values).size === values.length;
9397
9407
  /** Which detectors trigger an `events`-mode band. */
@@ -9399,34 +9409,34 @@ var RecordingTriggersSchema = object({
9399
9409
  motion: boolean().optional(),
9400
9410
  audioThresholdDbfs: number().optional(),
9401
9411
  /**
9402
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9403
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9404
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9405
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9412
+ * The macro classes whose detection opens a window. ABSENT = the source
9413
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9414
+ * trigger on, no class" is the same shape "enabled and records
9415
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9406
9416
  *
9407
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9408
- * quelle che hanno attraversato `enabledMacroClasses`, i
9409
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9410
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9411
- * finestre — vedi `recorder/object-trigger.ts`.
9417
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9418
+ * those that went through `enabledMacroClasses`, the
9419
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9420
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9421
+ * windows — see `recorder/object-trigger.ts`.
9412
9422
  */
9413
9423
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9414
9424
  /**
9415
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9416
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9425
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9426
+ * is not listened to; an empty array is rejected for the same reason as
9417
9427
  * `objectClasses`.
9418
9428
  *
9419
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9420
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9421
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9422
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9423
- * device (D12) — mai un elenco globale di cap.
9429
+ * These are SOURCE device ids, not cameras: the band names them, so the
9430
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9431
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9432
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9433
+ * device (D12) — never a global list of caps.
9424
9434
  *
9425
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9426
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9427
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9428
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9429
- * registrare.
9435
+ * What "high" means depends on the device TYPE and is not decided here:
9436
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9437
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9438
+ * the level: a contact found already open at runner restart does not
9439
+ * record.
9430
9440
  */
9431
9441
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9432
9442
  });
@@ -10503,7 +10513,7 @@ var DecoderSessionConfigSchema = object({
10503
10513
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10504
10514
  * thing: the logging settings document on the `system` cap. Two authorities
10505
10515
  * over the values is the exact defect
10506
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10516
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10507
10517
  * remove; re-introducing it from the cure side would be grotesque.
10508
10518
  *
10509
10519
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -12240,7 +12250,7 @@ var BrokerStatusEnum = _enum([
12240
12250
  "unreachable",
12241
12251
  "error"
12242
12252
  ]);
12243
- var BrokerInfoSchema$1 = object({
12253
+ var BrokerInfoSchema = object({
12244
12254
  /** Stable broker id. Persisted; survives addon restarts. */
12245
12255
  id: string(),
12246
12256
  /** Addon id of the provider that OWNS this broker.
@@ -12373,7 +12383,7 @@ var GetStateInputSchema = object({
12373
12383
  * HA: entity_id (returns the cached entity state). */
12374
12384
  key: string()
12375
12385
  });
12376
- method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12386
+ method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12377
12387
  kind: "mutation",
12378
12388
  auth: "admin"
12379
12389
  }), method(RemoveInputSchema, _void(), {
@@ -13982,7 +13992,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13982
13992
  *
13983
13993
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13984
13994
  * was wrong — corrected 2026-08-26 after the hop map
13985
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13995
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13986
13996
  * There is **no Zod parse at all** between the door and the engine: the
13987
13997
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13988
13998
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14763,7 +14773,17 @@ var SiteZoneSchema = object({
14763
14773
  var ProviderStatusSchema = object({
14764
14774
  connected: boolean(),
14765
14775
  deviceCount: number(),
14766
- error: string().optional()
14776
+ error: string().optional(),
14777
+ /**
14778
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14779
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14780
+ * which every provider bundles, so only a provider BUILT from D696-amended
14781
+ * code says it. ABSENT means an older provider that may still write it (raw
14782
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14783
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14784
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14785
+ */
14786
+ motionSliceWriter: literal("orchestrator").optional()
14767
14787
  });
14768
14788
  object({
14769
14789
  externalId: string(),
@@ -15561,28 +15581,10 @@ method(object({
15561
15581
  deviceIds: array(number()).readonly(),
15562
15582
  caps: array(string()).readonly().optional()
15563
15583
  }), record(string(), record(string(), unknown().nullable())));
15564
- object({
15565
- /** Ms epoch of the last press. null = never observed since this provider started. */
15566
- lastPressedAt: number().nullable(),
15567
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15568
- pressCountSinceStart: number()
15569
- });
15570
- object({
15571
- deviceId: number(),
15572
- timestamp: number()
15573
- });
15574
- /**
15575
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15576
- * virtual-doorbell trigger devices, exactly what the wrapper's
15577
- * `doorbellSources` persists (as `{ deviceId }` records).
15578
- */
15579
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15584
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15580
15585
  DeviceType.Camera;
15581
- DeviceType.Button, DeviceType.Camera;
15582
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15583
- DeviceType.Camera, DeviceType.Camera;
15584
15586
  /** A slot's editable value, discriminated on `slot`. */
15585
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15587
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15586
15588
  DeviceType.Camera;
15587
15589
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15588
15590
  state: literal("unreachable"),
@@ -15619,10 +15621,11 @@ var ExtensionSlotSourceSchema = object({
15619
15621
  });
15620
15622
  /**
15621
15623
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15622
- * reported read-only, named, never written through this contract (D62). The
15623
- * virtual doorbell's sources ring only while the wrapper is bound to the
15624
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15625
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15624
+ * reported read-only, named, never written through this contract (D62) — e.g.
15625
+ * a slot whose sources act only while a wrapper is bound to the host, a
15626
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15627
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15628
+ * not be read; it is not "inactive" (D315).
15626
15629
  */
15627
15630
  var ExtensionSlotActivationSchema = object({
15628
15631
  state: _enum([
@@ -15638,7 +15641,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15638
15641
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15639
15642
  slotRef: string(),
15640
15643
  slot: ExtensionSlotIdSchema,
15641
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15644
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15642
15645
  authority: string(),
15643
15646
  activation: ExtensionSlotActivationSchema.nullable(),
15644
15647
  value: ExtensionSlotValueSchema,
@@ -15916,7 +15919,7 @@ method(object({
15916
15919
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15917
15920
  * ends in a log line, and a log line is the thing the operator asked to stop
15918
15921
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15919
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15922
+ * 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
15920
15923
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15921
15924
  * media blackout were both diagnosed. The counters stay; this is where they can
15922
15925
  * be READ.
@@ -17292,7 +17295,7 @@ var BrokerStatusSchema$1 = _enum([
17292
17295
  "unreachable",
17293
17296
  "tls-error"
17294
17297
  ]);
17295
- var BrokerInfoSchema = object({
17298
+ var BrokerInfoSchema$1 = object({
17296
17299
  id: string(),
17297
17300
  name: string(),
17298
17301
  url: string(),
@@ -17355,7 +17358,7 @@ var StatusSchema = object({
17355
17358
  brokerCount: number(),
17356
17359
  embeddedRunning: boolean()
17357
17360
  });
17358
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17361
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17359
17362
  kind: "mutation",
17360
17363
  auth: "admin"
17361
17364
  }), method(IdInputSchema, _void(), {
@@ -19531,78 +19534,16 @@ var NcRuleInputSchema = object({
19531
19534
  */
19532
19535
  confirm: NcConfirmSchema.optional(),
19533
19536
  /**
19534
- * WAIT for face/plate recognition before saying anything.
19535
- *
19536
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19537
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19538
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19539
- * measured on this hub — and an `immediate` rule enqueues on the first object
19540
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19541
- * immediate path, and no amount of media re-resolution fixes a sentence.
19542
- *
19543
- * Only two honest answers exist, and this flag picks between them. It has
19544
- * effect ONLY on a rule that declares a recognition scope
19545
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19546
- * other rule there is nothing to wait for and the flag is inert.
19547
- *
19548
- * | value | what happens |
19549
- * | --- | --- |
19550
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19551
- * | 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) |
19552
- *
19553
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19554
- * on the addon cap path, and absent has to keep meaning exactly what every
19555
- * rule authored before this field meant.
19556
- *
19557
- * The cost of `true` is stated here because the editor states it too: a rule
19558
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19559
- * tests every zone the track visited and a `crossing` condition can no longer
19560
- * be satisfied, because a closed track carries no crossing.
19561
- */
19562
- waitForEnhancement: boolean().optional(),
19563
- /**
19564
- * GROUP a burst of subjects into ONE notification that grows.
19565
- *
19566
- * Seconds of quiet after the last matching subject before the burst is
19567
- * considered over. While it is open, the first subject enqueues immediately —
19568
- * **exactly as today, with no added latency** — and every real growth (a new
19569
- * subject, or a name confirmed on one already in it) REPLACES that
19570
- * notification with an updated one naming everybody. The push carries the
19571
- * group's own coalescing tag, so the phone replaces rather than stacks.
19572
- *
19573
- * `0` / absent = off, and off is today's behaviour byte for byte.
19574
- *
19575
- * ### Why an idle cutoff and not a window
19576
- *
19577
- * The measured seven-person arrival on device 590 spans 110 s with every
19578
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19579
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19580
- * 30 s is a widely shipped value for the same decision.
19581
- *
19582
- * ### What it replaces
19583
- *
19584
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19585
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19586
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19587
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19588
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19589
- * budget over GROUPS — which is what it always meant — and a growth is never
19590
- * throttled by the window its own first member spent.
19591
- *
19592
- * ### Interaction with {@link waitForEnhancement}
19593
- *
19594
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19595
- * TRACK CLOSE, so with both set the group is opened by the first member to
19596
- * CLOSE — already carrying its name — and grows as later members close. That
19597
- * is later, and complete. With grouping alone the group opens on the first
19598
- * object event and picks up names as they are confirmed, through the growth
19599
- * path. Neither combination fires twice for one subject.
19600
- *
19601
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19602
- * on the addon cap path, so absent must keep meaning what it meant before this
19603
- * field existed.
19537
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19538
+ * seconds; the rule sends ONE message when the window closes — early, once every
19539
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19540
+ * the rule matched, with one mosaic tile per subject.
19541
+ * Absent = plain immediate (one message per track, at the first match).
19542
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19543
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19544
+ * path does not run Zod defaults.
19604
19545
  */
19605
- groupIdleSec: number().int().min(0).max(600).optional()
19546
+ groupingSec: number().int().min(5).max(60).optional()
19606
19547
  });
19607
19548
  /**
19608
19549
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19650,9 +19591,10 @@ var NcRuleClearableKeySchema = _enum([
19650
19591
  "targetUsers",
19651
19592
  "snoozeOptions",
19652
19593
  "snoozeAllowGlobal",
19653
- "waitForEnhancement",
19654
19594
  "actions",
19655
19595
  "confirm",
19596
+ "groupingSec",
19597
+ "waitForEnhancement",
19656
19598
  "groupIdleSec"
19657
19599
  ]);
19658
19600
  var NcRuleUpdateInputSchema = object({
@@ -19674,9 +19616,25 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19674
19616
  */
19675
19617
  disabledTargetIds: array(string()).default([])
19676
19618
  });
19619
+ /**
19620
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19621
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19622
+ * job is not an open state, and the alarm stays instant.
19623
+ */
19624
+ var NcStateSummarySectionIdSchema = _enum([
19625
+ "device-state",
19626
+ "streams",
19627
+ "battery",
19628
+ "consumables",
19629
+ "tanks",
19630
+ "detection-blind",
19631
+ "nodes",
19632
+ "addons",
19633
+ "updates"
19634
+ ]);
19677
19635
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19678
19636
  var NcStateSummaryPreviewSectionSchema = object({
19679
- id: string(),
19637
+ id: NcStateSummarySectionIdSchema,
19680
19638
  status: _enum(["read", "unavailable"]),
19681
19639
  items: number().int().nonnegative(),
19682
19640
  reason: string().optional()
@@ -19686,7 +19644,7 @@ var NcStateSummaryPreviewSectionSchema = object({
19686
19644
  * a `rule` split (no `section`), one section's on a `section` split.
19687
19645
  */
19688
19646
  var NcStateSummaryPreviewRowSchema = object({
19689
- section: string().optional(),
19647
+ section: NcStateSummarySectionIdSchema.optional(),
19690
19648
  /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19691
19649
  title: string(),
19692
19650
  text: string(),
@@ -20123,6 +20081,29 @@ object({
20123
20081
  firstAt: number(),
20124
20082
  lastAt: number()
20125
20083
  });
20084
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
20085
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
20086
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
20087
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
20088
+ var NcAlarmModeDelaysListSchema = array(object({
20089
+ mode: AlarmArmModeSchema,
20090
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
20091
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
20092
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
20093
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20094
+ var NcAlarmModeDelaysEditListSchema = array(object({
20095
+ mode: AlarmArmModeSchema,
20096
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20097
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20098
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
20099
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20100
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20101
+ var NcAlarmNonBlockingEditSchema = object({
20102
+ mode: AlarmArmModeSchema,
20103
+ deviceId: number().int().positive(),
20104
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20105
+ nonBlocking: boolean()
20106
+ });
20126
20107
  /**
20127
20108
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20128
20109
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -20131,10 +20112,10 @@ object({
20131
20112
  var NcAlarmNonBlockingListSchema = array(object({
20132
20113
  mode: AlarmArmModeSchema,
20133
20114
  deviceIds: array(number().int()).max(200)
20134
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
20115
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20135
20116
  /**
20136
- * The three durations the panel's state machine runs on, who hears about its
20137
- * transitions, and which openings each mode tolerates.
20117
+ * The three durations — global, and per mode since D697 — the panel's state
20118
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20138
20119
  *
20139
20120
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20140
20121
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20144,15 +20125,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20144
20125
  */
20145
20126
  var NcAlarmSettingsSchema = object({
20146
20127
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20147
- exitDelaySec: number().int().min(0).max(600),
20128
+ exitDelaySec: NcAlarmDelaySecSchema,
20148
20129
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20149
- entryDelaySec: number().int().min(0).max(600),
20130
+ entryDelaySec: NcAlarmDelaySecSchema,
20150
20131
  /**
20151
20132
  * How long `triggered` lasts before the panel re-arms itself.
20152
20133
  * **0 = until an operator disarms it** — the behaviour before this field
20153
20134
  * existed, and therefore what an untouched install keeps doing.
20154
20135
  */
20155
- triggeredDurationSec: number().int().min(0).max(3600),
20136
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20156
20137
  /**
20157
20138
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20158
20139
  * remove after one release. Optional so an older admin's patch (which still
@@ -20175,17 +20156,30 @@ var NcAlarmSettingsSchema = object({
20175
20156
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20176
20157
  * parses as "every opening blocks" — the behaviour it was written under.
20177
20158
  */
20178
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20159
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20160
+ /**
20161
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20162
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20163
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20164
+ */
20165
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20179
20166
  });
20180
20167
  /**
20181
20168
  * Every field optional — a tab edits one control at a time.
20182
20169
  *
20183
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20184
- * remove an inner `.default()`, so a patch that never named the field would
20185
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20186
- * (the same trap {@link NcRulePatchSchema} documents).
20170
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20171
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20172
+ * named the field would parse to `[]` and wipe every mode's list on an
20173
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20174
+ * `modeDelays` means EDITS, not the stored overrides.
20187
20175
  */
20188
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20176
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20177
+ nonBlocking: NcAlarmNonBlockingListSchema,
20178
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20179
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20180
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20181
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20182
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20189
20183
  /**
20190
20184
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20191
20185
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20285,6 +20279,28 @@ var NcAlarmConfigSchema = object({
20285
20279
  */
20286
20280
  live: NcAlarmLiveSchema.optional()
20287
20281
  });
20282
+ var NcAlarmSensorReadingSchema = _enum([
20283
+ "open",
20284
+ "closed",
20285
+ "unknown",
20286
+ "not-opening"
20287
+ ]);
20288
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20289
+ var NcAlarmSensorStateSchema = object({
20290
+ deviceId: number().int().positive(),
20291
+ reading: NcAlarmSensorReadingSchema,
20292
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20293
+ state: string().nullable(),
20294
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20295
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20296
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20297
+ online: boolean().nullable()
20298
+ });
20299
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20300
+ var NcAlarmSensorsSchema = object({
20301
+ sensors: array(NcAlarmSensorStateSchema),
20302
+ readAt: number().int().nonnegative()
20303
+ });
20288
20304
  /**
20289
20305
  * ONE rule's demand on ONE camera's clip ring.
20290
20306
  *
@@ -20375,7 +20391,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20375
20391
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20376
20392
  kind: "mutation",
20377
20393
  auth: "admin"
20378
- });
20394
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20379
20395
  /**
20380
20396
  * Zod schemas for persisted record types.
20381
20397
  *
@@ -20738,12 +20754,26 @@ var CameraSensorRefSchema = object({
20738
20754
  function cameraSensorRefKey(ref) {
20739
20755
  return `${ref.addonId}/${ref.stableId}`;
20740
20756
  }
20741
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20742
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20743
- var CameraSensorsConfigSchema = object({
20744
- mode: CameraSensorsModeSchema,
20745
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20746
- }).superRefine((value, ctx) => {
20757
+ /**
20758
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20759
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20760
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20761
+ * camera's own motion counts only while a confirm sensor is active — with or
20762
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20763
+ */
20764
+ var CameraSensorModeSchema = _enum([
20765
+ "in-addition",
20766
+ "confirm",
20767
+ "replace"
20768
+ ]);
20769
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20770
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20771
+ /**
20772
+ * A camera's attached sensors. A CS-1 row carried an envelope
20773
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20774
+ * what the write stores (D672 rule 3), so it is never written again.
20775
+ */
20776
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20747
20777
  const seen = /* @__PURE__ */ new Set();
20748
20778
  value.sensors.forEach((ref, index) => {
20749
20779
  const key = cameraSensorRefKey(ref);
@@ -20755,39 +20785,41 @@ var CameraSensorsConfigSchema = object({
20755
20785
  seen.add(key);
20756
20786
  });
20757
20787
  });
20758
- var RefFields = {
20788
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20789
+ var AttachmentFields = {
20759
20790
  addonId: string(),
20760
- stableId: string()
20791
+ stableId: string(),
20792
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20761
20793
  };
20762
20794
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20763
20795
  object({
20764
- ...RefFields,
20796
+ ...AttachmentFields,
20765
20797
  state: literal("ready"),
20766
20798
  deviceId: number(),
20767
20799
  name: string(),
20768
20800
  caps: array(string()).readonly()
20769
20801
  }),
20770
20802
  object({
20771
- ...RefFields,
20803
+ ...AttachmentFields,
20772
20804
  state: literal("offline"),
20773
20805
  deviceId: number(),
20774
20806
  name: string(),
20775
20807
  reason: _enum(["offline", "disabled"])
20776
20808
  }),
20777
20809
  object({
20778
- ...RefFields,
20810
+ ...AttachmentFields,
20779
20811
  state: literal("missing"),
20780
20812
  reason: string()
20781
20813
  }),
20782
20814
  object({
20783
- ...RefFields,
20815
+ ...AttachmentFields,
20784
20816
  state: literal("unsupported"),
20785
20817
  deviceId: number(),
20786
20818
  name: string(),
20787
20819
  reason: string()
20788
20820
  }),
20789
20821
  object({
20790
- ...RefFields,
20822
+ ...AttachmentFields,
20791
20823
  state: literal("unreadable"),
20792
20824
  reason: string()
20793
20825
  })
@@ -20800,17 +20832,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20800
20832
  * say.
20801
20833
  */
20802
20834
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20835
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20836
+ var CameraOwnMotionSchema = _enum([
20837
+ "always",
20838
+ "when-confirmed",
20839
+ "never"
20840
+ ]);
20841
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20842
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20843
+ /**
20844
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20845
+ * never the effective value. The camera's `motion` slice is the effective one,
20846
+ * so a view of the camera's own row reads this.
20847
+ * - `atMs`: when the bridge received the camera's last own edge.
20848
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20849
+ * AND it is younger than the window the bridge counts the camera's own motion
20850
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20851
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20852
+ * within the window; a status read is a snapshot, so a live view re-asks.
20853
+ * Absent = this orchestrator has not received the camera's own motion since it
20854
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20855
+ */
20856
+ var CameraRawLevelSchema = object({
20857
+ detected: boolean(),
20858
+ atMs: number()
20859
+ });
20803
20860
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20804
- object({ state: literal("not-configured") }),
20861
+ object({
20862
+ state: literal("not-configured"),
20863
+ cameraLevel: CameraRawLevelSchema.optional()
20864
+ }),
20805
20865
  object({
20806
20866
  state: literal("unreadable"),
20807
- reason: string()
20867
+ reason: string(),
20868
+ cameraLevel: CameraRawLevelSchema.optional()
20808
20869
  }),
20809
20870
  object({
20810
20871
  state: literal("configured"),
20811
- mode: CameraSensorsModeSchema,
20872
+ cameraLevel: CameraRawLevelSchema.optional(),
20812
20873
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20813
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20874
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20875
+ cameraMotion: CameraOwnMotionSchema.optional(),
20876
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20877
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20814
20878
  })
20815
20879
  ]);
20816
20880
  Object.freeze({
@@ -21495,14 +21559,14 @@ var TrackSchema = object({
21495
21559
  * `derive-returned-null tile=standin`,
21496
21560
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21497
21561
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21498
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21562
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21499
21563
  * tile is a face/plate stand-in, a raster crop, or an icon.
21500
21564
  *
21501
21565
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21502
21566
  * the TILE ranked below native, so a track whose thumbnail landed natively
21503
21567
  * while its key frame missed carried no reason at all — three of the four
21504
21568
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21505
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21569
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21506
21570
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21507
21571
  * lastFrame copy source`), so its absence is now on this line whatever the
21508
21572
  * tile ended up being.
@@ -21589,7 +21653,7 @@ var MotionEventSchema = object({
21589
21653
  /**
21590
21654
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21591
21655
  * first entry is always `0`) of every genuine off→on transition the
21592
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21656
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21593
21657
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21594
21658
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21595
21659
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -22113,6 +22177,7 @@ var KeyEventSchema = object({
22113
22177
  bestEventId: string(),
22114
22178
  /** Track lifetime in ms (lastSeen - firstSeen). */
22115
22179
  windowMs: number().optional(),
22180
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
22116
22181
  ...TrackFlagFields,
22117
22182
  ...TrackRetrainFields
22118
22183
  });
@@ -24001,8 +24066,10 @@ var RunnerCameraConfigSchema = object({
24001
24066
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
24002
24067
  * analyzer providers) running on decoded frames in this runner.
24003
24068
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
24004
- * analytics). The provider emits ProviderMotion
24005
- * events; the orchestrator forwards to `reportMotion`.
24069
+ * analytics). The provider emits its raw push as `ProviderMotion`
24070
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
24071
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
24072
+ * `reportMotion`.
24006
24073
  */
24007
24074
  motionSources: MotionSourcesSchema.default(["analyzer"]),
24008
24075
  /**
@@ -24179,7 +24246,15 @@ var RunnerLocalLoadSchema = object({
24179
24246
  hasGpu: boolean(),
24180
24247
  inferenceBackend: string().optional(),
24181
24248
  cpuCores: number().optional()
24182
- })
24249
+ }),
24250
+ /**
24251
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24252
+ * runner never writes it. ABSENT = a runner older than D696, which still
24253
+ * writes the slice from its phase machine; the orchestrator does not write
24254
+ * while any runner node is absent-or-unanswered, so one slice never has two
24255
+ * writers.
24256
+ */
24257
+ motionSliceWriter: literal("orchestrator").optional()
24183
24258
  });
24184
24259
  /**
24185
24260
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -26815,7 +26890,7 @@ var ClipPlaybackSchema = object({
26815
26890
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26816
26891
  * time the camera is awake for its own reasons — is deliberately not a member:
26817
26892
  * it was proposed, it is free on the battery, and the operator declined it on
26818
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
26893
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26819
26894
  * D558 "Considered and not taken").
26820
26895
  */
26821
26896
  var ClipWakeSchema = _enum(["authorised"]);
@@ -27076,7 +27151,7 @@ DeviceType.Camera, method(object({
27076
27151
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
27077
27152
  * assumed the answer was a fan-out over everything a camera has. The
27078
27153
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
27079
- * solo"* — so the list is asked of one provider at a time and there is
27154
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
27080
27155
  * nothing to filter out of it.
27081
27156
  */
27082
27157
  provider: string().min(1)
@@ -29032,6 +29107,17 @@ object({
29032
29107
  deviceId: number(),
29033
29108
  status: DeviceStatusSchema
29034
29109
  });
29110
+ object({
29111
+ /** Ms epoch of the last press. null = never observed since this provider started. */
29112
+ lastPressedAt: number().nullable(),
29113
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
29114
+ pressCountSinceStart: number()
29115
+ });
29116
+ object({
29117
+ deviceId: number(),
29118
+ timestamp: number()
29119
+ });
29120
+ DeviceType.Button, DeviceType.Camera;
29035
29121
  /**
29036
29122
  * Enum-state sensor — a string value picked from a finite option set.
29037
29123
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30676,9 +30762,9 @@ object({
30676
30762
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30677
30763
  lastDetectedAt: number().nullable(),
30678
30764
  /**
30679
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30680
- * `null` while false and on every `Sensor` device (D475) — see that
30681
- * constant's doc for the one-authority rule.
30765
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30766
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30767
+ * device (D475) — see that constant's doc for the one-authority rule.
30682
30768
  *
30683
30769
  * ## Reading this field still arms nothing
30684
30770
  *
@@ -30696,38 +30782,41 @@ object({
30696
30782
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30697
30783
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30698
30784
  *
30699
- * ## Who writes it
30700
- *
30701
- * - **Cameras** — the runner's phase machine, `active → watching` on
30702
- * `cooldown_expired`, which then writes this slice with
30703
- * `detected: false` (`handlePhaseChanged` in
30704
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30705
- * matters most for the sources that only ever push a rising one:
30706
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30707
- * a false.
30708
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30709
- * provider pushes the false itself, from the upstream system's own
30710
- * state change. No phase machine is involved.
30711
- *
30712
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30713
- *
30714
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30715
- * phase machine is the sole writer for a camera. It is not.
30716
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30717
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30718
- * Hikvision's comment says why: it read THIS docblock, agreed the
30719
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30720
- * two authorities can disagree about one slice, which this repo
30721
- * forbids, and the doc said otherwise — which is worse than saying
30722
- * nothing, because it reads as verification.
30723
- *
30724
- * This predates D475 and is not fixed there: the fix touches every
30725
- * camera provider. Recorded in D475's Consequences. Do not restore the
30726
- * "sole writer" wording without also removing the other writers.
30785
+ * ## Who writes it (D696)
30786
+ *
30787
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30788
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30789
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30790
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30791
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30792
+ * device activity, as the camera's `motionSources` counts them. A source
30793
+ * that never sends a clear falls one window after its last `true`.
30794
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30795
+ * writes this slice (not even a boot seed), and neither does the runner's
30796
+ * phase machine: its phase is "a session is attached" for every on-motion
30797
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30798
+ * A provider's `isDetected` answers from the hub's slice.
30799
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30800
+ * imports) — the provider pushes both edges itself, from the upstream
30801
+ * system's own state change. That is the sensor's own fact.
30802
+ *
30803
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30804
+ * `motion` slice; each sensor writer is a named exemption.
30805
+ *
30806
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30807
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30808
+ * it, every provider seeded it at boot, and every session left it
30809
+ * `detected: true` after its detach.
30727
30810
  */
30728
30811
  autoClearAfterMs: number().nullable()
30729
30812
  });
30730
- object({
30813
+ /**
30814
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30815
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30816
+ * — both the cap event surface and the bus payload type alias to this
30817
+ * schema.
30818
+ */
30819
+ var MotionOnMotionChangedDataSchema = object({
30731
30820
  deviceId: number(),
30732
30821
  detected: boolean(),
30733
30822
  timestamp: number(),
@@ -33192,7 +33281,7 @@ var ReadGopBytesResultSchema = object({
33192
33281
  /**
33193
33282
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
33194
33283
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
33195
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
33284
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
33196
33285
  * a replay needs several seconds of native pixels, not one frame.
33197
33286
  *
33198
33287
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -35815,6 +35904,7 @@ DeviceType.Camera, method(object({
35815
35904
  detection: array(ZoneRuleSchema).readonly(),
35816
35905
  package: array(ZoneRuleSchema).readonly()
35817
35906
  });
35907
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
35818
35908
  /**
35819
35909
  * Accessory device helpers — shared across drivers.
35820
35910
  *
@@ -40793,6 +40883,12 @@ Object.freeze({
40793
40883
  addonId: null,
40794
40884
  access: "view"
40795
40885
  },
40886
+ "notificationRules.getAlarmSensors": {
40887
+ capName: "notification-rules",
40888
+ capScope: "system",
40889
+ addonId: null,
40890
+ access: "view"
40891
+ },
40796
40892
  "notificationRules.getClipRetentionAsks": {
40797
40893
  capName: "notification-rules",
40798
40894
  capScope: "system",
@@ -44371,6 +44467,11 @@ Object.freeze({
44371
44467
  }
44372
44468
  });
44373
44469
  Object.freeze({
44470
+ "accessories.getStatus": [{
44471
+ name: "deviceId",
44472
+ form: "single",
44473
+ optional: false
44474
+ }],
44374
44475
  "accessories.setChildHidden": [{
44375
44476
  name: "childDeviceId",
44376
44477
  form: "single",
@@ -44390,6 +44491,11 @@ Object.freeze({
44390
44491
  form: "single",
44391
44492
  optional: false
44392
44493
  }],
44494
+ "airQualitySensor.getStatus": [{
44495
+ name: "deviceId",
44496
+ form: "single",
44497
+ optional: false
44498
+ }],
44393
44499
  "alarmPanel.arm": [{
44394
44500
  name: "deviceId",
44395
44501
  form: "single",
@@ -44400,11 +44506,36 @@ Object.freeze({
44400
44506
  form: "single",
44401
44507
  optional: false
44402
44508
  }],
44509
+ "alarmPanel.getStatus": [{
44510
+ name: "deviceId",
44511
+ form: "single",
44512
+ optional: false
44513
+ }],
44403
44514
  "alarmPanel.trigger": [{
44404
44515
  name: "deviceId",
44405
44516
  form: "single",
44406
44517
  optional: false
44407
44518
  }],
44519
+ "ambientLightSensor.getStatus": [{
44520
+ name: "deviceId",
44521
+ form: "single",
44522
+ optional: false
44523
+ }],
44524
+ "audioAnalysis.applyDeviceSettingsPatch": [{
44525
+ name: "deviceId",
44526
+ form: "single",
44527
+ optional: false
44528
+ }],
44529
+ "audioAnalysis.getDeviceLiveContribution": [{
44530
+ name: "deviceId",
44531
+ form: "single",
44532
+ optional: false
44533
+ }],
44534
+ "audioAnalysis.getDeviceSettingsContribution": [{
44535
+ name: "deviceId",
44536
+ form: "single",
44537
+ optional: false
44538
+ }],
44408
44539
  "audioAnalysis.resolveDeviceSettings": [{
44409
44540
  name: "deviceId",
44410
44541
  form: "single",
@@ -44445,16 +44576,36 @@ Object.freeze({
44445
44576
  form: "single",
44446
44577
  optional: false
44447
44578
  }],
44579
+ "automationControl.getStatus": [{
44580
+ name: "deviceId",
44581
+ form: "single",
44582
+ optional: false
44583
+ }],
44448
44584
  "automationControl.trigger": [{
44449
44585
  name: "deviceId",
44450
44586
  form: "single",
44451
44587
  optional: false
44452
44588
  }],
44589
+ "battery.getStatus": [{
44590
+ name: "deviceId",
44591
+ form: "single",
44592
+ optional: false
44593
+ }],
44453
44594
  "battery.wakeForStream": [{
44454
44595
  name: "deviceId",
44455
44596
  form: "single",
44456
44597
  optional: false
44457
44598
  }],
44599
+ "binary.getStatus": [{
44600
+ name: "deviceId",
44601
+ form: "single",
44602
+ optional: false
44603
+ }],
44604
+ "brightness.getStatus": [{
44605
+ name: "deviceId",
44606
+ form: "single",
44607
+ optional: false
44608
+ }],
44458
44609
  "brightness.setBrightness": [{
44459
44610
  name: "deviceId",
44460
44611
  form: "single",
@@ -44470,6 +44621,26 @@ Object.freeze({
44470
44621
  form: "single",
44471
44622
  optional: false
44472
44623
  }],
44624
+ "cameraCredentials.getStatus": [{
44625
+ name: "deviceId",
44626
+ form: "single",
44627
+ optional: false
44628
+ }],
44629
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
44630
+ name: "deviceId",
44631
+ form: "single",
44632
+ optional: false
44633
+ }],
44634
+ "cameraGridLayout.getDeviceLiveContribution": [{
44635
+ name: "deviceId",
44636
+ form: "single",
44637
+ optional: false
44638
+ }],
44639
+ "cameraGridLayout.getDeviceSettingsContribution": [{
44640
+ name: "deviceId",
44641
+ form: "single",
44642
+ optional: false
44643
+ }],
44473
44644
  "cameraGridLayout.getLayout": [{
44474
44645
  name: "deviceId",
44475
44646
  form: "single",
@@ -44485,6 +44656,21 @@ Object.freeze({
44485
44656
  form: "single",
44486
44657
  optional: false
44487
44658
  }],
44659
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
44660
+ name: "deviceId",
44661
+ form: "single",
44662
+ optional: false
44663
+ }],
44664
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
44665
+ name: "deviceId",
44666
+ form: "single",
44667
+ optional: false
44668
+ }],
44669
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
44670
+ name: "deviceId",
44671
+ form: "single",
44672
+ optional: false
44673
+ }],
44488
44674
  "cameraStreams.getBrokerStreams": [{
44489
44675
  name: "deviceId",
44490
44676
  form: "single",
@@ -44510,6 +44696,16 @@ Object.freeze({
44510
44696
  form: "single",
44511
44697
  optional: false
44512
44698
  }],
44699
+ "carbonMonoxide.getStatus": [{
44700
+ name: "deviceId",
44701
+ form: "single",
44702
+ optional: false
44703
+ }],
44704
+ "climateControl.getStatus": [{
44705
+ name: "deviceId",
44706
+ form: "single",
44707
+ optional: false
44708
+ }],
44513
44709
  "climateControl.setFanMode": [{
44514
44710
  name: "deviceId",
44515
44711
  form: "single",
@@ -44550,16 +44746,41 @@ Object.freeze({
44550
44746
  form: "single",
44551
44747
  optional: false
44552
44748
  }],
44749
+ "color.getStatus": [{
44750
+ name: "deviceId",
44751
+ form: "single",
44752
+ optional: false
44753
+ }],
44553
44754
  "color.setColor": [{
44554
44755
  name: "deviceId",
44555
44756
  form: "single",
44556
44757
  optional: false
44557
44758
  }],
44759
+ "connectivity.getStatus": [{
44760
+ name: "deviceId",
44761
+ form: "single",
44762
+ optional: false
44763
+ }],
44764
+ "consumables.getStatus": [{
44765
+ name: "deviceId",
44766
+ form: "single",
44767
+ optional: false
44768
+ }],
44558
44769
  "consumables.reset": [{
44559
44770
  name: "deviceId",
44560
44771
  form: "single",
44561
44772
  optional: false
44562
44773
  }],
44774
+ "contact.getStatus": [{
44775
+ name: "deviceId",
44776
+ form: "single",
44777
+ optional: false
44778
+ }],
44779
+ "control.getStatus": [{
44780
+ name: "deviceId",
44781
+ form: "single",
44782
+ optional: false
44783
+ }],
44563
44784
  "control.setValue": [{
44564
44785
  name: "deviceId",
44565
44786
  form: "single",
@@ -44570,6 +44791,11 @@ Object.freeze({
44570
44791
  form: "single",
44571
44792
  optional: false
44572
44793
  }],
44794
+ "cover.getStatus": [{
44795
+ name: "deviceId",
44796
+ form: "single",
44797
+ optional: false
44798
+ }],
44573
44799
  "cover.open": [{
44574
44800
  name: "deviceId",
44575
44801
  form: "single",
@@ -44595,6 +44821,11 @@ Object.freeze({
44595
44821
  form: "single",
44596
44822
  optional: false
44597
44823
  }],
44824
+ "dayNight.getStatus": [{
44825
+ name: "deviceId",
44826
+ form: "single",
44827
+ optional: false
44828
+ }],
44598
44829
  "dayNight.setSettings": [{
44599
44830
  name: "deviceId",
44600
44831
  form: "single",
@@ -44605,11 +44836,31 @@ Object.freeze({
44605
44836
  form: "single",
44606
44837
  optional: true
44607
44838
  }],
44839
+ "detectionPipeline.applyDeviceSettingsPatch": [{
44840
+ name: "deviceId",
44841
+ form: "single",
44842
+ optional: false
44843
+ }],
44844
+ "detectionPipeline.getDeviceLiveContribution": [{
44845
+ name: "deviceId",
44846
+ form: "single",
44847
+ optional: false
44848
+ }],
44849
+ "detectionPipeline.getDeviceSettingsContribution": [{
44850
+ name: "deviceId",
44851
+ form: "single",
44852
+ optional: false
44853
+ }],
44608
44854
  "deviceAdminLink.getAdminLink": [{
44609
44855
  name: "deviceId",
44610
44856
  form: "single",
44611
44857
  optional: false
44612
44858
  }],
44859
+ "deviceAdoption.getStatus": [{
44860
+ name: "deviceId",
44861
+ form: "single",
44862
+ optional: false
44863
+ }],
44613
44864
  "deviceAdoption.release": [{
44614
44865
  name: "camDeviceId",
44615
44866
  form: "single",
@@ -44625,6 +44876,11 @@ Object.freeze({
44625
44876
  form: "single",
44626
44877
  optional: false
44627
44878
  }],
44879
+ "deviceDiscovery.getStatus": [{
44880
+ name: "deviceId",
44881
+ form: "single",
44882
+ optional: false
44883
+ }],
44628
44884
  "deviceDiscovery.listDiscovered": [{
44629
44885
  name: "deviceId",
44630
44886
  form: "single",
@@ -44644,6 +44900,21 @@ Object.freeze({
44644
44900
  form: "single",
44645
44901
  optional: false
44646
44902
  }],
44903
+ "deviceExport.applyDeviceSettingsPatch": [{
44904
+ name: "deviceId",
44905
+ form: "single",
44906
+ optional: false
44907
+ }],
44908
+ "deviceExport.getDeviceLiveContribution": [{
44909
+ name: "deviceId",
44910
+ form: "single",
44911
+ optional: false
44912
+ }],
44913
+ "deviceExport.getDeviceSettingsContribution": [{
44914
+ name: "deviceId",
44915
+ form: "single",
44916
+ optional: false
44917
+ }],
44647
44918
  "deviceExtension.listCandidates": [{
44648
44919
  name: "deviceId",
44649
44920
  form: "single",
@@ -44992,6 +45263,26 @@ Object.freeze({
44992
45263
  form: "single",
44993
45264
  optional: false
44994
45265
  }],
45266
+ "deviceStatus.getStatus": [{
45267
+ name: "deviceId",
45268
+ form: "single",
45269
+ optional: false
45270
+ }],
45271
+ "doorbell.getStatus": [{
45272
+ name: "deviceId",
45273
+ form: "single",
45274
+ optional: false
45275
+ }],
45276
+ "enumSensor.getStatus": [{
45277
+ name: "deviceId",
45278
+ form: "single",
45279
+ optional: false
45280
+ }],
45281
+ "eventEmitter.getStatus": [{
45282
+ name: "deviceId",
45283
+ form: "single",
45284
+ optional: false
45285
+ }],
44995
45286
  "faceGallery.getFaceByTrack": [{
44996
45287
  name: "deviceId",
44997
45288
  form: "single",
@@ -45006,6 +45297,11 @@ Object.freeze({
45006
45297
  form: "array",
45007
45298
  optional: true
45008
45299
  }],
45300
+ "fanControl.getStatus": [{
45301
+ name: "deviceId",
45302
+ form: "single",
45303
+ optional: false
45304
+ }],
45009
45305
  "fanControl.setDirection": [{
45010
45306
  name: "deviceId",
45011
45307
  form: "single",
@@ -45026,6 +45322,26 @@ Object.freeze({
45026
45322
  form: "single",
45027
45323
  optional: false
45028
45324
  }],
45325
+ "featureProbe.getStatus": [{
45326
+ name: "deviceId",
45327
+ form: "single",
45328
+ optional: false
45329
+ }],
45330
+ "flood.getStatus": [{
45331
+ name: "deviceId",
45332
+ form: "single",
45333
+ optional: false
45334
+ }],
45335
+ "gas.getStatus": [{
45336
+ name: "deviceId",
45337
+ form: "single",
45338
+ optional: false
45339
+ }],
45340
+ "humidifier.getStatus": [{
45341
+ name: "deviceId",
45342
+ form: "single",
45343
+ optional: false
45344
+ }],
45029
45345
  "humidifier.setMode": [{
45030
45346
  name: "deviceId",
45031
45347
  form: "single",
@@ -45041,11 +45357,26 @@ Object.freeze({
45041
45357
  form: "single",
45042
45358
  optional: false
45043
45359
  }],
45360
+ "humiditySensor.getStatus": [{
45361
+ name: "deviceId",
45362
+ form: "single",
45363
+ optional: false
45364
+ }],
45365
+ "image.getStatus": [{
45366
+ name: "deviceId",
45367
+ form: "single",
45368
+ optional: false
45369
+ }],
45044
45370
  "imageSettings.getOptions": [{
45045
45371
  name: "deviceId",
45046
45372
  form: "single",
45047
45373
  optional: false
45048
45374
  }],
45375
+ "imageSettings.getStatus": [{
45376
+ name: "deviceId",
45377
+ form: "single",
45378
+ optional: false
45379
+ }],
45049
45380
  "imageSettings.setSettings": [{
45050
45381
  name: "deviceId",
45051
45382
  form: "single",
@@ -45056,6 +45387,11 @@ Object.freeze({
45056
45387
  form: "single",
45057
45388
  optional: false
45058
45389
  }],
45390
+ "intercom.getStatus": [{
45391
+ name: "deviceId",
45392
+ form: "single",
45393
+ optional: false
45394
+ }],
45059
45395
  "intercom.handleAnswer": [{
45060
45396
  name: "deviceId",
45061
45397
  form: "single",
@@ -45086,6 +45422,11 @@ Object.freeze({
45086
45422
  form: "single",
45087
45423
  optional: false
45088
45424
  }],
45425
+ "lawnMowerControl.getStatus": [{
45426
+ name: "deviceId",
45427
+ form: "single",
45428
+ optional: false
45429
+ }],
45089
45430
  "lawnMowerControl.pause": [{
45090
45431
  name: "deviceId",
45091
45432
  form: "single",
@@ -45096,6 +45437,11 @@ Object.freeze({
45096
45437
  form: "single",
45097
45438
  optional: false
45098
45439
  }],
45440
+ "lockControl.getStatus": [{
45441
+ name: "deviceId",
45442
+ form: "single",
45443
+ optional: false
45444
+ }],
45099
45445
  "lockControl.lock": [{
45100
45446
  name: "deviceId",
45101
45447
  form: "single",
@@ -45111,6 +45457,11 @@ Object.freeze({
45111
45457
  form: "single",
45112
45458
  optional: false
45113
45459
  }],
45460
+ "mediaPlayer.getStatus": [{
45461
+ name: "deviceId",
45462
+ form: "single",
45463
+ optional: false
45464
+ }],
45114
45465
  "mediaPlayer.next": [{
45115
45466
  name: "deviceId",
45116
45467
  form: "single",
@@ -45171,6 +45522,11 @@ Object.freeze({
45171
45522
  form: "single",
45172
45523
  optional: false
45173
45524
  }],
45525
+ "motion.getStatus": [{
45526
+ name: "deviceId",
45527
+ form: "single",
45528
+ optional: false
45529
+ }],
45174
45530
  "motion.isDetected": [{
45175
45531
  name: "deviceId",
45176
45532
  form: "single",
@@ -45181,11 +45537,31 @@ Object.freeze({
45181
45537
  form: "single",
45182
45538
  optional: false
45183
45539
  }],
45540
+ "motionDetection.applyDeviceSettingsPatch": [{
45541
+ name: "deviceId",
45542
+ form: "single",
45543
+ optional: false
45544
+ }],
45545
+ "motionDetection.getDeviceLiveContribution": [{
45546
+ name: "deviceId",
45547
+ form: "single",
45548
+ optional: false
45549
+ }],
45550
+ "motionDetection.getDeviceSettingsContribution": [{
45551
+ name: "deviceId",
45552
+ form: "single",
45553
+ optional: false
45554
+ }],
45184
45555
  "motionDetection.removeCamera": [{
45185
45556
  name: "deviceId",
45186
45557
  form: "single",
45187
45558
  optional: false
45188
45559
  }],
45560
+ "motionTrigger.getStatus": [{
45561
+ name: "deviceId",
45562
+ form: "single",
45563
+ optional: false
45564
+ }],
45189
45565
  "motionTrigger.setMotionTrigger": [{
45190
45566
  name: "deviceId",
45191
45567
  form: "single",
@@ -45196,6 +45572,11 @@ Object.freeze({
45196
45572
  form: "single",
45197
45573
  optional: false
45198
45574
  }],
45575
+ "motionZones.getStatus": [{
45576
+ name: "deviceId",
45577
+ form: "single",
45578
+ optional: false
45579
+ }],
45199
45580
  "motionZones.setZone": [{
45200
45581
  name: "deviceId",
45201
45582
  form: "single",
@@ -45206,6 +45587,11 @@ Object.freeze({
45206
45587
  form: "single",
45207
45588
  optional: false
45208
45589
  }],
45590
+ "nativeObjectDetection.getStatus": [{
45591
+ name: "deviceId",
45592
+ form: "single",
45593
+ optional: false
45594
+ }],
45209
45595
  "nativeObjectDetection.setEnabled": [{
45210
45596
  name: "deviceId",
45211
45597
  form: "single",
@@ -45221,6 +45607,11 @@ Object.freeze({
45221
45607
  form: "single",
45222
45608
  optional: false
45223
45609
  }],
45610
+ "navigation.getStatus": [{
45611
+ name: "deviceId",
45612
+ form: "single",
45613
+ optional: false
45614
+ }],
45224
45615
  "navigation.goToPoint": [{
45225
45616
  name: "deviceId",
45226
45617
  form: "single",
@@ -45266,6 +45657,11 @@ Object.freeze({
45266
45657
  form: "single",
45267
45658
  optional: false
45268
45659
  }],
45660
+ "networkLink.getStatus": [{
45661
+ name: "deviceId",
45662
+ form: "single",
45663
+ optional: false
45664
+ }],
45269
45665
  "networkQuality.getDeviceStats": [{
45270
45666
  name: "deviceId",
45271
45667
  form: "single",
@@ -45276,6 +45672,11 @@ Object.freeze({
45276
45672
  form: "single",
45277
45673
  optional: false
45278
45674
  }],
45675
+ "notificationRules.getAlarmSensors": [{
45676
+ name: "deviceIds",
45677
+ form: "array",
45678
+ optional: false
45679
+ }],
45279
45680
  "notificationRules.setDeviceMuted": [{
45280
45681
  name: "deviceId",
45281
45682
  form: "single",
@@ -45286,11 +45687,26 @@ Object.freeze({
45286
45687
  form: "single",
45287
45688
  optional: false
45288
45689
  }],
45690
+ "notifier.getStatus": [{
45691
+ name: "deviceId",
45692
+ form: "single",
45693
+ optional: false
45694
+ }],
45289
45695
  "notifier.send": [{
45290
45696
  name: "deviceId",
45291
45697
  form: "single",
45292
45698
  optional: false
45293
45699
  }],
45700
+ "numericSensor.getStatus": [{
45701
+ name: "deviceId",
45702
+ form: "single",
45703
+ optional: false
45704
+ }],
45705
+ "osd.getStatus": [{
45706
+ name: "deviceId",
45707
+ form: "single",
45708
+ optional: false
45709
+ }],
45294
45710
  "osd.setOverlay": [{
45295
45711
  name: "deviceId",
45296
45712
  form: "single",
@@ -45350,6 +45766,11 @@ Object.freeze({
45350
45766
  form: "single",
45351
45767
  optional: false
45352
45768
  }],
45769
+ "petFeeder.getStatus": [{
45770
+ name: "deviceId",
45771
+ form: "single",
45772
+ optional: false
45773
+ }],
45353
45774
  "petFeeder.markFoodReplenished": [{
45354
45775
  name: "deviceId",
45355
45776
  form: "single",
@@ -45385,6 +45806,11 @@ Object.freeze({
45385
45806
  form: "single",
45386
45807
  optional: false
45387
45808
  }],
45809
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
45810
+ name: "deviceId",
45811
+ form: "single",
45812
+ optional: false
45813
+ }],
45388
45814
  "pipelineAnalytics.clearTrackRelation": [{
45389
45815
  name: "deviceId",
45390
45816
  form: "single",
@@ -45425,6 +45851,16 @@ Object.freeze({
45425
45851
  form: "single",
45426
45852
  optional: false
45427
45853
  }],
45854
+ "pipelineAnalytics.getDeviceLiveContribution": [{
45855
+ name: "deviceId",
45856
+ form: "single",
45857
+ optional: false
45858
+ }],
45859
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
45860
+ name: "deviceId",
45861
+ form: "single",
45862
+ optional: false
45863
+ }],
45428
45864
  "pipelineAnalytics.getEventDensity": [{
45429
45865
  name: "deviceId",
45430
45866
  form: "single",
@@ -45640,6 +46076,11 @@ Object.freeze({
45640
46076
  form: "single",
45641
46077
  optional: true
45642
46078
  }],
46079
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
46080
+ name: "deviceId",
46081
+ form: "single",
46082
+ optional: false
46083
+ }],
45643
46084
  "pipelineOrchestrator.assignAudio": [{
45644
46085
  name: "deviceId",
45645
46086
  form: "single",
@@ -45685,6 +46126,16 @@ Object.freeze({
45685
46126
  form: "single",
45686
46127
  optional: false
45687
46128
  }],
46129
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
46130
+ name: "deviceId",
46131
+ form: "single",
46132
+ optional: false
46133
+ }],
46134
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
46135
+ name: "deviceId",
46136
+ form: "single",
46137
+ optional: false
46138
+ }],
45688
46139
  "pipelineOrchestrator.getPipelineAssignment": [{
45689
46140
  name: "deviceId",
45690
46141
  form: "single",
@@ -45805,11 +46256,31 @@ Object.freeze({
45805
46256
  form: "single",
45806
46257
  optional: true
45807
46258
  }],
46259
+ "powerMeter.getStatus": [{
46260
+ name: "deviceId",
46261
+ form: "single",
46262
+ optional: false
46263
+ }],
46264
+ "presence.getStatus": [{
46265
+ name: "deviceId",
46266
+ form: "single",
46267
+ optional: false
46268
+ }],
46269
+ "pressureSensor.getStatus": [{
46270
+ name: "deviceId",
46271
+ form: "single",
46272
+ optional: false
46273
+ }],
45808
46274
  "privacyMask.getOptions": [{
45809
46275
  name: "deviceId",
45810
46276
  form: "single",
45811
46277
  optional: false
45812
46278
  }],
46279
+ "privacyMask.getStatus": [{
46280
+ name: "deviceId",
46281
+ form: "single",
46282
+ optional: false
46283
+ }],
45813
46284
  "privacyMask.setAudioEnabled": [{
45814
46285
  name: "deviceId",
45815
46286
  form: "single",
@@ -45860,6 +46331,11 @@ Object.freeze({
45860
46331
  form: "single",
45861
46332
  optional: false
45862
46333
  }],
46334
+ "ptz.getStatus": [{
46335
+ name: "deviceId",
46336
+ form: "single",
46337
+ optional: false
46338
+ }],
45863
46339
  "ptz.goHome": [{
45864
46340
  name: "deviceId",
45865
46341
  form: "single",
@@ -45950,6 +46426,11 @@ Object.freeze({
45950
46426
  form: "single",
45951
46427
  optional: false
45952
46428
  }],
46429
+ "recordingArchive.applyDeviceSettingsPatch": [{
46430
+ name: "deviceId",
46431
+ form: "single",
46432
+ optional: false
46433
+ }],
45953
46434
  "recordingArchive.deleteFootprint": [{
45954
46435
  name: "deviceId",
45955
46436
  form: "single",
@@ -45970,6 +46451,21 @@ Object.freeze({
45970
46451
  form: "single",
45971
46452
  optional: false
45972
46453
  }],
46454
+ "recordingArchive.getDeviceLiveContribution": [{
46455
+ name: "deviceId",
46456
+ form: "single",
46457
+ optional: false
46458
+ }],
46459
+ "recordingArchive.getDeviceSettingsContribution": [{
46460
+ name: "deviceId",
46461
+ form: "single",
46462
+ optional: false
46463
+ }],
46464
+ "recordingArchive.getStatus": [{
46465
+ name: "deviceId",
46466
+ form: "single",
46467
+ optional: false
46468
+ }],
45973
46469
  "recordingArchive.listOpsLog": [{
45974
46470
  name: "deviceId",
45975
46471
  form: "single",
@@ -46055,6 +46551,11 @@ Object.freeze({
46055
46551
  form: "single",
46056
46552
  optional: false
46057
46553
  }],
46554
+ "recordingOnboard.getStatus": [{
46555
+ name: "deviceId",
46556
+ form: "single",
46557
+ optional: false
46558
+ }],
46058
46559
  "recordingOnboard.setSettings": [{
46059
46560
  name: "deviceId",
46060
46561
  form: "single",
@@ -46085,6 +46586,11 @@ Object.freeze({
46085
46586
  form: "single",
46086
46587
  optional: false
46087
46588
  }],
46589
+ "sceneMonitor.getStatus": [{
46590
+ name: "deviceId",
46591
+ form: "single",
46592
+ optional: false
46593
+ }],
46088
46594
  "sceneMonitor.listScenes": [{
46089
46595
  name: "deviceId",
46090
46596
  form: "single",
@@ -46110,6 +46616,11 @@ Object.freeze({
46110
46616
  form: "single",
46111
46617
  optional: false
46112
46618
  }],
46619
+ "scriptRunner.getStatus": [{
46620
+ name: "deviceId",
46621
+ form: "single",
46622
+ optional: false
46623
+ }],
46113
46624
  "scriptRunner.run": [{
46114
46625
  name: "deviceId",
46115
46626
  form: "single",
@@ -46120,11 +46631,31 @@ Object.freeze({
46120
46631
  form: "single",
46121
46632
  optional: false
46122
46633
  }],
46634
+ "smoke.getStatus": [{
46635
+ name: "deviceId",
46636
+ form: "single",
46637
+ optional: false
46638
+ }],
46639
+ "snapshot.applyDeviceSettingsPatch": [{
46640
+ name: "deviceId",
46641
+ form: "single",
46642
+ optional: false
46643
+ }],
46123
46644
  "snapshot.getDebugState": [{
46124
46645
  name: "deviceId",
46125
46646
  form: "single",
46126
46647
  optional: false
46127
46648
  }],
46649
+ "snapshot.getDeviceLiveContribution": [{
46650
+ name: "deviceId",
46651
+ form: "single",
46652
+ optional: false
46653
+ }],
46654
+ "snapshot.getDeviceSettingsContribution": [{
46655
+ name: "deviceId",
46656
+ form: "single",
46657
+ optional: false
46658
+ }],
46128
46659
  "snapshot.getSnapshot": [{
46129
46660
  name: "deviceId",
46130
46661
  form: "single",
@@ -46141,6 +46672,11 @@ Object.freeze({
46141
46672
  form: "array",
46142
46673
  optional: false
46143
46674
  }],
46675
+ "snapshot.getStatus": [{
46676
+ name: "deviceId",
46677
+ form: "single",
46678
+ optional: false
46679
+ }],
46144
46680
  "snapshot.invalidateCache": [{
46145
46681
  name: "deviceId",
46146
46682
  form: "single",
@@ -46151,6 +46687,11 @@ Object.freeze({
46151
46687
  form: "single",
46152
46688
  optional: false
46153
46689
  }],
46690
+ "streamBroker.applyDeviceSettingsPatch": [{
46691
+ name: "deviceId",
46692
+ form: "single",
46693
+ optional: false
46694
+ }],
46154
46695
  "streamBroker.assignProfile": [{
46155
46696
  name: "deviceId",
46156
46697
  form: "single",
@@ -46166,6 +46707,16 @@ Object.freeze({
46166
46707
  form: "single",
46167
46708
  optional: false
46168
46709
  }],
46710
+ "streamBroker.getDeviceLiveContribution": [{
46711
+ name: "deviceId",
46712
+ form: "single",
46713
+ optional: false
46714
+ }],
46715
+ "streamBroker.getDeviceSettingsContribution": [{
46716
+ name: "deviceId",
46717
+ form: "single",
46718
+ optional: false
46719
+ }],
46169
46720
  "streamBroker.getStreamWithCodec": [{
46170
46721
  name: "deviceId",
46171
46722
  form: "single",
@@ -46226,16 +46777,46 @@ Object.freeze({
46226
46777
  form: "single",
46227
46778
  optional: false
46228
46779
  }],
46780
+ "streamParams.getStatus": [{
46781
+ name: "deviceId",
46782
+ form: "single",
46783
+ optional: false
46784
+ }],
46229
46785
  "streamParams.setProfile": [{
46230
46786
  name: "deviceId",
46231
46787
  form: "single",
46232
46788
  optional: false
46233
46789
  }],
46790
+ "switch.getStatus": [{
46791
+ name: "deviceId",
46792
+ form: "single",
46793
+ optional: false
46794
+ }],
46234
46795
  "switch.setState": [{
46235
46796
  name: "deviceId",
46236
46797
  form: "single",
46237
46798
  optional: false
46238
46799
  }],
46800
+ "tamper.getStatus": [{
46801
+ name: "deviceId",
46802
+ form: "single",
46803
+ optional: false
46804
+ }],
46805
+ "temperatureSensor.getStatus": [{
46806
+ name: "deviceId",
46807
+ form: "single",
46808
+ optional: false
46809
+ }],
46810
+ "update.getStatus": [{
46811
+ name: "deviceId",
46812
+ form: "single",
46813
+ optional: false
46814
+ }],
46815
+ "vacuumControl.getStatus": [{
46816
+ name: "deviceId",
46817
+ form: "single",
46818
+ optional: false
46819
+ }],
46239
46820
  "vacuumControl.locate": [{
46240
46821
  name: "deviceId",
46241
46822
  form: "single",
@@ -46271,6 +46852,11 @@ Object.freeze({
46271
46852
  form: "single",
46272
46853
  optional: false
46273
46854
  }],
46855
+ "valve.getStatus": [{
46856
+ name: "deviceId",
46857
+ form: "single",
46858
+ optional: false
46859
+ }],
46274
46860
  "valve.open": [{
46275
46861
  name: "deviceId",
46276
46862
  form: "single",
@@ -46286,6 +46872,11 @@ Object.freeze({
46286
46872
  form: "single",
46287
46873
  optional: false
46288
46874
  }],
46875
+ "vibration.getStatus": [{
46876
+ name: "deviceId",
46877
+ form: "single",
46878
+ optional: false
46879
+ }],
46289
46880
  "videoclips.dialClipStream": [{
46290
46881
  name: "deviceId",
46291
46882
  form: "single",
@@ -46321,6 +46912,11 @@ Object.freeze({
46321
46912
  form: "single",
46322
46913
  optional: false
46323
46914
  }],
46915
+ "waterHeater.getStatus": [{
46916
+ name: "deviceId",
46917
+ form: "single",
46918
+ optional: false
46919
+ }],
46324
46920
  "waterHeater.setAway": [{
46325
46921
  name: "deviceId",
46326
46922
  form: "single",
@@ -46336,6 +46932,11 @@ Object.freeze({
46336
46932
  form: "single",
46337
46933
  optional: false
46338
46934
  }],
46935
+ "weather.getStatus": [{
46936
+ name: "deviceId",
46937
+ form: "single",
46938
+ optional: false
46939
+ }],
46339
46940
  "webrtcSession.addIceCandidate": [{
46340
46941
  name: "deviceId",
46341
46942
  form: "single",
@@ -47073,7 +47674,7 @@ var PreviewTextField = string().max(200);
47073
47674
  * Whether the notification's preview is a STILL or a short animation.
47074
47675
  *
47075
47676
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
47076
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
47677
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
47077
47678
  * night reads better as three seconds of motion than as one frame of it. Both
47078
47679
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
47079
47680
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -47211,6 +47812,7 @@ TimelapseRuleInputSchema.extend({
47211
47812
  createdAt: number(),
47212
47813
  updatedAt: number()
47213
47814
  });
47815
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47214
47816
  object({
47215
47817
  /**
47216
47818
  * Fraction of the box's own size added on EACH side before cutting.