@camstack/addon-matter-broker 0.2.115 → 0.2.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +1542 -536
  2. package/dist/addon.mjs +1542 -536
  3. package/package.json +2 -4
package/dist/addon.mjs CHANGED
@@ -11,7 +11,7 @@ import { networkInterfaces, tmpdir, uptime } from "node:os";
11
11
  import { finished } from "node:stream/promises";
12
12
  import { createConnection, createServer as createServer$2 } from "node:net";
13
13
  import * as dgram from "node:dgram";
14
- //#region ../types/dist/event-category-C5xZWqz6.mjs
14
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
15
15
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
16
16
  EventCategory["SystemBoot"] = "system.boot";
17
17
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -544,6 +544,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
544
544
  */
545
545
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
546
546
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
547
+ /**
548
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
549
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
550
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
551
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
552
+ * for a camera with no attached sensor). Providers must not emit
553
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
554
+ * keeps it so (camera-grid is a named, deferred exception).
555
+ */
547
556
  EventCategory["ProviderMotion"] = "provider.motion";
548
557
  EventCategory["ProviderDetection"] = "provider.detection";
549
558
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5382,7 +5391,7 @@ var ZodIssueCode = {
5382
5391
  var ZodFirstPartyTypeKind;
5383
5392
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5384
5393
  //#endregion
5385
- //#region ../types/dist/sleep-C-v96WpE.mjs
5394
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5386
5395
  /**
5387
5396
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5388
5397
  * window to float samples (D455).
@@ -7425,23 +7434,74 @@ function errMsg(err) {
7425
7434
  return String(err);
7426
7435
  }
7427
7436
  //#endregion
7428
- //#region ../types/dist/composition-BK-GTFYb.mjs
7437
+ //#region ../types/dist/composition-Bzr7GwgP.mjs
7429
7438
  /**
7430
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7431
- * valutatore puro del suo FRONTE.
7439
+ * The ffmpeg build every CamStack node downloads, per platform and arch.
7440
+ *
7441
+ * ## Why a download, on every node, including the container
7442
+ *
7443
+ * The version of ffmpeg used to be CODE: baked into the image, changeable only
7444
+ * by rebuilding and rolling it. Now it is DATA — this file states the release,
7445
+ * and a node that boots with a different one downloads it. Changing ffmpeg is a
7446
+ * framework bump, not an image rebuild.
7447
+ *
7448
+ * It also settles the licence question by removing it. FFmpeg's licence is
7449
+ * chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
7450
+ * (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
7451
+ * makes the result undistributable. These are GPL v3 builds, and we do not
7452
+ * distribute them: the user's own node fetches the archive from the project
7453
+ * that publishes it, so we convey no GPL work and inherit none of the
7454
+ * obligations that come with conveying one. That is the whole reason the image
7455
+ * no longer installs ffmpeg at all — not a technical preference.
7456
+ *
7457
+ * ## Why jellyfin-ffmpeg
7432
7458
  *
7433
- * Viveva dentro il builtin virtual-doorbell
7434
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7435
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7436
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7437
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7438
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7439
- * SPOSTA qui e il doorbell la ri-esporta.
7459
+ * One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
7460
+ * from one release, with the widest hardware surface of anything maintained:
7461
+ * VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
7462
+ * MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
7463
+ * so the binary is not linked against a driver stack it may not find.
7440
7464
  *
7441
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7442
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7443
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7444
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7465
+ * **What it still needs from the host, measured 2026-09-19.** On the Unraid
7466
+ * HOST, which has no libva, `-vaapi_device` aborts the process outright —
7467
+ * `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
7468
+ * "Assertion in generated code"' failed`. Inside our container, which installs
7469
+ * libva plus the iHD driver, the same command encodes: verified end to end with
7470
+ * `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
7471
+ * KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
7472
+ *
7473
+ * ## Why each artifact carries a checksum and a claim
7474
+ *
7475
+ * The download is now the only source, so `sha256` is verified before anything
7476
+ * is extracted — jellyfin publishes no checksums on its release assets, so
7477
+ * these are ours, taken at the version bump. And `expectedHwaccels` is what the
7478
+ * build is supposed to be able to do: a binary that probes short of its own
7479
+ * claim is a source that changed under us, which is precisely what went
7480
+ * unnoticed for months when a static build turned out to carry no VAAPI at all
7481
+ * (D536).
7482
+ */
7483
+ /** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
7484
+ var FFMPEG_RELEASE = "8.1.2-5";
7485
+ var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
7486
+ function portable(target) {
7487
+ return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
7488
+ }
7489
+ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7490
+ /**
7491
+ * THE table "which boolean of this device type counts as HIGH", and the
7492
+ * pure evaluator of its EDGE.
7493
+ *
7494
+ * ONE table for everyone who asks "is this device HIGH?": the
7495
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7496
+ * sensor markers, the orchestrator's camera-sensors bridge and
7497
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7498
+ * A second copy would diverge at the first cap added, and the symptom — "the
7499
+ * sensor rings the doorbell but does not record" — is exactly D62.
7500
+ *
7501
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7502
+ * two must not be unified: that one answers "what is the state WORD for a
7503
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7504
+ * boolean's EDGE counts". Deliberately different vocabularies.
7445
7505
  */
7446
7506
  /**
7447
7507
  * Known binary / switch source caps → the boolean slice field whose
@@ -8899,6 +8959,82 @@ var ArchivedDebugNoteQueryInputSchema = object({
8899
8959
  /** Max rows returned, newest-first. */
8900
8960
  limit: number().int().min(1).max(1e3).optional()
8901
8961
  });
8962
+ var TrackRelationKindSchema = _enum(["same-subject", "different-subjects"]);
8963
+ var TrackSideSchema = _enum(["before", "after"]);
8964
+ var TrackPurityValueSchema = _enum(["mixed", "not-a-subject"]);
8965
+ var TrackRelationSchema = object({
8966
+ id: string$2(),
8967
+ deviceId: number().int(),
8968
+ leftTrackId: string$2(),
8969
+ rightTrackId: string$2(),
8970
+ leftSide: TrackSideSchema.nullable(),
8971
+ rightSide: TrackSideSchema.nullable(),
8972
+ relation: TrackRelationKindSchema,
8973
+ presence: literal("continuous").nullable(),
8974
+ leftStartedAt: number().int().nullable(),
8975
+ rightStartedAt: number().int().nullable(),
8976
+ createdAt: number().int(),
8977
+ updatedAt: number().int(),
8978
+ author: string$2().nullable()
8979
+ });
8980
+ var TrackPuritySchema = object({
8981
+ id: string$2(),
8982
+ deviceId: number().int(),
8983
+ sourceTrackId: string$2(),
8984
+ purity: TrackPurityValueSchema,
8985
+ switchAtMs: number().int().nullable(),
8986
+ startedAt: number().int().nullable(),
8987
+ createdAt: number().int(),
8988
+ updatedAt: number().int(),
8989
+ author: string$2().nullable()
8990
+ });
8991
+ var SetTrackRelationInputSchema = object({
8992
+ deviceId: number().int(),
8993
+ trackId: string$2().min(1),
8994
+ otherTrackId: string$2().min(1),
8995
+ relation: TrackRelationKindSchema,
8996
+ presence: literal("continuous").optional(),
8997
+ trackSide: TrackSideSchema.optional(),
8998
+ otherSide: TrackSideSchema.optional()
8999
+ });
9000
+ var ClearTrackRelationInputSchema = object({
9001
+ deviceId: number().int(),
9002
+ trackId: string$2().min(1),
9003
+ otherTrackId: string$2().min(1)
9004
+ });
9005
+ var ClearTrackRelationResultSchema = object({ cleared: boolean() });
9006
+ var SetTrackPurityInputSchema = object({
9007
+ deviceId: number().int(),
9008
+ trackId: string$2().min(1),
9009
+ purity: _enum([
9010
+ "pure",
9011
+ "mixed",
9012
+ "not-a-subject"
9013
+ ]),
9014
+ switchAtMs: number().int().optional()
9015
+ });
9016
+ /** `null` when `pure` cleared the row. */
9017
+ var SetTrackPurityResultSchema = object({ purity: TrackPuritySchema.nullable() });
9018
+ var ListTrackRelationsInputSchema = object({
9019
+ deviceId: number().int(),
9020
+ trackId: string$2().optional(),
9021
+ since: number().int().optional(),
9022
+ until: number().int().optional(),
9023
+ limit: number().int().min(1).max(5e3).optional()
9024
+ });
9025
+ var ListTrackRelationsResultSchema = object({
9026
+ relations: array(TrackRelationSchema).readonly(),
9027
+ purities: array(TrackPuritySchema).readonly(),
9028
+ truncated: boolean()
9029
+ });
9030
+ _enum([
9031
+ "unknown-track",
9032
+ "cross-device",
9033
+ "self-relation",
9034
+ "contradiction",
9035
+ "switch-outside-track",
9036
+ "switch-needs-mixed"
9037
+ ]);
8902
9038
  /**
8903
9039
  * Ops-log — the durable, append-only operations audit shared by the
8904
9040
  * recordings and events management surfaces.
@@ -8974,22 +9110,22 @@ var CLASS_MAP_MACRO_TARGETS = [
8974
9110
  "package"
8975
9111
  ];
8976
9112
  /**
8977
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
8978
- * un operatore può selezionare.
9113
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9114
+ * an operator can select.
8979
9115
  *
8980
- * Sono le tre offerte dallo step `object-detection`
9116
+ * They are the three offered by the `object-detection` step
8981
9117
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
8982
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
8983
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
8984
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
8985
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9118
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9119
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9120
+ * `package-detection`, whose enablement is driven by zones, and offering the
9121
+ * same word twice has already made an operator turn on the COCO proxy
8986
9122
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
8987
9123
  *
8988
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
8989
- * dello step e una seconda volta come union `FirstLevelMacro`
8990
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
8991
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
8992
- * successiva.
9124
+ * ONE list. Before today the same three were hand-written in the step's offer
9125
+ * and a second time as the union `FirstLevelMacro`
9126
+ * (`types/detection.ts`); a third copy for the recording trigger
9127
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9128
+ * invisible.
8993
9129
  */
8994
9130
  var FIRST_LEVEL_MACRO_CLASSES = [
8995
9131
  "person",
@@ -9054,17 +9190,17 @@ var RecordingStorageModeSchema = _enum([
9054
9190
  "on-device-decision"
9055
9191
  ]);
9056
9192
  /**
9057
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9058
- * tre offerte dallo step `object-detection`, da UNA lista
9193
+ * The macro classes that can open a recording window — the same
9194
+ * three offered by the `object-detection` step, from ONE list
9059
9195
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9060
9196
  */
9061
9197
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9062
9198
  /**
9063
- * True quando `values` non ripete un elemento.
9199
+ * True when `values` does not repeat an element.
9064
9200
  *
9065
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9066
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9067
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9201
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9202
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9203
+ * would count its windows twice in `segmentMissedByMs`.
9068
9204
  */
9069
9205
  var noDuplicates = (values) => new Set(values).size === values.length;
9070
9206
  /** Which detectors trigger an `events`-mode band. */
@@ -9072,34 +9208,34 @@ var RecordingTriggersSchema = object({
9072
9208
  motion: boolean().optional(),
9073
9209
  audioThresholdDbfs: number().optional(),
9074
9210
  /**
9075
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9076
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9077
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9078
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9211
+ * The macro classes whose detection opens a window. ABSENT = the source
9212
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9213
+ * trigger on, no class" is the same shape "enabled and records
9214
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9079
9215
  *
9080
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9081
- * quelle che hanno attraversato `enabledMacroClasses`, i
9082
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9083
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9084
- * finestre — vedi `recorder/object-trigger.ts`.
9216
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9217
+ * those that went through `enabledMacroClasses`, the
9218
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9219
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9220
+ * windows — see `recorder/object-trigger.ts`.
9085
9221
  */
9086
9222
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9087
9223
  /**
9088
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9089
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9224
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9225
+ * is not listened to; an empty array is rejected for the same reason as
9090
9226
  * `objectClasses`.
9091
9227
  *
9092
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9093
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9094
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9095
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9096
- * device (D12) — mai un elenco globale di cap.
9228
+ * These are SOURCE device ids, not cameras: the band names them, so the
9229
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9230
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9231
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9232
+ * device (D12) — never a global list of caps.
9097
9233
  *
9098
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9099
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9100
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9101
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9102
- * registrare.
9234
+ * What "high" means depends on the device TYPE and is not decided here:
9235
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9236
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9237
+ * the level: a contact found already open at runner restart does not
9238
+ * record.
9103
9239
  */
9104
9240
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9105
9241
  });
@@ -10176,7 +10312,7 @@ var DecoderSessionConfigSchema = object({
10176
10312
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10177
10313
  * thing: the logging settings document on the `system` cap. Two authorities
10178
10314
  * over the values is the exact defect
10179
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10315
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10180
10316
  * remove; re-introducing it from the cure side would be grotesque.
10181
10317
  *
10182
10318
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -11452,6 +11588,68 @@ method(object({
11452
11588
  password: string$2()
11453
11589
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string$2() }), string$2()), method(record(string$2(), string$2()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string$2() }), AuthResultSchema.nullable());
11454
11590
  /**
11591
+ * How long one snapshot may take end to end: the pinned copy of the whole
11592
+ * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
11593
+ * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
11594
+ * generous because a timeout fails the whole backup run.
11595
+ */
11596
+ var DATABASE_SNAPSHOT_TIMEOUT_MS = 30 * 6e4;
11597
+ /**
11598
+ * The databases an owner snapshots — declared by the OWNER, so a caller never
11599
+ * guesses which file is live from what sits beside it (a cleanly closed
11600
+ * database has no sidecars and is just as owned). Paths are absolute on the
11601
+ * owner's host.
11602
+ */
11603
+ var DatabaseSnapshotTargetsSchema = object({ databases: array(object({ livePath: string$2() })) });
11604
+ /** One b-tree table of the snapshot and the rows it holds. */
11605
+ var DatabaseSnapshotTableSchema = object({
11606
+ name: string$2(),
11607
+ rows: number().int().nonnegative()
11608
+ });
11609
+ /**
11610
+ * What the owner wrote. Every path is ABSOLUTE on the owner's host — the
11611
+ * caller pins the call to its own node, so it can read the file; a caller
11612
+ * that finds the path outside its own data root refuses it rather than
11613
+ * archiving something it cannot place.
11614
+ */
11615
+ var DatabaseSnapshotResultSchema = object({
11616
+ /** The live database the snapshot copies. Never to be copied as a file. */
11617
+ livePath: string$2(),
11618
+ /** The verified copy: `<livePath>` + {@link DATABASE_SNAPSHOT_SUFFIX}. */
11619
+ snapshotPath: string$2(),
11620
+ sizeBytes: number().int().nonnegative(),
11621
+ pageCount: number().int().nonnegative(),
11622
+ pageSize: number().int().positive(),
11623
+ /**
11624
+ * Every b-tree table with its row count. Read inside the SAME read
11625
+ * transaction the copy was taken from, and found EQUAL on the copy — so
11626
+ * this is the content of the snapshot, not an estimate of the source.
11627
+ */
11628
+ tables: array(DatabaseSnapshotTableSchema),
11629
+ /** Wall time of the pinned copy (row counts included). */
11630
+ copyMs: number().nonnegative(),
11631
+ /** Wall time of the checks on the copy (`integrity_check`, row counts). */
11632
+ verifyMs: number().nonnegative(),
11633
+ /** When the pinned read transaction began (ms epoch) — the snapshot's instant. */
11634
+ takenAt: number()
11635
+ });
11636
+ /**
11637
+ * A snapshot as an ARCHIVE records it (`ArchiveManifest.sqliteSnapshots`):
11638
+ * the same facts, with both paths relative to the archive root (the node's
11639
+ * data dir), because an archive outlives the host that made it.
11640
+ */
11641
+ var ArchiveSqliteSnapshotSchema = DatabaseSnapshotResultSchema.extend({
11642
+ /** Archive-relative path of the live database the archive does NOT carry. */
11643
+ livePath: string$2(),
11644
+ /** Archive-relative path of the verified copy it carries in its place. */
11645
+ snapshotPath: string$2()
11646
+ });
11647
+ method(object({}), DatabaseSnapshotTargetsSchema, { auth: "admin" }), method(object({}), DatabaseSnapshotResultSchema, {
11648
+ kind: "mutation",
11649
+ auth: "admin",
11650
+ timeoutMs: DATABASE_SNAPSHOT_TIMEOUT_MS
11651
+ });
11652
+ /**
11455
11653
  * Orchestrator-side destination metadata. The orchestrator computes
11456
11654
  * `id = <addonId>:<subId>` from its provider lookup so consumers
11457
11655
  * (admin UI, restore flow) see one canonical key.
@@ -11562,7 +11760,12 @@ var ArchiveManifestSchema = object({
11562
11760
  locations: array(string$2()),
11563
11761
  entries: array(ArchiveEntrySchema),
11564
11762
  totalBytes: number(),
11565
- totalFiles: number()
11763
+ totalFiles: number(),
11764
+ /**
11765
+ * Live SQLite databases this archive carries as their OWNER's verified
11766
+ * snapshot instead of as files (D693). Absent on archives made before.
11767
+ */
11768
+ sqliteSnapshots: array(ArchiveSqliteSnapshotSchema).optional()
11566
11769
  });
