@camstack/addon-provider-rtsp 1.2.116 → 1.2.118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +1450 -681
  2. package/dist/addon.mjs +1450 -681
  3. package/package.json +2 -4
package/dist/addon.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import net from "node:net";
2
- //#region ../types/dist/event-category-C5xZWqz6.mjs
2
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -532,6 +532,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
532
532
  */
533
533
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
534
534
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
535
+ /**
536
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
537
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
538
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
539
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
540
+ * for a camera with no attached sensor). Providers must not emit
541
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
542
+ * keeps it so (camera-grid is a named, deferred exception).
543
+ */
535
544
  EventCategory["ProviderMotion"] = "provider.motion";
536
545
  EventCategory["ProviderDetection"] = "provider.detection";
537
546
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5370,7 +5379,7 @@ var ZodIssueCode = {
5370
5379
  var ZodFirstPartyTypeKind;
5371
5380
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5372
5381
  //#endregion
5373
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5382
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5374
5383
  /**
5375
5384
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5376
5385
  * 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
@@ -9122,22 +9130,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9122
9130
  "package"
9123
9131
  ];
9124
9132
  /**
9125
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9126
- * un operatore può selezionare.
9133
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9134
+ * an operator can select.
9127
9135
  *
9128
- * Sono le tre offerte dallo step `object-detection`
9136
+ * They are the three offered by the `object-detection` step
9129
9137
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9130
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9131
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9132
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9133
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9138
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9139
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9140
+ * `package-detection`, whose enablement is driven by zones, and offering the
9141
+ * same word twice has already made an operator turn on the COCO proxy
9134
9142
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9135
9143
  *
9136
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9137
- * dello step e una seconda volta come union `FirstLevelMacro`
9138
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9139
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9140
- * successiva.
9144
+ * ONE list. Before today the same three were hand-written in the step's offer
9145
+ * and a second time as the union `FirstLevelMacro`
9146
+ * (`types/detection.ts`); a third copy for the recording trigger
9147
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9148
+ * invisible.
9141
9149
  */
9142
9150
  var FIRST_LEVEL_MACRO_CLASSES = [
9143
9151
  "person",
@@ -9202,17 +9210,17 @@ var RecordingStorageModeSchema = _enum([
9202
9210
  "on-device-decision"
9203
9211
  ]);
9204
9212
  /**
9205
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9206
- * tre offerte dallo step `object-detection`, da UNA lista
9213
+ * The macro classes that can open a recording window — the same
9214
+ * three offered by the `object-detection` step, from ONE list
9207
9215
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9208
9216
  */
9209
9217
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9210
9218
  /**
9211
- * True quando `values` non ripete un elemento.
9219
+ * True when `values` does not repeat an element.
9212
9220
  *
9213
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9214
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9215
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9221
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9222
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9223
+ * would count its windows twice in `segmentMissedByMs`.
9216
9224
  */
9217
9225
  var noDuplicates = (values) => new Set(values).size === values.length;
9218
9226
  /** Which detectors trigger an `events`-mode band. */
@@ -9220,34 +9228,34 @@ var RecordingTriggersSchema = object({
9220
9228
  motion: boolean().optional(),
9221
9229
  audioThresholdDbfs: number().optional(),
9222
9230
  /**
9223
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9224
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9225
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9226
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9231
+ * The macro classes whose detection opens a window. ABSENT = the source
9232
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9233
+ * trigger on, no class" is the same shape "enabled and records
9234
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9227
9235
  *
9228
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9229
- * quelle che hanno attraversato `enabledMacroClasses`, i
9230
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9231
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9232
- * finestre — vedi `recorder/object-trigger.ts`.
9236
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9237
+ * those that went through `enabledMacroClasses`, the
9238
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9239
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9240
+ * windows — see `recorder/object-trigger.ts`.
9233
9241
  */
9234
9242
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9235
9243
  /**
9236
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9237
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9244
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9245
+ * is not listened to; an empty array is rejected for the same reason as
9238
9246
  * `objectClasses`.
9239
9247
  *
9240
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9241
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9242
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9243
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9244
- * device (D12) — mai un elenco globale di cap.
9248
+ * These are SOURCE device ids, not cameras: the band names them, so the
9249
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9250
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9251
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9252
+ * device (D12) — never a global list of caps.
9245
9253
  *
9246
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9247
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9248
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9249
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9250
- * registrare.
9254
+ * What "high" means depends on the device TYPE and is not decided here:
9255
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9256
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9257
+ * the level: a contact found already open at runner restart does not
9258
+ * record.
9251
9259
  */
9252
9260
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9253
9261
  });
@@ -10324,7 +10332,7 @@ var DecoderSessionConfigSchema = object({
10324
10332
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10325
10333
  * thing: the logging settings document on the `system` cap. Two authorities
10326
10334
  * over the values is the exact defect
10327
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10335
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10328
10336
  * remove; re-introducing it from the cure side would be grotesque.
10329
10337
  *
10330
10338
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -11600,6 +11608,68 @@ method(object({
11600
11608
  password: string()
11601
11609
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
11602
11610
  /**
11611
+ * How long one snapshot may take end to end: the pinned copy of the whole
11612
+ * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
11613
+ * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
11614
+ * generous because a timeout fails the whole backup run.
11615
+ */
11616
+ var DATABASE_SNAPSHOT_TIMEOUT_MS = 30 * 6e4;
11617
+ /**
11618
+ * The databases an owner snapshots — declared by the OWNER, so a caller never
11619
+ * guesses which file is live from what sits beside it (a cleanly closed
11620
+ * database has no sidecars and is just as owned). Paths are absolute on the
11621
+ * owner's host.
11622
+ */
11623
+ var DatabaseSnapshotTargetsSchema = object({ databases: array(object({ livePath: string() })) });
11624
+ /** One b-tree table of the snapshot and the rows it holds. */
11625
+ var DatabaseSnapshotTableSchema = object({
11626
+ name: string(),
11627
+ rows: number().int().nonnegative()
11628
+ });
11629
+ /**
11630
+ * What the owner wrote. Every path is ABSOLUTE on the owner's host — the
11631
+ * caller pins the call to its own node, so it can read the file; a caller
11632
+ * that finds the path outside its own data root refuses it rather than
11633
+ * archiving something it cannot place.
11634
+ */
11635
+ var DatabaseSnapshotResultSchema = object({
11636
+ /** The live database the snapshot copies. Never to be copied as a file. */
11637
+ livePath: string(),
11638
+ /** The verified copy: `<livePath>` + {@link DATABASE_SNAPSHOT_SUFFIX}. */
11639
+ snapshotPath: string(),
11640
+ sizeBytes: number().int().nonnegative(),
11641
+ pageCount: number().int().nonnegative(),
11642
+ pageSize: number().int().positive(),
11643
+ /**
11644
+ * Every b-tree table with its row count. Read inside the SAME read
11645
+ * transaction the copy was taken from, and found EQUAL on the copy — so
11646
+ * this is the content of the snapshot, not an estimate of the source.
11647
+ */
11648
+ tables: array(DatabaseSnapshotTableSchema),
11649
+ /** Wall time of the pinned copy (row counts included). */
11650
+ copyMs: number().nonnegative(),
11651
+ /** Wall time of the checks on the copy (`integrity_check`, row counts). */
11652
+ verifyMs: number().nonnegative(),
11653
+ /** When the pinned read transaction began (ms epoch) — the snapshot's instant. */
11654
+ takenAt: number()
11655
+ });
11656
+ /**
11657
+ * A snapshot as an ARCHIVE records it (`ArchiveManifest.sqliteSnapshots`):
11658
+ * the same facts, with both paths relative to the archive root (the node's
11659
+ * data dir), because an archive outlives the host that made it.
11660
+ */
11661
+ var ArchiveSqliteSnapshotSchema = DatabaseSnapshotResultSchema.extend({
11662
+ /** Archive-relative path of the live database the archive does NOT carry. */
11663
+ livePath: string(),
11664
+ /** Archive-relative path of the verified copy it carries in its place. */
11665
+ snapshotPath: string()
11666
+ });
11667
+ method(object({}), DatabaseSnapshotTargetsSchema, { auth: "admin" }), method(object({}), DatabaseSnapshotResultSchema, {
11668
+ kind: "mutation",
11669
+ auth: "admin",
11670
+ timeoutMs: DATABASE_SNAPSHOT_TIMEOUT_MS
11671
+ });
11672
+ /**
11603
11673
  * Orchestrator-side destination metadata. The orchestrator computes
11604
11674
  * `id = <addonId>:<subId>` from its provider lookup so consumers
11605
11675
  * (admin UI, restore flow) see one canonical key.
@@ -11710,7 +11780,12 @@ var ArchiveManifestSchema = object({
11710
11780
  locations: array(string()),
11711
11781
  entries: array(ArchiveEntrySchema),
11712
11782
  totalBytes: number(),
11713
- totalFiles: number()
11783
+ totalFiles: number(),
11784
+ /**
11785
+ * Live SQLite databases this archive carries as their OWNER's verified
11786
+ * snapshot instead of as files (D693). Absent on archives made before.
11787
+ */
11788
+ sqliteSnapshots: array(ArchiveSqliteSnapshotSchema).optional()
11714
11789
  });
11715
11790
  var LocationStatSchema = object({
11716
11791
  name: string(),
@@ -11968,7 +12043,7 @@ var BrokerStatusEnum = _enum([
11968
12043
  "unreachable",
11969
12044
  "error"
11970
12045
  ]);
11971
- var BrokerInfoSchema$1 = object({
12046
+ var BrokerInfoSchema = object({
11972
12047
  /** Stable broker id. Persisted; survives addon restarts. */
11973
12048
  id: string(),
11974
12049
  /** Addon id of the provider that OWNS this broker.
@@ -12101,7 +12176,7 @@ var GetStateInputSchema = object({
12101
12176
  * HA: entity_id (returns the cached entity state). */
12102
12177
  key: string()
12103
12178
  });
12104
- method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12179
+ method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
12105
12180
  kind: "mutation",
12106
12181
  auth: "admin"
12107
12182
  }), method(RemoveInputSchema, _void(), {
@@ -13236,242 +13311,6 @@ var CompositionBlockStateSchema = object({
13236
13311
  reason: string().optional(),
13237
13312
  fields: array(CompositionFieldStateSchema)
13238
13313
  });
13239
- var NUMBER = {
13240
- kind: "fixed",
13241
- result: ["number"]
13242
- };
13243
- var BOOLEAN = {
13244
- kind: "fixed",
13245
- result: ["boolean"]
13246
- };
13247
- Object.freeze({
13248
- min: NUMBER,
13249
- max: NUMBER,
13250
- abs: NUMBER,
13251
- floor: NUMBER,
13252
- ceil: NUMBER,
13253
- sqrt: NUMBER,
13254
- round: NUMBER,
13255
- pow: NUMBER,
13256
- clamp: NUMBER,
13257
- avg: NUMBER,
13258
- sum: NUMBER,
13259
- age: NUMBER,
13260
- convert: NUMBER,
13261
- coalesce: { kind: "coalesce" },
13262
- any: BOOLEAN,
13263
- all: BOOLEAN,
13264
- within: BOOLEAN,
13265
- latest: {
13266
- kind: "fixed",
13267
- result: ["number", "null"]
13268
- },
13269
- number: {
13270
- kind: "fixed",
13271
- result: ["number", "null"]
13272
- },
13273
- rose: {
13274
- kind: "fixed",
13275
- result: ["number", "null"]
13276
- },
13277
- count: NUMBER
13278
- });
13279
- `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
13280
- /**
13281
- * What a composition may BUILD on a device type (G2, D676): the caps a new
13282
- * device of that type, or an existing one that lacks them, can be given. The
13283
- * blockers are the validator's own refusal codes, and a spec compares this
13284
- * listing with `planComposition` for every capability — the editor can never
13285
- * offer a feature the save would refuse for the same reason.
13286
- */
13287
- var ComposableFieldSchema = object({
13288
- path: string(),
13289
- kind: CompositionFieldKindSchema,
13290
- nullable: boolean(),
13291
- required: boolean(),
13292
- enumValues: array(string()).optional()
13293
- });
13294
- var ComposableCapabilitiesSchema = object({ caps: array(object({
13295
- cap: string(),
13296
- fields: array(ComposableFieldSchema),
13297
- /** Present for an item-array cap (`consumables.items`). */
13298
- itemArray: object({
13299
- path: string(),
13300
- keyField: string()
13301
- }).optional()
13302
- })) });
13303
- /** Where a block runs. The operator chooses — a block driving a device on an
13304
- * agent is the reason placement is not fixed to the hub. */
13305
- var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
13306
- /** What a block's process is doing. Mirrors the addon runner's own lifecycle so
13307
- * a failing block reads the same way a failing addon does. `degraded` is a
13308
- * composition on an existing device with a field handed back to the native
13309
- * provider while its source is unavailable (S1b, D663); the error names it. */
13310
- var CoreBlockStatusSchema = _enum([
13311
- "stopped",
13312
- "starting",
13313
- "running",
13314
- "degraded",
13315
- "failed"
13316
- ]);
13317
- /** Client-authored fields. */
13318
- var CoreBlockInputSchema = object({
13319
- name: string().min(1).max(120),
13320
- /** TypeScript source. Compiled server-side before it is ever stored — a
13321
- * block that does not compile is a fork failure the operator would meet
13322
- * minutes later, in a log, instead of in the editor. */
13323
- code: string().max(2e5),
13324
- enabled: boolean(),
13325
- placement: CoreBlockPlacementSchema,
13326
- /**
13327
- * Integration the block's devices hang from. Absent = the system integration
13328
- * blocks share. A block may declare its own instead.
13329
- */
13330
- integrationId: string().optional()
13331
- });
13332
- /**
13333
- * What a block IS (D659). Absent means `code` — every row stored before this
13334
- * union keeps working unchanged.
13335
- * - `code`: the TypeScript in `code`, compiled and run in its own runner;
13336
- * - `recipe`: RESERVED (automations), stored shape only; refused at run by name;
13337
- * - `composition`: declarative, run by the shared `composer`; `code` is `''`.
13338
- */
13339
- var CoreBlockSourceSchema = discriminatedUnion("kind", [
13340
- object({ kind: literal("code") }),
13341
- object({
13342
- kind: literal("recipe"),
13343
- recipe: AutomationRecipeSchema
13344
- }),
13345
- object({
13346
- kind: literal("composition"),
13347
- composition: CompositionSchema
13348
- })
13349
- ]);
13350
- /**
13351
- * The client-authored half of a composition block. No `code`, no placement (hub-only; Phase 7).
13352
- *
13353
- * `name`: REQUIRED for a `new` target — the block's name IS the device's name
13354
- * (D659). ABSENT for an `existing` target — a customization has no name of its
13355
- * own; the server derives one (`customizationBlockName`, D663) and refuses a
13356
- * client-sent name by name.
13357
- */
13358
- var CompositionBlockInputSchema = object({
13359
- name: string().min(1).max(120).optional(),
13360
- enabled: boolean(),
13361
- composition: CompositionSchema
13362
- });
13363
- /** A stored block. */
13364
- var CoreBlockSchema = CoreBlockInputSchema.extend({
13365
- id: string(),
13366
- createdAt: number(),
13367
- updatedAt: number(),
13368
- /** Server-stamped author. */
13369
- createdBy: string(),
13370
- status: CoreBlockStatusSchema,
13371
- /**
13372
- * Why the block is not running, when it is not. The operator's ONLY window
13373
- * into a block that failed at load — a block that is silently absent is the
13374
- * failure mode this whole feature has to avoid.
13375
- */
13376
- lastError: string().optional(),
13377
- /** Ms epoch of the last state change. */
13378
- lastChangedAt: number(),
13379
- /** What the block is. Absent means `code` (D659). */
13380
- source: CoreBlockSourceSchema.optional()
13381
- });
13382
- /** A stored composition block and what the composer says about it. */
13383
- var CompositionViewSchema = object({
13384
- block: CoreBlockSchema,
13385
- state: discriminatedUnion("kind", [object({
13386
- kind: literal("answered"),
13387
- state: CompositionBlockStateSchema.nullable()
13388
- }), object({
13389
- kind: literal("unreachable"),
13390
- error: string()
13391
- })])
13392
- });
13393
- /**
13394
- * A read of the stored compositions. `not-loaded` while the `core_blocks`
13395
- * table has not been READ: an empty answer then would make "not yet known"
13396
- * look like "none", and invite the operator to recreate what exists (D315).
13397
- */
13398
- var CompositionsNotLoadedSchema = object({
13399
- state: literal("not-loaded"),
13400
- reason: string()
13401
- });
13402
- var CompositionListReadSchema = discriminatedUnion("state", [object({
13403
- state: literal("loaded"),
13404
- views: array(CompositionViewSchema)
13405
- }), CompositionsNotLoadedSchema]);
13406
- var CompositionGetReadSchema = discriminatedUnion("state", [object({
13407
- state: literal("loaded"),
13408
- view: CompositionViewSchema.nullable()
13409
- }), CompositionsNotLoadedSchema]);
13410
- /** The customization of one existing device, found by its TARGET, or `not-loaded` (D315). */
13411
- var CompositionCustomizationReadSchema = discriminatedUnion("state", [object({
13412
- state: literal("loaded"),
13413
- view: CompositionViewSchema.nullable()
13414
- }), CompositionsNotLoadedSchema]);
13415
- /** What a compile attempt produced. */
13416
- var CoreBlockCompileResultSchema = object({
13417
- ok: boolean(),
13418
- /** Present when `ok` is false — the first error, in the author's words. */
13419
- error: string().optional(),
13420
- line: number().optional(),
13421
- column: number().optional()
13422
- });
13423
- method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }), method(object({ blockId: string() }), object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }), method(object({ block: CoreBlockInputSchema }), object({ block: CoreBlockSchema }), {
13424
- kind: "mutation",
13425
- auth: "admin",
13426
- caller: "required"
13427
- }), method(object({
13428
- blockId: string(),
13429
- block: CoreBlockInputSchema.partial()
13430
- }), object({ block: CoreBlockSchema }), {
13431
- kind: "mutation",
13432
- auth: "admin",
13433
- caller: "required"
13434
- }), method(object({ blockId: string() }), object({ success: literal(true) }), {
13435
- kind: "mutation",
13436
- auth: "admin"
13437
- }), method(object({
13438
- blockId: string(),
13439
- enabled: boolean()
13440
- }), object({ block: CoreBlockSchema }), {
13441
- kind: "mutation",
13442
- auth: "admin"
13443
- }), method(object({ blockId: string() }), object({ block: CoreBlockSchema }), {
13444
- kind: "mutation",
13445
- auth: "admin"
13446
- }), method(object({ code: string() }), CoreBlockCompileResultSchema, {
13447
- kind: "mutation",
13448
- auth: "admin"
13449
- }), method(object({}), object({ libs: array(object({
13450
- filePath: string(),
13451
- content: string()
13452
- })) }), { auth: "admin" }), method(object({ block: CompositionBlockInputSchema }), object({ block: CoreBlockSchema }), {
13453
- kind: "mutation",
13454
- auth: "admin",
13455
- caller: "required"
13456
- }), method(object({
13457
- blockId: string(),
13458
- expectedUpdatedAt: number(),
13459
- block: CompositionBlockInputSchema.partial()
13460
- }), object({ block: CoreBlockSchema }), {
13461
- kind: "mutation",
13462
- auth: "admin",
13463
- caller: "required"
13464
- }), method(object({ blockId: string() }), CompositionGetReadSchema, { auth: "admin" }), method(object({}), CompositionListReadSchema, { auth: "admin" }), method(object({
13465
- addonId: string().min(1),
13466
- stableId: string().min(1)
13467
- }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({ deviceType: _enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }), method(object({
13468
- composition: CompositionSchema,
13469
- blockId: string().optional()
13470
- }), CompositionValidationSchema, {
13471
- kind: "mutation",
13472
- auth: "admin",
13473
- access: "view"
13474
- });
13475
13314
  var ComposerBlockSchema = object({
13476
13315
  id: string().min(1),
13477
13316
  name: string().min(1).max(120),
@@ -13518,6 +13357,242 @@ method(object({
13518
13357
  kind: "mutation",
13519
13358
  auth: "admin"
13520
13359
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
13360
+ var NUMBER = {
13361
+ kind: "fixed",
13362
+ result: ["number"]
13363
+ };
13364
+ var BOOLEAN = {
13365
+ kind: "fixed",
13366
+ result: ["boolean"]
13367
+ };
13368
+ Object.freeze({
13369
+ min: NUMBER,
13370
+ max: NUMBER,
13371
+ abs: NUMBER,
13372
+ floor: NUMBER,
13373
+ ceil: NUMBER,
13374
+ sqrt: NUMBER,
13375
+ round: NUMBER,
13376
+ pow: NUMBER,
13377
+ clamp: NUMBER,
13378
+ avg: NUMBER,
13379
+ sum: NUMBER,
13380
+ age: NUMBER,
13381
+ convert: NUMBER,
13382
+ coalesce: { kind: "coalesce" },
13383
+ any: BOOLEAN,
13384
+ all: BOOLEAN,
13385
+ within: BOOLEAN,
13386
+ latest: {
13387
+ kind: "fixed",
13388
+ result: ["number", "null"]
13389
+ },
13390
+ number: {
13391
+ kind: "fixed",
13392
+ result: ["number", "null"]
13393
+ },
13394
+ rose: {
13395
+ kind: "fixed",
13396
+ result: ["number", "null"]
13397
+ },
13398
+ count: NUMBER
13399
+ });
13400
+ `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
13401
+ /**
13402
+ * What a composition may BUILD on a device type (G2, D676): the caps a new
13403
+ * device of that type, or an existing one that lacks them, can be given. The
13404
+ * blockers are the validator's own refusal codes, and a spec compares this
13405
+ * listing with `planComposition` for every capability — the editor can never
13406
+ * offer a feature the save would refuse for the same reason.
13407
+ */
13408
+ var ComposableFieldSchema = object({
13409
+ path: string(),
13410
+ kind: CompositionFieldKindSchema,
13411
+ nullable: boolean(),
13412
+ required: boolean(),
13413
+ enumValues: array(string()).optional()
13414
+ });
13415
+ var ComposableCapabilitiesSchema = object({ caps: array(object({
13416
+ cap: string(),
13417
+ fields: array(ComposableFieldSchema),
13418
+ /** Present for an item-array cap (`consumables.items`). */
13419
+ itemArray: object({
13420
+ path: string(),
13421
+ keyField: string()
13422
+ }).optional()
13423
+ })) });
13424
+ /** Where a block runs. The operator chooses — a block driving a device on an
13425
+ * agent is the reason placement is not fixed to the hub. */
13426
+ var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
13427
+ /** What a block's process is doing. Mirrors the addon runner's own lifecycle so
13428
+ * a failing block reads the same way a failing addon does. `degraded` is a
13429
+ * composition on an existing device with a field handed back to the native
13430
+ * provider while its source is unavailable (S1b, D663); the error names it. */
13431
+ var CoreBlockStatusSchema = _enum([
13432
+ "stopped",
13433
+ "starting",
13434
+ "running",
13435
+ "degraded",
13436
+ "failed"
13437
+ ]);
13438
+ /** Client-authored fields. */
13439
+ var CoreBlockInputSchema = object({
13440
+ name: string().min(1).max(120),
13441
+ /** TypeScript source. Compiled server-side before it is ever stored — a
13442
+ * block that does not compile is a fork failure the operator would meet
13443
+ * minutes later, in a log, instead of in the editor. */
13444
+ code: string().max(2e5),
13445
+ enabled: boolean(),
13446
+ placement: CoreBlockPlacementSchema,
13447
+ /**
13448
+ * Integration the block's devices hang from. Absent = the system integration
13449
+ * blocks share. A block may declare its own instead.
13450
+ */
13451
+ integrationId: string().optional()
13452
+ });
13453
+ /**
13454
+ * What a block IS (D659). Absent means `code` — every row stored before this
13455
+ * union keeps working unchanged.
13456
+ * - `code`: the TypeScript in `code`, compiled and run in its own runner;
13457
+ * - `recipe`: RESERVED (automations), stored shape only; refused at run by name;
13458
+ * - `composition`: declarative, run by the shared `composer`; `code` is `''`.
13459
+ */
13460
+ var CoreBlockSourceSchema = discriminatedUnion("kind", [
13461
+ object({ kind: literal("code") }),
13462
+ object({
13463
+ kind: literal("recipe"),
13464
+ recipe: AutomationRecipeSchema
13465
+ }),
13466
+ object({
13467
+ kind: literal("composition"),
13468
+ composition: CompositionSchema
13469
+ })
13470
+ ]);
13471
+ /**
13472
+ * The client-authored half of a composition block. No `code`, no placement (hub-only; Phase 7).
13473
+ *
13474
+ * `name`: REQUIRED for a `new` target — the block's name IS the device's name
13475
+ * (D659). ABSENT for an `existing` target — a customization has no name of its
13476
+ * own; the server derives one (`customizationBlockName`, D663) and refuses a
13477
+ * client-sent name by name.
13478
+ */
13479
+ var CompositionBlockInputSchema = object({
13480
+ name: string().min(1).max(120).optional(),
13481
+ enabled: boolean(),
13482
+ composition: CompositionSchema
13483
+ });
13484
+ /** A stored block. */
13485
+ var CoreBlockSchema = CoreBlockInputSchema.extend({
13486
+ id: string(),
13487
+ createdAt: number(),
13488
+ updatedAt: number(),
13489
+ /** Server-stamped author. */
13490
+ createdBy: string(),
13491
+ status: CoreBlockStatusSchema,
13492
+ /**
13493
+ * Why the block is not running, when it is not. The operator's ONLY window
13494
+ * into a block that failed at load — a block that is silently absent is the
13495
+ * failure mode this whole feature has to avoid.
13496
+ */
13497
+ lastError: string().optional(),
13498
+ /** Ms epoch of the last state change. */
13499
+ lastChangedAt: number(),
13500
+ /** What the block is. Absent means `code` (D659). */
13501
+ source: CoreBlockSourceSchema.optional()
13502
+ });
13503
+ /** A stored composition block and what the composer says about it. */
13504
+ var CompositionViewSchema = object({
13505
+ block: CoreBlockSchema,
13506
+ state: discriminatedUnion("kind", [object({
13507
+ kind: literal("answered"),
13508
+ state: CompositionBlockStateSchema.nullable()
13509
+ }), object({
13510
+ kind: literal("unreachable"),
13511
+ error: string()
13512
+ })])
13513
+ });
13514
+ /**
13515
+ * A read of the stored compositions. `not-loaded` while the `core_blocks`
13516
+ * table has not been READ: an empty answer then would make "not yet known"
13517
+ * look like "none", and invite the operator to recreate what exists (D315).
13518
+ */
13519
+ var CompositionsNotLoadedSchema = object({
13520
+ state: literal("not-loaded"),
13521
+ reason: string()
13522
+ });
13523
+ var CompositionListReadSchema = discriminatedUnion("state", [object({
13524
+ state: literal("loaded"),
13525
+ views: array(CompositionViewSchema)
13526
+ }), CompositionsNotLoadedSchema]);
13527
+ var CompositionGetReadSchema = discriminatedUnion("state", [object({
13528
+ state: literal("loaded"),
13529
+ view: CompositionViewSchema.nullable()
13530
+ }), CompositionsNotLoadedSchema]);
13531
+ /** The customization of one existing device, found by its TARGET, or `not-loaded` (D315). */
13532
+ var CompositionCustomizationReadSchema = discriminatedUnion("state", [object({
13533
+ state: literal("loaded"),
13534
+ view: CompositionViewSchema.nullable()
13535
+ }), CompositionsNotLoadedSchema]);
13536
+ /** What a compile attempt produced. */
13537
+ var CoreBlockCompileResultSchema = object({
13538
+ ok: boolean(),
13539
+ /** Present when `ok` is false — the first error, in the author's words. */
13540
+ error: string().optional(),
13541
+ line: number().optional(),
13542
+ column: number().optional()
13543
+ });
13544
+ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }), method(object({ blockId: string() }), object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }), method(object({ block: CoreBlockInputSchema }), object({ block: CoreBlockSchema }), {
13545
+ kind: "mutation",
13546
+ auth: "admin",
13547
+ caller: "required"
13548
+ }), method(object({
13549
+ blockId: string(),
13550
+ block: CoreBlockInputSchema.partial()
13551
+ }), object({ block: CoreBlockSchema }), {
13552
+ kind: "mutation",
13553
+ auth: "admin",
13554
+ caller: "required"
13555
+ }), method(object({ blockId: string() }), object({ success: literal(true) }), {
13556
+ kind: "mutation",
13557
+ auth: "admin"
13558
+ }), method(object({
13559
+ blockId: string(),
13560
+ enabled: boolean()
13561
+ }), object({ block: CoreBlockSchema }), {
13562
+ kind: "mutation",
13563
+ auth: "admin"
13564
+ }), method(object({ blockId: string() }), object({ block: CoreBlockSchema }), {
13565
+ kind: "mutation",
13566
+ auth: "admin"
13567
+ }), method(object({ code: string() }), CoreBlockCompileResultSchema, {
13568
+ kind: "mutation",
13569
+ auth: "admin"
13570
+ }), method(object({}), object({ libs: array(object({
13571
+ filePath: string(),
13572
+ content: string()
13573
+ })) }), { auth: "admin" }), method(object({ block: CompositionBlockInputSchema }), object({ block: CoreBlockSchema }), {
13574
+ kind: "mutation",
13575
+ auth: "admin",
13576
+ caller: "required"
13577
+ }), method(object({
13578
+ blockId: string(),
13579
+ expectedUpdatedAt: number(),
13580
+ block: CompositionBlockInputSchema.partial()
13581
+ }), object({ block: CoreBlockSchema }), {
13582
+ kind: "mutation",
13583
+ auth: "admin",
13584
+ caller: "required"
13585
+ }), method(object({ blockId: string() }), CompositionGetReadSchema, { auth: "admin" }), method(object({}), CompositionListReadSchema, { auth: "admin" }), method(object({
13586
+ addonId: string().min(1),
13587
+ stableId: string().min(1)
13588
+ }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({ deviceType: _enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }), method(object({
13589
+ composition: CompositionSchema,
13590
+ blockId: string().optional()
13591
+ }), CompositionValidationSchema, {
13592
+ kind: "mutation",
13593
+ auth: "admin",
13594
+ access: "view"
13595
+ });
13521
13596
  /**
13522
13597
  * `custom-model-registry` — collection cap exposing operator-registered
13523
13598
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -13812,7 +13887,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13812
13887
  *
13813
13888
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13814
13889
  * was wrong — corrected 2026-08-26 after the hop map
13815
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13890
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13816
13891
  * There is **no Zod parse at all** between the door and the engine: the
13817
13892
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13818
13893
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14675,7 +14750,17 @@ var SiteZoneSchema = object({
14675
14750
  var ProviderStatusSchema = object({
14676
14751
  connected: boolean(),
14677
14752
  deviceCount: number(),
14678
- error: string().optional()
14753
+ error: string().optional(),
14754
+ /**
14755
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14756
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14757
+ * which every provider bundles, so only a provider BUILT from D696-amended
14758
+ * code says it. ABSENT means an older provider that may still write it (raw
14759
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14760
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14761
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14762
+ */
14763
+ motionSliceWriter: literal("orchestrator").optional()
14679
14764
  });
14680
14765
  object({
14681
14766
  externalId: string(),
@@ -15554,6 +15639,121 @@ method(object({
15554
15639
  deviceIds: array(number()).readonly(),
15555
15640
  caps: array(string()).readonly().optional()
15556
15641
  }), record(string(), record(string(), unknown().nullable())));
15642
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15643
+ DeviceType.Camera;
15644
+ /** A slot's editable value, discriminated on `slot`. */
15645
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15646
+ DeviceType.Camera;
15647
+ var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15648
+ state: literal("unreachable"),
15649
+ reason: string()
15650
+ })]);
15651
+ /** One provider of slots — the D554 source-listing row. */
15652
+ var ExtensionSourceSchema = object({
15653
+ /** The routing prefix of every `slotRef` this provider serves. */
15654
+ source: string(),
15655
+ label: string(),
15656
+ /** Stamped by the dispatcher from the registry (D557). */
15657
+ addonId: string().optional(),
15658
+ availability: ExtensionSourceAvailabilitySchema
15659
+ });
15660
+ /**
15661
+ * How one picked source stands. `missing` — no such device (deleted, or an id
15662
+ * that never existed); `foreign` — a device the slot's owner cannot read from
15663
+ * (a source owned by another addon); `unavailable` — it exists but is not a
15664
+ * valid source now (a container, which is never a pick). A source in any of
15665
+ * these states is SHOWN, never silently dropped or re-pointed.
15666
+ */
15667
+ var ExtensionSourceHealthSchema = _enum([
15668
+ "ok",
15669
+ "missing",
15670
+ "foreign",
15671
+ "unavailable"
15672
+ ]);
15673
+ var ExtensionSlotSourceSchema = object({
15674
+ deviceId: number().int(),
15675
+ /** The device's name, or null when it could not be resolved (`missing`). */
15676
+ name: string().nullable(),
15677
+ health: ExtensionSourceHealthSchema,
15678
+ detail: string().optional()
15679
+ });
15680
+ /**
15681
+ * A DIFFERENT authority that gates whether the slot's sources do anything —
15682
+ * reported read-only, named, never written through this contract (D62) — e.g.
15683
+ * a slot whose sources act only while a wrapper is bound to the host, a
15684
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15685
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15686
+ * not be read; it is not "inactive" (D315).
15687
+ */
15688
+ var ExtensionSlotActivationSchema = object({
15689
+ state: _enum([
15690
+ "active",
15691
+ "inactive",
15692
+ "unknown"
15693
+ ]),
15694
+ authority: string(),
15695
+ detail: string().optional()
15696
+ });
15697
+ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15698
+ state: literal("ok"),
15699
+ /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15700
+ slotRef: string(),
15701
+ slot: ExtensionSlotIdSchema,
15702
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15703
+ authority: string(),
15704
+ activation: ExtensionSlotActivationSchema.nullable(),
15705
+ value: ExtensionSlotValueSchema,
15706
+ sources: array(ExtensionSlotSourceSchema),
15707
+ /**
15708
+ * Devices the owner links to the host ON ITS OWN, by structure, and that
15709
+ * are therefore not in `value` (D655): a camera's device-tree children, and
15710
+ * the child a picked container stands for (D356 — a container is never a
15711
+ * pick, its children are). Read-only; the editor shows them and may offer
15712
+ * per-device options on them (the linked-devices tracked-event opt-in).
15713
+ * Absent when the owner links nothing beyond the picks, and from an owner
15714
+ * older than D655.
15715
+ */
15716
+ impliedSources: array(ExtensionSlotSourceSchema).optional()
15717
+ }), object({
15718
+ state: literal("unreachable"),
15719
+ addonId: string(),
15720
+ reason: string()
15721
+ })]);
15722
+ var ExtensionCandidatesSchema = object({ candidates: array(object({
15723
+ deviceId: number().int(),
15724
+ name: string(),
15725
+ type: string(),
15726
+ location: string().nullable(),
15727
+ /** The container a child belongs to — "Spotlight 2" alone does not say which. */
15728
+ parentName: string().nullable(),
15729
+ /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
15730
+ suggested: boolean(),
15731
+ /** Why it is suggested ("same location"), null when it is not. */
15732
+ reason: string().nullable()
15733
+ })) });
15734
+ /**
15735
+ * What a write did. `none` — applied in place. `host` / `parent` — the owner
15736
+ * reloaded the host (or, for an accessory child, its parent) because the
15737
+ * write changed what the host REGISTERS. A slot never adds a capability to its
15738
+ * host — that is a composition (D663) — so today's owners answer `none`.
15739
+ */
15740
+ var ExtensionSetResultSchema = object({
15741
+ applied: literal(true),
15742
+ reload: _enum([
15743
+ "none",
15744
+ "host",
15745
+ "parent"
15746
+ ])
15747
+ });
15748
+ var ExtensionSlotRefInputSchema = object({
15749
+ deviceId: number().int(),
15750
+ slotRef: string().min(1)
15751
+ });
15752
+ var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
15753
+ method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)), method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)), method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema), method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
15754
+ kind: "mutation",
15755
+ auth: "admin"
15756
+ });
15557
15757
  /**
15558
15758
  * Per-field ownership (D663). A composition may CLAIM fields of a native
15559
15759
  * cap's runtime-state slice on an existing device: `replace` overlays the
@@ -15777,7 +15977,7 @@ method(object({
15777
15977
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15778
15978
  * ends in a log line, and a log line is the thing the operator asked to stop
15779
15979
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15780
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15980
+ * 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
15781
15981
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15782
15982
  * media blackout were both diagnosed. The counters stay; this is where they can
15783
15983
  * be READ.
@@ -17153,7 +17353,7 @@ var BrokerStatusSchema$1 = _enum([
17153
17353
  "unreachable",
17154
17354
  "tls-error"
17155
17355
  ]);
17156
- var BrokerInfoSchema = object({
17356
+ var BrokerInfoSchema$1 = object({
17157
17357
  id: string(),
17158
17358
  name: string(),
17159
17359
  url: string(),
@@ -17216,7 +17416,7 @@ var StatusSchema = object({
17216
17416
  brokerCount: number(),
17217
17417
  embeddedRunning: boolean()
17218
17418
  });
17219
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17419
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17220
17420
  kind: "mutation",
17221
17421
  auth: "admin"
17222
17422
  }), method(IdInputSchema, _void(), {
@@ -18564,17 +18764,25 @@ var NcScheduleSchema = object({
18564
18764
  */
