@camstack/addon-decoder-nodeav 1.2.116 → 1.2.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +836 -234
  2. package/dist/index.mjs +836 -234
  3. package/package.json +1 -3
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  let node_crypto = require("node:crypto");
6
- //#region ../types/dist/event-category-C5xZWqz6.mjs
6
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -536,6 +536,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
536
536
  */
537
537
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
538
538
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
539
+ /**
540
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
541
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
542
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
543
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
544
+ * for a camera with no attached sensor). Providers must not emit
545
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
546
+ * keeps it so (camera-grid is a named, deferred exception).
547
+ */
539
548
  EventCategory["ProviderMotion"] = "provider.motion";
540
549
  EventCategory["ProviderDetection"] = "provider.detection";
541
550
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5374,7 +5383,7 @@ var ZodIssueCode = {
5374
5383
  var ZodFirstPartyTypeKind;
5375
5384
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5376
5385
  //#endregion
5377
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5386
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5378
5387
  /**
5379
5388
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5380
5389
  * window to float samples (D455).
@@ -7413,7 +7422,7 @@ function errMsg(err) {
7413
7422
  return String(err);
7414
7423
  }
7415
7424
  //#endregion
7416
- //#region ../types/dist/composition-Dq42XWp0.mjs
7425
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7417
7426
  /**
7418
7427
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7419
7428
  *
@@ -7467,21 +7476,20 @@ function portable(target) {
7467
7476
  }
7468
7477
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7469
7478
  /**
7470
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7471
- * valutatore puro del suo FRONTE.
7479
+ * THE table "which boolean of this device type counts as HIGH", and the
7480
+ * pure evaluator of its EDGE.
7472
7481
  *
7473
- * Viveva dentro il builtin virtual-doorbell
7474
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7475
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7476
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7477
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7478
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7479
- * SPOSTA qui e il doorbell la ri-esporta.
7482
+ * ONE table for everyone who asks "is this device HIGH?": the
7483
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7484
+ * sensor markers, the orchestrator's camera-sensors bridge and
7485
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7486
+ * A second copy would diverge at the first cap added, and the symptom — "the
7487
+ * sensor rings the doorbell but does not record" — is exactly D62.
7480
7488
  *
7481
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7482
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7483
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7484
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7489
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7490
+ * two must not be unified: that one answers "what is the state WORD for a
7491
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7492
+ * boolean's EDGE counts". Deliberately different vocabularies.
7485
7493
  */
7486
7494
  /**
7487
7495
  * Known binary / switch source caps → the boolean slice field whose
@@ -7502,6 +7510,8 @@ var SOURCE_CAP_ACTIVE_FIELD = {
7502
7510
  vibration: "detected",
7503
7511
  tamper: "tampered"
7504
7512
  };
7513
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7514
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7505
7515
  /**
7506
7516
  * Error types for the safe expression engine. Two distinct classes so callers
7507
7517
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -9088,22 +9098,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9088
9098
  "package"
9089
9099
  ];
9090
9100
  /**
9091
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9092
- * un operatore può selezionare.
9101
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9102
+ * an operator can select.
9093
9103
  *
9094
- * Sono le tre offerte dallo step `object-detection`
9104
+ * They are the three offered by the `object-detection` step
9095
9105
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9096
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9097
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9098
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9099
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9106
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9107
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9108
+ * `package-detection`, whose enablement is driven by zones, and offering the
9109
+ * same word twice has already made an operator turn on the COCO proxy
9100
9110
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9101
9111
  *
9102
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9103
- * dello step e una seconda volta come union `FirstLevelMacro`
9104
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9105
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9106
- * successiva.
9112
+ * ONE list. Before today the same three were hand-written in the step's offer
9113
+ * and a second time as the union `FirstLevelMacro`
9114
+ * (`types/detection.ts`); a third copy for the recording trigger
9115
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9116
+ * invisible.
9107
9117
  */
9108
9118
  var FIRST_LEVEL_MACRO_CLASSES = [
9109
9119
  "person",
@@ -9168,17 +9178,17 @@ var RecordingStorageModeSchema = _enum([
9168
9178
  "on-device-decision"
9169
9179
  ]);
9170
9180
  /**
9171
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9172
- * tre offerte dallo step `object-detection`, da UNA lista
9181
+ * The macro classes that can open a recording window — the same
9182
+ * three offered by the `object-detection` step, from ONE list
9173
9183
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9174
9184
  */
9175
9185
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9176
9186
  /**
9177
- * True quando `values` non ripete un elemento.
9187
+ * True when `values` does not repeat an element.
9178
9188
  *
9179
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9180
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9181
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9189
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9190
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9191
+ * would count its windows twice in `segmentMissedByMs`.
9182
9192
  */
9183
9193
  var noDuplicates = (values) => new Set(values).size === values.length;
9184
9194
  /** Which detectors trigger an `events`-mode band. */
@@ -9186,34 +9196,34 @@ var RecordingTriggersSchema = object({
9186
9196
  motion: boolean().optional(),
9187
9197
  audioThresholdDbfs: number().optional(),
9188
9198
  /**
9189
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9190
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9191
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9192
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9199
+ * The macro classes whose detection opens a window. ABSENT = the source
9200
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9201
+ * trigger on, no class" is the same shape "enabled and records
9202
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9193
9203
  *
9194
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9195
- * quelle che hanno attraversato `enabledMacroClasses`, i
9196
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9197
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9198
- * finestre — vedi `recorder/object-trigger.ts`.
9204
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9205
+ * those that went through `enabledMacroClasses`, the
9206
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9207
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9208
+ * windows — see `recorder/object-trigger.ts`.
9199
9209
  */
9200
9210
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9201
9211
  /**
9202
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9203
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9212
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9213
+ * is not listened to; an empty array is rejected for the same reason as
9204
9214
  * `objectClasses`.
9205
9215
  *
9206
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9207
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9208
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9209
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9210
- * device (D12) — mai un elenco globale di cap.
9216
+ * These are SOURCE device ids, not cameras: the band names them, so the
9217
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9218
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9219
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9220
+ * device (D12) — never a global list of caps.
9211
9221
  *
9212
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9213
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9214
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9215
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9216
- * registrare.
9222
+ * What "high" means depends on the device TYPE and is not decided here:
9223
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9224
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9225
+ * the level: a contact found already open at runner restart does not
9226
+ * record.
9217
9227
  */
9218
9228
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9219
9229
  });
@@ -10290,7 +10300,7 @@ var DecoderSessionConfigSchema = object({
10290
10300
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10291
10301
  * thing: the logging settings document on the `system` cap. Two authorities
10292
10302
  * over the values is the exact defect
10293
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10303
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10294
10304
  * remove; re-introducing it from the cure side would be grotesque.
10295
10305
  *
10296
10306
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -12038,7 +12048,7 @@ var BrokerStatusEnum = _enum([
12038
12048
  "unreachable",
12039
12049
  "error"
12040
12050
  ]);
12041
- var BrokerInfoSchema$1 = object({
12051
+ var BrokerInfoSchema = object({
12042
12052
  /** Stable broker id. Persisted; survives addon restarts. */
12043
12053
  id: string(),
12044
12054
  /** Addon id of the provider that OWNS this broker.
@@ -12171,7 +12181,7 @@ var GetStateInputSchema = object({
12171
12181
  * HA: entity_id (returns the cached entity state). */
12172
12182
  key: string()
12173
12183
  });
12174
- method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12184
+ method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12175
12185
  kind: "mutation",
12176
12186
  auth: "admin"
12177
12187
  }), method(RemoveInputSchema, _void(), {
@@ -13780,7 +13790,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13780
13790
  *
13781
13791
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13782
13792
  * was wrong — corrected 2026-08-26 after the hop map
13783
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13793
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13784
13794
  * There is **no Zod parse at all** between the door and the engine: the
13785
13795
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13786
13796
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14629,7 +14639,17 @@ var SiteZoneSchema = object({
14629
14639
  var ProviderStatusSchema = object({
14630
14640
  connected: boolean(),
14631
14641
  deviceCount: number(),
14632
- error: string().optional()
14642
+ error: string().optional(),
14643
+ /**
14644
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14645
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14646
+ * which every provider bundles, so only a provider BUILT from D696-amended
14647
+ * code says it. ABSENT means an older provider that may still write it (raw
14648
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14649
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14650
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14651
+ */
14652
+ motionSliceWriter: literal("orchestrator").optional()
14633
14653
  });
14634
14654
  object({
14635
14655
  externalId: string(),
@@ -15427,28 +15447,10 @@ method(object({
15427
15447
  deviceIds: array(number()).readonly(),
15428
15448
  caps: array(string()).readonly().optional()
15429
15449
  }), record(string(), record(string(), unknown().nullable())));
15430
- object({
15431
- /** Ms epoch of the last press. null = never observed since this provider started. */
15432
- lastPressedAt: number().nullable(),
15433
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15434
- pressCountSinceStart: number()
15435
- });
15436
- object({
15437
- deviceId: number(),
15438
- timestamp: number()
15439
- });
15440
- /**
15441
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15442
- * virtual-doorbell trigger devices, exactly what the wrapper's
15443
- * `doorbellSources` persists (as `{ deviceId }` records).
15444
- */
15445
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15450
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15446
15451
  DeviceType.Camera;
15447
- DeviceType.Button, DeviceType.Camera;
15448
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15449
- DeviceType.Camera, DeviceType.Camera;
15450
15452
  /** A slot's editable value, discriminated on `slot`. */
15451
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15453
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15452
15454
  DeviceType.Camera;
15453
15455
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15454
15456
  state: literal("unreachable"),
@@ -15485,10 +15487,11 @@ var ExtensionSlotSourceSchema = object({
15485
15487
  });
15486
15488
  /**
15487
15489
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15488
- * reported read-only, named, never written through this contract (D62). The
15489
- * virtual doorbell's sources ring only while the wrapper is bound to the
15490
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15491
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15490
+ * reported read-only, named, never written through this contract (D62) — e.g.
15491
+ * a slot whose sources act only while a wrapper is bound to the host, a
15492
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15493
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15494
+ * not be read; it is not "inactive" (D315).
15492
15495
  */
15493
15496
  var ExtensionSlotActivationSchema = object({
15494
15497
  state: _enum([
@@ -15504,7 +15507,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15504
15507
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15505
15508
  slotRef: string(),
15506
15509
  slot: ExtensionSlotIdSchema,
15507
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15510
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15508
15511
  authority: string(),
15509
15512
  activation: ExtensionSlotActivationSchema.nullable(),
15510
15513
  value: ExtensionSlotValueSchema,
@@ -15782,7 +15785,7 @@ method(object({
15782
15785
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15783
15786
  * ends in a log line, and a log line is the thing the operator asked to stop
15784
15787
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15785
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15788
+ * 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
15786
15789
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15787
15790
  * media blackout were both diagnosed. The counters stay; this is where they can
15788
15791
  * be READ.
@@ -17158,7 +17161,7 @@ var BrokerStatusSchema$1 = _enum([
17158
17161
  "unreachable",
17159
17162
  "tls-error"
17160
17163
  ]);
17161
- var BrokerInfoSchema = object({
17164
+ var BrokerInfoSchema$1 = object({
17162
17165
  id: string(),
17163
17166
  name: string(),
17164
17167
  url: string(),
@@ -17221,7 +17224,7 @@ var StatusSchema = object({
17221
17224
  brokerCount: number(),
17222
17225
  embeddedRunning: boolean()
17223
17226
  });
17224
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17227
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17225
17228
  kind: "mutation",
17226
17229
  auth: "admin"
17227
17230
  }), method(IdInputSchema, _void(), {
@@ -19397,78 +19400,16 @@ var NcRuleInputSchema = object({
19397
19400
  */
19398
19401
  confirm: NcConfirmSchema.optional(),
19399
19402
  /**
19400
- * WAIT for face/plate recognition before saying anything.
19401
- *
19402
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19403
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19404
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19405
- * measured on this hub — and an `immediate` rule enqueues on the first object
19406
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19407
- * immediate path, and no amount of media re-resolution fixes a sentence.
19408
- *
19409
- * Only two honest answers exist, and this flag picks between them. It has
19410
- * effect ONLY on a rule that declares a recognition scope
19411
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19412
- * other rule there is nothing to wait for and the flag is inert.
19413
- *
19414
- * | value | what happens |
19415
- * | --- | --- |
19416
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19417
- * | 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) |
19418
- *
19419
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19420
- * on the addon cap path, and absent has to keep meaning exactly what every
19421
- * rule authored before this field meant.
19422
- *
19423
- * The cost of `true` is stated here because the editor states it too: a rule
19424
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19425
- * tests every zone the track visited and a `crossing` condition can no longer
19426
- * be satisfied, because a closed track carries no crossing.
19427
- */
19428
- waitForEnhancement: boolean().optional(),
19429
- /**
19430
- * GROUP a burst of subjects into ONE notification that grows.
19431
- *
19432
- * Seconds of quiet after the last matching subject before the burst is
19433
- * considered over. While it is open, the first subject enqueues immediately —
19434
- * **exactly as today, with no added latency** — and every real growth (a new
19435
- * subject, or a name confirmed on one already in it) REPLACES that
19436
- * notification with an updated one naming everybody. The push carries the
19437
- * group's own coalescing tag, so the phone replaces rather than stacks.
19438
- *
19439
- * `0` / absent = off, and off is today's behaviour byte for byte.
19440
- *
19441
- * ### Why an idle cutoff and not a window
19442
- *
19443
- * The measured seven-person arrival on device 590 spans 110 s with every
19444
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19445
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19446
- * 30 s is a widely shipped value for the same decision.
19447
- *
19448
- * ### What it replaces
19449
- *
19450
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19451
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19452
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19453
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19454
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19455
- * budget over GROUPS — which is what it always meant — and a growth is never
19456
- * throttled by the window its own first member spent.
19457
- *
19458
- * ### Interaction with {@link waitForEnhancement}
19459
- *
19460
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19461
- * TRACK CLOSE, so with both set the group is opened by the first member to
19462
- * CLOSE — already carrying its name — and grows as later members close. That
19463
- * is later, and complete. With grouping alone the group opens on the first
19464
- * object event and picks up names as they are confirmed, through the growth
19465
- * path. Neither combination fires twice for one subject.
19466
- *
19467
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19468
- * on the addon cap path, so absent must keep meaning what it meant before this
19469
- * field existed.
19403
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19404
+ * seconds; the rule sends ONE message when the window closes — early, once every
19405
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19406
+ * the rule matched, with one mosaic tile per subject.
19407
+ * Absent = plain immediate (one message per track, at the first match).
19408
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19409
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19410
+ * path does not run Zod defaults.
19470
19411
  */
19471
- groupIdleSec: number().int().min(0).max(600).optional()
19412
+ groupingSec: number().int().min(5).max(60).optional()
19472
19413
  });
19473
19414
  /**
19474
19415
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19516,9 +19457,10 @@ var NcRuleClearableKeySchema = _enum([
19516
19457
  "targetUsers",
19517
19458
  "snoozeOptions",
19518
19459
  "snoozeAllowGlobal",
19519
- "waitForEnhancement",
19520
19460
  "actions",
19521
19461
  "confirm",
19462
+ "groupingSec",
19463
+ "waitForEnhancement",
19522
19464
  "groupIdleSec"
19523
19465
  ]);
19524
19466
  var NcRuleUpdateInputSchema = object({
@@ -19540,9 +19482,25 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19540
19482
  */
19541
19483
  disabledTargetIds: array(string()).default([])
19542
19484
  });
19485
+ /**
19486
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19487
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19488
+ * job is not an open state, and the alarm stays instant.
19489
+ */
19490
+ var NcStateSummarySectionIdSchema = _enum([
19491
+ "device-state",
19492
+ "streams",
19493
+ "battery",
19494
+ "consumables",
19495
+ "tanks",
19496
+ "detection-blind",
19497
+ "nodes",
19498
+ "addons",
19499
+ "updates"
19500
+ ]);
19543
19501
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19544
19502
  var NcStateSummaryPreviewSectionSchema = object({
19545
- id: string(),
19503
+ id: NcStateSummarySectionIdSchema,
19546
19504
  status: _enum(["read", "unavailable"]),
19547
19505
  items: number().int().nonnegative(),
19548
19506
  reason: string().optional()
@@ -19552,7 +19510,7 @@ var NcStateSummaryPreviewSectionSchema = object({
19552
19510
  * a `rule` split (no `section`), one section's on a `section` split.
19553
19511
  */
19554
19512
  var NcStateSummaryPreviewRowSchema = object({
19555
- section: string().optional(),
19513
+ section: NcStateSummarySectionIdSchema.optional(),
19556
19514
  /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19557
19515
  title: string(),
19558
19516
  text: string(),
@@ -19989,6 +19947,29 @@ object({
19989
19947
  firstAt: number(),
19990
19948
  lastAt: number()
19991
19949
  });
19950
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
19951
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
19952
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
19953
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
19954
+ var NcAlarmModeDelaysListSchema = array(object({
19955
+ mode: AlarmArmModeSchema,
19956
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
19957
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
19958
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
19959
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
19960
+ var NcAlarmModeDelaysEditListSchema = array(object({
19961
+ mode: AlarmArmModeSchema,
19962
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19963
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
19964
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
19965
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
19966
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
19967
+ var NcAlarmNonBlockingEditSchema = object({
19968
+ mode: AlarmArmModeSchema,
19969
+ deviceId: number().int().positive(),
19970
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
19971
+ nonBlocking: boolean()
19972
+ });
19992
19973
  /**
19993
19974
  * The per-mode lists, at most one per mode. Two lists for one mode have no
19994
19975
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -19997,10 +19978,10 @@ object({
19997
19978
  var NcAlarmNonBlockingListSchema = array(object({
19998
19979
  mode: AlarmArmModeSchema,
19999
19980
  deviceIds: array(number().int()).max(200)
20000
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
19981
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20001
19982
  /**
20002
- * The three durations the panel's state machine runs on, who hears about its
20003
- * transitions, and which openings each mode tolerates.
19983
+ * The three durations — global, and per mode since D697 — the panel's state
19984
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20004
19985
  *
20005
19986
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20006
19987
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20010,15 +19991,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20010
19991
  */
20011
19992
  var NcAlarmSettingsSchema = object({
20012
19993
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20013
- exitDelaySec: number().int().min(0).max(600),
19994
+ exitDelaySec: NcAlarmDelaySecSchema,
20014
19995
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20015
- entryDelaySec: number().int().min(0).max(600),
19996
+ entryDelaySec: NcAlarmDelaySecSchema,
20016
19997
  /**
20017
19998
  * How long `triggered` lasts before the panel re-arms itself.
20018
19999
  * **0 = until an operator disarms it** — the behaviour before this field
20019
20000
  * existed, and therefore what an untouched install keeps doing.
20020
20001
  */
20021
- triggeredDurationSec: number().int().min(0).max(3600),
20002
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20022
20003
  /**
20023
20004
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20024
20005
  * remove after one release. Optional so an older admin's patch (which still
@@ -20041,17 +20022,30 @@ var NcAlarmSettingsSchema = object({
20041
20022
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20042
20023
  * parses as "every opening blocks" — the behaviour it was written under.
20043
20024
  */
20044
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20025
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20026
+ /**
20027
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20028
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20029
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20030
+ */
20031
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20045
20032
  });
20046
20033
  /**
20047
20034
  * Every field optional — a tab edits one control at a time.
20048
20035
  *
20049
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20050
- * remove an inner `.default()`, so a patch that never named the field would
20051
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20052
- * (the same trap {@link NcRulePatchSchema} documents).
20036
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20037
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20038
+ * named the field would parse to `[]` and wipe every mode's list on an
20039
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20040
+ * `modeDelays` means EDITS, not the stored overrides.
20053
20041
  */
20054
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20042
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20043
+ nonBlocking: NcAlarmNonBlockingListSchema,
20044
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20045
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20046
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20047
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20048
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20055
20049
  /**
20056
20050
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20057
20051
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20151,6 +20145,28 @@ var NcAlarmConfigSchema = object({
20151
20145
  */
20152
20146
  live: NcAlarmLiveSchema.optional()
20153
20147
  });
20148
+ var NcAlarmSensorReadingSchema = _enum([
20149
+ "open",
20150
+ "closed",
20151
+ "unknown",
20152
+ "not-opening"
20153
+ ]);
20154
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20155
+ var NcAlarmSensorStateSchema = object({
20156
+ deviceId: number().int().positive(),
20157
+ reading: NcAlarmSensorReadingSchema,
20158
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20159
+ state: string().nullable(),
20160
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20161
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20162
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20163
+ online: boolean().nullable()
20164
+ });
20165
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20166
+ var NcAlarmSensorsSchema = object({
20167
+ sensors: array(NcAlarmSensorStateSchema),
20168
+ readAt: number().int().nonnegative()
20169
+ });
20154
20170
  /**
20155
20171
  * ONE rule's demand on ONE camera's clip ring.
20156
20172
  *
@@ -20241,7 +20257,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20241
20257
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20242
20258
  kind: "mutation",
20243
20259
  auth: "admin"
20244
- });
20260
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20245
20261
  /**
20246
20262
  * Zod schemas for persisted record types.
20247
20263
  *
@@ -20588,12 +20604,26 @@ var CameraSensorRefSchema = object({
20588
20604
  function cameraSensorRefKey(ref) {
20589
20605
  return `${ref.addonId}/${ref.stableId}`;
20590
20606
  }
20591
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20592
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20593
- var CameraSensorsConfigSchema = object({
20594
- mode: CameraSensorsModeSchema,
20595
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20596
- }).superRefine((value, ctx) => {
20607
+ /**
20608
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20609
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20610
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20611
+ * camera's own motion counts only while a confirm sensor is active — with or
20612
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20613
+ */
20614
+ var CameraSensorModeSchema = _enum([
20615
+ "in-addition",
20616
+ "confirm",
20617
+ "replace"
20618
+ ]);
20619
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20620
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20621
+ /**
20622
+ * A camera's attached sensors. A CS-1 row carried an envelope
20623
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20624
+ * what the write stores (D672 rule 3), so it is never written again.
20625
+ */
20626
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20597
20627
  const seen = /* @__PURE__ */ new Set();
20598
20628
  value.sensors.forEach((ref, index) => {
20599
20629
  const key = cameraSensorRefKey(ref);
@@ -20605,39 +20635,41 @@ var CameraSensorsConfigSchema = object({
20605
20635
  seen.add(key);
20606
20636
  });
20607
20637
  });
20608
- var RefFields = {
20638
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20639
+ var AttachmentFields = {
20609
20640
  addonId: string(),
20610
- stableId: string()
20641
+ stableId: string(),
20642
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20611
20643
  };
20612
20644
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20613
20645
  object({
20614
- ...RefFields,
20646
+ ...AttachmentFields,
20615
20647
  state: literal("ready"),
20616
20648
  deviceId: number(),
20617
20649
  name: string(),
20618
20650
  caps: array(string()).readonly()
20619
20651
  }),
20620
20652
  object({
20621
- ...RefFields,
20653
+ ...AttachmentFields,
20622
20654
  state: literal("offline"),
20623
20655
  deviceId: number(),
20624
20656
  name: string(),
20625
20657
  reason: _enum(["offline", "disabled"])
20626
20658
  }),
20627
20659
  object({
20628
- ...RefFields,
20660
+ ...AttachmentFields,
20629
20661
  state: literal("missing"),
20630
20662
  reason: string()
20631
20663
  }),
20632
20664
  object({
20633
- ...RefFields,
20665
+ ...AttachmentFields,
20634
20666
  state: literal("unsupported"),
20635
20667
  deviceId: number(),
20636
20668
  name: string(),
20637
20669
  reason: string()
20638
20670
  }),
20639
20671
  object({
20640
- ...RefFields,
20672
+ ...AttachmentFields,
20641
20673
  state: literal("unreadable"),
20642
20674
  reason: string()
20643
20675
  })
@@ -20650,17 +20682,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20650
20682
  * say.
20651
20683
  */
20652
20684
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20685
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20686
+ var CameraOwnMotionSchema = _enum([
20687
+ "always",
20688
+ "when-confirmed",
20689
+ "never"
20690
+ ]);
20691
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20692
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20693
+ /**
20694
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20695
+ * never the effective value. The camera's `motion` slice is the effective one,
20696
+ * so a view of the camera's own row reads this.
20697
+ * - `atMs`: when the bridge received the camera's last own edge.
20698
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20699
+ * AND it is younger than the window the bridge counts the camera's own motion
20700
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20701
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20702
+ * within the window; a status read is a snapshot, so a live view re-asks.
20703
+ * Absent = this orchestrator has not received the camera's own motion since it
20704
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20705
+ */
20706
+ var CameraRawLevelSchema = object({
20707
+ detected: boolean(),
20708
+ atMs: number()
20709
+ });
20653
20710
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20654
- object({ state: literal("not-configured") }),
20711
+ object({
20712
+ state: literal("not-configured"),
20713
+ cameraLevel: CameraRawLevelSchema.optional()
20714
+ }),
20655
20715
  object({
20656
20716
  state: literal("unreadable"),
20657
- reason: string()
20717
+ reason: string(),
20718
+ cameraLevel: CameraRawLevelSchema.optional()
20658
20719
  }),
20659
20720
  object({
20660
20721
  state: literal("configured"),
20661
- mode: CameraSensorsModeSchema,
20722
+ cameraLevel: CameraRawLevelSchema.optional(),
20662
20723
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20663
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20724
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20725
+ cameraMotion: CameraOwnMotionSchema.optional(),
20726
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20727
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20664
20728
  })
20665
20729
  ]);
20666
20730
  Object.freeze({
@@ -21345,14 +21409,14 @@ var TrackSchema = object({
21345
21409
  * `derive-returned-null tile=standin`,
21346
21410
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21347
21411
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21348
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21412
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21349
21413
  * tile is a face/plate stand-in, a raster crop, or an icon.
21350
21414
  *
21351
21415
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21352
21416
  * the TILE ranked below native, so a track whose thumbnail landed natively
21353
21417
  * while its key frame missed carried no reason at all — three of the four
21354
21418
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21355
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21419
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21356
21420
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21357
21421
  * lastFrame copy source`), so its absence is now on this line whatever the
21358
21422
  * tile ended up being.
@@ -21439,7 +21503,7 @@ var MotionEventSchema = object({
21439
21503
  /**
21440
21504
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21441
21505
  * first entry is always `0`) of every genuine off→on transition the
21442
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21506
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21443
21507
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21444
21508
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21445
21509
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -21963,6 +22027,7 @@ var KeyEventSchema = object({
21963
22027
  bestEventId: string(),
21964
22028
  /** Track lifetime in ms (lastSeen - firstSeen). */
21965
22029
  windowMs: number().optional(),
22030
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
21966
22031
  ...TrackFlagFields,
21967
22032
  ...TrackRetrainFields
21968
22033
  });
@@ -23851,8 +23916,10 @@ var RunnerCameraConfigSchema = object({
23851
23916
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23852
23917
  * analyzer providers) running on decoded frames in this runner.
23853
23918
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
23854
- * analytics). The provider emits ProviderMotion
23855
- * events; the orchestrator forwards to `reportMotion`.
23919
+ * analytics). The provider emits its raw push as `ProviderMotion`
23920
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
23921
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
23922
+ * `reportMotion`.
23856
23923
  */
23857
23924
  motionSources: MotionSourcesSchema.default(["analyzer"]),
23858
23925
  /**
@@ -24029,7 +24096,15 @@ var RunnerLocalLoadSchema = object({
24029
24096
  hasGpu: boolean(),
24030
24097
  inferenceBackend: string().optional(),
24031
24098
  cpuCores: number().optional()
24032
- })
24099
+ }),
24100
+ /**
24101
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24102
+ * runner never writes it. ABSENT = a runner older than D696, which still
24103
+ * writes the slice from its phase machine; the orchestrator does not write
24104
+ * while any runner node is absent-or-unanswered, so one slice never has two
24105
+ * writers.
24106
+ */
24107
+ motionSliceWriter: literal("orchestrator").optional()
24033
24108
  });
24034
24109
  /**
24035
24110
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -26665,7 +26740,7 @@ var ClipPlaybackSchema = object({
26665
26740
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26666
26741
  * time the camera is awake for its own reasons — is deliberately not a member:
26667
26742
  * it was proposed, it is free on the battery, and the operator declined it on
26668
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
26743
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26669
26744
  * D558 "Considered and not taken").
26670
26745
  */
26671
26746
  var ClipWakeSchema = _enum(["authorised"]);
@@ -26926,7 +27001,7 @@ DeviceType.Camera, method(object({
26926
27001
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
26927
27002
  * assumed the answer was a fan-out over everything a camera has. The
26928
27003
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
26929
- * solo"* — so the list is asked of one provider at a time and there is
27004
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
26930
27005
  * nothing to filter out of it.
26931
27006
  */
26932
27007
  provider: string().min(1)
@@ -28870,6 +28945,17 @@ object({
28870
28945
  deviceId: number(),
28871
28946
  status: DeviceStatusSchema
28872
28947
  });
28948
+ object({
28949
+ /** Ms epoch of the last press. null = never observed since this provider started. */
28950
+ lastPressedAt: number().nullable(),
28951
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
28952
+ pressCountSinceStart: number()
28953
+ });
28954
+ object({
28955
+ deviceId: number(),
28956
+ timestamp: number()
28957
+ });
28958
+ DeviceType.Button, DeviceType.Camera;
28873
28959
  /**
28874
28960
  * Enum-state sensor — a string value picked from a finite option set.
28875
28961
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30514,9 +30600,9 @@ object({
30514
30600
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30515
30601
  lastDetectedAt: number().nullable(),
30516
30602
  /**
30517
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30518
- * `null` while false and on every `Sensor` device (D475) — see that
30519
- * constant's doc for the one-authority rule.
30603
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30604
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30605
+ * device (D475) — see that constant's doc for the one-authority rule.
30520
30606
  *
30521
30607
  * ## Reading this field still arms nothing
30522
30608
  *
@@ -30534,38 +30620,41 @@ object({
30534
30620
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30535
30621
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30536
30622
  *
30537
- * ## Who writes it
30538
- *
30539
- * - **Cameras** — the runner's phase machine, `active → watching` on
30540
- * `cooldown_expired`, which then writes this slice with
30541
- * `detected: false` (`handlePhaseChanged` in
30542
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30543
- * matters most for the sources that only ever push a rising one:
30544
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30545
- * a false.
30546
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30547
- * provider pushes the false itself, from the upstream system's own
30548
- * state change. No phase machine is involved.
30549
- *
30550
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30551
- *
30552
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30553
- * phase machine is the sole writer for a camera. It is not.
30554
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30555
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30556
- * Hikvision's comment says why: it read THIS docblock, agreed the
30557
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30558
- * two authorities can disagree about one slice, which this repo
30559
- * forbids, and the doc said otherwise — which is worse than saying
30560
- * nothing, because it reads as verification.
30561
- *
30562
- * This predates D475 and is not fixed there: the fix touches every
30563
- * camera provider. Recorded in D475's Consequences. Do not restore the
30564
- * "sole writer" wording without also removing the other writers.
30623
+ * ## Who writes it (D696)
30624
+ *
30625
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30626
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30627
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30628
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30629
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30630
+ * device activity, as the camera's `motionSources` counts them. A source
30631
+ * that never sends a clear falls one window after its last `true`.
30632
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30633
+ * writes this slice (not even a boot seed), and neither does the runner's
30634
+ * phase machine: its phase is "a session is attached" for every on-motion
30635
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30636
+ * A provider's `isDetected` answers from the hub's slice.
30637
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30638
+ * imports) — the provider pushes both edges itself, from the upstream
30639
+ * system's own state change. That is the sensor's own fact.
30640
+ *
30641
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30642
+ * `motion` slice; each sensor writer is a named exemption.
30643
+ *
30644
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30645
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30646
+ * it, every provider seeded it at boot, and every session left it
30647
+ * `detected: true` after its detach.
30565
30648
  */
30566
30649
  autoClearAfterMs: number().nullable()
30567
30650
  });
30568
- object({
30651
+ /**
30652
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30653
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30654
+ * — both the cap event surface and the bus payload type alias to this
30655
+ * schema.
30656
+ */
30657
+ var MotionOnMotionChangedDataSchema = object({
30569
30658
  deviceId: number(),
30570
30659
  detected: boolean(),
30571
30660
  timestamp: number(),
@@ -33030,7 +33119,7 @@ var ReadGopBytesResultSchema = object({
33030
33119
  /**
33031
33120
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
33032
33121
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
33033
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
33122
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
33034
33123
  * a replay needs several seconds of native pixels, not one frame.
33035
33124
  *
33036
33125
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -35646,6 +35735,7 @@ DeviceType.Camera, method(object({
35646
35735
  detection: array(ZoneRuleSchema).readonly(),
35647
35736
  package: array(ZoneRuleSchema).readonly()
35648
35737
  });
35738
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
35649
35739
  /**
35650
35740
  * Accessory device helpers — shared across drivers.
35651
35741
  *
@@ -40624,6 +40714,12 @@ Object.freeze({
40624
40714
  addonId: null,
40625
40715
  access: "view"
40626
40716
  },
40717
+ "notificationRules.getAlarmSensors": {
40718
+ capName: "notification-rules",
40719
+ capScope: "system",
40720
+ addonId: null,
40721
+ access: "view"
40722
+ },
40627
40723
  "notificationRules.getClipRetentionAsks": {
40628
40724
  capName: "notification-rules",
40629
40725
  capScope: "system",
@@ -44202,6 +44298,11 @@ Object.freeze({
44202
44298
  }
44203
44299
  });
44204
44300
  Object.freeze({
44301
+ "accessories.getStatus": [{
44302
+ name: "deviceId",
44303
+ form: "single",
44304
+ optional: false
44305
+ }],
44205
44306
  "accessories.setChildHidden": [{
44206
44307
  name: "childDeviceId",
44207
44308
  form: "single",
@@ -44221,6 +44322,11 @@ Object.freeze({
44221
44322
  form: "single",
44222
44323
  optional: false
44223
44324
  }],
44325
+ "airQualitySensor.getStatus": [{
44326
+ name: "deviceId",
44327
+ form: "single",
44328
+ optional: false
44329
+ }],
44224
44330
  "alarmPanel.arm": [{
44225
44331
  name: "deviceId",
44226
44332
  form: "single",
@@ -44231,11 +44337,36 @@ Object.freeze({
44231
44337
  form: "single",
44232
44338
  optional: false
44233
44339
  }],
44340
+ "alarmPanel.getStatus": [{
44341
+ name: "deviceId",
44342
+ form: "single",
44343
+ optional: false
44344
+ }],
44234
44345
  "alarmPanel.trigger": [{
44235
44346
  name: "deviceId",
44236
44347
  form: "single",
44237
44348
  optional: false
44238
44349
  }],
44350
+ "ambientLightSensor.getStatus": [{
44351
+ name: "deviceId",
44352
+ form: "single",
44353
+ optional: false
44354
+ }],
44355
+ "audioAnalysis.applyDeviceSettingsPatch": [{
44356
+ name: "deviceId",
44357
+ form: "single",
44358
+ optional: false
44359
+ }],
44360
+ "audioAnalysis.getDeviceLiveContribution": [{
44361
+ name: "deviceId",
44362
+ form: "single",
44363
+ optional: false
44364
+ }],
44365
+ "audioAnalysis.getDeviceSettingsContribution": [{
44366
+ name: "deviceId",
44367
+ form: "single",
44368
+ optional: false
44369
+ }],
44239
44370
  "audioAnalysis.resolveDeviceSettings": [{
44240
44371
  name: "deviceId",
44241
44372
  form: "single",
@@ -44276,16 +44407,36 @@ Object.freeze({
44276
44407
  form: "single",
44277
44408
  optional: false
44278
44409
  }],
44410
+ "automationControl.getStatus": [{
44411
+ name: "deviceId",
44412
+ form: "single",
44413
+ optional: false
44414
+ }],
44279
44415
  "automationControl.trigger": [{
44280
44416
  name: "deviceId",
44281
44417
  form: "single",
44282
44418
  optional: false
44283
44419
  }],
44420
+ "battery.getStatus": [{
44421
+ name: "deviceId",
44422
+ form: "single",
44423
+ optional: false
44424
+ }],
44284
44425
  "battery.wakeForStream": [{
44285
44426
  name: "deviceId",
44286
44427
  form: "single",
44287
44428
  optional: false
44288
44429
  }],
44430
+ "binary.getStatus": [{
44431
+ name: "deviceId",
44432
+ form: "single",
44433
+ optional: false
44434
+ }],
44435
+ "brightness.getStatus": [{
44436
+ name: "deviceId",
44437
+ form: "single",
44438
+ optional: false
44439
+ }],
44289
44440
  "brightness.setBrightness": [{
44290
44441
  name: "deviceId",
44291
44442
  form: "single",
@@ -44301,6 +44452,26 @@ Object.freeze({
44301
44452
  form: "single",
44302
44453
  optional: false
44303
44454
  }],
44455
+ "cameraCredentials.getStatus": [{
44456
+ name: "deviceId",
44457
+ form: "single",
44458
+ optional: false
44459
+ }],
44460
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
44461
+ name: "deviceId",
44462
+ form: "single",
44463
+ optional: false
44464
+ }],
44465
+ "cameraGridLayout.getDeviceLiveContribution": [{
44466
+ name: "deviceId",
44467
+ form: "single",
44468
+ optional: false
44469
+ }],
44470
+ "cameraGridLayout.getDeviceSettingsContribution": [{
44471
+ name: "deviceId",
44472
+ form: "single",
44473
+ optional: false
44474
+ }],
44304
44475
  "cameraGridLayout.getLayout": [{
44305
44476
  name: "deviceId",
44306
44477
  form: "single",
@@ -44316,6 +44487,21 @@ Object.freeze({
44316
44487
  form: "single",
44317
44488
  optional: false
44318
44489
  }],
44490
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
44491
+ name: "deviceId",
44492
+ form: "single",
44493
+ optional: false
44494
+ }],
44495
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
44496
+ name: "deviceId",
44497
+ form: "single",
44498
+ optional: false
44499
+ }],
44500
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
44501
+ name: "deviceId",
44502
+ form: "single",
44503
+ optional: false
44504
+ }],
44319
44505
  "cameraStreams.getBrokerStreams": [{
44320
44506
  name: "deviceId",
44321
44507
  form: "single",
@@ -44341,6 +44527,16 @@ Object.freeze({
44341
44527
  form: "single",
44342
44528
  optional: false
44343
44529
  }],
44530
+ "carbonMonoxide.getStatus": [{
44531
+ name: "deviceId",
44532
+ form: "single",
44533
+ optional: false
44534
+ }],
44535
+ "climateControl.getStatus": [{
44536
+ name: "deviceId",
44537
+ form: "single",
44538
+ optional: false
44539
+ }],
44344
44540
  "climateControl.setFanMode": [{
44345
44541
  name: "deviceId",
44346
44542
  form: "single",
@@ -44381,16 +44577,41 @@ Object.freeze({
44381
44577
  form: "single",
44382
44578
  optional: false
44383
44579
  }],
44580
+ "color.getStatus": [{
44581
+ name: "deviceId",
44582
+ form: "single",
44583
+ optional: false
44584
+ }],
44384
44585
  "color.setColor": [{
44385
44586
  name: "deviceId",
44386
44587
  form: "single",
44387
44588
  optional: false
44388
44589
  }],
44590
+ "connectivity.getStatus": [{
44591
+ name: "deviceId",
44592
+ form: "single",
44593
+ optional: false
44594
+ }],
44595
+ "consumables.getStatus": [{
44596
+ name: "deviceId",
44597
+ form: "single",
44598
+ optional: false
44599
+ }],
44389
44600
  "consumables.reset": [{
44390
44601
  name: "deviceId",
44391
44602
  form: "single",
44392
44603
  optional: false
44393
44604
  }],
44605
+ "contact.getStatus": [{
44606
+ name: "deviceId",
44607
+ form: "single",
44608
+ optional: false
44609
+ }],
44610
+ "control.getStatus": [{
44611
+ name: "deviceId",
44612
+ form: "single",
44613
+ optional: false
44614
+ }],
44394
44615
  "control.setValue": [{
44395
44616
  name: "deviceId",
44396
44617
  form: "single",
@@ -44401,6 +44622,11 @@ Object.freeze({
44401
44622
  form: "single",
44402
44623
  optional: false
44403
44624
  }],
44625
+ "cover.getStatus": [{
44626
+ name: "deviceId",
44627
+ form: "single",
44628
+ optional: false
44629
+ }],
44404
44630
  "cover.open": [{
44405
44631
  name: "deviceId",
44406
44632
  form: "single",
@@ -44426,6 +44652,11 @@ Object.freeze({
44426
44652
  form: "single",
44427
44653
  optional: false
44428
44654
  }],
44655
+ "dayNight.getStatus": [{
44656
+ name: "deviceId",
44657
+ form: "single",
44658
+ optional: false
44659
+ }],
44429
44660
  "dayNight.setSettings": [{
44430
44661
  name: "deviceId",
44431
44662
  form: "single",
@@ -44436,11 +44667,31 @@ Object.freeze({
44436
44667
  form: "single",
44437
44668
  optional: true
44438
44669
  }],
44670
+ "detectionPipeline.applyDeviceSettingsPatch": [{
44671
+ name: "deviceId",
44672
+ form: "single",
44673
+ optional: false
44674
+ }],
44675
+ "detectionPipeline.getDeviceLiveContribution": [{
44676
+ name: "deviceId",
44677
+ form: "single",
44678
+ optional: false
44679
+ }],
44680
+ "detectionPipeline.getDeviceSettingsContribution": [{
44681
+ name: "deviceId",
44682
+ form: "single",
44683
+ optional: false
44684
+ }],
44439
44685
  "deviceAdminLink.getAdminLink": [{
44440
44686
  name: "deviceId",
44441
44687
  form: "single",
44442
44688
  optional: false
44443
44689
  }],
44690
+ "deviceAdoption.getStatus": [{
44691
+ name: "deviceId",
44692
+ form: "single",
44693
+ optional: false
44694
+ }],
44444
44695
  "deviceAdoption.release": [{
44445
44696
  name: "camDeviceId",
44446
44697
  form: "single",
@@ -44456,6 +44707,11 @@ Object.freeze({
44456
44707
  form: "single",
44457
44708
  optional: false
44458
44709
  }],
44710
+ "deviceDiscovery.getStatus": [{
44711
+ name: "deviceId",
44712
+ form: "single",
44713
+ optional: false
44714
+ }],
44459
44715
  "deviceDiscovery.listDiscovered": [{
44460
44716
  name: "deviceId",
44461
44717
  form: "single",
@@ -44475,6 +44731,21 @@ Object.freeze({
44475
44731
  form: "single",
44476
44732
  optional: false
44477
44733
  }],
44734
+ "deviceExport.applyDeviceSettingsPatch": [{
44735
+ name: "deviceId",
44736
+ form: "single",
44737
+ optional: false
44738
+ }],
44739
+ "deviceExport.getDeviceLiveContribution": [{
44740
+ name: "deviceId",
44741
+ form: "single",
44742
+ optional: false
44743
+ }],
44744
+ "deviceExport.getDeviceSettingsContribution": [{
44745
+ name: "deviceId",
44746
+ form: "single",
44747
+ optional: false
44748
+ }],
44478
44749
  "deviceExtension.listCandidates": [{
44479
44750
  name: "deviceId",
44480
44751
  form: "single",
@@ -44823,6 +45094,26 @@ Object.freeze({
44823
45094
  form: "single",
44824
45095
  optional: false
44825
45096
  }],
45097
+ "deviceStatus.getStatus": [{
45098
+ name: "deviceId",
45099
+ form: "single",
45100
+ optional: false
45101
+ }],
45102
+ "doorbell.getStatus": [{
45103
+ name: "deviceId",
45104
+ form: "single",
45105
+ optional: false
45106
+ }],
45107
+ "enumSensor.getStatus": [{
45108
+ name: "deviceId",
45109
+ form: "single",
45110
+ optional: false
45111
+ }],
45112
+ "eventEmitter.getStatus": [{
45113
+ name: "deviceId",
45114
+ form: "single",
45115
+ optional: false
45116
+ }],
44826
45117
  "faceGallery.getFaceByTrack": [{
44827
45118
  name: "deviceId",
44828
45119
  form: "single",
@@ -44837,6 +45128,11 @@ Object.freeze({
44837
45128
  form: "array",
44838
45129
  optional: true
44839
45130
  }],
45131
+ "fanControl.getStatus": [{
45132
+ name: "deviceId",
45133
+ form: "single",
45134
+ optional: false
45135
+ }],
44840
45136
  "fanControl.setDirection": [{
44841
45137
  name: "deviceId",
44842
45138
  form: "single",
@@ -44857,6 +45153,26 @@ Object.freeze({
44857
45153
  form: "single",
44858
45154
  optional: false
44859
45155
  }],
45156
+ "featureProbe.getStatus": [{
45157
+ name: "deviceId",
45158
+ form: "single",
45159
+ optional: false
45160
+ }],
45161
+ "flood.getStatus": [{
45162
+ name: "deviceId",
45163
+ form: "single",
45164
+ optional: false
45165
+ }],
45166
+ "gas.getStatus": [{
45167
+ name: "deviceId",
45168
+ form: "single",
45169
+ optional: false
45170
+ }],
45171
+ "humidifier.getStatus": [{
45172
+ name: "deviceId",
45173
+ form: "single",
45174
+ optional: false
45175
+ }],
44860
45176
  "humidifier.setMode": [{
44861
45177
  name: "deviceId",
44862
45178
  form: "single",
@@ -44872,11 +45188,26 @@ Object.freeze({
44872
45188
  form: "single",
44873
45189
  optional: false
44874
45190
  }],
45191
+ "humiditySensor.getStatus": [{
45192
+ name: "deviceId",
45193
+ form: "single",
45194
+ optional: false
45195
+ }],
45196
+ "image.getStatus": [{
45197
+ name: "deviceId",
45198
+ form: "single",
45199
+ optional: false
45200
+ }],
44875
45201
  "imageSettings.getOptions": [{
44876
45202
  name: "deviceId",
44877
45203
  form: "single",
44878
45204
  optional: false
44879
45205
  }],
45206
+ "imageSettings.getStatus": [{
45207
+ name: "deviceId",
45208
+ form: "single",
45209
+ optional: false
45210
+ }],
44880
45211
  "imageSettings.setSettings": [{
44881
45212
  name: "deviceId",
44882
45213
  form: "single",
@@ -44887,6 +45218,11 @@ Object.freeze({
44887
45218
  form: "single",
44888
45219
  optional: false
44889
45220
  }],
45221
+ "intercom.getStatus": [{
45222
+ name: "deviceId",
45223
+ form: "single",
45224
+ optional: false
45225
+ }],
44890
45226
  "intercom.handleAnswer": [{
44891
45227
  name: "deviceId",
44892
45228
  form: "single",
@@ -44917,6 +45253,11 @@ Object.freeze({
44917
45253
  form: "single",
44918
45254
  optional: false
44919
45255
  }],
45256
+ "lawnMowerControl.getStatus": [{
45257
+ name: "deviceId",
45258
+ form: "single",
45259
+ optional: false
45260
+ }],
44920
45261
  "lawnMowerControl.pause": [{
44921
45262
  name: "deviceId",
44922
45263
  form: "single",
@@ -44927,6 +45268,11 @@ Object.freeze({
44927
45268
  form: "single",
44928
45269
  optional: false
44929
45270
  }],
45271
+ "lockControl.getStatus": [{
45272
+ name: "deviceId",
45273
+ form: "single",
45274
+ optional: false
45275
+ }],
44930
45276
  "lockControl.lock": [{
44931
45277
  name: "deviceId",
44932
45278
  form: "single",
@@ -44942,6 +45288,11 @@ Object.freeze({
44942
45288
  form: "single",
44943
45289
  optional: false
44944
45290
  }],
45291
+ "mediaPlayer.getStatus": [{
45292
+ name: "deviceId",
45293
+ form: "single",
45294
+ optional: false
45295
+ }],
44945
45296
  "mediaPlayer.next": [{
44946
45297
  name: "deviceId",
44947
45298
  form: "single",
@@ -45002,6 +45353,11 @@ Object.freeze({
45002
45353
  form: "single",
45003
45354
  optional: false
45004
45355
  }],
45356
+ "motion.getStatus": [{
45357
+ name: "deviceId",
45358
+ form: "single",
45359
+ optional: false
45360
+ }],
45005
45361
  "motion.isDetected": [{
45006
45362
  name: "deviceId",
45007
45363
  form: "single",
@@ -45012,11 +45368,31 @@ Object.freeze({
45012
45368
  form: "single",
45013
45369
  optional: false
45014
45370
  }],
45371
+ "motionDetection.applyDeviceSettingsPatch": [{
45372
+ name: "deviceId",
45373
+ form: "single",
45374
+ optional: false
45375
+ }],
45376
+ "motionDetection.getDeviceLiveContribution": [{
45377
+ name: "deviceId",
45378
+ form: "single",
45379
+ optional: false
45380
+ }],
45381
+ "motionDetection.getDeviceSettingsContribution": [{
45382
+ name: "deviceId",
45383
+ form: "single",
45384
+ optional: false
45385
+ }],
45015
45386
  "motionDetection.removeCamera": [{
45016
45387
  name: "deviceId",
45017
45388
  form: "single",
45018
45389
  optional: false
45019
45390
  }],
45391
+ "motionTrigger.getStatus": [{
45392
+ name: "deviceId",
45393
+ form: "single",
45394
+ optional: false
45395
+ }],
45020
45396
  "motionTrigger.setMotionTrigger": [{
45021
45397
  name: "deviceId",
45022
45398
  form: "single",
@@ -45027,6 +45403,11 @@ Object.freeze({
45027
45403
  form: "single",
45028
45404
  optional: false
45029
45405
  }],
45406
+ "motionZones.getStatus": [{
45407
+ name: "deviceId",
45408
+ form: "single",
45409
+ optional: false
45410
+ }],
45030
45411
  "motionZones.setZone": [{
45031
45412
  name: "deviceId",
45032
45413
  form: "single",
@@ -45037,6 +45418,11 @@ Object.freeze({
45037
45418
  form: "single",
45038
45419
  optional: false
45039
45420
  }],
45421
+ "nativeObjectDetection.getStatus": [{
45422
+ name: "deviceId",
45423
+ form: "single",
45424
+ optional: false
45425
+ }],
45040
45426
  "nativeObjectDetection.setEnabled": [{
45041
45427
  name: "deviceId",
45042
45428
  form: "single",
@@ -45052,6 +45438,11 @@ Object.freeze({
45052
45438
  form: "single",
45053
45439
  optional: false
45054
45440
  }],
45441
+ "navigation.getStatus": [{
45442
+ name: "deviceId",
45443
+ form: "single",
45444
+ optional: false
45445
+ }],
45055
45446
  "navigation.goToPoint": [{
45056
45447
  name: "deviceId",
45057
45448
  form: "single",
@@ -45097,6 +45488,11 @@ Object.freeze({
45097
45488
  form: "single",
45098
45489
  optional: false
45099
45490
  }],
45491
+ "networkLink.getStatus": [{
45492
+ name: "deviceId",
45493
+ form: "single",
45494
+ optional: false
45495
+ }],
45100
45496
  "networkQuality.getDeviceStats": [{
45101
45497
  name: "deviceId",
45102
45498
  form: "single",
@@ -45107,6 +45503,11 @@ Object.freeze({
45107
45503
  form: "single",
45108
45504
  optional: false
45109
45505
  }],
45506
+ "notificationRules.getAlarmSensors": [{
45507
+ name: "deviceIds",
45508
+ form: "array",
45509
+ optional: false
45510
+ }],
45110
45511
  "notificationRules.setDeviceMuted": [{
45111
45512
  name: "deviceId",
45112
45513
  form: "single",
@@ -45117,11 +45518,26 @@ Object.freeze({
45117
45518
  form: "single",
45118
45519
  optional: false
45119
45520
  }],
45521
+ "notifier.getStatus": [{
45522
+ name: "deviceId",
45523
+ form: "single",
45524
+ optional: false
45525
+ }],
45120
45526
  "notifier.send": [{
45121
45527
  name: "deviceId",
45122
45528
  form: "single",
45123
45529
  optional: false
45124
45530
  }],
45531
+ "numericSensor.getStatus": [{
45532
+ name: "deviceId",
45533
+ form: "single",
45534
+ optional: false
45535
+ }],
45536
+ "osd.getStatus": [{
45537
+ name: "deviceId",
45538
+ form: "single",
45539
+ optional: false
45540
+ }],
45125
45541
  "osd.setOverlay": [{
45126
45542
  name: "deviceId",
45127
45543
  form: "single",
@@ -45181,6 +45597,11 @@ Object.freeze({
45181
45597
  form: "single",
45182
45598
  optional: false
45183
45599
  }],
45600
+ "petFeeder.getStatus": [{
45601
+ name: "deviceId",
45602
+ form: "single",
45603
+ optional: false
45604
+ }],
45184
45605
  "petFeeder.markFoodReplenished": [{
45185
45606
  name: "deviceId",
45186
45607
  form: "single",
@@ -45216,6 +45637,11 @@ Object.freeze({
45216
45637
  form: "single",
45217
45638
  optional: false
45218
45639
  }],
45640
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
45641
+ name: "deviceId",
45642
+ form: "single",
45643
+ optional: false
45644
+ }],
45219
45645
  "pipelineAnalytics.clearTrackRelation": [{
45220
45646
  name: "deviceId",
45221
45647
  form: "single",
@@ -45256,6 +45682,16 @@ Object.freeze({
45256
45682
  form: "single",
45257
45683
  optional: false
45258
45684
  }],
45685
+ "pipelineAnalytics.getDeviceLiveContribution": [{
45686
+ name: "deviceId",
45687
+ form: "single",
45688
+ optional: false
45689
+ }],
45690
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
45691
+ name: "deviceId",
45692
+ form: "single",
45693
+ optional: false
45694
+ }],
45259
45695
  "pipelineAnalytics.getEventDensity": [{
45260
45696
  name: "deviceId",
45261
45697
  form: "single",
@@ -45471,6 +45907,11 @@ Object.freeze({
45471
45907
  form: "single",
45472
45908
  optional: true
45473
45909
  }],
45910
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
45911
+ name: "deviceId",
45912
+ form: "single",
45913
+ optional: false
45914
+ }],
45474
45915
  "pipelineOrchestrator.assignAudio": [{
45475
45916
  name: "deviceId",
45476
45917
  form: "single",
@@ -45516,6 +45957,16 @@ Object.freeze({
45516
45957
  form: "single",
45517
45958
  optional: false
45518
45959
  }],
45960
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
45961
+ name: "deviceId",
45962
+ form: "single",
45963
+ optional: false
45964
+ }],
45965
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
45966
+ name: "deviceId",
45967
+ form: "single",
45968
+ optional: false
45969
+ }],
45519
45970
  "pipelineOrchestrator.getPipelineAssignment": [{
45520
45971
  name: "deviceId",
45521
45972
  form: "single",
@@ -45636,11 +46087,31 @@ Object.freeze({
45636
46087
  form: "single",
45637
46088
  optional: true
45638
46089
  }],
46090
+ "powerMeter.getStatus": [{
46091
+ name: "deviceId",
46092
+ form: "single",
46093
+ optional: false
46094
+ }],
46095
+ "presence.getStatus": [{
46096
+ name: "deviceId",
46097
+ form: "single",
46098
+ optional: false
46099
+ }],
46100
+ "pressureSensor.getStatus": [{
46101
+ name: "deviceId",
46102
+ form: "single",
46103
+ optional: false
46104
+ }],
45639
46105
  "privacyMask.getOptions": [{
45640
46106
  name: "deviceId",
45641
46107
  form: "single",
45642
46108
  optional: false
45643
46109
  }],
46110
+ "privacyMask.getStatus": [{
46111
+ name: "deviceId",
46112
+ form: "single",
46113
+ optional: false
46114
+ }],
45644
46115
  "privacyMask.setAudioEnabled": [{
45645
46116
  name: "deviceId",
45646
46117
  form: "single",
@@ -45691,6 +46162,11 @@ Object.freeze({
45691
46162
  form: "single",
45692
46163
  optional: false
45693
46164
  }],
46165
+ "ptz.getStatus": [{
46166
+ name: "deviceId",
46167
+ form: "single",
46168
+ optional: false
46169
+ }],
45694
46170
  "ptz.goHome": [{
45695
46171
  name: "deviceId",
45696
46172
  form: "single",
@@ -45781,6 +46257,11 @@ Object.freeze({
45781
46257
  form: "single",
45782
46258
  optional: false
45783
46259
  }],
46260
+ "recordingArchive.applyDeviceSettingsPatch": [{
46261
+ name: "deviceId",
46262
+ form: "single",
46263
+ optional: false
46264
+ }],
45784
46265
  "recordingArchive.deleteFootprint": [{
45785
46266
  name: "deviceId",
45786
46267
  form: "single",
@@ -45801,6 +46282,21 @@ Object.freeze({
45801
46282
  form: "single",
45802
46283
  optional: false
45803
46284
  }],
46285
+ "recordingArchive.getDeviceLiveContribution": [{
46286
+ name: "deviceId",
46287
+ form: "single",
46288
+ optional: false
46289
+ }],
46290
+ "recordingArchive.getDeviceSettingsContribution": [{
46291
+ name: "deviceId",
46292
+ form: "single",
46293
+ optional: false
46294
+ }],
46295
+ "recordingArchive.getStatus": [{
46296
+ name: "deviceId",
46297
+ form: "single",
46298
+ optional: false
46299
+ }],
45804
46300
  "recordingArchive.listOpsLog": [{
45805
46301
  name: "deviceId",
45806
46302
  form: "single",
@@ -45886,6 +46382,11 @@ Object.freeze({
45886
46382
  form: "single",
45887
46383
  optional: false
45888
46384
  }],
46385
+ "recordingOnboard.getStatus": [{
46386
+ name: "deviceId",
46387
+ form: "single",
46388
+ optional: false
46389
+ }],
45889
46390
  "recordingOnboard.setSettings": [{
45890
46391
  name: "deviceId",
45891
46392
  form: "single",
@@ -45916,6 +46417,11 @@ Object.freeze({
45916
46417
  form: "single",
45917
46418
  optional: false
45918
46419
  }],
46420
+ "sceneMonitor.getStatus": [{
46421
+ name: "deviceId",
46422
+ form: "single",
46423
+ optional: false
46424
+ }],
45919
46425
  "sceneMonitor.listScenes": [{
45920
46426
  name: "deviceId",
45921
46427
  form: "single",
@@ -45941,6 +46447,11 @@ Object.freeze({
45941
46447
  form: "single",
45942
46448
  optional: false
45943
46449
  }],
46450
+ "scriptRunner.getStatus": [{
46451
+ name: "deviceId",
46452
+ form: "single",
46453
+ optional: false
46454
+ }],
45944
46455
  "scriptRunner.run": [{
45945
46456
  name: "deviceId",
45946
46457
  form: "single",
@@ -45951,11 +46462,31 @@ Object.freeze({
45951
46462
  form: "single",
45952
46463
  optional: false
45953
46464
  }],
46465
+ "smoke.getStatus": [{
46466
+ name: "deviceId",
46467
+ form: "single",
46468
+ optional: false
46469
+ }],
46470
+ "snapshot.applyDeviceSettingsPatch": [{
46471
+ name: "deviceId",
46472
+ form: "single",
46473
+ optional: false
46474
+ }],
45954
46475
  "snapshot.getDebugState": [{
45955
46476
  name: "deviceId",
45956
46477
  form: "single",
45957
46478
  optional: false
45958
46479
  }],
46480
+ "snapshot.getDeviceLiveContribution": [{
46481
+ name: "deviceId",
46482
+ form: "single",
46483
+ optional: false
46484
+ }],
46485
+ "snapshot.getDeviceSettingsContribution": [{
46486
+ name: "deviceId",
46487
+ form: "single",
46488
+ optional: false
46489
+ }],
45959
46490
  "snapshot.getSnapshot": [{
45960
46491
  name: "deviceId",
45961
46492
  form: "single",
@@ -45972,6 +46503,11 @@ Object.freeze({
45972
46503
  form: "array",
45973
46504
  optional: false
45974
46505
  }],
46506
+ "snapshot.getStatus": [{
46507
+ name: "deviceId",
46508
+ form: "single",
46509
+ optional: false
46510
+ }],
45975
46511
  "snapshot.invalidateCache": [{
45976
46512
  name: "deviceId",
45977
46513
  form: "single",
@@ -45982,6 +46518,11 @@ Object.freeze({
45982
46518
  form: "single",
45983
46519
  optional: false
45984
46520
  }],
46521
+ "streamBroker.applyDeviceSettingsPatch": [{
46522
+ name: "deviceId",
46523
+ form: "single",
46524
+ optional: false
46525
+ }],
45985
46526
  "streamBroker.assignProfile": [{
45986
46527
  name: "deviceId",
45987
46528
  form: "single",
@@ -45997,6 +46538,16 @@ Object.freeze({
45997
46538
  form: "single",
45998
46539
  optional: false
45999
46540
  }],
46541
+ "streamBroker.getDeviceLiveContribution": [{
46542
+ name: "deviceId",
46543
+ form: "single",
46544
+ optional: false
46545
+ }],
46546
+ "streamBroker.getDeviceSettingsContribution": [{
46547
+ name: "deviceId",
46548
+ form: "single",
46549
+ optional: false
46550
+ }],
46000
46551
  "streamBroker.getStreamWithCodec": [{
46001
46552
  name: "deviceId",
46002
46553
  form: "single",
@@ -46057,16 +46608,46 @@ Object.freeze({
46057
46608
  form: "single",
46058
46609
  optional: false
46059
46610
  }],
46611
+ "streamParams.getStatus": [{
46612
+ name: "deviceId",
46613
+ form: "single",
46614
+ optional: false
46615
+ }],
46060
46616
  "streamParams.setProfile": [{
46061
46617
  name: "deviceId",
46062
46618
  form: "single",
46063
46619
  optional: false
46064
46620
  }],
46621
+ "switch.getStatus": [{
46622
+ name: "deviceId",
46623
+ form: "single",
46624
+ optional: false
46625
+ }],
46065
46626
  "switch.setState": [{
46066
46627
  name: "deviceId",
46067
46628
  form: "single",
46068
46629
  optional: false
46069
46630
  }],
46631
+ "tamper.getStatus": [{
46632
+ name: "deviceId",
46633
+ form: "single",
46634
+ optional: false
46635
+ }],
46636
+ "temperatureSensor.getStatus": [{
46637
+ name: "deviceId",
46638
+ form: "single",
46639
+ optional: false
46640
+ }],
46641
+ "update.getStatus": [{
46642
+ name: "deviceId",
46643
+ form: "single",
46644
+ optional: false
46645
+ }],
46646
+ "vacuumControl.getStatus": [{
46647
+ name: "deviceId",
46648
+ form: "single",
46649
+ optional: false
46650
+ }],
46070
46651
  "vacuumControl.locate": [{
46071
46652
  name: "deviceId",
46072
46653
  form: "single",
@@ -46102,6 +46683,11 @@ Object.freeze({
46102
46683
  form: "single",
46103
46684
  optional: false
46104
46685
  }],
46686
+ "valve.getStatus": [{
46687
+ name: "deviceId",
46688
+ form: "single",
46689
+ optional: false
46690
+ }],
46105
46691
  "valve.open": [{
46106
46692
  name: "deviceId",
46107
46693
  form: "single",
@@ -46117,6 +46703,11 @@ Object.freeze({
46117
46703
  form: "single",
46118
46704
  optional: false
46119
46705
  }],
46706
+ "vibration.getStatus": [{
46707
+ name: "deviceId",
46708
+ form: "single",
46709
+ optional: false
46710
+ }],
46120
46711
  "videoclips.dialClipStream": [{
46121
46712
  name: "deviceId",
46122
46713
  form: "single",
@@ -46152,6 +46743,11 @@ Object.freeze({
46152
46743
  form: "single",
46153
46744
  optional: false
46154
46745
  }],
46746
+ "waterHeater.getStatus": [{
46747
+ name: "deviceId",
46748
+ form: "single",
46749
+ optional: false
46750
+ }],
46155
46751
  "waterHeater.setAway": [{
46156
46752
  name: "deviceId",
46157
46753
  form: "single",
@@ -46167,6 +46763,11 @@ Object.freeze({
46167
46763
  form: "single",
46168
46764
  optional: false
46169
46765
  }],
46766
+ "weather.getStatus": [{
46767
+ name: "deviceId",
46768
+ form: "single",
46769
+ optional: false
46770
+ }],
46170
46771
  "webrtcSession.addIceCandidate": [{
46171
46772
  name: "deviceId",
46172
46773
  form: "single",
@@ -46904,7 +47505,7 @@ var PreviewTextField = string().max(200);
46904
47505
  * Whether the notification's preview is a STILL or a short animation.
46905
47506
  *
46906
47507
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
46907
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
47508
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
46908
47509
  * night reads better as three seconds of motion than as one frame of it. Both
46909
47510
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
46910
47511
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -47042,6 +47643,7 @@ TimelapseRuleInputSchema.extend({
47042
47643
  createdAt: number(),
47043
47644
  updatedAt: number()
47044
47645
  });
47646
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47045
47647
  object({
47046
47648
  /**
47047
47649
  * Fraction of the box's own size added on EACH side before cutting.