11567
11770
  var LocationStatSchema = object({
11568
11771
  name: string$2(),
@@ -11820,7 +12023,7 @@ var BrokerStatusEnum = _enum([
11820
12023
  "unreachable",
11821
12024
  "error"
11822
12025
  ]);
11823
- var BrokerInfoSchema$1 = object({
12026
+ var BrokerInfoSchema = object({
11824
12027
  /** Stable broker id. Persisted; survives addon restarts. */
11825
12028
  id: string$2(),
11826
12029
  /** Addon id of the provider that OWNS this broker.
@@ -11963,8 +12166,8 @@ var brokerCapability = {
11963
12166
  kind: "poll"
11964
12167
  },
11965
12168
  methods: {
11966
- list: method(ListInputSchema, array(BrokerInfoSchema$1)),
11967
- get: method(GetInputSchema, BrokerInfoSchema$1.nullable()),
12169
+ list: method(ListInputSchema, array(BrokerInfoSchema)),
12170
+ get: method(GetInputSchema, BrokerInfoSchema.nullable()),
11968
12171
  /** Enumerate which addon provides which broker kind(s) for the
11969
12172
  * unified create picker. The auto-mount fans this array across
11970
12173
  * every registered `broker` provider (array-output method), so the
@@ -13137,6 +13340,116 @@ var CompositionBlockStateSchema = object({
13137
13340
  reason: string$2().optional(),
13138
13341
  fields: array(CompositionFieldStateSchema)
13139
13342
  });
13343
+ var ComposerBlockSchema = object({
13344
+ id: string$2().min(1),
13345
+ name: string$2().min(1).max(120),
13346
+ enabled: boolean(),
13347
+ updatedAt: number(),
13348
+ composition: CompositionSchema
13349
+ });
13350
+ /**
13351
+ * A stored composition row that no longer PARSES — a schema a release
13352
+ * tightened, a row written by a newer build. Sent by id so the composer keeps
13353
+ * its device as a placeholder and reports the block `failed` naming the parse
13354
+ * error. Leaving it out would read, twice, exactly like a delete (D49).
13355
+ */
13356
+ var ComposerUnreadableBlockSchema = object({
13357
+ id: string$2().min(1),
13358
+ /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
13359
+ name: string$2().nullable(),
13360
+ /** The parse error, in the operator's words. */
13361
+ error: string$2()
13362
+ });
13363
+ method(object({
13364
+ /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
13365
+ integrationId: string$2().nullable(),
13366
+ /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
13367
+ storeLoaded: boolean(),
13368
+ blocks: array(ComposerBlockSchema),
13369
+ /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
13370
+ restartBlockIds: array(string$2()),
13371
+ /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
13372
+ unreadableBlocks: array(ComposerUnreadableBlockSchema)
13373
+ }), object({
13374
+ blocks: array(object({
13375
+ blockId: string$2(),
13376
+ status: CompositionRunStatusSchema,
13377
+ reason: string$2().optional()
13378
+ })),
13379
+ /**
13380
+ * False when the composer declared and withdrew NOTHING (store not loaded,
13381
+ * integration unknown, its own rows unread or read empty once). An empty
13382
+ * apply the caller owes is paid off only by one the composer acted on (D49).
13383
+ */
13384
+ acted: boolean()
13385
+ }), {
13386
+ kind: "mutation",
13387
+ auth: "admin"
13388
+ }), method(object({ blockIds: array(string$2()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
13389
+ var NUMBER = {
13390
+ kind: "fixed",
13391
+ result: ["number"]
13392
+ };
13393
+ var BOOLEAN = {
13394
+ kind: "fixed",
13395
+ result: ["boolean"]
13396
+ };
13397
+ Object.freeze({
13398
+ min: NUMBER,
13399
+ max: NUMBER,
13400
+ abs: NUMBER,
13401
+ floor: NUMBER,
13402
+ ceil: NUMBER,
13403
+ sqrt: NUMBER,
13404
+ round: NUMBER,
13405
+ pow: NUMBER,
13406
+ clamp: NUMBER,
13407
+ avg: NUMBER,
13408
+ sum: NUMBER,
13409
+ age: NUMBER,
13410
+ convert: NUMBER,
13411
+ coalesce: { kind: "coalesce" },
13412
+ any: BOOLEAN,
13413
+ all: BOOLEAN,
13414
+ within: BOOLEAN,
13415
+ latest: {
13416
+ kind: "fixed",
13417
+ result: ["number", "null"]
13418
+ },
13419
+ number: {
13420
+ kind: "fixed",
13421
+ result: ["number", "null"]
13422
+ },
13423
+ rose: {
13424
+ kind: "fixed",
13425
+ result: ["number", "null"]
13426
+ },
13427
+ count: NUMBER
13428
+ });
13429
+ `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
13430
+ /**
13431
+ * What a composition may BUILD on a device type (G2, D676): the caps a new
13432
+ * device of that type, or an existing one that lacks them, can be given. The
13433
+ * blockers are the validator's own refusal codes, and a spec compares this
13434
+ * listing with `planComposition` for every capability — the editor can never
13435
+ * offer a feature the save would refuse for the same reason.
13436
+ */
13437
+ var ComposableFieldSchema = object({
13438
+ path: string$2(),
13439
+ kind: CompositionFieldKindSchema,
13440
+ nullable: boolean(),
13441
+ required: boolean(),
13442
+ enumValues: array(string$2()).optional()
13443
+ });
13444
+ var ComposableCapabilitiesSchema = object({ caps: array(object({
13445
+ cap: string$2(),
13446
+ fields: array(ComposableFieldSchema),
13447
+ /** Present for an item-array cap (`consumables.items`). */
13448
+ itemArray: object({
13449
+ path: string$2(),
13450
+ keyField: string$2()
13451
+ }).optional()
13452
+ })) });
13140
13453
  /** Where a block runs. The operator chooses — a block driving a device on an
13141
13454
  * agent is the reason placement is not fixed to the hub. */
13142
13455
  var CoreBlockPlacementSchema = union([literal("hub"), string$2().min(1)]);
@@ -13301,7 +13614,7 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
13301
13614
  }), method(object({ blockId: string$2() }), CompositionGetReadSchema, { auth: "admin" }), method(object({}), CompositionListReadSchema, { auth: "admin" }), method(object({
13302
13615
  addonId: string$2().min(1),
13303
13616
  stableId: string$2().min(1)
13304
- }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({
13617
+ }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({ deviceType: _enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }), method(object({
13305
13618
  composition: CompositionSchema,
13306
13619
  blockId: string$2().optional()
13307
13620
  }), CompositionValidationSchema, {
@@ -13309,52 +13622,6 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
13309
13622
  auth: "admin",
13310
13623
  access: "view"
13311
13624
  });
13312
- var ComposerBlockSchema = object({
13313
- id: string$2().min(1),
13314
- name: string$2().min(1).max(120),
13315
- enabled: boolean(),
13316
- updatedAt: number(),
13317
- composition: CompositionSchema
13318
- });
13319
- /**
13320
- * A stored composition row that no longer PARSES — a schema a release
13321
- * tightened, a row written by a newer build. Sent by id so the composer keeps
13322
- * its device as a placeholder and reports the block `failed` naming the parse
13323
- * error. Leaving it out would read, twice, exactly like a delete (D49).
13324
- */
13325
- var ComposerUnreadableBlockSchema = object({
13326
- id: string$2().min(1),
13327
- /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
13328
- name: string$2().nullable(),
13329
- /** The parse error, in the operator's words. */
13330
- error: string$2()
13331
- });
13332
- method(object({
13333
- /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
13334
- integrationId: string$2().nullable(),
13335
- /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
13336
- storeLoaded: boolean(),
13337
- blocks: array(ComposerBlockSchema),
13338
- /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
13339
- restartBlockIds: array(string$2()),
13340
- /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
13341
- unreadableBlocks: array(ComposerUnreadableBlockSchema)
13342
- }), object({
13343
- blocks: array(object({
13344
- blockId: string$2(),
13345
- status: CompositionRunStatusSchema,
13346
- reason: string$2().optional()
13347
- })),
13348
- /**
13349
- * False when the composer declared and withdrew NOTHING (store not loaded,
13350
- * integration unknown, its own rows unread or read empty once). An empty
13351
- * apply the caller owes is paid off only by one the composer acted on (D49).
13352
- */
13353
- acted: boolean()
13354
- }), {
13355
- kind: "mutation",
13356
- auth: "admin"
13357
- }), method(object({ blockIds: array(string$2()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
13358
13625
  /**
13359
13626
  * `custom-model-registry` — collection cap exposing operator-registered
13360
13627
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -13649,7 +13916,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13649
13916
  *
13650
13917
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
13651
13918
  * was wrong — corrected 2026-08-26 after the hop map
13652
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
13919
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
13653
13920
  * There is **no Zod parse at all** between the door and the engine: the
13654
13921
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
13655
13922
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -14529,7 +14796,17 @@ var SiteZoneSchema = object({
14529
14796
  var ProviderStatusSchema = object({
14530
14797
  connected: boolean(),
14531
14798
  deviceCount: number(),
14532
- error: string$2().optional()
14799
+ error: string$2().optional(),
14800
+ /**
14801
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
14802
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
14803
+ * which every provider bundles, so only a provider BUILT from D696-amended
14804
+ * code says it. ABSENT means an older provider that may still write it (raw
14805
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
14806
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
14807
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
14808
+ */
14809
+ motionSliceWriter: literal("orchestrator").optional()
14533
14810
  });
14534
14811
  object({
14535
14812
  externalId: string$2(),
@@ -15408,6 +15685,121 @@ method(object({
15408
15685
  deviceIds: array(number()).readonly(),
15409
15686
  caps: array(string$2()).readonly().optional()
15410
15687
  }), record(string$2(), record(string$2(), unknown().nullable())));
15688
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15689
+ DeviceType.Camera;
15690
+ /** A slot's editable value, discriminated on `slot`. */
15691
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15692
+ DeviceType.Camera;
15693
+ var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15694
+ state: literal("unreachable"),
15695
+ reason: string$2()
15696
+ })]);
15697
+ /** One provider of slots — the D554 source-listing row. */
15698
+ var ExtensionSourceSchema = object({
15699
+ /** The routing prefix of every `slotRef` this provider serves. */
15700
+ source: string$2(),
15701
+ label: string$2(),
15702
+ /** Stamped by the dispatcher from the registry (D557). */
15703
+ addonId: string$2().optional(),
15704
+ availability: ExtensionSourceAvailabilitySchema
15705
+ });
15706
+ /**
15707
+ * How one picked source stands. `missing` — no such device (deleted, or an id
15708
+ * that never existed); `foreign` — a device the slot's owner cannot read from
15709
+ * (a source owned by another addon); `unavailable` — it exists but is not a
15710
+ * valid source now (a container, which is never a pick). A source in any of
15711
+ * these states is SHOWN, never silently dropped or re-pointed.
15712
+ */
15713
+ var ExtensionSourceHealthSchema = _enum([
15714
+ "ok",
15715
+ "missing",
15716
+ "foreign",
15717
+ "unavailable"
15718
+ ]);
15719
+ var ExtensionSlotSourceSchema = object({
15720
+ deviceId: number().int(),
15721
+ /** The device's name, or null when it could not be resolved (`missing`). */
15722
+ name: string$2().nullable(),
15723
+ health: ExtensionSourceHealthSchema,
15724
+ detail: string$2().optional()
15725
+ });
15726
+ /**
15727
+ * A DIFFERENT authority that gates whether the slot's sources do anything —
15728
+ * reported read-only, named, never written through this contract (D62) — e.g.
15729
+ * a slot whose sources act only while a wrapper is bound to the host, a
15730
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15731
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15732
+ * not be read; it is not "inactive" (D315).
15733
+ */
15734
+ var ExtensionSlotActivationSchema = object({
15735
+ state: _enum([
15736
+ "active",
15737
+ "inactive",
15738
+ "unknown"
15739
+ ]),
15740
+ authority: string$2(),
15741
+ detail: string$2().optional()
15742
+ });
15743
+ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15744
+ state: literal("ok"),
15745
+ /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15746
+ slotRef: string$2(),
15747
+ slot: ExtensionSlotIdSchema,
15748
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15749
+ authority: string$2(),
15750
+ activation: ExtensionSlotActivationSchema.nullable(),
15751
+ value: ExtensionSlotValueSchema,
15752
+ sources: array(ExtensionSlotSourceSchema),
15753
+ /**
15754
+ * Devices the owner links to the host ON ITS OWN, by structure, and that
15755
+ * are therefore not in `value` (D655): a camera's device-tree children, and
15756
+ * the child a picked container stands for (D356 — a container is never a
15757
+ * pick, its children are). Read-only; the editor shows them and may offer
15758
+ * per-device options on them (the linked-devices tracked-event opt-in).
15759
+ * Absent when the owner links nothing beyond the picks, and from an owner
15760
+ * older than D655.
15761
+ */
15762
+ impliedSources: array(ExtensionSlotSourceSchema).optional()
15763
+ }), object({
15764
+ state: literal("unreachable"),
15765
+ addonId: string$2(),
15766
+ reason: string$2()
15767
+ })]);
15768
+ var ExtensionCandidatesSchema = object({ candidates: array(object({
15769
+ deviceId: number().int(),
15770
+ name: string$2(),
15771
+ type: string$2(),
15772
+ location: string$2().nullable(),
15773
+ /** The container a child belongs to — "Spotlight 2" alone does not say which. */
15774
+ parentName: string$2().nullable(),
15775
+ /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
15776
+ suggested: boolean(),
15777
+ /** Why it is suggested ("same location"), null when it is not. */
15778
+ reason: string$2().nullable()
15779
+ })) });
15780
+ /**
15781
+ * What a write did. `none` — applied in place. `host` / `parent` — the owner
15782
+ * reloaded the host (or, for an accessory child, its parent) because the
15783
+ * write changed what the host REGISTERS. A slot never adds a capability to its
15784
+ * host — that is a composition (D663) — so today's owners answer `none`.
15785
+ */
15786
+ var ExtensionSetResultSchema = object({
15787
+ applied: literal(true),
15788
+ reload: _enum([
15789
+ "none",
15790
+ "host",
15791
+ "parent"
15792
+ ])
15793
+ });
15794
+ var ExtensionSlotRefInputSchema = object({
15795
+ deviceId: number().int(),
15796
+ slotRef: string$2().min(1)
15797
+ });
15798
+ var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
15799
+ method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)), method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)), method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema), method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
15800
+ kind: "mutation",
15801
+ auth: "admin"
15802
+ });
15411
15803
  /**
15412
15804
  * Per-field ownership (D663). A composition may CLAIM fields of a native
15413
15805
  * cap's runtime-state slice on an existing device: `replace` overlays the
@@ -15631,7 +16023,7 @@ method(object({
15631
16023
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15632
16024
  * ends in a log line, and a log line is the thing the operator asked to stop
15633
16025
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15634
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
16026
+ * 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
15635
16027
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15636
16028
  * media blackout were both diagnosed. The counters stay; this is where they can
15637
16029
  * be READ.
@@ -17007,7 +17399,7 @@ var BrokerStatusSchema$1 = _enum([
17007
17399
  "unreachable",
17008
17400
  "tls-error"
17009
17401
  ]);
17010
- var BrokerInfoSchema = object({
17402
+ var BrokerInfoSchema$1 = object({
17011
17403
  id: string$2(),
17012
17404
  name: string$2(),
17013
17405
  url: string$2(),
@@ -17070,7 +17462,7 @@ var StatusSchema = object({
17070
17462
  brokerCount: number(),
17071
17463
  embeddedRunning: boolean()
17072
17464
  });
17073
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17465
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
17074
17466
  kind: "mutation",
17075
17467
  auth: "admin"
17076
17468
  }), method(IdInputSchema, _void(), {
@@ -18418,17 +18810,25 @@ var NcScheduleSchema = object({
18418
18810
  */
18419
18811
  var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
18420
18812
  /**
18813
+ * How a summary window is SENT (D698): `rule` (absent) = ONE notification with
18814
+ * every section; `section` = one notification per section with something to
18815
+ * say (an unavailable section included), each titled by its section.
18816
+ */
18817
+ var NcStateSummarySplitSchema = _enum(["rule", "section"]);
18818
+ /**
18421
18819
  * `summary`: the rule sends NOTHING per event. When a window of `schedule`
18422
18820
  * CLOSES, it sends one notification with the current state of its kinds
18423
18821
  * (low batteries, consumables to replace, devices offline, updates
18424
- * available, ...), after its own filters. Nothing to report sends nothing.
18822
+ * available, ...), after its own filters — or, with `split: 'section'`
18823
+ * (D698), one notification per section. Nothing to report sends nothing.
18425
18824
  * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
18426
18825
  * midnight crossing); the send time is each window's END. Refused on the
18427
18826
  * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
18428
18827
  */
18429
18828
  var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
18430
18829
  mode: literal("summary"),
18431
- schedule: NcScheduleSchema
18830
+ schedule: NcScheduleSchema,
18831
+ split: NcStateSummarySplitSchema.optional()
18432
18832
  })]);
18433
18833
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
18434
18834
  var NcPlateMatcherSchema = object({
@@ -18568,8 +18968,14 @@ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
18568
18968
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
18569
18969
  var NcZoneConditionSchema = object({
18570
18970
  ids: array(string$2().min(1)).min(1),
18571
- /** Quantifier over `ids` — at least one / every one visited. */
18572
- match: _enum(["any", "all"]).default("any")
18971
+ /**
18972
+ * LEGACY quantifier over `ids`. Absent = `any` (at least one visited), which
18973
+ * is what both editors mean and all they write since D690: "all of" was
18974
+ * removed from the editors because on an immediate rule it asked one
18975
+ * detection to stand in every zone at once. Still ACCEPTED, and `all` still
18976
+ * evaluated, so a stored rule keeps parsing and meaning what it meant.
18977
+ */
18978
+ match: _enum(["any", "all"]).optional()
18573
18979
  });
18574
18980
  /**
18575
18981
  * The P1 condition set — a flat AND of groups; absent group = pass;
@@ -19073,10 +19479,12 @@ var NcMediaPolicySchema = object({
19073
19479
  /**
19074
19480
  * Cooldown GRANULARITY over the subject's class — how much a fired
19075
19481
  * notification suppresses.
19076
- * - `shared` (default, and the absent value) — one window for the whole
19077
- * rule/scope: a cat silences the next dog for `cooldownSec`.
19078
- * - `per-class` — an independent window per detected class, so cat→dog fires
19079
- * at once and cat→cat still waits.
19482
+ * - `per-class` (default, and the absent value since 2026-10-01 — D36) — an
19483
+ * independent window per detected class: cat→dog fires at once, cat→cat
19484
+ * still waits.
19485
+ * - `shared` — one window for the whole rule/scope: a cat silences the next
19486
+ * dog for `cooldownSec`. Only written when an operator picks it; it is no
19487
+ * longer what an unset rule means.
19080
19488
  *
19081
19489
  * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
19082
19490
  * must not be swallowed by a bark's window (the precedent this generalizes —
@@ -19091,8 +19499,11 @@ var NcThrottleSchema = object({
19091
19499
  /**
19092
19500
  * Class granularity of the cooldown key. Optional rather than defaulted:
19093
19501
  * a Zod default does NOT run on the addon→addon cap path, so a persisted
19094
- * rule authored before this field simply carries none — and the engine
19095
- * reads absent as `shared`, the pre-existing behaviour.
19502
+ * rule authored before this field simply carries none. The engine used to
19503
+ * read absent as `shared` (D36); since 2026-10-01 it reads absent as
19504
+ * `per-class` — the operator's default — and only an explicit `shared`
19505
+ * still means `shared`. See `cooldownKey` / `keysPerClass` in the rule
19506
+ * engine, and D36's dated note.
19096
19507
  */
19097
19508
  granularity: NcThrottleGranularitySchema.optional()
19098
19509
  });
@@ -19265,78 +19676,16 @@ var NcRuleInputSchema = object({
19265
19676
  */
19266
19677
  confirm: NcConfirmSchema.optional(),
19267
19678
  /**
19268
- * WAIT for face/plate recognition before saying anything.
19269
- *
19270
- * A notification's TEXT is frozen at enqueue and its media is re-resolved at
19271
- * send; the identity is neither. A face is confirmed after `confirmFrames`
19272
- * agreeing observations — p50 **11.4 s** after the track was first seen,
19273
- * measured on this hub — and an `immediate` rule enqueues on the first object
19274
- * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
19275
- * immediate path, and no amount of media re-resolution fixes a sentence.
19276
- *
19277
- * Only two honest answers exist, and this flag picks between them. It has
19278
- * effect ONLY on a rule that declares a recognition scope
19279
- * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
19280
- * other rule there is nothing to wait for and the flag is inert.
19281
- *
19282
- * | value | what happens |
19283
- * | --- | --- |
19284
- * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
19285
- * | 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) |
19286
- *
19287
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19288
- * on the addon cap path, and absent has to keep meaning exactly what every
19289
- * rule authored before this field meant.
19290
- *
19291
- * The cost of `true` is stated here because the editor states it too: a rule
19292
- * that waits also inherits track-close SEMANTICS — its `zones` condition
19293
- * tests every zone the track visited and a `crossing` condition can no longer
19294
- * be satisfied, because a closed track carries no crossing.
19295
- */
19296
- waitForEnhancement: boolean().optional(),
19297
- /**
19298
- * GROUP a burst of subjects into ONE notification that grows.
19299
- *
19300
- * Seconds of quiet after the last matching subject before the burst is
19301
- * considered over. While it is open, the first subject enqueues immediately —
19302
- * **exactly as today, with no added latency** — and every real growth (a new
19303
- * subject, or a name confirmed on one already in it) REPLACES that
19304
- * notification with an updated one naming everybody. The push carries the
19305
- * group's own coalescing tag, so the phone replaces rather than stacks.
19306
- *
19307
- * `0` / absent = off, and off is today's behaviour byte for byte.
19308
- *
19309
- * ### Why an idle cutoff and not a window
19310
- *
19311
- * The measured seven-person arrival on device 590 spans 110 s with every
19312
- * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
19313
- * idle cutoff holds it as one and ends it when the arrival actually ends.
19314
- * 30 s is a widely shipped value for the same decision.
19315
- *
19316
- * ### What it replaces
19317
- *
19318
- * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
19319
- * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
19320
- * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
19321
- * track that did fire and 7 carrying a confirmed identity nobody heard about.
19322
- * A group collapses the same volume by MERGING, so the cooldown becomes a
19323
- * budget over GROUPS — which is what it always meant — and a growth is never
19324
- * throttled by the window its own first member spent.
19325
- *
19326
- * ### Interaction with {@link waitForEnhancement}
19327
- *
19328
- * They compose, and the order matters. `waitForEnhancement` defers the rule to
19329
- * TRACK CLOSE, so with both set the group is opened by the first member to
19330
- * CLOSE — already carrying its name — and grows as later members close. That
19331
- * is later, and complete. With grouping alone the group opens on the first
19332
- * object event and picks up names as they are confirmed, through the growth
19333
- * path. Neither combination fires twice for one subject.
19334
- *
19335
- * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
19336
- * on the addon cap path, so absent must keep meaning what it meant before this
19337
- * field existed.
19679
+ * IMMEDIATE GROUPED (D692): the first matching subject opens a window of this many
19680
+ * seconds; the rule sends ONE message when the window closes — early, once every
19681
+ * subject in it is final, never before {@link NC_GROUPING_MIN_SEC} — naming everybody
19682
+ * the rule matched, with one mosaic tile per subject.
19683
+ * Absent = plain immediate (one message per track, at the first match).
19684
+ * Only an `immediate` DETECTION rule may carry it (`ruleGroupingApplies`, enforced on
19685
+ * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
19686
+ * path does not run Zod defaults.
19338
19687
  */
19339
- groupIdleSec: number().int().min(0).max(600).optional()
19688
+ groupingSec: number().int().min(5).max(60).optional()
19340
19689
  });
19341
19690
  /**
19342
19691
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -19384,9 +19733,10 @@ var NcRuleClearableKeySchema = _enum([
19384
19733
  "targetUsers",
19385
19734
  "snoozeOptions",
19386
19735
  "snoozeAllowGlobal",
19387
- "waitForEnhancement",
19388
19736
  "actions",
19389
19737
  "confirm",
19738
+ "groupingSec",
19739
+ "waitForEnhancement",
19390
19740
  "groupIdleSec"
19391
19741
  ]);
19392
19742
  var NcRuleUpdateInputSchema = object({
@@ -19408,19 +19758,47 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19408
19758
  */
19409
19759
  disabledTargetIds: array(string$2()).default([])
19410
19760
  });
19761
+ /**
19762
+ * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
19763
+ * still-true state. `backups-exports` and `alarm` are not sections — a completed
19764
+ * job is not an open state, and the alarm stays instant.
19765
+ */
19766
+ var NcStateSummarySectionIdSchema = _enum([
19767
+ "device-state",
19768
+ "streams",
19769
+ "battery",
19770
+ "consumables",
19771
+ "tanks",
19772
+ "detection-blind",
19773
+ "nodes",
19774
+ "addons",
19775
+ "updates"
19776
+ ]);
19411
19777
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19412
19778
  var NcStateSummaryPreviewSectionSchema = object({
19413
- id: string$2(),
19779
+ id: NcStateSummarySectionIdSchema,
19414
19780
  status: _enum(["read", "unavailable"]),
19415
19781
  items: number().int().nonnegative(),
19416
19782
  reason: string$2().optional()
19417
19783
  });
19418
19784
  /**
19785
+ * One notification a previewed summary would send (D698): the whole summary on
19786
+ * a `rule` split (no `section`), one section's on a `section` split.
19787
+ */
19788
+ var NcStateSummaryPreviewRowSchema = object({
19789
+ section: NcStateSummarySectionIdSchema.optional(),
19790
+ /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
19791
+ title: string$2(),
19792
+ text: string$2(),
19793
+ count: number().int().nonnegative()
19794
+ });
19795
+ /**
19419
19796
  * The summary a rule would send now. `empty` = nothing to report and every
19420
19797
  * section read: such a summary is NOT sent. `dropped` counts the open states
19421
19798
  * the rule's own filters (or a camera mute) left out, by reason.
19422
19799
  * `excludedGroups` names the kind groups selected that have no state to report
19423
- * (backups & exports).
19800
+ * (backups & exports). `split` and `rows` (D698) say how it would be sent —
19801
+ * optional, so an older hub's answer still parses.
19424
19802
  */
19425
19803
  var NcStateSummaryPreviewSchema = object({
19426
19804
  empty: boolean(),
@@ -19428,7 +19806,9 @@ var NcStateSummaryPreviewSchema = object({
19428
19806
  text: string$2(),
19429
19807
  sections: array(NcStateSummaryPreviewSectionSchema),
19430
19808
  dropped: record(string$2(), number().int().nonnegative()),
19431
- excludedGroups: array(string$2())
19809
+ excludedGroups: array(string$2()),
19810
+ split: NcStateSummarySplitSchema.optional(),
19811
+ rows: array(NcStateSummaryPreviewRowSchema).optional()
19432
19812
  });
19433
19813
  var NcTestResultSchema = object({
19434
19814
  recordId: string$2(),
@@ -19843,6 +20223,29 @@ object({
19843
20223
  firstAt: number(),
19844
20224
  lastAt: number()
19845
20225
  });
20226
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
20227
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
20228
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
20229
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
20230
+ var NcAlarmModeDelaysListSchema = array(object({
20231
+ mode: AlarmArmModeSchema,
20232
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
20233
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
20234
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
20235
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20236
+ var NcAlarmModeDelaysEditListSchema = array(object({
20237
+ mode: AlarmArmModeSchema,
20238
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20239
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20240
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
20241
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20242
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20243
+ var NcAlarmNonBlockingEditSchema = object({
20244
+ mode: AlarmArmModeSchema,
20245
+ deviceId: number().int().positive(),
20246
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20247
+ nonBlocking: boolean()
20248
+ });
19846
20249
  /**
19847
20250
  * The per-mode lists, at most one per mode. Two lists for one mode have no
19848
20251
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -19851,10 +20254,10 @@ object({
19851
20254
  var NcAlarmNonBlockingListSchema = array(object({
19852
20255
  mode: AlarmArmModeSchema,
19853
20256
  deviceIds: array(number().int()).max(200)
19854
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
20257
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
19855
20258
  /**
19856
- * The three durations the panel's state machine runs on, who hears about its
19857
- * transitions, and which openings each mode tolerates.
20259
+ * The three durations — global, and per mode since D697 — the panel's state
20260
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
19858
20261
  *
19859
20262
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
19860
20263
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -19864,15 +20267,15 @@ var NcAlarmNonBlockingListSchema = array(object({
19864
20267
  */
19865
20268
  var NcAlarmSettingsSchema = object({
19866
20269
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
19867
- exitDelaySec: number().int().min(0).max(600),
20270
+ exitDelaySec: NcAlarmDelaySecSchema,
19868
20271
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
19869
- entryDelaySec: number().int().min(0).max(600),
20272
+ entryDelaySec: NcAlarmDelaySecSchema,
19870
20273
  /**
19871
20274
  * How long `triggered` lasts before the panel re-arms itself.
19872
20275
  * **0 = until an operator disarms it** — the behaviour before this field
19873
20276
  * existed, and therefore what an untouched install keeps doing.
19874
20277
  */
19875
- triggeredDurationSec: number().int().min(0).max(3600),
20278
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
19876
20279
  /**
19877
20280
  * Deprecated by D630 — every transition is announced. Parsed, never read;
19878
20281
  * remove after one release. Optional so an older admin's patch (which still
@@ -19895,17 +20298,30 @@ var NcAlarmSettingsSchema = object({
19895
20298
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
19896
20299
  * parses as "every opening blocks" — the behaviour it was written under.
19897
20300
  */
19898
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20301
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20302
+ /**
20303
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20304
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20305
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20306
+ */
20307
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
19899
20308
  });
19900
20309
  /**
19901
20310
  * Every field optional — a tab edits one control at a time.
19902
20311
  *
19903
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
19904
- * remove an inner `.default()`, so a patch that never named the field would
19905
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
19906
- * (the same trap {@link NcRulePatchSchema} documents).
20312
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20313
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20314
+ * named the field would parse to `[]` and wipe every mode's list on an
20315
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20316
+ * `modeDelays` means EDITS, not the stored overrides.
19907
20317
  */
19908
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20318
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20319
+ nonBlocking: NcAlarmNonBlockingListSchema,
20320
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20321
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20322
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20323
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20324
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
19909
20325
  /**
19910
20326
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
19911
20327
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20005,6 +20421,28 @@ var NcAlarmConfigSchema = object({
20005
20421
  */
20006
20422
  live: NcAlarmLiveSchema.optional()
20007
20423
  });
20424
+ var NcAlarmSensorReadingSchema = _enum([
20425
+ "open",
20426
+ "closed",
20427
+ "unknown",
20428
+ "not-opening"
20429
+ ]);
20430
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20431
+ var NcAlarmSensorStateSchema = object({
20432
+ deviceId: number().int().positive(),
20433
+ reading: NcAlarmSensorReadingSchema,
20434
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20435
+ state: string$2().nullable(),
20436
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20437
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20438
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20439
+ online: boolean().nullable()
20440
+ });
20441
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20442
+ var NcAlarmSensorsSchema = object({
20443
+ sensors: array(NcAlarmSensorStateSchema),
20444
+ readAt: number().int().nonnegative()
20445
+ });
20008
20446
  /**
20009
20447
  * ONE rule's demand on ONE camera's clip ring.
20010
20448
  *
@@ -20095,7 +20533,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20095
20533
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20096
20534
  kind: "mutation",
20097
20535
  auth: "admin"
20098
- });
20536
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20099
20537
  /**
20100
20538
  * Zod schemas for persisted record types.
20101
20539
  *
@@ -20442,12 +20880,26 @@ var CameraSensorRefSchema = object({
20442
20880
  function cameraSensorRefKey(ref) {
20443
20881
  return `${ref.addonId}/${ref.stableId}`;
20444
20882
  }
20445
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20446
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20447
- object({
20448
- mode: CameraSensorsModeSchema,
20449
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20450
- }).superRefine((value, ctx) => {
20883
+ /**
20884
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20885
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20886
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20887
+ * camera's own motion counts only while a confirm sensor is active — with or
20888
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20889
+ */
20890
+ var CameraSensorModeSchema = _enum([
20891
+ "in-addition",
20892
+ "confirm",
20893
+ "replace"
20894
+ ]);
20895
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20896
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20897
+ /**
20898
+ * A camera's attached sensors. A CS-1 row carried an envelope
20899
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20900
+ * what the write stores (D672 rule 3), so it is never written again.
20901
+ */
20902
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20451
20903
  const seen = /* @__PURE__ */ new Set();
20452
20904
  value.sensors.forEach((ref, index) => {
20453
20905
  const key = cameraSensorRefKey(ref);
@@ -20459,39 +20911,41 @@ object({
20459
20911
  seen.add(key);
20460
20912
  });
20461
20913
  });
20462
- var RefFields = {
20914
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20915
+ var AttachmentFields = {
20463
20916
  addonId: string$2(),
20464
- stableId: string$2()
20917
+ stableId: string$2(),
20918
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20465
20919
  };
20466
20920
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20467
20921
  object({
20468
- ...RefFields,
20922
+ ...AttachmentFields,
20469
20923
  state: literal("ready"),
20470
20924
  deviceId: number(),
20471
20925
  name: string$2(),
20472
20926
  caps: array(string$2()).readonly()
20473
20927
  }),
20474
20928
  object({
20475
- ...RefFields,
20929
+ ...AttachmentFields,
20476
20930
  state: literal("offline"),
20477
20931
  deviceId: number(),
20478
20932
  name: string$2(),
20479
20933
  reason: _enum(["offline", "disabled"])
20480
20934
  }),
20481
20935
  object({
20482
- ...RefFields,
20936
+ ...AttachmentFields,
20483
20937
  state: literal("missing"),
20484
20938
  reason: string$2()
20485
20939
  }),
20486
20940
  object({
20487
- ...RefFields,
20941
+ ...AttachmentFields,
20488
20942
  state: literal("unsupported"),
20489
20943
  deviceId: number(),
20490
20944
  name: string$2(),
20491
20945
  reason: string$2()
20492
20946
  }),
20493
20947
  object({
20494
- ...RefFields,
20948
+ ...AttachmentFields,
20495
20949
  state: literal("unreadable"),
20496
20950
  reason: string$2()
20497
20951
  })
@@ -20504,17 +20958,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20504
20958
  * say.
20505
20959
  */
20506
20960
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20961
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20962
+ var CameraOwnMotionSchema = _enum([
20963
+ "always",
20964
+ "when-confirmed",
20965
+ "never"
20966
+ ]);
20967
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20968
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20969
+ /**
20970
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20971
+ * never the effective value. The camera's `motion` slice is the effective one,
20972
+ * so a view of the camera's own row reads this.
20973
+ * - `atMs`: when the bridge received the camera's last own edge.
20974
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20975
+ * AND it is younger than the window the bridge counts the camera's own motion
20976
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20977
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20978
+ * within the window; a status read is a snapshot, so a live view re-asks.
20979
+ * Absent = this orchestrator has not received the camera's own motion since it
20980
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20981
+ */
20982
+ var CameraRawLevelSchema = object({
20983
+ detected: boolean(),
20984
+ atMs: number()
20985
+ });
20507
20986
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20508
- object({ state: literal("not-configured") }),
20987
+ object({
20988
+ state: literal("not-configured"),
20989
+ cameraLevel: CameraRawLevelSchema.optional()
20990
+ }),
20509
20991
  object({
20510
20992
  state: literal("unreadable"),
20511
- reason: string$2()
20993
+ reason: string$2(),
20994
+ cameraLevel: CameraRawLevelSchema.optional()
20512
20995
  }),
20513
20996
  object({
20514
20997
  state: literal("configured"),
20515
- mode: CameraSensorsModeSchema,
20998
+ cameraLevel: CameraRawLevelSchema.optional(),
20516
20999
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20517
- notForwarded: CameraSensorsNotForwardedSchema.optional()
21000
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
21001
+ cameraMotion: CameraOwnMotionSchema.optional(),
21002
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
21003
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20518
21004
  })
20519
21005
  ]);
20520
21006
  Object.freeze({
@@ -21199,14 +21685,14 @@ var TrackSchema = object({
21199
21685
  * `derive-returned-null tile=standin`,
21200
21686
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
21201
21687
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
21202
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
21688
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
21203
21689
  * tile is a face/plate stand-in, a raster crop, or an icon.
21204
21690
  *
21205
21691
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
21206
21692
  * the TILE ranked below native, so a track whose thumbnail landed natively
21207
21693
  * while its key frame missed carried no reason at all — three of the four
21208
21694
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
21209
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
21695
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
21210
21696
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
21211
21697
  * lastFrame copy source`), so its absence is now on this line whatever the
21212
21698
  * tile ended up being.
@@ -21293,7 +21779,7 @@ var MotionEventSchema = object({
21293
21779
  /**
21294
21780
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
21295
21781
  * first entry is always `0`) of every genuine off→on transition the
21296
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
21782
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
21297
21783
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
21298
21784
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
21299
21785
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -21817,6 +22303,7 @@ var KeyEventSchema = object({
21817
22303
  bestEventId: string$2(),
21818
22304
  /** Track lifetime in ms (lastSeen - firstSeen). */
21819
22305
  windowMs: number().optional(),
22306
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
21820
22307
  ...TrackFlagFields,
21821
22308
  ...TrackRetrainFields
21822
22309
  });
@@ -22349,7 +22836,8 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
22349
22836
  trackIds: array(string$2()).min(1)
22350
22837
  }), object({
22351
22838
  deleted: number().int(),
22352
- failed: array(string$2()).readonly()
22839
+ failed: array(string$2()).readonly(),
22840
+ sparedMediaIds: array(number().int()).readonly().optional()
22353
22841
  }), {
22354
22842
  kind: "mutation",
22355
22843
  auth: "admin"
@@ -22358,7 +22846,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
22358
22846
  deviceId: number(),
22359
22847
  trackId: string$2(),
22360
22848
  flags: TrackFlagsPatchSchema
22361
- }), TrackFlagsSchema, { kind: "mutation" }), method(ArchivedDebugNoteQueryInputSchema, array(ArchivedDebugNoteSchema).readonly(), { kind: "query" }), method(BirthDecisionQueryInputSchema, array(BirthDecisionRecordSchema).readonly(), { kind: "query" }), method(object({}), EventStoreFootprintSchema, {
22849
+ }), TrackFlagsSchema, { kind: "mutation" }), method(ArchivedDebugNoteQueryInputSchema, array(ArchivedDebugNoteSchema).readonly(), { kind: "query" }), method(SetTrackRelationInputSchema, TrackRelationSchema, { kind: "mutation" }), method(ClearTrackRelationInputSchema, ClearTrackRelationResultSchema, { kind: "mutation" }), method(SetTrackPurityInputSchema, SetTrackPurityResultSchema, { kind: "mutation" }), method(ListTrackRelationsInputSchema, ListTrackRelationsResultSchema, { kind: "query" }), method(BirthDecisionQueryInputSchema, array(BirthDecisionRecordSchema).readonly(), { kind: "query" }), method(object({}), EventStoreFootprintSchema, {
22362
22850
  kind: "query",
22363
22851
  auth: "admin"
22364
22852
  }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
@@ -23744,8 +24232,10 @@ var RunnerCameraConfigSchema = object({
23744
24232
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
23745
24233
  * analyzer providers) running on decoded frames in this runner.
23746
24234
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
23747
- * analytics). The provider emits ProviderMotion
23748
- * events; the orchestrator forwards to `reportMotion`.
24235
+ * analytics). The provider emits its raw push as `ProviderMotion`
24236
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
24237
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
24238
+ * `reportMotion`.
23749
24239
  */
23750
24240
  motionSources: MotionSourcesSchema.default(["analyzer"]),
23751
24241
  /**
@@ -23922,7 +24412,15 @@ var RunnerLocalLoadSchema = object({
23922
24412
  hasGpu: boolean(),
23923
24413
  inferenceBackend: string$2().optional(),
23924
24414
  cpuCores: number().optional()
23925
- })
24415
+ }),
24416
+ /**
24417
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24418
+ * runner never writes it. ABSENT = a runner older than D696, which still
24419
+ * writes the slice from its phase machine; the orchestrator does not write
24420
+ * while any runner node is absent-or-unanswered, so one slice never has two
24421
+ * writers.
24422
+ */
24423
+ motionSliceWriter: literal("orchestrator").optional()
23926
24424
  });
23927
24425
  /**
23928
24426
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -24556,7 +25054,64 @@ var NodeInferenceDevicesSchema = object({
24556
25054
  reachable: boolean(),
24557
25055
  devices: array(NodeInferenceDeviceSchema).readonly()
24558
25056
  });
24559
- method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
25057
+ /**
25058
+ * Pipeline Orchestrator capability — global load balancer + camera dispatcher.
25059
+ *
25060
+ * Lives on the hub. Tracks the camera-to-agent assignment map, listens to
25061
+ * device lifecycle events, queries each runner's capacity + hardware
25062
+ * profile, and dispatches `attachCamera` / `detachCamera` cap calls to the
25063
+ * appropriate runner instances. Persists assignments via the multi-level
25064
+ * settings store so they survive hub restarts.
25065
+ *
25066
+ * Strictly the control plane: never sees a frame, never calls inference,
25067
+ * never drains queues. The runner does that work locally on each agent.
25068
+ */
25069
+ /**
25070
+ * The input of `setCameraSensors` (G5, D676): the ONE typed write path of a
25071
+ * camera's attached sensors (D672). `config: null` removes the attachment —
25072
+ * the camera reads `not-configured` again, never `unreadable`.
25073
+ */
25074
+ var SetCameraSensorsInputSchema = object({
25075
+ deviceId: number().int().nonnegative(),
25076
+ /** `null` removes the attachment: the camera goes back to `not-configured`. */
25077
+ config: CameraSensorsConfigSchema.nullable()
25078
+ });
25079
+ /**
25080
+ * Why an early occupancy look (D691) was not scheduled.
25081
+ *
25082
+ * - `unknown-camera` — no session was ever dispatched for it, so there is no
25083
+ * node and no config to attach a burst with.
25084
+ * - `out-of-range` — the ask is negative or further out than the orchestrator
25085
+ * will hold a timer for; the parked-object cadence is the answer there.
25086
+ * - `no-burst-producer` — this build has no burst producer at all.
25087
+ * - `already-looked` — this camera's ONE early look since its last session has
25088
+ * been spent. A look's own burst can re-arm a flickering flip, and a look
25089
+ * per arm would chain a burst every ~30 s forever (review H2); the next
25090
+ * real session re-arms the budget.
25091
+ */
25092
+ var OccupancyLookRefusalSchema = _enum([
25093
+ "unknown-camera",
25094
+ "out-of-range",
25095
+ "no-burst-producer",
25096
+ "already-looked"
25097
+ ]);
25098
+ method(object({
25099
+ deviceId: number().int().nonnegative(),
25100
+ /** How long from NOW until the pending flip's sustain window ends. RELATIVE,
25101
+ * so the caller's clock and the orchestrator's never have to agree. */
25102
+ afterMs: number().finite(),
25103
+ /** Why, for the log. */
25104
+ reason: string$2()
25105
+ }), object({
25106
+ scheduled: boolean(),
25107
+ /** When `scheduled`: how long until the burst is dialled. */
25108
+ dueInMs: number().optional(),
25109
+ /** When not `scheduled`: why. */
25110
+ refusedBecause: OccupancyLookRefusalSchema.optional()
25111
+ }), {
25112
+ kind: "mutation",
25113
+ auth: "admin"
25114
+ }), method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
24560
25115
  kind: "mutation",
24561
25116
  auth: "admin"
24562
25117
  }), method(StorageMigrationLeaseInputSchema, object({ resumed: literal(true) }), {
@@ -24710,7 +25265,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
24710
25265
  }), CameraSwitchGroupSchema, {
24711
25266
  kind: "mutation",
24712
25267
  auth: "admin"
24713
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
25268
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(SetCameraSensorsInputSchema, object({ success: literal(true) }), {
25269
+ kind: "mutation",
25270
+ auth: "admin"
25271
+ }), method(_void(), DiskReconcileJobSchema, {
24714
25272
  kind: "mutation",
24715
25273
  auth: "admin"
24716
25274
  }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
@@ -26527,7 +27085,7 @@ var ClipPlaybackSchema = object({
26527
27085
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
26528
27086
  * time the camera is awake for its own reasons — is deliberately not a member:
26529
27087
  * it was proposed, it is free on the battery, and the operator declined it on
26530
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
27088
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
26531
27089
  * D558 "Considered and not taken").
26532
27090
  */
26533
27091
  var ClipWakeSchema = _enum(["authorised"]);
@@ -26788,7 +27346,7 @@ DeviceType.Camera, method(object({
26788
27346
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
26789
27347
  * assumed the answer was a fan-out over everything a camera has. The
26790
27348
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
26791
- * solo"* — so the list is asked of one provider at a time and there is
27349
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
26792
27350
  * nothing to filter out of it.
26793
27351
  */
26794
27352
  provider: string$2().min(1)
@@ -26916,219 +27474,6 @@ DeviceType.Camera, method(object({
26916
27474
  auth: "protected"
26917
27475
  });
26918
27476
  /**
26919
- * Doorbell button cap. Two kinds of providers coexist behind this cap
26920
- * name (same pattern as `snapshot`):
26921
- *
26922
- * - **Native** providers: registered per-device by device-driver
26923
- * addons via `ctx.registerNativeCap` — either on a
26924
- * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
26925
- * or directly on the camera (Reolink registers at camera level).
26926
- * Emits an `onPressed` event every time the firmware pushes a
26927
- * ring; status tracks the last press and a pressCount since start
26928
- * (diagnostic).
26929
- *
26930
- * - **Wrapper** provider: the `virtual-doorbell` system builtin
26931
- * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
26932
- * device (contact / switch / event-emitter …) into a doorbell for
26933
- * a camera. `defaultActive: false` — the operator explicitly binds
26934
- * it per camera in the device-bindings UI, then picks the source
26935
- * device + trigger in the per-device settings.
26936
- *
26937
- * The DeviceEventPropagator re-emits `onPressed` on the camera parent
26938
- * — subscribers listening at the camera level receive ring events
26939
- * with `via[]` populated. No code on the parent needed.
26940
- */
26941
- var DoorbellStatusSchema = object({
26942
- /** Ms epoch of the last press. null = never observed since this provider started. */
26943
- lastPressedAt: number().nullable(),
26944
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
26945
- pressCountSinceStart: number()
26946
- });
26947
- var DoorbellPressEventSchema = object({
26948
- deviceId: number(),
26949
- timestamp: number()
26950
- });
26951
- /**
26952
- * The `doorbell-source` extension slot's editable value (D648): the camera's
26953
- * virtual-doorbell trigger devices, exactly what the wrapper's
26954
- * `doorbellSources` persists (as `{ deviceId }` records).
26955
- */
26956
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
26957
- /**
26958
- * The `doorbell-source` slot — which devices ring this camera's VIRTUAL
26959
- * doorbell, owned by the `virtual-doorbell` wrapper over its
26960
- * `doorbellSources` store (D648). A source rings when one of its
26961
- * `SOURCE_CAPS` rises. Whether the wrapper is bound to the camera at all is a
26962
- * different authority (`deviceManager.setWrapperActive`, D62) — the slot
26963
- * reports it, it never writes it.
26964
- */
26965
- var DOORBELL_SOURCE_SLOT = {
26966
- id: "doorbell-source",
26967
- label: "Doorbell trigger devices",
26968
- 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.",
26969
- shape: "association",
26970
- hostDeviceTypes: [DeviceType.Camera],
26971
- sourceCaps: SOURCE_CAPS,
26972
- cardinality: "many",
26973
- value: DoorbellSourceSlotValueSchema
26974
- };
26975
- var doorbellCapability = {
26976
- name: "doorbell",
26977
- scope: "device",
26978
- deviceNative: true,
26979
- mode: "singleton",
26980
- kind: "wrapper",
26981
- defaultActive: false,
26982
- deviceTypes: [DeviceType.Button, DeviceType.Camera],
26983
- extensionSlots: [DOORBELL_SOURCE_SLOT],
26984
- methods: {},
26985
- events: {
26986
- /**
26987
- * Fires once per physical press. Reolink delivers via Baichuan
26988
- * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
26989
- * release/duration — it's a pulse.
26990
- */
26991
- onPressed: {
26992
- data: DoorbellPressEventSchema,
26993
- fromState: {
26994
- field: "lastPressedAt",
26995
- on: "advance"
26996
- }
26997
- } },
26998
- status: {
26999
- schema: DoorbellStatusSchema,
27000
- kind: "push"
27001
- },
27002
- /**
27003
- * Runtime-state slice — last press timestamp + lifetime press count.
27004
- * Mirrored by the kernel and readable via
27005
- * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
27006
- * without subscribing.
27007
- */
27008
- runtimeState: DoorbellStatusSchema,
27009
- /**
27010
- * 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.
27011
- *
27012
- * See `RuntimeStateDurability`. Enforced by
27013
- * `scripts/check-runtime-state-durability.ts`.
27014
- */
27015
- durability: "restored"
27016
- };
27017
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
27018
- DeviceType.Camera, DeviceType.Camera;
27019
- /** A slot's editable value, discriminated on `slot`. */
27020
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
27021
- DeviceType.Camera;
27022
- var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
27023
- state: literal("unreachable"),
27024
- reason: string$2()
27025
- })]);
27026
- /** One provider of slots — the D554 source-listing row. */
27027
- var ExtensionSourceSchema = object({
27028
- /** The routing prefix of every `slotRef` this provider serves. */
27029
- source: string$2(),
27030
- label: string$2(),
27031
- /** Stamped by the dispatcher from the registry (D557). */
27032
- addonId: string$2().optional(),
27033
- availability: ExtensionSourceAvailabilitySchema
27034
- });
27035
- /**
27036
- * How one picked source stands. `missing` — no such device (deleted, or an id
27037
- * that never existed); `foreign` — a device the slot's owner cannot read from
27038
- * (a source owned by another addon); `unavailable` — it exists but is not a
27039
- * valid source now (a container, which is never a pick). A source in any of
27040
- * these states is SHOWN, never silently dropped or re-pointed.
27041
- */
27042
- var ExtensionSourceHealthSchema = _enum([
27043
- "ok",
27044
- "missing",
27045
- "foreign",
27046
- "unavailable"
27047
- ]);
27048
- var ExtensionSlotSourceSchema = object({
27049
- deviceId: number().int(),
27050
- /** The device's name, or null when it could not be resolved (`missing`). */
27051
- name: string$2().nullable(),
27052
- health: ExtensionSourceHealthSchema,
27053
- detail: string$2().optional()
27054
- });
27055
- /**
27056
- * A DIFFERENT authority that gates whether the slot's sources do anything —
27057
- * reported read-only, named, never written through this contract (D62). The
27058
- * virtual doorbell's sources ring only while the wrapper is bound to the
27059
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
27060
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
27061
- */
27062
- var ExtensionSlotActivationSchema = object({
27063
- state: _enum([
27064
- "active",
27065
- "inactive",
27066
- "unknown"
27067
- ]),
27068
- authority: string$2(),
27069
- detail: string$2().optional()
27070
- });
27071
- var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
27072
- state: literal("ok"),
27073
- /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
27074
- slotRef: string$2(),
27075
- slot: ExtensionSlotIdSchema,
27076
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
27077
- authority: string$2(),
27078
- activation: ExtensionSlotActivationSchema.nullable(),
27079
- value: ExtensionSlotValueSchema,
27080
- sources: array(ExtensionSlotSourceSchema),
27081
- /**
27082
- * Devices the owner links to the host ON ITS OWN, by structure, and that
27083
- * are therefore not in `value` (D655): a camera's device-tree children, and
27084
- * the child a picked container stands for (D356 — a container is never a
27085
- * pick, its children are). Read-only; the editor shows them and may offer
27086
- * per-device options on them (the linked-devices tracked-event opt-in).
27087
- * Absent when the owner links nothing beyond the picks, and from an owner
27088
- * older than D655.
27089
- */
27090
- impliedSources: array(ExtensionSlotSourceSchema).optional()
27091
- }), object({
27092
- state: literal("unreachable"),
27093
- addonId: string$2(),
27094
- reason: string$2()
27095
- })]);
27096
- var ExtensionCandidatesSchema = object({ candidates: array(object({
27097
- deviceId: number().int(),
27098
- name: string$2(),
27099
- type: string$2(),
27100
- location: string$2().nullable(),
27101
- /** The container a child belongs to — "Spotlight 2" alone does not say which. */
27102
- parentName: string$2().nullable(),
27103
- /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
27104
- suggested: boolean(),
27105
- /** Why it is suggested ("same location"), null when it is not. */
27106
- reason: string$2().nullable()
27107
- })) });
27108
- /**
27109
- * What a write did. `none` — applied in place. `host` / `parent` — the owner
27110
- * reloaded the host (or, for an accessory child, its parent) because the
27111
- * write changed what the host REGISTERS. A slot never adds a capability to its
27112
- * host — that is a composition (D663) — so today's owners answer `none`.
27113
- */
27114
- var ExtensionSetResultSchema = object({
27115
- applied: literal(true),
27116
- reload: _enum([
27117
- "none",
27118
- "host",
27119
- "parent"
27120
- ])
27121
- });
27122
- var ExtensionSlotRefInputSchema = object({
27123
- deviceId: number().int(),
27124
- slotRef: string$2().min(1)
27125
- });
27126
- var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
27127
- method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)), method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)), method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema), method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
27128
- kind: "mutation",
27129
- auth: "admin"
27130
- });
27131
- /**
27132
27477
  * Optional client-side hints sent at session creation to help the provider
27133
27478
  * pick the best native source. All fields optional — a viewer that knows
27134
27479
  * nothing still gets a sane default. (Relocated from the retired `webrtc`
@@ -29613,6 +29958,70 @@ onStatusChanged: { data: object({
29613
29958
  * whether persisting is worth a SQLite commit. */