18565
18765
  var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
18566
18766
  /**
18767
+ * How a summary window is SENT (D698): `rule` (absent) = ONE notification with
18768
+ * every section; `section` = one notification per section with something to
18769
+ * say (an unavailable section included), each titled by its section.
18770
+ */
18771
+ var NcStateSummarySplitSchema = _enum(["rule", "section"]);
18772
+ /**
18567
18773
  * `summary`: the rule sends NOTHING per event. When a window of `schedule`
18568
18774
  * CLOSES, it sends one notification with the current state of its kinds
18569
18775
  * (low batteries, consumables to replace, devices offline, updates
18570
- * available, ...), after its own filters. Nothing to report sends nothing.
18776
+ * available, ...), after its own filters — or, with `split: 'section'`
18777
+ * (D698), one notification per section. Nothing to report sends nothing.
18571
18778
  * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
18572
18779
  * midnight crossing); the send time is each window's END. Refused on the
18573
18780
  * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
18574
18781
  */
18575
18782
  var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
18576
18783
  mode: literal("summary"),
18577
- schedule: NcScheduleSchema
18784
+ schedule: NcScheduleSchema,
18785
+ split: NcStateSummarySplitSchema.optional()
18578
18786
  })]);
18579
18787
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
18580
18788
  var NcPlateMatcherSchema = object({
@@ -18714,8 +18922,14 @@ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
18714
18922
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
18715
18923
  var NcZoneConditionSchema = object({
18716
18924
  ids: array(string().min(1)).min(1),
18717
- /** Quantifier over `ids` — at least one / every one visited. */
18718
- match: _enum(["any", "all"]).default("any")
18925
+ /**
18926
+ * LEGACY quantifier over `ids`. Absent = `any` (at least one visited), which
18927
+ * is what both editors mean and all they write since D690: "all of" was
18928
+ * removed from the editors because on an immediate rule it asked one
18929
+ * detection to stand in every zone at once. Still ACCEPTED, and `all` still
18930
+ * evaluated, so a stored rule keeps parsing and meaning what it meant.
18931
+ */
18932
+ match: _enum(["any", "all"]).optional()
18719
18933
  });
18720
18934
  /**
18721
18935
  * The P1 condition set — a flat AND of groups; absent group = pass;
@@ -19219,10 +19433,12 @@ var NcMediaPolicySchema = object({
19219
19433
  /**
19220
19434
  * Cooldown GRANULARITY over the subject's class — how much a fired
19221
19435
  * notification suppresses.
19222
- * - `shared` (default, and the absent value) — one window for the whole
19223
- * rule/scope: a cat silences the next dog for `cooldownSec`.
19224
- * - `per-class` — an independent window per detected class, so cat→dog fires
19225
- * at once and cat→cat still waits.
19436
+ * - `per-class` (default, and the absent value since 2026-10-01 — D36) — an
19437
+ * independent window per detected class: cat→dog fires at once, cat→cat
19438
+ * still waits.
19439
+ * - `shared` — one window for the whole rule/scope: a cat silences the next
19440
+ * dog for `cooldownSec`. Only written when an operator picks it; it is no
19441
+ * longer what an unset rule means.
19226
19442
  *
19227
19443
  * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
19228
19444
  * must not be swallowed by a bark's window (the precedent this generalizes —
@@ -19237,8 +19453,11 @@ var NcThrottleSchema = object({
19237
19453
  /**
19238
19454
  * Class granularity of the cooldown key. Optional rather than defaulted:
19239
19455
  * a Zod default does NOT run on the addon→addon cap path, so a persisted
19240
- * rule authored before this field simply carries none — and the engine
19241
- * reads absent as `shared`, the pre-existing behaviour.
19456
+ * rule authored before this field simply carries none. The engine used to
19457
+ * read absent as `shared` (D36); since 2026-10-01 it reads absent as
19458
+ * `per-class` — the operator's default — and only an explicit `shared`
19459
+ * still means `shared`. See `cooldownKey` / `keysPerClass` in the rule
19460
+ * engine, and D36's dated note.
19242
19461
  */
19243
19462
  granularity: NcThrottleGranularitySchema.optional()
19244
19463
  });
@@ -19411,78 +19630,16 @@ var NcRuleInputSchema = object({
19411
19630
  */
19412
19631
  confirm: NcConfirmSchema.optional(),
19413
19632
  /**
19414
- * WAIT for face/plate recognition before saying anything.
19415
- *
19416
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19417
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19418
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19419
- * measured on this hub — and an `immediate` rule enqueues on the first object
19420
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19421
- * immediate path, and no amount of media re-resolution fixes a sentence.
19422
- *
19423
- * Only two honest answers exist, and this flag picks between them. It has
19424
- * effect ONLY on a rule that declares a recognition scope
19425
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19426
- * other rule there is nothing to wait for and the flag is inert.
19427
- *
19428
- * | value | what happens |
19429
- * | --- | --- |
19430
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19431
- * | 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) |
19432
- *
19433
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19434
- * on the addon cap path, and absent has to keep meaning exactly what every
19435
- * rule authored before this field meant.
19436
- *
19437
- * The cost of `true` is stated here because the editor states it too: a rule
19438
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19439
- * tests every zone the track visited and a `crossing` condition can no longer
19440
- * be satisfied, because a closed track carries no crossing.
19441
- */
19442
- waitForEnhancement: boolean().optional(),
19443
- /**
19444
- * GROUP a burst of subjects into ONE notification that grows.
19445
- *
19446
- * Seconds of quiet after the last matching subject before the burst is
19447
- * considered over. While it is open, the first subject enqueues immediately —
19448
- * **exactly as today, with no added latency** — and every real growth (a new
19449
- * subject, or a name confirmed on one already in it) REPLACES that
19450
- * notification with an updated one naming everybody. The push carries the
19451
- * group's own coalescing tag, so the phone replaces rather than stacks.
19452
- *
19453
- * `0` / absent = off, and off is today's behaviour byte for byte.
19454
- *
19455
- * ### Why an idle cutoff and not a window
19456
- *
19457
- * The measured seven-person arrival on device 590 spans 110 s with every
19458
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19459
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19460
- * 30 s is a widely shipped value for the same decision.
19461
- *
19462
- * ### What it replaces
19463
- *
19464
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19465
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19466
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19467
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19468
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19469
- * budget over GROUPS — which is what it always meant — and a growth is never
19470
- * throttled by the window its own first member spent.
19471
- *
19472
- * ### Interaction with {@link waitForEnhancement}
19473
- *
19474
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19475
- * TRACK CLOSE, so with both set the group is opened by the first member to
19476
- * CLOSE — already carrying its name — and grows as later members close. That
19477
- * is later, and complete. With grouping alone the group opens on the first
19478
- * object event and picks up names as they are confirmed, through the growth
19479
- * path. Neither combination fires twice for one subject.
19480
- *
19481
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19482
- * on the addon cap path, so absent must keep meaning what it meant before this
19483
- * field existed.
19633
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19634
+ * seconds; the rule sends ONE message when the window closes — early, once every
19635
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19636
+ * the rule matched, with one mosaic tile per subject.
19637
+ * Absent = plain immediate (one message per track, at the first match).
19638
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19639
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19640
+ * path does not run Zod defaults.
19484
19641
  */
19485
- groupIdleSec: number().int().min(0).max(600).optional()
19642
+ groupingSec: number().int().min(5).max(60).optional()
19486
19643
  });