29614
29959
  volatileStateFields: ["lastChangedAt"]
29615
29960
  };
29961
+ var DoorbellStatusSchema = object({
29962
+ /** Ms epoch of the last press. null = never observed since this provider started. */
29963
+ lastPressedAt: number().nullable(),
29964
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
29965
+ pressCountSinceStart: number()
29966
+ });
29967
+ var DoorbellPressEventSchema = object({
29968
+ deviceId: number(),
29969
+ timestamp: number()
29970
+ });
29971
+ /**
29972
+ * Doorbell button cap. Two kinds of provider serve it, and to every consumer
29973
+ * they are the same cap:
29974
+ *
29975
+ * - **Native** providers, registered per device by a driver addon through
29976
+ * `ctx.registerNativeCap` — a `DeviceType.Button` accessory with
29977
+ * `role: DeviceRole.Doorbell`, or the camera itself (Reolink registers at
29978
+ * camera level). They emit `onPressed` on every firmware ring.
29979
+ * - **A composed doorbell** (D670, D671): a camera's customization gives it
29980
+ * the cap, its `lastPressedAt` rising from the sources the operator
29981
+ * assigned in Customize → Doorbell, and the composer emits `onPressed`
29982
+ * from that field's advance (`fromState`).
29983
+ *
29984
+ * The DeviceEventPropagator re-emits `onPressed` on the camera parent.
29985
+ */
29986
+ var doorbellCapability = {
29987
+ name: "doorbell",
29988
+ scope: "device",
29989
+ deviceNative: true,
29990
+ mode: "singleton",
29991
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
29992
+ methods: {},
29993
+ events: {
29994
+ /**
29995
+ * Fires once per physical press. Reolink delivers via Baichuan
29996
+ * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
29997
+ * release/duration — it's a pulse.
29998
+ */
29999
+ onPressed: {
30000
+ data: DoorbellPressEventSchema,
30001
+ fromState: {
30002
+ field: "lastPressedAt",
30003
+ on: "advance"
30004
+ }
30005
+ } },
30006
+ status: {
30007
+ schema: DoorbellStatusSchema,
30008
+ kind: "push"
30009
+ },
30010
+ /**
30011
+ * Runtime-state slice — last press timestamp + lifetime press count.
30012
+ * Mirrored by the kernel and readable via
30013
+ * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
30014
+ * without subscribing.
30015
+ */
30016
+ runtimeState: DoorbellStatusSchema,
30017
+ /**
30018
+ * 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.
30019
+ *
30020
+ * See `RuntimeStateDurability`. Enforced by
30021
+ * `scripts/check-runtime-state-durability.ts`.
30022
+ */
30023
+ durability: "restored"
30024
+ };
29616
30025
  /**
29617
30026
  * Enum-state sensor — a string value picked from a finite option set.
29618
30027
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -31757,9 +32166,9 @@ var MotionStatusSchema = object({
31757
32166
  /** Ms epoch of the last detected-true observation. Null if never detected. */
31758
32167
  lastDetectedAt: number().nullable(),
31759
32168
  /**
31760
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
31761
- * `null` while false and on every `Sensor` device (D475) — see that
31762
- * constant's doc for the one-authority rule.
32169
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
32170
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
32171
+ * device (D475) — see that constant's doc for the one-authority rule.
31763
32172
  *
31764
32173
  * ## Reading this field still arms nothing
31765
32174
  *
@@ -31777,34 +32186,31 @@ var MotionStatusSchema = object({
31777
32186
  * `addon-export-alexa/src/motion-clear-hold.ts` and
31778
32187
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
31779
32188
  *
31780
- * ## Who writes it
31781
- *
31782
- * - **Cameras** — the runner's phase machine, `active → watching` on
31783
- * `cooldown_expired`, which then writes this slice with
31784
- * `detected: false` (`handlePhaseChanged` in
31785
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
31786
- * matters most for the sources that only ever push a rising one:
31787
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
31788
- * a false.
31789
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
31790
- * provider pushes the false itself, from the upstream system's own
31791
- * state change. No phase machine is involved.
31792
- *
31793
- * ### The phase machine is CANONICAL, not sole — and that is a defect
31794
- *
31795
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
31796
- * phase machine is the sole writer for a camera. It is not.
31797
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
31798
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
31799
- * Hikvision's comment says why: it read THIS docblock, agreed the
31800
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
31801
- * two authorities can disagree about one slice, which this repo
31802
- * forbids, and the doc said otherwise — which is worse than saying
31803
- * nothing, because it reads as verification.
31804
- *
31805
- * This predates D475 and is not fixed there: the fix touches every
31806
- * camera provider. Recorded in D475's Consequences. Do not restore the
31807
- * "sole writer" wording without also removing the other writers.
32189
+ * ## Who writes it (D696)
32190
+ *
32191
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
32192
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
32193
+ * `camera-sensors/motion-slice.ts`), alongside the effective
32194
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
32195
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
32196
+ * device activity, as the camera's `motionSources` counts them. A source
32197
+ * that never sends a clear falls one window after its last `true`.
32198
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
32199
+ * writes this slice (not even a boot seed), and neither does the runner's
32200
+ * phase machine: its phase is "a session is attached" for every on-motion
32201
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
32202
+ * A provider's `isDetected` answers from the hub's slice.
32203
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
32204
+ * imports) — the provider pushes both edges itself, from the upstream
32205
+ * system's own state change. That is the sensor's own fact.
32206
+ *
32207
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
32208
+ * `motion` slice; each sensor writer is a named exemption.
32209
+ *
32210
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
32211
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
32212
+ * it, every provider seeded it at boot, and every session left it
32213
+ * `detected: true` after its detach.
31808
32214
  */