19487
19644
  /**
19488
19645
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19530,9 +19687,10 @@ var NcRuleClearableKeySchema = _enum([
19530
19687
  "targetUsers",
19531
19688
  "snoozeOptions",
19532
19689
  "snoozeAllowGlobal",
19533
- "waitForEnhancement",
19534
19690
  "actions",
19535
19691
  "confirm",
19692
+ "groupingSec",
19693
+ "waitForEnhancement",
19536
19694
  "groupIdleSec"
19537
19695
  ]);
19538
19696
  var NcRuleUpdateInputSchema = object({
@@ -19554,19 +19712,47 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19554
19712
  */
19555
19713
  disabledTargetIds: array(string()).default([])
19556
19714
  });
19715
+ /**
19716
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19717
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19718
+ * job is not an open state, and the alarm stays instant.
19719
+ */
19720
+ var NcStateSummarySectionIdSchema = _enum([
19721
+ "device-state",
19722
+ "streams",
19723
+ "battery",
19724
+ "consumables",
19725
+ "tanks",
19726
+ "detection-blind",
19727
+ "nodes",
19728
+ "addons",
19729
+ "updates"
19730
+ ]);
19557
19731
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19558
19732
  var NcStateSummaryPreviewSectionSchema = object({
19559
- id: string(),
19733
+ id: NcStateSummarySectionIdSchema,
19560
19734
  status: _enum(["read", "unavailable"]),
19561
19735
  items: number().int().nonnegative(),
19562
19736
  reason: string().optional()
19563
19737
  });
19564
19738
  /**
19739
+ * One notification a previewed summary would send (D698): the whole summary on
19740
+ * a `rule` split (no `section`), one section's on a `section` split.
19741
+ */
19742
+ var NcStateSummaryPreviewRowSchema = object({
19743
+ section: NcStateSummarySectionIdSchema.optional(),
19744
+ /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19745
+ title: string(),
19746
+ text: string(),
19747
+ count: number().int().nonnegative()
19748
+ });
19749
+ /**
19565
19750
  * The summary a rule would send now. `empty` = nothing to report and every
19566
19751
  * section read: such a summary is NOT sent. `dropped` counts the open states
19567
19752
  * the rule's own filters (or a camera mute) left out, by reason.
19568
19753
  * `excludedGroups` names the kind groups selected that have no state to report
19569
- * (backups & exports).
19754
+ * (backups & exports). `split` and `rows` (D698) say how it would be sent —
19755
+ * optional, so an older hub's answer still parses.
19570
19756
  */
19571
19757
  var NcStateSummaryPreviewSchema = object({
19572
19758
  empty: boolean(),
@@ -19574,7 +19760,9 @@ var NcStateSummaryPreviewSchema = object({
19574
19760
  text: string(),
19575
19761
  sections: array(NcStateSummaryPreviewSectionSchema),
19576
19762
  dropped: record(string(), number().int().nonnegative()),
19577
- excludedGroups: array(string())
19763
+ excludedGroups: array(string()),
19764
+ split: NcStateSummarySplitSchema.optional(),
19765
+ rows: array(NcStateSummaryPreviewRowSchema).optional()
19578
19766
  });
19579
19767
  var NcTestResultSchema = object({
19580
19768
  recordId: string(),
@@ -19989,6 +20177,29 @@ object({
19989
20177
  firstAt: number(),
19990
20178
  lastAt: number()
19991
20179
  });
20180
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
20181
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
20182
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
20183
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
20184
+ var NcAlarmModeDelaysListSchema = array(object({
20185
+ mode: AlarmArmModeSchema,
20186
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
20187
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
20188
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
20189
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20190
+ var NcAlarmModeDelaysEditListSchema = array(object({
20191
+ mode: AlarmArmModeSchema,
20192
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20193
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20194
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
20195
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20196
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20197
+ var NcAlarmNonBlockingEditSchema = object({
20198
+ mode: AlarmArmModeSchema,
20199
+ deviceId: number().int().positive(),
20200
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20201
+ nonBlocking: boolean()
20202
+ });
19992
20203
  /**
19993
20204
  * The per-mode lists, at most one per mode. Two lists for one mode have no
19994
20205
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -19997,10 +20208,10 @@ object({
19997
20208
  var NcAlarmNonBlockingListSchema = array(object({
19998
20209
  mode: AlarmArmModeSchema,
19999
20210
  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" });
20211
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20001
20212
  /**
20002
- * The three durations the panel's state machine runs on, who hears about its
20003
- * transitions, and which openings each mode tolerates.
20213
+ * The three durations — global, and per mode since D697 — the panel's state
20214
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20004
20215
  *
20005
20216
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20006
20217
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20010,15 +20221,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20010
20221
  */
20011
20222
  var NcAlarmSettingsSchema = object({
20012
20223
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20013
- exitDelaySec: number().int().min(0).max(600),
20224
+ exitDelaySec: NcAlarmDelaySecSchema,
20014
20225
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20015
- entryDelaySec: number().int().min(0).max(600),
20226
+ entryDelaySec: NcAlarmDelaySecSchema,
20016
20227
  /**
20017
20228
  * How long `triggered` lasts before the panel re-arms itself.
20018
20229
  * **0 = until an operator disarms it** — the behaviour before this field
20019
20230
  * existed, and therefore what an untouched install keeps doing.
20020
20231
  */
20021
- triggeredDurationSec: number().int().min(0).max(3600),
20232
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20022
20233
  /**
20023
20234
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20024
20235
  * remove after one release. Optional so an older admin's patch (which still
@@ -20041,17 +20252,30 @@ var NcAlarmSettingsSchema = object({
20041
20252
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20042
20253
  * parses as "every opening blocks" — the behaviour it was written under.
20043
20254
  */
20044
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20255
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20256
+ /**
20257
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20258
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20259
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20260
+ */
20261
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20045
20262
  });
20046
20263
  /**
20047
20264
  * Every field optional — a tab edits one control at a time.
20048
20265
  *
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).
20266
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20267
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20268
+ * named the field would parse to `[]` and wipe every mode's list on an
20269
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20270
+ * `modeDelays` means EDITS, not the stored overrides.
20053
20271
  */
20054
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20272
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20273
+ nonBlocking: NcAlarmNonBlockingListSchema,
20274
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20275
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20276
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20277
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20278
+ }).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
20279
  /**
20056
20280
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20057
20281
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20151,6 +20375,28 @@ var NcAlarmConfigSchema = object({
20151
20375
  */
20152
20376
  live: NcAlarmLiveSchema.optional()
20153
20377
  });
20378
+ var NcAlarmSensorReadingSchema = _enum([
20379
+ "open",
20380
+ "closed",
20381
+ "unknown",
20382
+ "not-opening"
20383
+ ]);
20384
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20385
+ var NcAlarmSensorStateSchema = object({
20386
+ deviceId: number().int().positive(),
20387
+ reading: NcAlarmSensorReadingSchema,
20388
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20389
+ state: string().nullable(),
20390
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20391
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20392
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20393
+ online: boolean().nullable()
20394
+ });
20395
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20396
+ var NcAlarmSensorsSchema = object({
20397
+ sensors: array(NcAlarmSensorStateSchema),
20398
+ readAt: number().int().nonnegative()
20399
+ });
20154
20400
  /**
20155
20401
  * ONE rule's demand on ONE camera's clip ring.
20156
20402
  *
@@ -20241,7 +20487,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20241
20487
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20242
20488
  kind: "mutation",
20243
20489
  auth: "admin"
20244
- });
20490
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20245
20491
  /**
20246
20492
  * Zod schemas for persisted record types.
20247
20493
  *
@@ -20588,12 +20834,26 @@ var CameraSensorRefSchema = object({
20588
20834
  function cameraSensorRefKey(ref) {
20589
20835
  return `${ref.addonId}/${ref.stableId}`;
20590
20836
  }
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) => {
20837
+ /**
20838
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20839
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20840
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20841
+ * camera's own motion counts only while a confirm sensor is active — with or
20842
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20843
+ */
20844
+ var CameraSensorModeSchema = _enum([
20845
+ "in-addition",
20846
+ "confirm",
20847
+ "replace"
20848
+ ]);
20849
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20850
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20851
+ /**
20852
+ * A camera's attached sensors. A CS-1 row carried an envelope
20853
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20854
+ * what the write stores (D672 rule 3), so it is never written again.
20855
+ */
20856
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20597
20857
  const seen = /* @__PURE__ */ new Set();
20598
20858
  value.sensors.forEach((ref, index) => {
20599
20859
  const key = cameraSensorRefKey(ref);
@@ -20605,39 +20865,41 @@ var CameraSensorsConfigSchema = object({
20605
20865
  seen.add(key);
20606
20866
  });
20607
20867
  });
20608
- var RefFields = {
20868
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20869
+ var AttachmentFields = {
20609
20870
  addonId: string(),
20610
- stableId: string()
20871
+ stableId: string(),
20872
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20611
20873
  };
20612
20874
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20613
20875
  object({
20614
- ...RefFields,
20876
+ ...AttachmentFields,
20615
20877
  state: literal("ready"),
20616
20878
  deviceId: number(),
20617
20879
  name: string(),
20618
20880
  caps: array(string()).readonly()
20619
20881
  }),
20620
20882
  object({
20621
- ...RefFields,
20883
+ ...AttachmentFields,
20622
20884
  state: literal("offline"),
20623
20885
  deviceId: number(),
20624
20886
  name: string(),
20625
20887
  reason: _enum(["offline", "disabled"])
20626
20888
  }),
20627
20889
  object({
20628
- ...RefFields,
20890
+ ...AttachmentFields,
20629
20891
  state: literal("missing"),
20630
20892
  reason: string()
20631
20893
  }),
20632
20894
  object({
20633
- ...RefFields,
20895
+ ...AttachmentFields,
20634
20896
  state: literal("unsupported"),
20635
20897
  deviceId: number(),
20636
20898
  name: string(),
20637
20899
  reason: string()
20638
20900
  }),
20639
20901
  object({
20640
- ...RefFields,
20902
+ ...AttachmentFields,
20641
20903
  state: literal("unreadable"),
20642
20904
  reason: string()
20643
20905
  })
@@ -20650,17 +20912,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20650
20912
  * say.
20651
20913
  */
20652
20914
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20915
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20916
+ var CameraOwnMotionSchema = _enum([
20917
+ "always",
20918
+ "when-confirmed",
20919
+ "never"
20920
+ ]);
20921
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20922
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20923
+ /**
20924
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20925
+ * never the effective value. The camera's `motion` slice is the effective one,
20926
+ * so a view of the camera's own row reads this.
20927
+ * - `atMs`: when the bridge received the camera's last own edge.
20928
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20929
+ * AND it is younger than the window the bridge counts the camera's own motion
20930
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20931
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20932
+ * within the window; a status read is a snapshot, so a live view re-asks.
20933
+ * Absent = this orchestrator has not received the camera's own motion since it
20934
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20935
+ */
20936
+ var CameraRawLevelSchema = object({
20937
+ detected: boolean(),
20938
+ atMs: number()
20939
+ });
20653
20940
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20654
- object({ state: literal("not-configured") }),
20941
+ object({
20942
+ state: literal("not-configured"),
20943
+ cameraLevel: CameraRawLevelSchema.optional()
20944
+ }),
20655
20945
  object({
20656
20946
  state: literal("unreadable"),
20657
- reason: string()
20947
+ reason: string(),
20948
+ cameraLevel: CameraRawLevelSchema.optional()
20658
20949
  }),
20659
20950
  object({
20660
20951
  state: literal("configured"),
20661
- mode: CameraSensorsModeSchema,
20952
+ cameraLevel: CameraRawLevelSchema.optional(),
20662
20953
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20663
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20954
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20955
+ cameraMotion: CameraOwnMotionSchema.optional(),
20956
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20957
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20664
20958
  })
20665
20959
  ]);
20666
20960
  Object.freeze({
@@ -21345,14 +21639,14 @@ var TrackSchema = object({
21345
21639
  * `derive-returned-null tile=standin`,
21346
21640
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21347
21641
  * 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
21642
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21349
21643
  * tile is a face/plate stand-in, a raster crop, or an icon.
21350
21644
  *
21351
21645
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21352
21646
  * the TILE ranked below native, so a track whose thumbnail landed natively
21353
21647
  * while its key frame missed carried no reason at all — three of the four
21354
21648
  * `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.
21649
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21356
21650
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21357
21651
  * lastFrame copy source`), so its absence is now on this line whatever the
21358
21652
  * tile ended up being.
@@ -21439,7 +21733,7 @@ var MotionEventSchema = object({
21439
21733
  /**
21440
21734
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21441
21735
  * 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
21736
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21443
21737
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21444
21738
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21445
21739
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -21963,6 +22257,7 @@ var KeyEventSchema = object({
21963
22257
  bestEventId: string(),
21964
22258
  /** Track lifetime in ms (lastSeen - firstSeen). */
21965
22259
  windowMs: number().optional(),
22260
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
21966
22261
  ...TrackFlagFields,
21967
22262
  ...TrackRetrainFields
21968
22263
  });
@@ -22495,7 +22790,8 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
22495
22790
  trackIds: array(string()).min(1)
22496
22791
  }), object({
22497
22792
  deleted: number().int(),
22498
- failed: array(string()).readonly()
22793
+ failed: array(string()).readonly(),
22794
+ sparedMediaIds: array(number().int()).readonly().optional()
22499
22795
  }), {
22500
22796
  kind: "mutation",
22501
22797
  auth: "admin"
@@ -23890,8 +24186,10 @@ var RunnerCameraConfigSchema = object({
23890
24186
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23891
24187
  * analyzer providers) running on decoded frames in this runner.
23892
24188
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
23893
- * analytics). The provider emits ProviderMotion
23894
- * events; the orchestrator forwards to `reportMotion`.
24189
+ * analytics). The provider emits its raw push as `ProviderMotion`
24190
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
24191
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
24192
+ * `reportMotion`.
23895
24193
  */
23896
24194
  motionSources: MotionSourcesSchema.default(["analyzer"]),
23897
24195
  /**
@@ -24068,7 +24366,15 @@ var RunnerLocalLoadSchema = object({
24068
24366
  hasGpu: boolean(),
24069
24367
  inferenceBackend: string().optional(),
24070
24368
  cpuCores: number().optional()
24071
- })
24369
+ }),
24370
+ /**
24371
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24372
+ * runner never writes it. ABSENT = a runner older than D696, which still
24373
+ * writes the slice from its phase machine; the orchestrator does not write
24374
+ * while any runner node is absent-or-unanswered, so one slice never has two
24375
+ * writers.
24376
+ */
24377
+ motionSliceWriter: literal("orchestrator").optional()
24072
24378
  });
24073
24379
  /**
24074
24380
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -24724,7 +25030,42 @@ var SetCameraSensorsInputSchema = object({
24724
25030
  /** `null` removes the attachment: the camera goes back to `not-configured`. */
24725
25031
  config: CameraSensorsConfigSchema.nullable()
24726
25032
  });
24727
- method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
25033
+ /**
25034
+ * Why an early occupancy look (D691) was not scheduled.
25035
+ *
25036
+ * - `unknown-camera` — no session was ever dispatched for it, so there is no
25037
+ * node and no config to attach a burst with.
25038
+ * - `out-of-range` — the ask is negative or further out than the orchestrator
25039
+ * will hold a timer for; the parked-object cadence is the answer there.
25040
+ * - `no-burst-producer` — this build has no burst producer at all.
25041
+ * - `already-looked` — this camera's ONE early look since its last session has
25042
+ * been spent. A look's own burst can re-arm a flickering flip, and a look
25043
+ * per arm would chain a burst every ~30 s forever (review H2); the next
25044
+ * real session re-arms the budget.
25045
+ */
25046
+ var OccupancyLookRefusalSchema = _enum([
25047
+ "unknown-camera",
25048
+ "out-of-range",
25049
+ "no-burst-producer",
25050
+ "already-looked"
25051
+ ]);
25052
+ method(object({
25053
+ deviceId: number().int().nonnegative(),
25054
+ /** How long from NOW until the pending flip's sustain window ends. RELATIVE,
25055
+ * so the caller's clock and the orchestrator's never have to agree. */
25056
+ afterMs: number().finite(),
25057
+ /** Why, for the log. */
25058
+ reason: string()
25059
+ }), object({
25060
+ scheduled: boolean(),
25061
+ /** When `scheduled`: how long until the burst is dialled. */
25062
+ dueInMs: number().optional(),
25063
+ /** When not `scheduled`: why. */
25064
+ refusedBecause: OccupancyLookRefusalSchema.optional()
25065
+ }), {
25066
+ kind: "mutation",
25067
+ auth: "admin"
25068
+ }), method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
24728
25069
  kind: "mutation",
24729
25070
  auth: "admin"
24730
25071
  }), method(StorageMigrationLeaseInputSchema, object({ resumed: literal(true) }), {
@@ -26802,7 +27143,7 @@ var ClipPlaybackSchema = object({
26802
27143
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26803
27144
  * time the camera is awake for its own reasons — is deliberately not a member:
26804
27145
  * it was proposed, it is free on the battery, and the operator declined it on
26805
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
27146
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26806
27147
  * D558 "Considered and not taken").
26807
27148
  */
26808
27149
  var ClipWakeSchema = _enum(["authorised"]);
@@ -27063,7 +27404,7 @@ DeviceType.Camera, method(object({
27063
27404
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
27064
27405
  * assumed the answer was a fan-out over everything a camera has. The
27065
27406
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
27066
- * solo"* — so the list is asked of one provider at a time and there is
27407
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
27067
27408
  * nothing to filter out of it.
27068
27409
  */
27069
27410
  provider: string().min(1)
@@ -27191,219 +27532,6 @@ DeviceType.Camera, method(object({
27191
27532
  auth: "protected"
27192
27533
  });
27193
27534
  /**
27194
- * Doorbell button cap. Two kinds of providers coexist behind this cap
27195
- * name (same pattern as `snapshot`):
27196
- *
27197
- * - **Native** providers: registered per-device by device-driver
27198
- * addons via `ctx.registerNativeCap` — either on a
27199
- * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
27200
- * or directly on the camera (Reolink registers at camera level).
27201
- * Emits an `onPressed` event every time the firmware pushes a
27202
- * ring; status tracks the last press and a pressCount since start
27203
- * (diagnostic).
27204
- *
27205
- * - **Wrapper** provider: the `virtual-doorbell` system builtin
27206
- * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
27207
- * device (contact / switch / event-emitter …) into a doorbell for
27208
- * a camera. `defaultActive: false` — the operator explicitly binds
27209
- * it per camera in the device-bindings UI, then picks the source
27210
- * device + trigger in the per-device settings.
27211
- *
27212
- * The DeviceEventPropagator re-emits `onPressed` on the camera parent
27213
- * — subscribers listening at the camera level receive ring events
27214
- * with `via[]` populated. No code on the parent needed.
27215
- */
27216
- var DoorbellStatusSchema = object({
27217
- /** Ms epoch of the last press. null = never observed since this provider started. */
27218
- lastPressedAt: number().nullable(),
27219
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
27220
- pressCountSinceStart: number()
27221
- });
27222
- var DoorbellPressEventSchema = object({
27223
- deviceId: number(),
27224
- timestamp: number()
27225
- });
27226
- /**
27227
- * The `doorbell-source` extension slot's editable value (D648): the camera's
27228
- * virtual-doorbell trigger devices, exactly what the wrapper's
27229
- * `doorbellSources` persists (as `{ deviceId }` records).
27230
- */
27231
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
27232
- /**
27233
- * The `doorbell-source` slot — which devices ring this camera's VIRTUAL
27234
- * doorbell, owned by the `virtual-doorbell` wrapper over its
27235
- * `doorbellSources` store (D648). A source rings when one of its
27236
- * `SOURCE_CAPS` rises. Whether the wrapper is bound to the camera at all is a
27237
- * different authority (`deviceManager.setWrapperActive`, D62) — the slot
27238
- * reports it, it never writes it.
27239
- */
27240
- var DOORBELL_SOURCE_SLOT = {
27241
- id: "doorbell-source",
27242
- label: "Doorbell trigger devices",
27243
- description: "Any of these becoming active rings this camera's virtual doorbell: a binary sensor when it goes active, a switch when it turns on.",
27244
- shape: "association",
27245
- hostDeviceTypes: [DeviceType.Camera],
27246
- sourceCaps: SOURCE_CAPS,
27247
- cardinality: "many",
27248
- value: DoorbellSourceSlotValueSchema
27249
- };
27250
- var doorbellCapability = {
27251
- name: "doorbell",
27252
- scope: "device",
27253
- deviceNative: true,
27254
- mode: "singleton",
27255
- kind: "wrapper",
27256
- defaultActive: false,
27257
- deviceTypes: [DeviceType.Button, DeviceType.Camera],
27258
- extensionSlots: [DOORBELL_SOURCE_SLOT],
27259
- methods: {},
27260
- events: {
27261
- /**
27262
- * Fires once per physical press. Reolink delivers via Baichuan
27263
- * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
27264
- * release/duration — it's a pulse.
27265
- */
27266
- onPressed: {
27267
- data: DoorbellPressEventSchema,
27268
- fromState: {
27269
- field: "lastPressedAt",
27270
- on: "advance"
27271
- }
27272
- } },
27273
- status: {
27274
- schema: DoorbellStatusSchema,
27275
- kind: "push"
27276
- },
27277
- /**
27278
- * Runtime-state slice — last press timestamp + lifetime press count.
27279
- * Mirrored by the kernel and readable via
27280
- * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
27281
- * without subscribing.
27282
- */
27283
- runtimeState: DoorbellStatusSchema,
27284
- /**
27285
- * Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
27286
- *
27287
- * See `RuntimeStateDurability`. Enforced by
27288
- * `scripts/check-runtime-state-durability.ts`.
27289
- */
27290
- durability: "restored"
27291
- };
27292
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
27293
- DeviceType.Camera, DeviceType.Camera;
27294
- /** A slot's editable value, discriminated on `slot`. */
27295
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
27296
- DeviceType.Camera;
27297
- var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
27298
- state: literal("unreachable"),
27299
- reason: string()
27300
- })]);
27301
- /** One provider of slots — the D554 source-listing row. */
27302
- var ExtensionSourceSchema = object({
27303
- /** The routing prefix of every `slotRef` this provider serves. */
27304
- source: string(),
27305
- label: string(),
27306
- /** Stamped by the dispatcher from the registry (D557). */
27307
- addonId: string().optional(),
27308
- availability: ExtensionSourceAvailabilitySchema
27309
- });
27310
- /**
27311
- * How one picked source stands. `missing` — no such device (deleted, or an id
27312
- * that never existed); `foreign` — a device the slot's owner cannot read from
27313
- * (a source owned by another addon); `unavailable` — it exists but is not a
27314
- * valid source now (a container, which is never a pick). A source in any of
27315
- * these states is SHOWN, never silently dropped or re-pointed.
27316
- */
27317
- var ExtensionSourceHealthSchema = _enum([
27318
- "ok",
27319
- "missing",
27320
- "foreign",
27321
- "unavailable"
27322
- ]);
27323
- var ExtensionSlotSourceSchema = object({
27324
- deviceId: number().int(),
27325
- /** The device's name, or null when it could not be resolved (`missing`). */
27326
- name: string().nullable(),
27327
- health: ExtensionSourceHealthSchema,
27328
- detail: string().optional()
27329
- });
27330
- /**
27331
- * A DIFFERENT authority that gates whether the slot's sources do anything —
27332
- * reported read-only, named, never written through this contract (D62). The
27333
- * virtual doorbell's sources ring only while the wrapper is bound to the
27334
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
27335
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
27336
- */
27337
- var ExtensionSlotActivationSchema = object({
27338
- state: _enum([
27339
- "active",
27340
- "inactive",
27341
- "unknown"
27342
- ]),
27343
- authority: string(),
27344
- detail: string().optional()
27345
- });
27346
- var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
27347
- state: literal("ok"),
27348
- /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
27349
- slotRef: string(),
27350
- slot: ExtensionSlotIdSchema,
27351
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
27352
- authority: string(),
27353
- activation: ExtensionSlotActivationSchema.nullable(),
27354
- value: ExtensionSlotValueSchema,
27355
- sources: array(ExtensionSlotSourceSchema),
27356
- /**
27357
- * Devices the owner links to the host ON ITS OWN, by structure, and that
27358
- * are therefore not in `value` (D655): a camera's device-tree children, and
27359
- * the child a picked container stands for (D356 — a container is never a
27360
- * pick, its children are). Read-only; the editor shows them and may offer
27361
- * per-device options on them (the linked-devices tracked-event opt-in).
27362
- * Absent when the owner links nothing beyond the picks, and from an owner
27363
- * older than D655.
27364
- */
27365
- impliedSources: array(ExtensionSlotSourceSchema).optional()
27366
- }), object({
27367
- state: literal("unreachable"),
27368
- addonId: string(),
27369
- reason: string()
27370
- })]);
27371
- var ExtensionCandidatesSchema = object({ candidates: array(object({
27372
- deviceId: number().int(),
27373
- name: string(),
27374
- type: string(),
27375
- location: string().nullable(),
27376
- /** The container a child belongs to — "Spotlight 2" alone does not say which. */
27377
- parentName: string().nullable(),
27378
- /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
27379
- suggested: boolean(),
27380
- /** Why it is suggested ("same location"), null when it is not. */
27381
- reason: string().nullable()
27382
- })) });
27383
- /**
27384
- * What a write did. `none` — applied in place. `host` / `parent` — the owner
27385
- * reloaded the host (or, for an accessory child, its parent) because the
27386
- * write changed what the host REGISTERS. A slot never adds a capability to its
27387
- * host — that is a composition (D663) — so today's owners answer `none`.
27388
- */
27389
- var ExtensionSetResultSchema = object({
27390
- applied: literal(true),
27391
- reload: _enum([
27392
- "none",
27393
- "host",
27394
- "parent"
27395
- ])
27396
- });
27397
- var ExtensionSlotRefInputSchema = object({
27398
- deviceId: number().int(),
27399
- slotRef: string().min(1)
27400
- });
27401
- var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
27402
- method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)), method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)), method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema), method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
27403
- kind: "mutation",
27404
- auth: "admin"
27405
- });
27406
- /**
27407
27535
  * Optional client-side hints sent at session creation to help the provider
27408
27536
  * pick the best native source. All fields optional — a viewer that knows
27409
27537
  * nothing still gets a sane default. (Relocated from the retired `webrtc`
@@ -29879,6 +30007,70 @@ onStatusChanged: { data: object({
29879
30007
  * whether persisting is worth a SQLite commit. */
29880
30008
  volatileStateFields: ["lastChangedAt"]
29881
30009
  };