31809
32215
  autoClearAfterMs: number().nullable()
31810
32216
  });
@@ -31876,11 +32282,12 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
31876
32282
  */
31877
32283
  runtimeState: MotionStatusSchema,
31878
32284
  /**
31879
- * Runtime-state durability: **session** — every writer of this slice
32285
+ * Runtime-state durability: **session** — the writer of this slice
31880
32286
  * writes only on an EDGE, so a restored `detected: true` would stay
31881
32287
  * frozen until the next one instead of being corrected. The next edge
31882
- * re-publishes the real state. (On who the writers are, and why there
31883
- * is more than one, see `autoClearAfterMs` above.)
32288
+ * re-publishes the real state, and the orchestrator re-publishes every
32289
+ * camera when it starts writing (D696; on who writes it, see
32290
+ * `autoClearAfterMs` above).
31884
32291
  *
31885
32292
  * See `RuntimeStateDurability`. Enforced by
31886
32293
  * `scripts/check-runtime-state-durability.ts`.
@@ -34263,8 +34670,7 @@ var ptzAutotrackCapability = {
34263
34670
  deviceNative: true,
34264
34671
  mode: "singleton",
34265
34672
  /**
34266
- * Two kinds of provider behind one name, the same shape `snapshot` and
34267
- * `doorbell` use:
34673
+ * Two kinds of provider behind one name, the same shape `snapshot` uses:
34268
34674
  *
34269
34675
  * - **Native**: the camera's own firmware autotrack, registered per device by
34270
34676
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
@@ -34272,7 +34678,7 @@ var ptzAutotrackCapability = {
34272
34678
  * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
34273
34679
  * which follows a subject with `ptz.move` pulses driven by the detection
34274
34680
  * pipeline. `defaultActive: false` — the operator binds it per camera in
34275
- * the bindings UI, exactly as `virtual-doorbell` is bound.
34681
+ * the bindings UI.
34276
34682
  *
34277
34683
  * The binding chooses WHICH implementation answers; `setEnabled` stays the
34278
34684
  * one switch for the function either way. A camera never has both driving its
@@ -34947,7 +35353,7 @@ var ReadGopBytesResultSchema = object({
34947
35353
  /**
34948
35354
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
34949
35355
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
34950
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
35356
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
34951
35357
  * a replay needs several seconds of native pixels, not one frame.
34952
35358
  *
34953
35359
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -38311,47 +38717,7 @@ var zoneRulesCapability = {
38311
38717
  */
38312
38718
  durability: "restored"
38313
38719
  };
38314
- var NUMBER = {
38315
- kind: "fixed",
38316
- result: ["number"]
38317
- };
38318
- var BOOLEAN = {
38319
- kind: "fixed",
38320
- result: ["boolean"]
38321
- };
38322
- Object.freeze({
38323
- min: NUMBER,
38324
- max: NUMBER,
38325
- abs: NUMBER,
38326
- floor: NUMBER,
38327
- ceil: NUMBER,
38328
- sqrt: NUMBER,
38329
- round: NUMBER,
38330
- pow: NUMBER,
38331
- clamp: NUMBER,
38332
- avg: NUMBER,
38333
- sum: NUMBER,
38334
- age: NUMBER,
38335
- convert: NUMBER,
38336
- coalesce: { kind: "coalesce" },
38337
- any: BOOLEAN,
38338
- all: BOOLEAN,
38339
- within: BOOLEAN,
38340
- latest: {
38341
- kind: "fixed",
38342
- result: ["number", "null"]
38343
- },
38344
- number: {
38345
- kind: "fixed",
38346
- result: ["number", "null"]
38347
- },
38348
- rose: {
38349
- kind: "fixed",
38350
- result: ["number", "null"]
38351
- },
38352
- count: NUMBER
38353
- });
38354
- `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
38720
+ SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
38355
38721
  /**
38356
38722
  * Accessory device helpers — shared across drivers.
38357
38723
  *
@@ -39411,13 +39777,14 @@ var BaseDeviceProvider = class extends BaseAddon {
39411
39777
  async getStatus() {
39412
39778
  const all = await this.ctx.kernel.devices?.getAll() ?? [];
39413
39779
  const summary = this.restoreFailureSummary();
39414
- if (summary === null) return {
39780
+ const base = {
39415
39781
  connected: true,
39416
- deviceCount: all.length
39782
+ deviceCount: all.length,
39783
+ motionSliceWriter: "orchestrator"
39417
39784
  };
39785
+ if (summary === null) return base;
39418
39786
  return {
39419
- connected: true,
39420
- deviceCount: all.length,
39787
+ ...base,
39421
39788
  error: summary
39422
39789
  };
39423
39790
  }
@@ -42297,6 +42664,12 @@ Object.freeze({
42297
42664
  addonId: null,
42298
42665
  access: "view"
42299
42666
  },
42667
+ "coreBlocks.listComposableCaps": {
42668
+ capName: "core-blocks",
42669
+ capScope: "system",
42670
+ addonId: null,
42671
+ access: "view"
42672
+ },
42300
42673
  "coreBlocks.listCompositions": {
42301
42674
  capName: "core-blocks",
42302
42675
  capScope: "system",
@@ -42375,6 +42748,18 @@ Object.freeze({
42375
42748
  addonId: null,
42376
42749
  access: "view"
42377
42750
  },
42751
+ "databaseSnapshot.listDatabases": {
42752
+ capName: "database-snapshot",
42753
+ capScope: "system",
42754
+ addonId: null,
42755
+ access: "view"
42756
+ },
42757
+ "databaseSnapshot.writeSnapshot": {
42758
+ capName: "database-snapshot",
42759
+ capScope: "system",
42760
+ addonId: null,
42761
+ access: "create"
42762
+ },
42378
42763
  "dataStoreProvider.aggregate": {
42379
42764
  capName: "data-store-provider",
42380
42765
  capScope: "system",
@@ -44835,6 +45220,12 @@ Object.freeze({
44835
45220
  addonId: null,
44836
45221
  access: "view"
44837
45222
  },
45223
+ "notificationRules.getAlarmSensors": {
45224
+ capName: "notification-rules",
45225
+ capScope: "system",
45226
+ addonId: null,
45227
+ access: "view"
45228
+ },
44838
45229
  "notificationRules.getClipRetentionAsks": {
44839
45230
  capName: "notification-rules",
44840
45231
  capScope: "system",
@@ -45111,6 +45502,12 @@ Object.freeze({
45111
45502
  addonId: null,
45112
45503
  access: "create"
45113
45504
  },
45505
+ "pipelineAnalytics.clearTrackRelation": {
45506
+ capName: "pipeline-analytics",
45507
+ capScope: "device",
45508
+ addonId: null,
45509
+ access: "delete"
45510
+ },
45114
45511
  "pipelineAnalytics.clearTracks": {
45115
45512
  capName: "pipeline-analytics",
45116
45513
  capScope: "device",
@@ -45363,6 +45760,12 @@ Object.freeze({
45363
45760
  addonId: null,
45364
45761
  access: "view"
45365
45762
  },
45763
+ "pipelineAnalytics.listTrackRelations": {
45764
+ capName: "pipeline-analytics",
45765
+ capScope: "device",
45766
+ addonId: null,
45767
+ access: "view"
45768
+ },
45366
45769
  "pipelineAnalytics.listTracks": {
45367
45770
  capName: "pipeline-analytics",
45368
45771
  capScope: "device",
@@ -45477,6 +45880,18 @@ Object.freeze({
45477
45880
  addonId: null,
45478
45881
  access: "create"
45479
45882
  },
45883
+ "pipelineAnalytics.setTrackPurity": {
45884
+ capName: "pipeline-analytics",
45885
+ capScope: "device",
45886
+ addonId: null,
45887
+ access: "create"
45888
+ },
45889
+ "pipelineAnalytics.setTrackRelation": {
45890
+ capName: "pipeline-analytics",
45891
+ capScope: "device",
45892
+ addonId: null,
45893
+ access: "create"
45894
+ },
45480
45895
  "pipelineAnalytics.startStorageMigrationMove": {
45481
45896
  capName: "pipeline-analytics",
45482
45897
  capScope: "device",
@@ -45915,6 +46330,12 @@ Object.freeze({
45915
46330
  addonId: null,
45916
46331
  access: "delete"
45917
46332
  },
46333
+ "pipelineOrchestrator.requestOccupancyLook": {
46334
+ capName: "pipeline-orchestrator",
46335
+ capScope: "system",
46336
+ addonId: null,
46337
+ access: "create"
46338
+ },
45918
46339
  "pipelineOrchestrator.resetNodePipelineDefaults": {
45919
46340
  capName: "pipeline-orchestrator",
45920
46341
  capScope: "system",
@@ -45981,6 +46402,12 @@ Object.freeze({
45981
46402
  addonId: null,
45982
46403
  access: "create"
45983
46404
  },
46405
+ "pipelineOrchestrator.setCameraSensors": {
46406
+ capName: "pipeline-orchestrator",
46407
+ capScope: "system",
46408
+ addonId: null,
46409
+ access: "create"
46410
+ },
45984
46411
  "pipelineOrchestrator.setCameraStepOverride": {
45985
46412
  capName: "pipeline-orchestrator",
45986
46413
  capScope: "system",
@@ -48377,6 +48804,11 @@ Object.freeze({
48377
48804
  }
48378
48805
  });
48379
48806
  Object.freeze({
48807
+ "accessories.getStatus": [{
48808
+ name: "deviceId",
48809
+ form: "single",
48810
+ optional: false
48811
+ }],
48380
48812
  "accessories.setChildHidden": [{
48381
48813
  name: "childDeviceId",
48382
48814
  form: "single",
@@ -48396,6 +48828,11 @@ Object.freeze({
48396
48828
  form: "single",
48397
48829
  optional: false
48398
48830
  }],
48831
+ "airQualitySensor.getStatus": [{
48832
+ name: "deviceId",
48833
+ form: "single",
48834
+ optional: false
48835
+ }],
48399
48836
  "alarmPanel.arm": [{
48400
48837
  name: "deviceId",
48401
48838
  form: "single",
@@ -48406,11 +48843,36 @@ Object.freeze({
48406
48843
  form: "single",
48407
48844
  optional: false
48408
48845
  }],
48846
+ "alarmPanel.getStatus": [{
48847
+ name: "deviceId",
48848
+ form: "single",
48849
+ optional: false
48850
+ }],
48409
48851
  "alarmPanel.trigger": [{
48410
48852
  name: "deviceId",
48411
48853
  form: "single",
48412
48854
  optional: false
48413
48855
  }],
48856
+ "ambientLightSensor.getStatus": [{
48857
+ name: "deviceId",
48858
+ form: "single",
48859
+ optional: false
48860
+ }],
48861
+ "audioAnalysis.applyDeviceSettingsPatch": [{
48862
+ name: "deviceId",
48863
+ form: "single",
48864
+ optional: false
48865
+ }],
48866
+ "audioAnalysis.getDeviceLiveContribution": [{
48867
+ name: "deviceId",
48868
+ form: "single",
48869
+ optional: false
48870
+ }],
48871
+ "audioAnalysis.getDeviceSettingsContribution": [{
48872
+ name: "deviceId",
48873
+ form: "single",
48874
+ optional: false
48875
+ }],
48414
48876
  "audioAnalysis.resolveDeviceSettings": [{
48415
48877
  name: "deviceId",
48416
48878
  form: "single",
@@ -48451,16 +48913,36 @@ Object.freeze({
48451
48913
  form: "single",
48452
48914
  optional: false
48453
48915
  }],
48916
+ "automationControl.getStatus": [{
48917
+ name: "deviceId",
48918
+ form: "single",
48919
+ optional: false
48920
+ }],
48454
48921
  "automationControl.trigger": [{
48455
48922
  name: "deviceId",
48456
48923
  form: "single",
48457
48924
  optional: false
48458
48925
  }],
48926
+ "battery.getStatus": [{
48927
+ name: "deviceId",
48928
+ form: "single",
48929
+ optional: false
48930
+ }],
48459
48931
  "battery.wakeForStream": [{
48460
48932
  name: "deviceId",
48461
48933
  form: "single",
48462
48934
  optional: false
48463
48935
  }],
48936
+ "binary.getStatus": [{
48937
+ name: "deviceId",
48938
+ form: "single",
48939
+ optional: false
48940
+ }],
48941
+ "brightness.getStatus": [{
48942
+ name: "deviceId",
48943
+ form: "single",
48944
+ optional: false
48945
+ }],
48464
48946
  "brightness.setBrightness": [{
48465
48947
  name: "deviceId",
48466
48948
  form: "single",
@@ -48476,6 +48958,26 @@ Object.freeze({
48476
48958
  form: "single",
48477
48959
  optional: false
48478
48960
  }],
48961
+ "cameraCredentials.getStatus": [{
48962
+ name: "deviceId",
48963
+ form: "single",
48964
+ optional: false
48965
+ }],
48966
+ "cameraGridLayout.applyDeviceSettingsPatch": [{
48967
+ name: "deviceId",
48968
+ form: "single",
48969
+ optional: false
48970
+ }],
48971
+ "cameraGridLayout.getDeviceLiveContribution": [{
48972
+ name: "deviceId",
48973
+ form: "single",
48974
+ optional: false
48975
+ }],
48976
+ "cameraGridLayout.getDeviceSettingsContribution": [{
48977
+ name: "deviceId",
48978
+ form: "single",
48979
+ optional: false
48980
+ }],
48479
48981
  "cameraGridLayout.getLayout": [{
48480
48982
  name: "deviceId",
48481
48983
  form: "single",
@@ -48491,6 +48993,21 @@ Object.freeze({
48491
48993
  form: "single",
48492
48994
  optional: false
48493
48995
  }],
48996
+ "cameraPipelineConfig.applyDeviceSettingsPatch": [{
48997
+ name: "deviceId",
48998
+ form: "single",
48999
+ optional: false
49000
+ }],
49001
+ "cameraPipelineConfig.getDeviceLiveContribution": [{
49002
+ name: "deviceId",
49003
+ form: "single",
49004
+ optional: false
49005
+ }],
49006
+ "cameraPipelineConfig.getDeviceSettingsContribution": [{
49007
+ name: "deviceId",
49008
+ form: "single",
49009
+ optional: false
49010
+ }],
48494
49011
  "cameraStreams.getBrokerStreams": [{
48495
49012
  name: "deviceId",
48496
49013
  form: "single",
@@ -48516,6 +49033,16 @@ Object.freeze({
48516
49033
  form: "single",
48517
49034
  optional: false
48518
49035
  }],
49036
+ "carbonMonoxide.getStatus": [{
49037
+ name: "deviceId",
49038
+ form: "single",
49039
+ optional: false
49040
+ }],
49041
+ "climateControl.getStatus": [{
49042
+ name: "deviceId",
49043
+ form: "single",
49044
+ optional: false
49045
+ }],
48519
49046
  "climateControl.setFanMode": [{
48520
49047
  name: "deviceId",
48521
49048
  form: "single",
@@ -48556,16 +49083,41 @@ Object.freeze({
48556
49083
  form: "single",
48557
49084
  optional: false
48558
49085
  }],
49086
+ "color.getStatus": [{
49087
+ name: "deviceId",
49088
+ form: "single",
49089
+ optional: false
49090
+ }],
48559
49091
  "color.setColor": [{
48560
49092
  name: "deviceId",
48561
49093
  form: "single",
48562
49094
  optional: false
48563
49095
  }],
49096
+ "connectivity.getStatus": [{
49097
+ name: "deviceId",
49098
+ form: "single",
49099
+ optional: false
49100
+ }],
49101
+ "consumables.getStatus": [{
49102
+ name: "deviceId",
49103
+ form: "single",
49104
+ optional: false
49105
+ }],
48564
49106
  "consumables.reset": [{
48565
49107
  name: "deviceId",
48566
49108
  form: "single",
48567
49109
  optional: false
48568
49110
  }],
49111
+ "contact.getStatus": [{
49112
+ name: "deviceId",
49113
+ form: "single",
49114
+ optional: false
49115
+ }],
49116
+ "control.getStatus": [{
49117
+ name: "deviceId",
49118
+ form: "single",
49119
+ optional: false
49120
+ }],
48569
49121
  "control.setValue": [{
48570
49122
  name: "deviceId",
48571
49123
  form: "single",
@@ -48576,6 +49128,11 @@ Object.freeze({
48576
49128
  form: "single",
48577
49129
  optional: false
48578
49130
  }],
49131
+ "cover.getStatus": [{
49132
+ name: "deviceId",
49133
+ form: "single",
49134
+ optional: false
49135
+ }],
48579
49136
  "cover.open": [{
48580
49137
  name: "deviceId",
48581
49138
  form: "single",
@@ -48601,6 +49158,11 @@ Object.freeze({
48601
49158
  form: "single",
48602
49159
  optional: false
48603
49160
  }],
49161
+ "dayNight.getStatus": [{
49162
+ name: "deviceId",
49163
+ form: "single",
49164
+ optional: false
49165
+ }],
48604
49166
  "dayNight.setSettings": [{
48605
49167
  name: "deviceId",
48606
49168
  form: "single",
@@ -48611,11 +49173,31 @@ Object.freeze({
48611
49173
  form: "single",
48612
49174
  optional: true
48613
49175
  }],
49176
+ "detectionPipeline.applyDeviceSettingsPatch": [{
49177
+ name: "deviceId",
49178
+ form: "single",
49179
+ optional: false
49180
+ }],
49181
+ "detectionPipeline.getDeviceLiveContribution": [{
49182
+ name: "deviceId",
49183
+ form: "single",
49184
+ optional: false
49185
+ }],
49186
+ "detectionPipeline.getDeviceSettingsContribution": [{
49187
+ name: "deviceId",
49188
+ form: "single",
49189
+ optional: false
49190
+ }],
48614
49191
  "deviceAdminLink.getAdminLink": [{
48615
49192
  name: "deviceId",
48616
49193
  form: "single",
48617
49194
  optional: false
48618
49195
  }],
49196
+ "deviceAdoption.getStatus": [{
49197
+ name: "deviceId",
49198
+ form: "single",
49199
+ optional: false
49200
+ }],
48619
49201
  "deviceAdoption.release": [{
48620
49202
  name: "camDeviceId",
48621
49203
  form: "single",
@@ -48631,6 +49213,11 @@ Object.freeze({
48631
49213
  form: "single",
48632
49214
  optional: false
48633
49215
  }],
49216
+ "deviceDiscovery.getStatus": [{
49217
+ name: "deviceId",
49218
+ form: "single",
49219
+ optional: false
49220
+ }],
48634
49221
  "deviceDiscovery.listDiscovered": [{
48635
49222
  name: "deviceId",
48636
49223
  form: "single",
@@ -48650,6 +49237,21 @@ Object.freeze({
48650
49237
  form: "single",
48651
49238
  optional: false
48652
49239
  }],
49240
+ "deviceExport.applyDeviceSettingsPatch": [{
49241
+ name: "deviceId",
49242
+ form: "single",
49243
+ optional: false
49244
+ }],
49245
+ "deviceExport.getDeviceLiveContribution": [{
49246
+ name: "deviceId",
49247
+ form: "single",
49248
+ optional: false
49249
+ }],
49250
+ "deviceExport.getDeviceSettingsContribution": [{
49251
+ name: "deviceId",
49252
+ form: "single",
49253
+ optional: false
49254
+ }],
48653
49255
  "deviceExtension.listCandidates": [{
48654
49256
  name: "deviceId",
48655
49257
  form: "single",
@@ -48998,6 +49600,26 @@ Object.freeze({
48998
49600
  form: "single",
48999
49601
  optional: false
49000
49602
  }],
49603
+ "deviceStatus.getStatus": [{
49604
+ name: "deviceId",
49605
+ form: "single",
49606
+ optional: false
49607
+ }],
49608
+ "doorbell.getStatus": [{
49609
+ name: "deviceId",
49610
+ form: "single",
49611
+ optional: false
49612
+ }],
49613
+ "enumSensor.getStatus": [{
49614
+ name: "deviceId",
49615
+ form: "single",
49616
+ optional: false
49617
+ }],
49618
+ "eventEmitter.getStatus": [{
49619
+ name: "deviceId",
49620
+ form: "single",
49621
+ optional: false
49622
+ }],
49001
49623
  "faceGallery.getFaceByTrack": [{
49002
49624
  name: "deviceId",
49003
49625
  form: "single",
@@ -49012,6 +49634,11 @@ Object.freeze({
49012
49634
  form: "array",
49013
49635
  optional: true
49014
49636
  }],
49637
+ "fanControl.getStatus": [{
49638
+ name: "deviceId",
49639
+ form: "single",
49640
+ optional: false
49641
+ }],
49015
49642
  "fanControl.setDirection": [{
49016
49643
  name: "deviceId",
49017
49644
  form: "single",
@@ -49032,6 +49659,26 @@ Object.freeze({
49032
49659
  form: "single",
49033
49660
  optional: false
49034
49661
  }],
49662
+ "featureProbe.getStatus": [{
49663
+ name: "deviceId",
49664
+ form: "single",
49665
+ optional: false
49666
+ }],
49667
+ "flood.getStatus": [{
49668
+ name: "deviceId",
49669
+ form: "single",
49670
+ optional: false
49671
+ }],
49672
+ "gas.getStatus": [{
49673
+ name: "deviceId",
49674
+ form: "single",
49675
+ optional: false
49676
+ }],
49677
+ "humidifier.getStatus": [{
49678
+ name: "deviceId",
49679
+ form: "single",
49680
+ optional: false
49681
+ }],
49035
49682
  "humidifier.setMode": [{
49036
49683
  name: "deviceId",
49037
49684
  form: "single",
@@ -49047,11 +49694,26 @@ Object.freeze({
49047
49694
  form: "single",
49048
49695
  optional: false
49049
49696
  }],
49697
+ "humiditySensor.getStatus": [{
49698
+ name: "deviceId",
49699
+ form: "single",
49700
+ optional: false
49701
+ }],
49702
+ "image.getStatus": [{
49703
+ name: "deviceId",
49704
+ form: "single",
49705
+ optional: false
49706
+ }],
49050
49707
  "imageSettings.getOptions": [{
49051
49708
  name: "deviceId",
49052
49709
  form: "single",
49053
49710
  optional: false
49054
49711
  }],
49712
+ "imageSettings.getStatus": [{
49713
+ name: "deviceId",
49714
+ form: "single",
49715
+ optional: false
49716
+ }],
49055
49717
  "imageSettings.setSettings": [{
49056
49718
  name: "deviceId",
49057
49719
  form: "single",
@@ -49062,6 +49724,11 @@ Object.freeze({
49062
49724
  form: "single",
49063
49725
  optional: false
49064
49726
  }],
49727
+ "intercom.getStatus": [{
49728
+ name: "deviceId",
49729
+ form: "single",
49730
+ optional: false
49731
+ }],
49065
49732
  "intercom.handleAnswer": [{
49066
49733
  name: "deviceId",
49067
49734
  form: "single",
@@ -49092,6 +49759,11 @@ Object.freeze({
49092
49759
  form: "single",
49093
49760
  optional: false
49094
49761
  }],
49762
+ "lawnMowerControl.getStatus": [{
49763
+ name: "deviceId",
49764
+ form: "single",
49765
+ optional: false
49766
+ }],
49095
49767
  "lawnMowerControl.pause": [{
49096
49768
  name: "deviceId",
49097
49769
  form: "single",
@@ -49102,6 +49774,11 @@ Object.freeze({
49102
49774
  form: "single",
49103
49775
  optional: false
49104
49776
  }],
49777
+ "lockControl.getStatus": [{
49778
+ name: "deviceId",
49779
+ form: "single",
49780
+ optional: false
49781
+ }],
49105
49782
  "lockControl.lock": [{
49106
49783
  name: "deviceId",
49107
49784
  form: "single",
@@ -49117,6 +49794,11 @@ Object.freeze({
49117
49794
  form: "single",
49118
49795
  optional: false
49119
49796
  }],
49797
+ "mediaPlayer.getStatus": [{
49798
+ name: "deviceId",
49799
+ form: "single",
49800
+ optional: false
49801
+ }],
49120
49802
  "mediaPlayer.next": [{
49121
49803
  name: "deviceId",
49122
49804
  form: "single",
@@ -49177,6 +49859,11 @@ Object.freeze({
49177
49859
  form: "single",
49178
49860
  optional: false
49179
49861
  }],
49862
+ "motion.getStatus": [{
49863
+ name: "deviceId",
49864
+ form: "single",
49865
+ optional: false
49866
+ }],
49180
49867
  "motion.isDetected": [{
49181
49868
  name: "deviceId",
49182
49869
  form: "single",
@@ -49187,11 +49874,31 @@ Object.freeze({
49187
49874
  form: "single",
49188
49875
  optional: false
49189
49876
  }],
49877
+ "motionDetection.applyDeviceSettingsPatch": [{
49878
+ name: "deviceId",
49879
+ form: "single",
49880
+ optional: false
49881
+ }],
49882
+ "motionDetection.getDeviceLiveContribution": [{
49883
+ name: "deviceId",
49884
+ form: "single",
49885
+ optional: false
49886
+ }],
49887
+ "motionDetection.getDeviceSettingsContribution": [{
49888
+ name: "deviceId",
49889
+ form: "single",
49890
+ optional: false
49891
+ }],
49190
49892
  "motionDetection.removeCamera": [{
49191
49893
  name: "deviceId",
49192
49894
  form: "single",
49193
49895
  optional: false
49194
49896
  }],
49897
+ "motionTrigger.getStatus": [{
49898
+ name: "deviceId",
49899
+ form: "single",
49900
+ optional: false
49901
+ }],
49195
49902
  "motionTrigger.setMotionTrigger": [{
49196
49903
  name: "deviceId",
49197
49904
  form: "single",
@@ -49202,6 +49909,11 @@ Object.freeze({
49202
49909
  form: "single",
49203
49910
  optional: false
49204
49911
  }],
49912
+ "motionZones.getStatus": [{
49913
+ name: "deviceId",
49914
+ form: "single",
49915
+ optional: false
49916
+ }],
49205
49917
  "motionZones.setZone": [{
49206
49918
  name: "deviceId",
49207
49919
  form: "single",
@@ -49212,6 +49924,11 @@ Object.freeze({
49212
49924
  form: "single",
49213
49925
  optional: false
49214
49926
  }],
49927
+ "nativeObjectDetection.getStatus": [{
49928
+ name: "deviceId",
49929
+ form: "single",
49930
+ optional: false
49931
+ }],
49215
49932
  "nativeObjectDetection.setEnabled": [{
49216
49933
  name: "deviceId",
49217
49934
  form: "single",
@@ -49227,6 +49944,11 @@ Object.freeze({
49227
49944
  form: "single",
49228
49945
  optional: false
49229
49946
  }],
49947
+ "navigation.getStatus": [{
49948
+ name: "deviceId",
49949
+ form: "single",
49950
+ optional: false
49951
+ }],
49230
49952
  "navigation.goToPoint": [{
49231
49953
  name: "deviceId",
49232
49954
  form: "single",
@@ -49272,6 +49994,11 @@ Object.freeze({
49272
49994
  form: "single",
49273
49995
  optional: false
49274
49996
  }],
49997
+ "networkLink.getStatus": [{
49998
+ name: "deviceId",
49999
+ form: "single",
50000
+ optional: false
50001
+ }],
49275
50002
  "networkQuality.getDeviceStats": [{
49276
50003
  name: "deviceId",
49277
50004
  form: "single",
@@ -49282,6 +50009,11 @@ Object.freeze({
49282
50009
  form: "single",
49283
50010
  optional: false
49284
50011
  }],
50012
+ "notificationRules.getAlarmSensors": [{
50013
+ name: "deviceIds",
50014
+ form: "array",
50015
+ optional: false
50016
+ }],
49285
50017
  "notificationRules.setDeviceMuted": [{
49286
50018
  name: "deviceId",
49287
50019
  form: "single",
@@ -49292,11 +50024,26 @@ Object.freeze({
49292
50024
  form: "single",
49293
50025
  optional: false
49294
50026
  }],
50027
+ "notifier.getStatus": [{
50028
+ name: "deviceId",
50029
+ form: "single",
50030
+ optional: false
50031
+ }],
49295
50032
  "notifier.send": [{
49296
50033
  name: "deviceId",
49297
50034
  form: "single",
49298
50035
  optional: false
49299
50036
  }],
50037
+ "numericSensor.getStatus": [{
50038
+ name: "deviceId",
50039
+ form: "single",
50040
+ optional: false
50041
+ }],
50042
+ "osd.getStatus": [{
50043
+ name: "deviceId",
50044
+ form: "single",
50045
+ optional: false
50046
+ }],
49300
50047
  "osd.setOverlay": [{
49301
50048
  name: "deviceId",
49302
50049
  form: "single",
@@ -49356,6 +50103,11 @@ Object.freeze({
49356
50103
  form: "single",
49357
50104
  optional: false
49358
50105
  }],
50106
+ "petFeeder.getStatus": [{
50107
+ name: "deviceId",
50108
+ form: "single",
50109
+ optional: false
50110
+ }],
49359
50111
  "petFeeder.markFoodReplenished": [{
49360
50112
  name: "deviceId",
49361
50113
  form: "single",
@@ -49391,6 +50143,16 @@ Object.freeze({
49391
50143
  form: "single",
49392
50144
  optional: false
49393
50145
  }],
50146
+ "pipelineAnalytics.applyDeviceSettingsPatch": [{
50147
+ name: "deviceId",
50148
+ form: "single",
50149
+ optional: false
50150
+ }],
50151
+ "pipelineAnalytics.clearTrackRelation": [{
50152
+ name: "deviceId",
50153
+ form: "single",
50154
+ optional: false
50155
+ }],
49394
50156
  "pipelineAnalytics.clearTracks": [{
49395
50157
  name: "deviceId",
49396
50158
  form: "single",
@@ -49426,6 +50188,16 @@ Object.freeze({
49426
50188
  form: "single",
49427
50189
  optional: false
49428
50190
  }],
50191
+ "pipelineAnalytics.getDeviceLiveContribution": [{
50192
+ name: "deviceId",
50193
+ form: "single",
50194
+ optional: false
50195
+ }],
50196
+ "pipelineAnalytics.getDeviceSettingsContribution": [{
50197
+ name: "deviceId",
50198
+ form: "single",
50199
+ optional: false
50200
+ }],
49429
50201
  "pipelineAnalytics.getEventDensity": [{
49430
50202
  name: "deviceId",
49431
50203
  form: "single",
@@ -49541,6 +50313,11 @@ Object.freeze({
49541
50313
  form: "single",
49542
50314
  optional: false
49543
50315
  }],
50316
+ "pipelineAnalytics.listTrackRelations": [{
50317
+ name: "deviceId",
50318
+ form: "single",
50319
+ optional: false
50320
+ }],
49544
50321
  "pipelineAnalytics.listTracks": [{
49545
50322
  name: "deviceId",
49546
50323
  form: "single",
@@ -49611,6 +50388,16 @@ Object.freeze({
49611
50388
  form: "single",
49612
50389
  optional: false
49613
50390
  }],
50391
+ "pipelineAnalytics.setTrackPurity": [{
50392
+ name: "deviceId",
50393
+ form: "single",
50394
+ optional: false
50395
+ }],
50396
+ "pipelineAnalytics.setTrackRelation": [{
50397
+ name: "deviceId",
50398
+ form: "single",
50399
+ optional: false
50400
+ }],
49614
50401
  "pipelineAnalytics.wipeAllAnalytics": [{
49615
50402
  name: "deviceId",
49616
50403
  form: "single",
@@ -49626,6 +50413,11 @@ Object.freeze({
49626
50413
  form: "single",
49627
50414
  optional: true
49628
50415
  }],
50416
+ "pipelineOrchestrator.applyDeviceSettingsPatch": [{
50417
+ name: "deviceId",
50418
+ form: "single",
50419
+ optional: false
50420
+ }],
49629
50421
  "pipelineOrchestrator.assignAudio": [{
49630
50422
  name: "deviceId",
49631
50423
  form: "single",
@@ -49671,6 +50463,16 @@ Object.freeze({
49671
50463
  form: "single",
49672
50464
  optional: false
49673
50465
  }],
50466
+ "pipelineOrchestrator.getDeviceLiveContribution": [{
50467
+ name: "deviceId",
50468
+ form: "single",
50469
+ optional: false
50470
+ }],
50471
+ "pipelineOrchestrator.getDeviceSettingsContribution": [{
50472
+ name: "deviceId",
50473
+ form: "single",
50474
+ optional: false
50475
+ }],
49674
50476
  "pipelineOrchestrator.getPipelineAssignment": [{
49675
50477
  name: "deviceId",
49676
50478
  form: "single",
@@ -49681,6 +50483,11 @@ Object.freeze({
49681
50483
  form: "single",
49682
50484
  optional: false
49683
50485
  }],
50486
+ "pipelineOrchestrator.requestOccupancyLook": [{
50487
+ name: "deviceId",
50488
+ form: "single",
50489
+ optional: false
50490
+ }],
49684
50491
  "pipelineOrchestrator.resolvePipeline": [{
49685
50492
  name: "deviceId",
49686
50493
  form: "single",
@@ -49691,6 +50498,11 @@ Object.freeze({
49691
50498
  form: "single",
49692
50499
  optional: false
49693
50500
  }],
50501
+ "pipelineOrchestrator.setCameraSensors": [{
50502
+ name: "deviceId",
50503
+ form: "single",
50504
+ optional: false
50505
+ }],
49694
50506
  "pipelineOrchestrator.setCameraStepOverride": [{
49695
50507
  name: "deviceId",
49696
50508
  form: "single",
@@ -49781,11 +50593,31 @@ Object.freeze({
49781
50593
  form: "single",
49782
50594
  optional: true
49783
50595
  }],
50596
+ "powerMeter.getStatus": [{
50597
+ name: "deviceId",
50598
+ form: "single",
50599
+ optional: false
50600
+ }],
50601
+ "presence.getStatus": [{
50602
+ name: "deviceId",
50603
+ form: "single",
50604
+ optional: false
50605
+ }],
50606
+ "pressureSensor.getStatus": [{
50607
+ name: "deviceId",
50608
+ form: "single",
50609
+ optional: false
50610
+ }],
49784
50611
  "privacyMask.getOptions": [{
49785
50612
  name: "deviceId",
49786
50613
  form: "single",
49787
50614
  optional: false
49788
50615
  }],
50616
+ "privacyMask.getStatus": [{
50617
+ name: "deviceId",
50618
+ form: "single",
50619
+ optional: false
50620
+ }],
49789
50621
  "privacyMask.setAudioEnabled": [{
49790
50622
  name: "deviceId",
49791
50623
  form: "single",
@@ -49836,6 +50668,11 @@ Object.freeze({
49836
50668
  form: "single",
49837
50669
  optional: false
49838
50670
  }],
50671
+ "ptz.getStatus": [{
50672
+ name: "deviceId",
50673
+ form: "single",
50674
+ optional: false
50675
+ }],
49839
50676
  "ptz.goHome": [{
49840
50677
  name: "deviceId",
49841
50678
  form: "single",
@@ -49926,6 +50763,11 @@ Object.freeze({
49926
50763
  form: "single",
49927
50764
  optional: false
49928
50765
  }],
50766
+ "recordingArchive.applyDeviceSettingsPatch": [{
50767
+ name: "deviceId",
50768
+ form: "single",
50769
+ optional: false
50770
+ }],
49929
50771
  "recordingArchive.deleteFootprint": [{
49930
50772
  name: "deviceId",
49931
50773
  form: "single",
@@ -49946,6 +50788,21 @@ Object.freeze({
49946
50788
  form: "single",
49947
50789
  optional: false
49948
50790
  }],
50791
+ "recordingArchive.getDeviceLiveContribution": [{
50792
+ name: "deviceId",
50793
+ form: "single",
50794
+ optional: false
50795
+ }],
50796
+ "recordingArchive.getDeviceSettingsContribution": [{
50797
+ name: "deviceId",
50798
+ form: "single",
50799
+ optional: false
50800
+ }],
50801
+ "recordingArchive.getStatus": [{
50802
+ name: "deviceId",
50803
+ form: "single",
50804
+ optional: false
50805
+ }],
49949
50806
  "recordingArchive.listOpsLog": [{
49950
50807
  name: "deviceId",
49951
50808
  form: "single",
@@ -50031,6 +50888,11 @@ Object.freeze({
50031
50888
  form: "single",
50032
50889
  optional: false
50033
50890
  }],
50891
+ "recordingOnboard.getStatus": [{
50892
+ name: "deviceId",
50893
+ form: "single",
50894
+ optional: false
50895
+ }],
50034
50896
  "recordingOnboard.setSettings": [{
50035
50897
  name: "deviceId",
50036
50898
  form: "single",
@@ -50061,6 +50923,11 @@ Object.freeze({
50061
50923
  form: "single",
50062
50924
  optional: false
50063
50925
  }],
50926
+ "sceneMonitor.getStatus": [{
50927
+ name: "deviceId",
50928
+ form: "single",
50929
+ optional: false
50930
+ }],
50064
50931
  "sceneMonitor.listScenes": [{
50065
50932
  name: "deviceId",
50066
50933
  form: "single",
@@ -50086,6 +50953,11 @@ Object.freeze({
50086
50953
  form: "single",
50087
50954
  optional: false
50088
50955
  }],
50956
+ "scriptRunner.getStatus": [{
50957
+ name: "deviceId",
50958
+ form: "single",
50959
+ optional: false
50960
+ }],
50089
50961
  "scriptRunner.run": [{
50090
50962
  name: "deviceId",
50091
50963
  form: "single",
@@ -50096,11 +50968,31 @@ Object.freeze({
50096
50968
  form: "single",
50097
50969
  optional: false
50098
50970
  }],
50971
+ "smoke.getStatus": [{
50972
+ name: "deviceId",
50973
+ form: "single",
50974
+ optional: false
50975
+ }],
50976
+ "snapshot.applyDeviceSettingsPatch": [{
50977
+ name: "deviceId",
50978
+ form: "single",
50979
+ optional: false
50980
+ }],
50099
50981
  "snapshot.getDebugState": [{
50100
50982
  name: "deviceId",
50101
50983
  form: "single",
50102
50984
  optional: false
50103
50985
  }],
50986
+ "snapshot.getDeviceLiveContribution": [{
50987
+ name: "deviceId",
50988
+ form: "single",
50989
+ optional: false
50990
+ }],
50991
+ "snapshot.getDeviceSettingsContribution": [{
50992
+ name: "deviceId",
50993
+ form: "single",
50994
+ optional: false
50995
+ }],
50104
50996
  "snapshot.getSnapshot": [{
50105
50997
  name: "deviceId",
50106
50998
  form: "single",
@@ -50117,6 +51009,11 @@ Object.freeze({
50117
51009
  form: "array",
50118
51010
  optional: false
50119
51011
  }],
51012
+ "snapshot.getStatus": [{
51013
+ name: "deviceId",
51014
+ form: "single",
51015
+ optional: false
51016
+ }],
50120
51017
  "snapshot.invalidateCache": [{
50121
51018
  name: "deviceId",
50122
51019
  form: "single",
@@ -50127,6 +51024,11 @@ Object.freeze({
50127
51024
  form: "single",
50128
51025
  optional: false
50129
51026
  }],
51027
+ "streamBroker.applyDeviceSettingsPatch": [{
51028
+ name: "deviceId",
51029
+ form: "single",
51030
+ optional: false
51031
+ }],
50130
51032
  "streamBroker.assignProfile": [{
50131
51033
  name: "deviceId",
50132
51034
  form: "single",
@@ -50142,6 +51044,16 @@ Object.freeze({
50142
51044
  form: "single",
50143
51045
  optional: false
50144
51046
  }],
51047
+ "streamBroker.getDeviceLiveContribution": [{
51048
+ name: "deviceId",
51049
+ form: "single",
51050
+ optional: false
51051
+ }],
51052
+ "streamBroker.getDeviceSettingsContribution": [{
51053
+ name: "deviceId",
51054
+ form: "single",
51055
+ optional: false
51056
+ }],
50145
51057
  "streamBroker.getStreamWithCodec": [{
50146
51058
  name: "deviceId",
50147
51059
  form: "single",
@@ -50202,16 +51114,46 @@ Object.freeze({
50202
51114
  form: "single",
50203
51115
  optional: false
50204
51116
  }],
51117
+ "streamParams.getStatus": [{
51118
+ name: "deviceId",
51119
+ form: "single",
51120
+ optional: false
51121
+ }],
50205
51122
  "streamParams.setProfile": [{
50206
51123
  name: "deviceId",
50207
51124
  form: "single",
50208
51125
  optional: false
50209
51126
  }],
51127
+ "switch.getStatus": [{
51128
+ name: "deviceId",
51129
+ form: "single",
51130
+ optional: false
51131
+ }],
50210
51132
  "switch.setState": [{
50211
51133
  name: "deviceId",
50212
51134
  form: "single",
50213
51135
  optional: false
50214
51136
  }],
51137
+ "tamper.getStatus": [{
51138
+ name: "deviceId",
51139
+ form: "single",
51140
+ optional: false
51141
+ }],
51142
+ "temperatureSensor.getStatus": [{
51143
+ name: "deviceId",
51144
+ form: "single",
51145
+ optional: false
51146
+ }],
51147
+ "update.getStatus": [{
51148
+ name: "deviceId",
51149
+ form: "single",
51150
+ optional: false
51151
+ }],
51152
+ "vacuumControl.getStatus": [{
51153
+ name: "deviceId",
51154
+ form: "single",
51155
+ optional: false
51156
+ }],
50215
51157
  "vacuumControl.locate": [{
50216
51158
  name: "deviceId",
50217
51159
  form: "single",
@@ -50247,6 +51189,11 @@ Object.freeze({
50247
51189
  form: "single",
50248
51190
  optional: false
50249
51191
  }],
51192
+ "valve.getStatus": [{
51193
+ name: "deviceId",
51194
+ form: "single",
51195
+ optional: false
51196
+ }],
50250
51197
  "valve.open": [{
50251
51198
  name: "deviceId",
50252
51199
  form: "single",
@@ -50262,6 +51209,11 @@ Object.freeze({
50262
51209
  form: "single",
50263
51210
  optional: false
50264
51211
  }],
51212
+ "vibration.getStatus": [{
51213
+ name: "deviceId",
51214
+ form: "single",
51215
+ optional: false
51216
+ }],
50265
51217
  "videoclips.dialClipStream": [{
50266
51218
  name: "deviceId",
50267
51219
  form: "single",
@@ -50297,6 +51249,11 @@ Object.freeze({
50297
51249
  form: "single",
50298
51250
  optional: false
50299
51251
  }],
51252
+ "waterHeater.getStatus": [{
51253
+ name: "deviceId",
51254
+ form: "single",
51255
+ optional: false
51256
+ }],
50300
51257
  "waterHeater.setAway": [{
50301
51258
  name: "deviceId",
50302
51259
  form: "single",
@@ -50312,6 +51269,11 @@ Object.freeze({
50312
51269
  form: "single",
50313
51270
  optional: false
50314
51271
  }],
51272
+ "weather.getStatus": [{
51273
+ name: "deviceId",
51274
+ form: "single",
51275
+ optional: false
51276
+ }],
50315
51277
  "webrtcSession.addIceCandidate": [{
50316
51278
  name: "deviceId",
50317
51279
  form: "single",
@@ -50837,6 +51799,18 @@ var STATE_SUMMARY_VARS = [
50837
51799
  systemEventKinds: [...SUMMARISABLE],
50838
51800
  systemEventDeliveries: ["summary"]
50839
51801
  },
51802
+ {
51803
+ name: "summarySection",
51804
+ label: "Summary: this section",
51805
+ description: "The section a per-section summary row is about (D698); on a summary sent as one message, every section it reports, comma-separated.",
51806
+ example: "Batterie scariche",
51807
+ group: "system",
51808
+ families: ["rule"],
51809
+ fields: [...RULE_TEXT],
51810
+ kinds: ["system"],
51811
+ systemEventKinds: [...SUMMARISABLE],
51812
+ systemEventDeliveries: ["summary"]
51813
+ },
50840
51814
  ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
50841
51815
  name: v.name,
50842
51816
  label: v.label,
@@ -50913,6 +51887,37 @@ var STATE_SUMMARY_VARS = [
50913
51887
  [...RULE_TEXT],
50914
51888
  ...STATE_SUMMARY_VARS
50915
51889
  ];
51890
+ new Set([
51891
+ "disarmed",
51892
+ "armed_home",
51893
+ "armed_away",
51894
+ "armed_night",
51895
+ "armed_vacation",
51896
+ "armed_custom_bypass",
51897
+ "arming",
51898
+ "disarming",
51899
+ "pending",
51900
+ "triggered",
51901
+ "open",
51902
+ "opening",
51903
+ "closing",
51904
+ "closed",
51905
+ "stopped",
51906
+ "locked",
51907
+ "unlocked",
51908
+ "locking",
51909
+ "unlocking",
51910
+ "jammed",
51911
+ "on",
51912
+ "off",
51913
+ "home",
51914
+ "not_home",
51915
+ "matched",
51916
+ "diverged",
51917
+ "unknown",
51918
+ "latched",
51919
+ "live"
51920
+ ]);
50916
51921
  /**
50917
51922
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
50918
51923
  *
@@ -51006,7 +52011,7 @@ var PreviewTextField = string$2().max(200);
51006
52011
  * Whether the notification's preview is a STILL or a short animation.
51007
52012
  *
51008
52013
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
51009
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
52014
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
51010
52015
  * night reads better as three seconds of motion than as one frame of it. Both
51011
52016
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
51012
52017
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -51144,6 +52149,7 @@ TimelapseRuleInputSchema.extend({
51144
52149
  createdAt: number(),
51145
52150
  updatedAt: number()
51146
52151
  });
52152
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
51147
52153
  object({
51148
52154
  /**
51149
52155
  * Fraction of the box's own size added on EACH side before cutting.