30010
+ var DoorbellStatusSchema = object({
30011
+ /** Ms epoch of the last press. null = never observed since this provider started. */
30012
+ lastPressedAt: number().nullable(),
30013
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
30014
+ pressCountSinceStart: number()
30015
+ });
30016
+ var DoorbellPressEventSchema = object({
30017
+ deviceId: number(),
30018
+ timestamp: number()
30019
+ });
30020
+ /**
30021
+ * Doorbell button cap. Two kinds of provider serve it, and to every consumer
30022
+ * they are the same cap:
30023
+ *
30024
+ * - **Native** providers, registered per device by a driver addon through
30025
+ * `ctx.registerNativeCap` — a `DeviceType.Button` accessory with
30026
+ * `role: DeviceRole.Doorbell`, or the camera itself (Reolink registers at
30027
+ * camera level). They emit `onPressed` on every firmware ring.
30028
+ * - **A composed doorbell** (D670, D671): a camera's customization gives it
30029
+ * the cap, its `lastPressedAt` rising from the sources the operator
30030
+ * assigned in Customize → Doorbell, and the composer emits `onPressed`
30031
+ * from that field's advance (`fromState`).
30032
+ *
30033
+ * The DeviceEventPropagator re-emits `onPressed` on the camera parent.
30034
+ */
30035
+ var doorbellCapability = {
30036
+ name: "doorbell",
30037
+ scope: "device",
30038
+ deviceNative: true,
30039
+ mode: "singleton",
30040
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
30041
+ methods: {},
30042
+ events: {
30043
+ /**
30044
+ * Fires once per physical press. Reolink delivers via Baichuan
30045
+ * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
30046
+ * release/duration — it's a pulse.
30047
+ */
30048
+ onPressed: {
30049
+ data: DoorbellPressEventSchema,
30050
+ fromState: {
30051
+ field: "lastPressedAt",
30052
+ on: "advance"
30053
+ }
30054
+ } },
30055
+ status: {
30056
+ schema: DoorbellStatusSchema,
30057
+ kind: "push"
30058
+ },
30059
+ /**
30060
+ * Runtime-state slice — last press timestamp + lifetime press count.
30061
+ * Mirrored by the kernel and readable via
30062
+ * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
30063
+ * without subscribing.
30064
+ */
30065
+ runtimeState: DoorbellStatusSchema,
30066
+ /**
30067
+ * Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
30068
+ *
30069
+ * See `RuntimeStateDurability`. Enforced by
30070
+ * `scripts/check-runtime-state-durability.ts`.
30071
+ */
30072
+ durability: "restored"
30073
+ };
29882
30074
  /**
29883
30075
  * Enum-state sensor — a string value picked from a finite option set.
29884
30076
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -32023,9 +32215,9 @@ var MotionStatusSchema = object({
32023
32215
  /** Ms epoch of the last detected-true observation. Null if never detected. */
32024
32216
  lastDetectedAt: number().nullable(),
32025
32217
  /**
32026
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
32027
- * `null` while false and on every `Sensor` device (D475) — see that
32028
- * constant's doc for the one-authority rule.
32218
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
32219
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
32220
+ * device (D475) — see that constant's doc for the one-authority rule.
32029
32221
  *
32030
32222
  * ## Reading this field still arms nothing
32031
32223
  *
@@ -32043,34 +32235,31 @@ var MotionStatusSchema = object({
32043
32235
  * `addon-export-alexa/src/motion-clear-hold.ts` and
32044
32236
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
32045
32237
  *
32046
- * ## Who writes it
32047
- *
32048
- * - **Cameras** — the runner's phase machine, `active → watching` on
32049
- * `cooldown_expired`, which then writes this slice with
32050
- * `detected: false` (`handlePhaseChanged` in
32051
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
32052
- * matters most for the sources that only ever push a rising one:
32053
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
32054
- * a false.
32055
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
32056
- * provider pushes the false itself, from the upstream system's own
32057
- * state change. No phase machine is involved.
32058
- *
32059
- * ### The phase machine is CANONICAL, not sole — and that is a defect
32060
- *
32061
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
32062
- * phase machine is the sole writer for a camera. It is not.
32063
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
32064
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
32065
- * Hikvision's comment says why: it read THIS docblock, agreed the
32066
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
32067
- * two authorities can disagree about one slice, which this repo
32068
- * forbids, and the doc said otherwise — which is worse than saying
32069
- * nothing, because it reads as verification.
32070
- *
32071
- * This predates D475 and is not fixed there: the fix touches every
32072
- * camera provider. Recorded in D475's Consequences. Do not restore the
32073
- * "sole writer" wording without also removing the other writers.
32238
+ * ## Who writes it (D696)
32239
+ *
32240
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
32241
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
32242
+ * `camera-sensors/motion-slice.ts`), alongside the effective
32243
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
32244
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
32245
+ * device activity, as the camera's `motionSources` counts them. A source
32246
+ * that never sends a clear falls one window after its last `true`.
32247
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
32248
+ * writes this slice (not even a boot seed), and neither does the runner's
32249
+ * phase machine: its phase is "a session is attached" for every on-motion
32250
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
32251
+ * A provider's `isDetected` answers from the hub's slice.
32252
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
32253
+ * imports) — the provider pushes both edges itself, from the upstream
32254
+ * system's own state change. That is the sensor's own fact.
32255
+ *
32256
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
32257
+ * `motion` slice; each sensor writer is a named exemption.
32258
+ *
32259
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
32260
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
32261
+ * it, every provider seeded it at boot, and every session left it
32262
+ * `detected: true` after its detach.
32074
32263
  */
32075
32264
  autoClearAfterMs: number().nullable()
32076
32265
  });
@@ -32142,11 +32331,12 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
32142
32331
  */
32143
32332
  runtimeState: MotionStatusSchema,
32144
32333
  /**
32145
- * Runtime-state durability: **session** — every writer of this slice
32334
+ * Runtime-state durability: **session** — the writer of this slice
32146
32335
  * writes only on an EDGE, so a restored `detected: true` would stay
32147
32336
  * frozen until the next one instead of being corrected. The next edge
32148
- * re-publishes the real state. (On who the writers are, and why there
32149
- * is more than one, see `autoClearAfterMs` above.)
32337
+ * re-publishes the real state, and the orchestrator re-publishes every
32338
+ * camera when it starts writing (D696; on who writes it, see
32339
+ * `autoClearAfterMs` above).
32150
32340
  *
32151
32341
  * See `RuntimeStateDurability`. Enforced by
32152
32342
  * `scripts/check-runtime-state-durability.ts`.
@@ -34529,8 +34719,7 @@ var ptzAutotrackCapability = {
34529
34719
  deviceNative: true,
34530
34720
  mode: "singleton",
34531
34721
  /**
34532
- * Two kinds of provider behind one name, the same shape `snapshot` and
34533
- * `doorbell` use:
34722
+ * Two kinds of provider behind one name, the same shape `snapshot` uses:
34534
34723
  *
34535
34724
  * - **Native**: the camera's own firmware autotrack, registered per device by
34536
34725
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
@@ -34538,7 +34727,7 @@ var ptzAutotrackCapability = {
34538
34727
  * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
34539
34728
  * which follows a subject with `ptz.move` pulses driven by the detection
34540
34729
  * pipeline. `defaultActive: false` — the operator binds it per camera in
34541
- * the bindings UI, exactly as `virtual-doorbell` is bound.
34730
+ * the bindings UI.
34542
34731
  *
34543
34732
  * The binding chooses WHICH implementation answers; `setEnabled` stays the
34544
34733
  * one switch for the function either way. A camera never has both driving its
@@ -35213,7 +35402,7 @@ var ReadGopBytesResultSchema = object({
35213
35402
  /**
35214
35403
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
35215
35404
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
35216
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
35405
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
35217
35406
  * a replay needs several seconds of native pixels, not one frame.
35218
35407
  *
35219
35408
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -38577,6 +38766,7 @@ var zoneRulesCapability = {
38577
38766
  */
38578
38767
  durability: "restored"
38579
38768
  };
38769
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
38580
38770
  /**
38581
38771
  * Accessory device helpers — shared across drivers.
38582
38772
  *
@@ -39636,13 +39826,14 @@ var BaseDeviceProvider = class extends BaseAddon {
39636
39826
  async getStatus() {
39637
39827
  const all = await this.ctx.kernel.devices?.getAll() ?? [];
39638
39828
  const summary = this.restoreFailureSummary();
39639
- if (summary === null) return {
39829
+ const base = {
39640
39830
  connected: true,
39641
- deviceCount: all.length
39831
+ deviceCount: all.length,
39832
+ motionSliceWriter: "orchestrator"
39642
39833
  };
39834
+ if (summary === null) return base;
39643
39835
  return {
39644
- connected: true,
39645
- deviceCount: all.length,
39836
+ ...base,
39646
39837
  error: summary
39647
39838
  };
39648
39839
  }
@@ -42672,6 +42863,18 @@ Object.freeze({
42672
42863
  addonId: null,
42673
42864
  access: "view"
42674
42865
  },
42866
+ "databaseSnapshot.listDatabases": {
42867
+ capName: "database-snapshot",
42868
+ capScope: "system",
42869
+ addonId: null,
42870
+ access: "view"
42871
+ },
42872
+ "databaseSnapshot.writeSnapshot": {
42873
+ capName: "database-snapshot",
42874
+ capScope: "system",
42875
+ addonId: null,
42876
+ access: "create"
42877
+ },
42675
42878
  "dataStoreProvider.aggregate": {
42676
42879
  capName: "data-store-provider",
42677
42880
  capScope: "system",
@@ -45132,6 +45335,12 @@ Object.freeze({
45132
45335
  addonId: null,
45133
45336
  access: "view"
45134
45337
  },
45338
+ "notificationRules.getAlarmSensors": {
45339
+ capName: "notification-rules",
45340
+ capScope: "system",
45341
+ addonId: null,
45342
+ access: "view"
45343
+ },
45135
45344
  "notificationRules.getClipRetentionAsks": {
45136
45345
  capName: "notification-rules",
45137
45346
  capScope: "system",
@@ -46236,6 +46445,12 @@ Object.freeze({
46236
46445
  addonId: null,
46237
46446
  access: "delete"
46238
46447
  },
46448
+ "pipelineOrchestrator.requestOccupancyLook": {
46449
+ capName: "pipeline-orchestrator",
46450
+ capScope: "system",
46451
+ addonId: null,
46452
+ access: "create"
46453
+ },
46239
46454
  "pipelineOrchestrator.resetNodePipelineDefaults": {
46240
46455
  capName: "pipeline-orchestrator",
46241
46456
  capScope: "system",
@@ -48704,6 +48919,11 @@ Object.freeze({
48704
48919
  }
48705
48920
  });
48706
48921
  Object.freeze({
48922
+ "accessories.getStatus": [{
48923
+ name: "deviceId",
48924
+ form: "single",
48925
+ optional: false
48926
+ }],
48707
48927
  "accessories.setChildHidden": [{
48708
48928
  name: "childDeviceId",
48709
48929
  form: "single",
@@ -48723,6 +48943,11 @@ Object.freeze({
48723
48943
  form: "single",
48724
48944
  optional: false
48725
48945
  }],
48946
+ "airQualitySensor.getStatus": [{
48947
+ name: "deviceId",
48948
+ form: "single",
48949
+ optional: false
48950
+ }],
48726
48951
  "alarmPanel.arm": [{
48727
48952
  name: "deviceId",
48728
48953
  form: "single",
@@ -48733,11 +48958,36 @@ Object.freeze({
48733
48958
  form: "single",
48734
48959
  optional: false
48735
48960
  }],
48961
+ "alarmPanel.getStatus": [{
48962
+ name: "deviceId",
48963
+ form: "single",
48964
+ optional: false
48965
+ }],
48736
48966
  "alarmPanel.trigger": [{
48737
48967
  name: "deviceId",
48738
48968
  form: "single",
48739
48969
  optional: false
48740
48970
  }],
48971
+ "ambientLightSensor.getStatus": [{
48972
+ name: "deviceId",
48973
+ form: "single",
48974
+ optional: false
48975
+ }],
48976
+ "audioAnalysis.applyDeviceSettingsPatch": [{
48977
+ name: "deviceId",
48978
+ form: "single",
48979
+ optional: false
48980
+ }],
48981
+ "audioAnalysis.getDeviceLiveContribution": [{
48982
+ name: "deviceId",
48983
+ form: "single",
48984
+ optional: false
48985
+ }],
48986
+ "audioAnalysis.getDeviceSettingsContribution": [{
48987
+ name: "deviceId",
48988
+ form: "single",
48989
+ optional: false
48990
+ }],
48741
48991
  "audioAnalysis.resolveDeviceSettings": [{
48742
48992
  name: "deviceId",
48743
48993
  form: "single",
@@ -48778,16 +49028,36 @@ Object.freeze({
48778
49028
  form: "single",
48779
49029
  optional: false
48780
49030
  }],
49031
+ "automationControl.getStatus": [{
49032
+ name: "deviceId",
49033
+ form: "single",
49034
+ optional: false
49035
+ }],
48781
49036
  "automationControl.trigger": [{
48782
49037
  name: "deviceId",
48783
49038
  form: "single",
48784
49039
  optional: false
48785
49040
  }],
49041
+ "battery.getStatus": [{
49042
+ name: "deviceId",
49043
+ form: "single",
49044
+ optional: false
49045
+ }],
48786
49046
  "battery.wakeForStream": [{
48787
49047
  name: "deviceId",
48788
49048
  form: "single",
48789
49049
  optional: false
48790
49050
  }],
49051
+ "binary.getStatus": [{
49052
+ name: "deviceId",
49053
+ form: "single",
49054
+ optional: false
49055
+ }],
49056
+ "brightness.getStatus": [{
49057
+ name: "deviceId",
49058
+ form: "single",
49059
+ optional: false
49060
+ }],
48791
49061
  "brightness.setBrightness": [{
48792
49062
  name: "deviceId",
48793
49063
  form: "single",
@@ -48803,6 +49073,26 @@ Object.freeze({
48803
49073
  form: "single",
48804
49074
  optional: false
48805
49075
  }],
49076
+ "cameraCredentials.getStatus": [{
49077
+ name: "deviceId",
49078
+ form: "single",
49079
+ optional: false
49080
+ }],
49081
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
49082
+ name: "deviceId",
49083
+ form: "single",
49084
+ optional: false
49085
+ }],
49086
+ "cameraGridLayout.getDeviceLiveContribution": [{
49087
+ name: "deviceId",
49088
+ form: "single",
49089
+ optional: false
49090
+ }],
49091
+ "cameraGridLayout.getDeviceSettingsContribution": [{
49092
+ name: "deviceId",
49093
+ form: "single",
49094
+ optional: false
49095
+ }],
48806
49096
  "cameraGridLayout.getLayout": [{
48807
49097
  name: "deviceId",
48808
49098
  form: "single",
@@ -48818,6 +49108,21 @@ Object.freeze({
48818
49108
  form: "single",
48819
49109
  optional: false
48820
49110
  }],
49111
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
49112
+ name: "deviceId",
49113
+ form: "single",
49114
+ optional: false
49115
+ }],
49116
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
49117
+ name: "deviceId",
49118
+ form: "single",
49119
+ optional: false
49120
+ }],
49121
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
49122
+ name: "deviceId",
49123
+ form: "single",
49124
+ optional: false
49125
+ }],
48821
49126
  "cameraStreams.getBrokerStreams": [{
48822
49127
  name: "deviceId",
48823
49128
  form: "single",
@@ -48843,6 +49148,16 @@ Object.freeze({
48843
49148
  form: "single",
48844
49149
  optional: false
48845
49150
  }],
49151
+ "carbonMonoxide.getStatus": [{
49152
+ name: "deviceId",
49153
+ form: "single",
49154
+ optional: false
49155
+ }],
49156
+ "climateControl.getStatus": [{
49157
+ name: "deviceId",
49158
+ form: "single",
49159
+ optional: false
49160
+ }],
48846
49161
  "climateControl.setFanMode": [{
48847
49162
  name: "deviceId",
48848
49163
  form: "single",
@@ -48883,16 +49198,41 @@ Object.freeze({
48883
49198
  form: "single",
48884
49199
  optional: false
48885
49200
  }],
49201
+ "color.getStatus": [{
49202
+ name: "deviceId",
49203
+ form: "single",
49204
+ optional: false
49205
+ }],
48886
49206
  "color.setColor": [{
48887
49207
  name: "deviceId",
48888
49208
  form: "single",
48889
49209
  optional: false
48890
49210
  }],
49211
+ "connectivity.getStatus": [{
49212
+ name: "deviceId",
49213
+ form: "single",
49214
+ optional: false
49215
+ }],
49216
+ "consumables.getStatus": [{
49217
+ name: "deviceId",
49218
+ form: "single",
49219
+ optional: false
49220
+ }],
48891
49221
  "consumables.reset": [{
48892
49222
  name: "deviceId",
48893
49223
  form: "single",
48894
49224
  optional: false
48895
49225
  }],
49226
+ "contact.getStatus": [{
49227
+ name: "deviceId",
49228
+ form: "single",
49229
+ optional: false
49230
+ }],
49231
+ "control.getStatus": [{
49232
+ name: "deviceId",
49233
+ form: "single",
49234
+ optional: false
49235
+ }],
48896
49236
  "control.setValue": [{
48897
49237
  name: "deviceId",
48898
49238
  form: "single",
@@ -48903,6 +49243,11 @@ Object.freeze({
48903
49243
  form: "single",
48904
49244
  optional: false
48905
49245
  }],
49246
+ "cover.getStatus": [{
49247
+ name: "deviceId",
49248
+ form: "single",
49249
+ optional: false
49250
+ }],
48906
49251
  "cover.open": [{
48907
49252
  name: "deviceId",
48908
49253
  form: "single",
@@ -48928,6 +49273,11 @@ Object.freeze({
48928
49273
  form: "single",
48929
49274
  optional: false
48930
49275
  }],
49276
+ "dayNight.getStatus": [{
49277
+ name: "deviceId",
49278
+ form: "single",
49279
+ optional: false
49280
+ }],
48931
49281
  "dayNight.setSettings": [{
48932
49282
  name: "deviceId",
48933
49283
  form: "single",
@@ -48938,11 +49288,31 @@ Object.freeze({
48938
49288
  form: "single",
48939
49289
  optional: true
48940
49290
  }],
49291
+ "detectionPipeline.applyDeviceSettingsPatch": [{
49292
+ name: "deviceId",
49293
+ form: "single",
49294
+ optional: false
49295
+ }],
49296
+ "detectionPipeline.getDeviceLiveContribution": [{
49297
+ name: "deviceId",
49298
+ form: "single",
49299
+ optional: false
49300
+ }],
49301
+ "detectionPipeline.getDeviceSettingsContribution": [{
49302
+ name: "deviceId",
49303
+ form: "single",
49304
+ optional: false
49305
+ }],
48941
49306
  "deviceAdminLink.getAdminLink": [{
48942
49307
  name: "deviceId",
48943
49308
  form: "single",
48944
49309
  optional: false
48945
49310
  }],
49311
+ "deviceAdoption.getStatus": [{
49312
+ name: "deviceId",
49313
+ form: "single",
49314
+ optional: false
49315
+ }],
48946
49316
  "deviceAdoption.release": [{
48947
49317
  name: "camDeviceId",
48948
49318
  form: "single",
@@ -48958,6 +49328,11 @@ Object.freeze({
48958
49328
  form: "single",
48959
49329
  optional: false
48960
49330
  }],
49331
+ "deviceDiscovery.getStatus": [{
49332
+ name: "deviceId",
49333
+ form: "single",
49334
+ optional: false
49335
+ }],
48961
49336
  "deviceDiscovery.listDiscovered": [{
48962
49337
  name: "deviceId",
48963
49338
  form: "single",
@@ -48977,6 +49352,21 @@ Object.freeze({
48977
49352
  form: "single",
48978
49353
  optional: false
48979
49354
  }],
49355
+ "deviceExport.applyDeviceSettingsPatch": [{
49356
+ name: "deviceId",
49357
+ form: "single",
49358
+ optional: false
49359
+ }],
49360
+ "deviceExport.getDeviceLiveContribution": [{
49361
+ name: "deviceId",
49362
+ form: "single",
49363
+ optional: false
49364
+ }],
49365
+ "deviceExport.getDeviceSettingsContribution": [{
49366
+ name: "deviceId",
49367
+ form: "single",
49368
+ optional: false
49369
+ }],
48980
49370
  "deviceExtension.listCandidates": [{
48981
49371
  name: "deviceId",
48982
49372
  form: "single",
@@ -49325,6 +49715,26 @@ Object.freeze({
49325
49715
  form: "single",
49326
49716
  optional: false
49327
49717
  }],
49718
+ "deviceStatus.getStatus": [{
49719
+ name: "deviceId",
49720
+ form: "single",
49721
+ optional: false
49722
+ }],
49723
+ "doorbell.getStatus": [{
49724
+ name: "deviceId",
49725
+ form: "single",
49726
+ optional: false
49727
+ }],
49728
+ "enumSensor.getStatus": [{
49729
+ name: "deviceId",
49730
+ form: "single",
49731
+ optional: false
49732
+ }],
49733
+ "eventEmitter.getStatus": [{
49734
+ name: "deviceId",
49735
+ form: "single",
49736
+ optional: false
49737
+ }],
49328
49738
  "faceGallery.getFaceByTrack": [{
49329
49739
  name: "deviceId",
49330
49740
  form: "single",
@@ -49339,6 +49749,11 @@ Object.freeze({
49339
49749
  form: "array",
49340
49750
  optional: true
49341
49751
  }],
49752
+ "fanControl.getStatus": [{
49753
+ name: "deviceId",
49754
+ form: "single",
49755
+ optional: false
49756
+ }],
49342
49757
  "fanControl.setDirection": [{
49343
49758
  name: "deviceId",
49344
49759
  form: "single",
@@ -49359,6 +49774,26 @@ Object.freeze({
49359
49774
  form: "single",
49360
49775
  optional: false
49361
49776
  }],
49777
+ "featureProbe.getStatus": [{
49778
+ name: "deviceId",
49779
+ form: "single",
49780
+ optional: false
49781
+ }],
49782
+ "flood.getStatus": [{
49783
+ name: "deviceId",
49784
+ form: "single",
49785
+ optional: false
49786
+ }],
49787
+ "gas.getStatus": [{
49788
+ name: "deviceId",
49789
+ form: "single",
49790
+ optional: false
49791
+ }],
49792
+ "humidifier.getStatus": [{
49793
+ name: "deviceId",
49794
+ form: "single",
49795
+ optional: false
49796
+ }],
49362
49797
  "humidifier.setMode": [{
49363
49798
  name: "deviceId",
49364
49799
  form: "single",
@@ -49374,11 +49809,26 @@ Object.freeze({
49374
49809
  form: "single",
49375
49810
  optional: false
49376
49811
  }],
49812
+ "humiditySensor.getStatus": [{
49813
+ name: "deviceId",
49814
+ form: "single",
49815
+ optional: false
49816
+ }],
49817
+ "image.getStatus": [{
49818
+ name: "deviceId",
49819
+ form: "single",
49820
+ optional: false
49821
+ }],
49377
49822
  "imageSettings.getOptions": [{
49378
49823
  name: "deviceId",
49379
49824
  form: "single",
49380
49825
  optional: false
49381
49826
  }],
49827
+ "imageSettings.getStatus": [{
49828
+ name: "deviceId",
49829
+ form: "single",
49830
+ optional: false
49831
+ }],
49382
49832
  "imageSettings.setSettings": [{
49383
49833
  name: "deviceId",
49384
49834
  form: "single",
@@ -49389,6 +49839,11 @@ Object.freeze({
49389
49839
  form: "single",
49390
49840
  optional: false
49391
49841
  }],
49842
+ "intercom.getStatus": [{
49843
+ name: "deviceId",
49844
+ form: "single",
49845
+ optional: false
49846
+ }],
49392
49847
  "intercom.handleAnswer": [{
49393
49848
  name: "deviceId",
49394
49849
  form: "single",
@@ -49419,6 +49874,11 @@ Object.freeze({
49419
49874
  form: "single",
49420
49875
  optional: false
49421
49876
  }],
49877
+ "lawnMowerControl.getStatus": [{
49878
+ name: "deviceId",
49879
+ form: "single",
49880
+ optional: false
49881
+ }],
49422
49882
  "lawnMowerControl.pause": [{
49423
49883
  name: "deviceId",
49424
49884
  form: "single",
@@ -49429,6 +49889,11 @@ Object.freeze({
49429
49889
  form: "single",
49430
49890
  optional: false
49431
49891
  }],
49892
+ "lockControl.getStatus": [{
49893
+ name: "deviceId",
49894
+ form: "single",
49895
+ optional: false
49896
+ }],
49432
49897
  "lockControl.lock": [{
49433
49898
  name: "deviceId",
49434
49899
  form: "single",
@@ -49444,6 +49909,11 @@ Object.freeze({
49444
49909
  form: "single",
49445
49910
  optional: false
49446
49911
  }],
49912
+ "mediaPlayer.getStatus": [{
49913
+ name: "deviceId",
49914
+ form: "single",
49915
+ optional: false
49916
+ }],
49447
49917
  "mediaPlayer.next": [{
49448
49918
  name: "deviceId",
49449
49919
  form: "single",
@@ -49504,6 +49974,11 @@ Object.freeze({
49504
49974
  form: "single",
49505
49975
  optional: false
49506
49976
  }],
49977
+ "motion.getStatus": [{
49978
+ name: "deviceId",
49979
+ form: "single",
49980
+ optional: false
49981
+ }],
49507
49982
  "motion.isDetected": [{
49508
49983
  name: "deviceId",
49509
49984
  form: "single",
@@ -49514,11 +49989,31 @@ Object.freeze({
49514
49989
  form: "single",
49515
49990
  optional: false
49516
49991
  }],
49992
+ "motionDetection.applyDeviceSettingsPatch": [{
49993
+ name: "deviceId",
49994
+ form: "single",
49995
+ optional: false
49996
+ }],
49997
+ "motionDetection.getDeviceLiveContribution": [{
49998
+ name: "deviceId",
49999
+ form: "single",
50000
+ optional: false
50001
+ }],
50002
+ "motionDetection.getDeviceSettingsContribution": [{
50003
+ name: "deviceId",
50004
+ form: "single",
50005
+ optional: false
50006
+ }],
49517
50007
  "motionDetection.removeCamera": [{
49518
50008
  name: "deviceId",
49519
50009
  form: "single",
49520
50010
  optional: false
49521
50011
  }],
50012
+ "motionTrigger.getStatus": [{
50013
+ name: "deviceId",
50014
+ form: "single",
50015
+ optional: false
50016
+ }],
49522
50017
  "motionTrigger.setMotionTrigger": [{
49523
50018
  name: "deviceId",
49524
50019
  form: "single",
@@ -49529,6 +50024,11 @@ Object.freeze({
49529
50024
  form: "single",
49530
50025
  optional: false
49531
50026
  }],
50027
+ "motionZones.getStatus": [{
50028
+ name: "deviceId",
50029
+ form: "single",
50030
+ optional: false
50031
+ }],
49532
50032
  "motionZones.setZone": [{
49533
50033
  name: "deviceId",
49534
50034
  form: "single",
@@ -49539,6 +50039,11 @@ Object.freeze({
49539
50039
  form: "single",
49540
50040
  optional: false
49541
50041
  }],
50042
+ "nativeObjectDetection.getStatus": [{
50043
+ name: "deviceId",
50044
+ form: "single",
50045
+ optional: false
50046
+ }],
49542
50047
  "nativeObjectDetection.setEnabled": [{
49543
50048
  name: "deviceId",
49544
50049
  form: "single",
@@ -49554,6 +50059,11 @@ Object.freeze({
49554
50059
  form: "single",
49555
50060
  optional: false
49556
50061
  }],
50062
+ "navigation.getStatus": [{
50063
+ name: "deviceId",
50064
+ form: "single",
50065
+ optional: false
50066
+ }],
49557
50067
  "navigation.goToPoint": [{
49558
50068
  name: "deviceId",
49559
50069
  form: "single",
@@ -49599,6 +50109,11 @@ Object.freeze({
49599
50109
  form: "single",
49600
50110
  optional: false
49601
50111
  }],
50112
+ "networkLink.getStatus": [{
50113
+ name: "deviceId",
50114
+ form: "single",
50115
+ optional: false
50116
+ }],
49602
50117
  "networkQuality.getDeviceStats": [{
49603
50118
  name: "deviceId",
49604
50119
  form: "single",
@@ -49609,6 +50124,11 @@ Object.freeze({
49609
50124
  form: "single",
49610
50125
  optional: false
49611
50126
  }],
50127
+ "notificationRules.getAlarmSensors": [{
50128
+ name: "deviceIds",
50129
+ form: "array",
50130
+ optional: false
50131
+ }],
49612
50132
  "notificationRules.setDeviceMuted": [{
49613
50133
  name: "deviceId",
49614
50134
  form: "single",
@@ -49619,11 +50139,26 @@ Object.freeze({
49619
50139
  form: "single",
49620
50140
  optional: false
49621
50141
  }],
50142
+ "notifier.getStatus": [{
50143
+ name: "deviceId",
50144
+ form: "single",
50145
+ optional: false
50146
+ }],
49622
50147
  "notifier.send": [{
49623
50148
  name: "deviceId",
49624
50149
  form: "single",
49625
50150
  optional: false
49626
50151
  }],
50152
+ "numericSensor.getStatus": [{
50153
+ name: "deviceId",
50154
+ form: "single",
50155
+ optional: false
50156
+ }],
50157
+ "osd.getStatus": [{
50158
+ name: "deviceId",
50159
+ form: "single",
50160
+ optional: false
50161
+ }],
49627
50162
  "osd.setOverlay": [{
49628
50163
  name: "deviceId",
49629
50164
  form: "single",
@@ -49683,6 +50218,11 @@ Object.freeze({
49683
50218
  form: "single",
49684
50219
  optional: false
49685
50220
  }],
50221
+ "petFeeder.getStatus": [{
50222
+ name: "deviceId",
50223
+ form: "single",
50224
+ optional: false
50225
+ }],
49686
50226
  "petFeeder.markFoodReplenished": [{
49687
50227
  name: "deviceId",
49688
50228
  form: "single",
@@ -49718,6 +50258,11 @@ Object.freeze({
49718
50258
  form: "single",
49719
50259
  optional: false
49720
50260
  }],
50261
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
50262
+ name: "deviceId",
50263
+ form: "single",
50264
+ optional: false
50265
+ }],
49721
50266
  "pipelineAnalytics.clearTrackRelation": [{
49722
50267
  name: "deviceId",
49723
50268
  form: "single",
@@ -49758,6 +50303,16 @@ Object.freeze({
49758
50303
  form: "single",
49759
50304
  optional: false
49760
50305
  }],
50306
+ "pipelineAnalytics.getDeviceLiveContribution": [{
50307
+ name: "deviceId",
50308
+ form: "single",
50309
+ optional: false
50310
+ }],
50311
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
50312
+ name: "deviceId",
50313
+ form: "single",
50314
+ optional: false
50315
+ }],
49761
50316
  "pipelineAnalytics.getEventDensity": [{
49762
50317
  name: "deviceId",
49763
50318
  form: "single",
@@ -49973,6 +50528,11 @@ Object.freeze({
49973
50528
  form: "single",
49974
50529
  optional: true
49975
50530
  }],
50531
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
50532
+ name: "deviceId",
50533
+ form: "single",
50534
+ optional: false
50535
+ }],
49976
50536
  "pipelineOrchestrator.assignAudio": [{
49977
50537
  name: "deviceId",
49978
50538
  form: "single",
@@ -50018,6 +50578,16 @@ Object.freeze({
50018
50578
  form: "single",
50019
50579
  optional: false
50020
50580
  }],
50581
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
50582
+ name: "deviceId",
50583
+ form: "single",
50584
+ optional: false
50585
+ }],
50586
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
50587
+ name: "deviceId",
50588
+ form: "single",
50589
+ optional: false
50590
+ }],
50021
50591
  "pipelineOrchestrator.getPipelineAssignment": [{
50022
50592
  name: "deviceId",
50023
50593
  form: "single",
@@ -50028,6 +50598,11 @@ Object.freeze({
50028
50598
  form: "single",
50029
50599
  optional: false
50030
50600
  }],
50601
+ "pipelineOrchestrator.requestOccupancyLook": [{
50602
+ name: "deviceId",
50603
+ form: "single",
50604
+ optional: false
50605
+ }],
50031
50606
  "pipelineOrchestrator.resolvePipeline": [{
50032
50607
  name: "deviceId",
50033
50608
  form: "single",
@@ -50133,11 +50708,31 @@ Object.freeze({
50133
50708
  form: "single",
50134
50709
  optional: true
50135
50710
  }],
50711
+ "powerMeter.getStatus": [{
50712
+ name: "deviceId",
50713
+ form: "single",
50714
+ optional: false
50715
+ }],
50716
+ "presence.getStatus": [{
50717
+ name: "deviceId",
50718
+ form: "single",
50719
+ optional: false
50720
+ }],
50721
+ "pressureSensor.getStatus": [{
50722
+ name: "deviceId",
50723
+ form: "single",
50724
+ optional: false
50725
+ }],
50136
50726
  "privacyMask.getOptions": [{
50137
50727
  name: "deviceId",
50138
50728
  form: "single",
50139
50729
  optional: false
50140
50730
  }],
50731
+ "privacyMask.getStatus": [{
50732
+ name: "deviceId",
50733
+ form: "single",
50734
+ optional: false
50735
+ }],
50141
50736
  "privacyMask.setAudioEnabled": [{
50142
50737
  name: "deviceId",
50143
50738
  form: "single",
@@ -50188,6 +50783,11 @@ Object.freeze({
50188
50783
  form: "single",
50189
50784
  optional: false
50190
50785
  }],
50786
+ "ptz.getStatus": [{
50787
+ name: "deviceId",
50788
+ form: "single",
50789
+ optional: false
50790
+ }],
50191
50791
  "ptz.goHome": [{
50192
50792
  name: "deviceId",
50193
50793
  form: "single",
@@ -50278,6 +50878,11 @@ Object.freeze({
50278
50878
  form: "single",
50279
50879
  optional: false
50280
50880
  }],
50881
+ "recordingArchive.applyDeviceSettingsPatch": [{
50882
+ name: "deviceId",
50883
+ form: "single",
50884
+ optional: false
50885
+ }],
50281
50886
  "recordingArchive.deleteFootprint": [{
50282
50887
  name: "deviceId",
50283
50888
  form: "single",
@@ -50298,6 +50903,21 @@ Object.freeze({
50298
50903
  form: "single",
50299
50904
  optional: false
50300
50905
  }],
50906
+ "recordingArchive.getDeviceLiveContribution": [{
50907
+ name: "deviceId",
50908
+ form: "single",
50909
+ optional: false
50910
+ }],
50911
+ "recordingArchive.getDeviceSettingsContribution": [{
50912
+ name: "deviceId",
50913
+ form: "single",
50914
+ optional: false
50915
+ }],
50916
+ "recordingArchive.getStatus": [{
50917
+ name: "deviceId",
50918
+ form: "single",
50919
+ optional: false
50920
+ }],
50301
50921
  "recordingArchive.listOpsLog": [{
50302
50922
  name: "deviceId",
50303
50923
  form: "single",
@@ -50383,6 +51003,11 @@ Object.freeze({
50383
51003
  form: "single",
50384
51004
  optional: false
50385
51005
  }],
51006
+ "recordingOnboard.getStatus": [{
51007
+ name: "deviceId",
51008
+ form: "single",
51009
+ optional: false
51010
+ }],
50386
51011
  "recordingOnboard.setSettings": [{
50387
51012
  name: "deviceId",
50388
51013
  form: "single",
@@ -50413,6 +51038,11 @@ Object.freeze({
50413
51038
  form: "single",
50414
51039
  optional: false
50415
51040
  }],
51041
+ "sceneMonitor.getStatus": [{
51042
+ name: "deviceId",
51043
+ form: "single",
51044
+ optional: false
51045
+ }],
50416
51046
  "sceneMonitor.listScenes": [{
50417
51047
  name: "deviceId",
50418
51048
  form: "single",
@@ -50438,6 +51068,11 @@ Object.freeze({
50438
51068
  form: "single",
50439
51069
  optional: false
50440
51070
  }],
51071
+ "scriptRunner.getStatus": [{
51072
+ name: "deviceId",
51073
+ form: "single",
51074
+ optional: false
51075
+ }],
50441
51076
  "scriptRunner.run": [{
50442
51077
  name: "deviceId",
50443
51078
  form: "single",
@@ -50448,11 +51083,31 @@ Object.freeze({
50448
51083
  form: "single",
50449
51084
  optional: false
50450
51085
  }],
51086
+ "smoke.getStatus": [{
51087
+ name: "deviceId",
51088
+ form: "single",
51089
+ optional: false
51090
+ }],
51091
+ "snapshot.applyDeviceSettingsPatch": [{
51092
+ name: "deviceId",
51093
+ form: "single",
51094
+ optional: false
51095
+ }],
50451
51096
  "snapshot.getDebugState": [{
50452
51097
  name: "deviceId",
50453
51098
  form: "single",
50454
51099
  optional: false
50455
51100
  }],
51101
+ "snapshot.getDeviceLiveContribution": [{
51102
+ name: "deviceId",
51103
+ form: "single",
51104
+ optional: false
51105
+ }],
51106
+ "snapshot.getDeviceSettingsContribution": [{
51107
+ name: "deviceId",
51108
+ form: "single",
51109
+ optional: false
51110
+ }],
50456
51111
  "snapshot.getSnapshot": [{
50457
51112
  name: "deviceId",
50458
51113
  form: "single",
@@ -50469,6 +51124,11 @@ Object.freeze({
50469
51124
  form: "array",
50470
51125
  optional: false
50471
51126
  }],
51127
+ "snapshot.getStatus": [{
51128
+ name: "deviceId",
51129
+ form: "single",
51130
+ optional: false
51131
+ }],
50472
51132
  "snapshot.invalidateCache": [{
50473
51133
  name: "deviceId",
50474
51134
  form: "single",
@@ -50479,6 +51139,11 @@ Object.freeze({
50479
51139
  form: "single",
50480
51140
  optional: false
50481
51141
  }],
51142
+ "streamBroker.applyDeviceSettingsPatch": [{
51143
+ name: "deviceId",
51144
+ form: "single",
51145
+ optional: false
51146
+ }],
50482
51147
  "streamBroker.assignProfile": [{
50483
51148
  name: "deviceId",
50484
51149
  form: "single",
@@ -50494,6 +51159,16 @@ Object.freeze({
50494
51159
  form: "single",
50495
51160
  optional: false
50496
51161
  }],
51162
+ "streamBroker.getDeviceLiveContribution": [{
51163
+ name: "deviceId",
51164
+ form: "single",
51165
+ optional: false
51166
+ }],
51167
+ "streamBroker.getDeviceSettingsContribution": [{
51168
+ name: "deviceId",
51169
+ form: "single",
51170
+ optional: false
51171
+ }],
50497
51172
  "streamBroker.getStreamWithCodec": [{
50498
51173
  name: "deviceId",
50499
51174
  form: "single",
@@ -50554,16 +51229,46 @@ Object.freeze({
50554
51229
  form: "single",
50555
51230
  optional: false
50556
51231
  }],
51232
+ "streamParams.getStatus": [{
51233
+ name: "deviceId",
51234
+ form: "single",
51235
+ optional: false
51236
+ }],
50557
51237
  "streamParams.setProfile": [{
50558
51238
  name: "deviceId",
50559
51239
  form: "single",
50560
51240
  optional: false
50561
51241
  }],
51242
+ "switch.getStatus": [{
51243
+ name: "deviceId",
51244
+ form: "single",
51245
+ optional: false
51246
+ }],
50562
51247
  "switch.setState": [{
50563
51248
  name: "deviceId",
50564
51249
  form: "single",
50565
51250
  optional: false
50566
51251
  }],
51252
+ "tamper.getStatus": [{
51253
+ name: "deviceId",
51254
+ form: "single",
51255
+ optional: false
51256
+ }],
51257
+ "temperatureSensor.getStatus": [{
51258
+ name: "deviceId",
51259
+ form: "single",
51260
+ optional: false
51261
+ }],
51262
+ "update.getStatus": [{
51263
+ name: "deviceId",
51264
+ form: "single",
51265
+ optional: false
51266
+ }],
51267
+ "vacuumControl.getStatus": [{
51268
+ name: "deviceId",
51269
+ form: "single",
51270
+ optional: false
51271
+ }],
50567
51272
  "vacuumControl.locate": [{
50568
51273
  name: "deviceId",
50569
51274
  form: "single",
@@ -50599,6 +51304,11 @@ Object.freeze({
50599
51304
  form: "single",
50600
51305
  optional: false
50601
51306
  }],
51307
+ "valve.getStatus": [{
51308
+ name: "deviceId",
51309
+ form: "single",
51310
+ optional: false
51311
+ }],
50602
51312
  "valve.open": [{
50603
51313
  name: "deviceId",
50604
51314
  form: "single",
@@ -50614,6 +51324,11 @@ Object.freeze({
50614
51324
  form: "single",
50615
51325
  optional: false
50616
51326
  }],
51327
+ "vibration.getStatus": [{
51328
+ name: "deviceId",
51329
+ form: "single",
51330
+ optional: false
51331
+ }],
50617
51332
  "videoclips.dialClipStream": [{
50618
51333
  name: "deviceId",
50619
51334
  form: "single",
@@ -50649,6 +51364,11 @@ Object.freeze({
50649
51364
  form: "single",
50650
51365
  optional: false
50651
51366
  }],
51367
+ "waterHeater.getStatus": [{
51368
+ name: "deviceId",
51369
+ form: "single",
51370
+ optional: false
51371
+ }],
50652
51372
  "waterHeater.setAway": [{
50653
51373
  name: "deviceId",
50654
51374
  form: "single",
@@ -50664,6 +51384,11 @@ Object.freeze({
50664
51384
  form: "single",
50665
51385
  optional: false
50666
51386
  }],
51387
+ "weather.getStatus": [{
51388
+ name: "deviceId",
51389
+ form: "single",
51390
+ optional: false
51391
+ }],
50667
51392
  "webrtcSession.addIceCandidate": [{
50668
51393
  name: "deviceId",
50669
51394
  form: "single",
@@ -51189,6 +51914,18 @@ var STATE_SUMMARY_VARS = [
51189
51914
  systemEventKinds: [...SUMMARISABLE],
51190
51915
  systemEventDeliveries: ["summary"]
51191
51916
  },
51917
+ {
51918
+ name: "summarySection",
51919
+ label: "Summary: this section",
51920
+ description: "The section a per-section summary row is about (D698); on a summary sent as one message, every section it reports, comma-separated.",
51921
+ example: "Batterie scariche",
51922
+ group: "system",
51923
+ families: ["rule"],
51924
+ fields: [...RULE_TEXT],
51925
+ kinds: ["system"],
51926
+ systemEventKinds: [...SUMMARISABLE],
51927
+ systemEventDeliveries: ["summary"]
51928
+ },
51192
51929
  ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
51193
51930
  name: v.name,
51194
51931
  label: v.label,
@@ -51265,6 +52002,37 @@ var STATE_SUMMARY_VARS = [
51265
52002
  [...RULE_TEXT],
51266
52003
  ...STATE_SUMMARY_VARS
51267
52004
  ];
52005
+ new Set([
52006
+ "disarmed",
52007
+ "armed_home",
52008
+ "armed_away",
52009
+ "armed_night",
52010
+ "armed_vacation",
52011
+ "armed_custom_bypass",
52012
+ "arming",
52013
+ "disarming",
52014
+ "pending",
52015
+ "triggered",
52016
+ "open",
52017
+ "opening",
52018
+ "closing",
52019
+ "closed",
52020
+ "stopped",
52021
+ "locked",
52022
+ "unlocked",
52023
+ "locking",
52024
+ "unlocking",
52025
+ "jammed",
52026
+ "on",
52027
+ "off",
52028
+ "home",
52029
+ "not_home",
52030
+ "matched",
52031
+ "diverged",
52032
+ "unknown",
52033
+ "latched",
52034
+ "live"
52035
+ ]);
51268
52036
  /**
51269
52037
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
51270
52038
  *
@@ -51358,7 +52126,7 @@ var PreviewTextField = string().max(200);
51358
52126
  * Whether the notification's preview is a STILL or a short animation.
51359
52127
  *
51360
52128
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
51361
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
52129
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
51362
52130
  * night reads better as three seconds of motion than as one frame of it. Both
51363
52131
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
51364
52132
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -51496,6 +52264,7 @@ TimelapseRuleInputSchema.extend({
51496
52264
  createdAt: number(),
51497
52265
  updatedAt: number()
51498
52266
  });
52267
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
51499
52268
  object({
51500
52269
  /**
51501
52270
  * Fraction of the box's own size added on EACH side before cutting.