@camstack/addon-pipeline 1.2.313 → 1.2.315

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 (31) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-BKL87wl7.mjs → default-detection-model-BTlf5MpT.mjs} +1 -1
  4. package/dist/{default-detection-model-BdGFby5Z.js → default-detection-model-CteW4o8U.js} +1 -1
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-D0zhkadD.mjs → dist-C8W_MWSz.mjs} +438 -278
  8. package/dist/{dist-BPVKYeQX.js → dist-sCjZdzKn.js} +443 -283
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-B9daDdJP.mjs → node-CnLKPz1A.mjs} +1 -1
  12. package/dist/{node-C7rarKVD.js → node-D4xdwpPU.js} +1 -1
  13. package/dist/pipeline-runner/index.js +19 -58
  14. package/dist/pipeline-runner/index.mjs +17 -56
  15. package/dist/{process-memory-BUDBSDt2.js → process-memory-CMnhjGyE.js} +1 -1
  16. package/dist/{process-memory-Ci9SR5oX.mjs → process-memory-DqTBUteS.mjs} +1 -1
  17. package/dist/recorder/index.js +3 -3
  18. package/dist/recorder/index.mjs +3 -3
  19. package/dist/{segment-demux-js-CsReVvrn.mjs → segment-demux-js-CCSsDyMw.mjs} +1 -1
  20. package/dist/{segment-demux-js-BBX4yDqb.js → segment-demux-js-DGpdNqsG.js} +1 -1
  21. package/dist/stream-broker/_stub.js +1 -1
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B6JI3d7K.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BOKNSh-e.mjs} +3 -3
  23. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BTvQXobK.mjs +26 -0
  24. package/dist/stream-broker/demux-worker-child.js +1 -1
  25. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  26. package/dist/stream-broker/{hostInit-CQOvL7HB.mjs → hostInit-BkvtmJ2D.mjs} +3 -3
  27. package/dist/stream-broker/index.js +3 -3
  28. package/dist/stream-broker/index.mjs +3 -3
  29. package/dist/stream-broker/remoteEntry.js +1 -1
  30. package/package.json +2 -2
  31. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DuQsOkk7.mjs +0 -26
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- //#region ../types/dist/event-category-C5xZWqz6.mjs
2
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -532,6 +532,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
532
532
  */
533
533
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
534
534
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
535
+ /**
536
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
537
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
538
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
539
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
540
+ * for a camera with no attached sensor). Providers must not emit
541
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
542
+ * keeps it so (camera-grid is a named, deferred exception).
543
+ */
535
544
  EventCategory["ProviderMotion"] = "provider.motion";
536
545
  EventCategory["ProviderDetection"] = "provider.detection";
537
546
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5370,7 +5379,7 @@ var ZodIssueCode = {
5370
5379
  var ZodFirstPartyTypeKind;
5371
5380
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5372
5381
  //#endregion
5373
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5382
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5374
5383
  /**
5375
5384
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5376
5385
  * window to float samples (D455).
@@ -7684,7 +7693,7 @@ function errMsg(err) {
7684
7693
  return String(err);
7685
7694
  }
7686
7695
  //#endregion
7687
- //#region ../types/dist/composition-Dq42XWp0.mjs
7696
+ //#region ../types/dist/composition-43aqsZQj.mjs
7688
7697
  var AUDIO_ENCODER_BY_CODEC = {
7689
7698
  opus: "libopus",
7690
7699
  aac: "aac",
@@ -8448,13 +8457,12 @@ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac
8448
8457
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
8449
8458
  * valutatore puro del suo FRONTE.
8450
8459
  *
8451
- * Viveva dentro il builtin virtual-doorbell
8452
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
8453
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
8454
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
8455
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
8456
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
8457
- * SPOSTA qui e il doorbell la ri-esporta.
8460
+ * UNA tabella per tutti quelli che chiedono "questo device è ALTO?": il
8461
+ * trigger `RecordingTriggers.sensorDeviceIds` del recorder, i marker dei
8462
+ * sensori della post-analysis, il bridge camera-sensors dell'orchestratore e
8463
+ * le sorgenti di un campanello composto (`doorbellFeatureSources`, D671).
8464
+ * Una seconda copia divergerebbe alla prima cap aggiunta, e il sintomo — "il
8465
+ * sensore fa suonare il campanello ma non registra" — è esattamente D62.
8458
8466
  *
8459
8467
  * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
8460
8468
  * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
@@ -10678,7 +10686,7 @@ var RecordingTriggersSchema = object({
10678
10686
  *
10679
10687
  * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
10680
10688
  * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
10681
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
10689
+ * la stessa del campanello composto (D671). Ed è il FRONTE, non
10682
10690
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
10683
10691
  * registrare.
10684
10692
  */
@@ -13832,6 +13840,68 @@ method(object({
13832
13840
  password: string()
13833
13841
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
13834
13842
  /**
13843
+ * How long one snapshot may take end to end: the pinned copy of the whole
13844
+ * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
13845
+ * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
13846
+ * generous because a timeout fails the whole backup run.
13847
+ */
13848
+ var DATABASE_SNAPSHOT_TIMEOUT_MS = 30 * 6e4;
13849
+ /**
13850
+ * The databases an owner snapshots — declared by the OWNER, so a caller never
13851
+ * guesses which file is live from what sits beside it (a cleanly closed
13852
+ * database has no sidecars and is just as owned). Paths are absolute on the
13853
+ * owner's host.
13854
+ */
13855
+ var DatabaseSnapshotTargetsSchema = object({ databases: array(object({ livePath: string() })) });
13856
+ /** One b-tree table of the snapshot and the rows it holds. */
13857
+ var DatabaseSnapshotTableSchema = object({
13858
+ name: string(),
13859
+ rows: number().int().nonnegative()
13860
+ });
13861
+ /**
13862
+ * What the owner wrote. Every path is ABSOLUTE on the owner's host — the
13863
+ * caller pins the call to its own node, so it can read the file; a caller
13864
+ * that finds the path outside its own data root refuses it rather than
13865
+ * archiving something it cannot place.
13866
+ */
13867
+ var DatabaseSnapshotResultSchema = object({
13868
+ /** The live database the snapshot copies. Never to be copied as a file. */
13869
+ livePath: string(),
13870
+ /** The verified copy: `<livePath>` + {@link DATABASE_SNAPSHOT_SUFFIX}. */
13871
+ snapshotPath: string(),
13872
+ sizeBytes: number().int().nonnegative(),
13873
+ pageCount: number().int().nonnegative(),
13874
+ pageSize: number().int().positive(),
13875
+ /**
13876
+ * Every b-tree table with its row count. Read inside the SAME read
13877
+ * transaction the copy was taken from, and found EQUAL on the copy — so
13878
+ * this is the content of the snapshot, not an estimate of the source.
13879
+ */
13880
+ tables: array(DatabaseSnapshotTableSchema),
13881
+ /** Wall time of the pinned copy (row counts included). */
13882
+ copyMs: number().nonnegative(),
13883
+ /** Wall time of the checks on the copy (`integrity_check`, row counts). */
13884
+ verifyMs: number().nonnegative(),
13885
+ /** When the pinned read transaction began (ms epoch) — the snapshot's instant. */
13886
+ takenAt: number()
13887
+ });
13888
+ /**
13889
+ * A snapshot as an ARCHIVE records it (`ArchiveManifest.sqliteSnapshots`):
13890
+ * the same facts, with both paths relative to the archive root (the node's
13891
+ * data dir), because an archive outlives the host that made it.
13892
+ */
13893
+ var ArchiveSqliteSnapshotSchema = DatabaseSnapshotResultSchema.extend({
13894
+ /** Archive-relative path of the live database the archive does NOT carry. */
13895
+ livePath: string(),
13896
+ /** Archive-relative path of the verified copy it carries in its place. */
13897
+ snapshotPath: string()
13898
+ });
13899
+ method(object({}), DatabaseSnapshotTargetsSchema, { auth: "admin" }), method(object({}), DatabaseSnapshotResultSchema, {
13900
+ kind: "mutation",
13901
+ auth: "admin",
13902
+ timeoutMs: DATABASE_SNAPSHOT_TIMEOUT_MS
13903
+ });
13904
+ /**
13835
13905
  * Orchestrator-side destination metadata. The orchestrator computes
13836
13906
  * `id = <addonId>:<subId>` from its provider lookup so consumers
13837
13907
  * (admin UI, restore flow) see one canonical key.
@@ -13942,7 +14012,12 @@ var ArchiveManifestSchema = object({
13942
14012
  locations: array(string()),
13943
14013
  entries: array(ArchiveEntrySchema),
13944
14014
  totalBytes: number(),
13945
- totalFiles: number()
14015
+ totalFiles: number(),
14016
+ /**
14017
+ * Live SQLite databases this archive carries as their OWNER's verified
14018
+ * snapshot instead of as files (D693). Absent on archives made before.
14019
+ */
14020
+ sqliteSnapshots: array(ArchiveSqliteSnapshotSchema).optional()
13946
14021
  });
13947
14022
  var LocationStatSchema = object({
13948
14023
  name: string(),
@@ -15720,6 +15795,52 @@ var CompositionBlockStateSchema = object({
15720
15795
  reason: string().optional(),
15721
15796
  fields: array(CompositionFieldStateSchema)
15722
15797
  });
15798
+ var ComposerBlockSchema = object({
15799
+ id: string().min(1),
15800
+ name: string().min(1).max(120),
15801
+ enabled: boolean(),
15802
+ updatedAt: number(),
15803
+ composition: CompositionSchema
15804
+ });
15805
+ /**
15806
+ * A stored composition row that no longer PARSES — a schema a release
15807
+ * tightened, a row written by a newer build. Sent by id so the composer keeps
15808
+ * its device as a placeholder and reports the block `failed` naming the parse
15809
+ * error. Leaving it out would read, twice, exactly like a delete (D49).
15810
+ */
15811
+ var ComposerUnreadableBlockSchema = object({
15812
+ id: string().min(1),
15813
+ /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
15814
+ name: string().nullable(),
15815
+ /** The parse error, in the operator's words. */
15816
+ error: string()
15817
+ });
15818
+ method(object({
15819
+ /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
15820
+ integrationId: string().nullable(),
15821
+ /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
15822
+ storeLoaded: boolean(),
15823
+ blocks: array(ComposerBlockSchema),
15824
+ /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
15825
+ restartBlockIds: array(string()),
15826
+ /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
15827
+ unreadableBlocks: array(ComposerUnreadableBlockSchema)
15828
+ }), object({
15829
+ blocks: array(object({
15830
+ blockId: string(),
15831
+ status: CompositionRunStatusSchema,
15832
+ reason: string().optional()
15833
+ })),
15834
+ /**
15835
+ * False when the composer declared and withdrew NOTHING (store not loaded,
15836
+ * integration unknown, its own rows unread or read empty once). An empty
15837
+ * apply the caller owes is paid off only by one the composer acted on (D49).
15838
+ */
15839
+ acted: boolean()
15840
+ }), {
15841
+ kind: "mutation",
15842
+ auth: "admin"
15843
+ }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
15723
15844
  var NUMBER = {
15724
15845
  kind: "fixed",
15725
15846
  result: ["number"]
@@ -15956,52 +16077,6 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
15956
16077
  auth: "admin",
15957
16078
  access: "view"
15958
16079
  });
15959
- var ComposerBlockSchema = object({
15960
- id: string().min(1),
15961
- name: string().min(1).max(120),
15962
- enabled: boolean(),
15963
- updatedAt: number(),
15964
- composition: CompositionSchema
15965
- });
15966
- /**
15967
- * A stored composition row that no longer PARSES — a schema a release
15968
- * tightened, a row written by a newer build. Sent by id so the composer keeps
15969
- * its device as a placeholder and reports the block `failed` naming the parse
15970
- * error. Leaving it out would read, twice, exactly like a delete (D49).
15971
- */
15972
- var ComposerUnreadableBlockSchema = object({
15973
- id: string().min(1),
15974
- /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
15975
- name: string().nullable(),
15976
- /** The parse error, in the operator's words. */
15977
- error: string()
15978
- });
15979
- method(object({
15980
- /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
15981
- integrationId: string().nullable(),
15982
- /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
15983
- storeLoaded: boolean(),
15984
- blocks: array(ComposerBlockSchema),
15985
- /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
15986
- restartBlockIds: array(string()),
15987
- /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
15988
- unreadableBlocks: array(ComposerUnreadableBlockSchema)
15989
- }), object({
15990
- blocks: array(object({
15991
- blockId: string(),
15992
- status: CompositionRunStatusSchema,
15993
- reason: string().optional()
15994
- })),
15995
- /**
15996
- * False when the composer declared and withdrew NOTHING (store not loaded,
15997
- * integration unknown, its own rows unread or read empty once). An empty
15998
- * apply the caller owes is paid off only by one the composer acted on (D49).
15999
- */
16000
- acted: boolean()
16001
- }), {
16002
- kind: "mutation",
16003
- auth: "admin"
16004
- }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
16005
16080
  /**
16006
16081
  * `custom-model-registry` — collection cap exposing operator-registered
16007
16082
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -17881,6 +17956,121 @@ method(object({
17881
17956
  deviceIds: array(number()).readonly(),
17882
17957
  caps: array(string()).readonly().optional()
17883
17958
  }), record(string(), record(string(), unknown().nullable())));
17959
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
17960
+ DeviceType.Camera;
17961
+ /** A slot's editable value, discriminated on `slot`. */
17962
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
17963
+ DeviceType.Camera;
17964
+ var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
17965
+ state: literal("unreachable"),
17966
+ reason: string()
17967
+ })]);
17968
+ /** One provider of slots — the D554 source-listing row. */
17969
+ var ExtensionSourceSchema = object({
17970
+ /** The routing prefix of every `slotRef` this provider serves. */
17971
+ source: string(),
17972
+ label: string(),
17973
+ /** Stamped by the dispatcher from the registry (D557). */
17974
+ addonId: string().optional(),
17975
+ availability: ExtensionSourceAvailabilitySchema
17976
+ });
17977
+ /**
17978
+ * How one picked source stands. `missing` — no such device (deleted, or an id
17979
+ * that never existed); `foreign` — a device the slot's owner cannot read from
17980
+ * (a source owned by another addon); `unavailable` — it exists but is not a
17981
+ * valid source now (a container, which is never a pick). A source in any of
17982
+ * these states is SHOWN, never silently dropped or re-pointed.
17983
+ */
17984
+ var ExtensionSourceHealthSchema = _enum([
17985
+ "ok",
17986
+ "missing",
17987
+ "foreign",
17988
+ "unavailable"
17989
+ ]);
17990
+ var ExtensionSlotSourceSchema = object({
17991
+ deviceId: number().int(),
17992
+ /** The device's name, or null when it could not be resolved (`missing`). */
17993
+ name: string().nullable(),
17994
+ health: ExtensionSourceHealthSchema,
17995
+ detail: string().optional()
17996
+ });
17997
+ /**
17998
+ * A DIFFERENT authority that gates whether the slot's sources do anything —
17999
+ * reported read-only, named, never written through this contract (D62) — e.g.
18000
+ * a slot whose sources act only while a wrapper is bound to the host, a
18001
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
18002
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
18003
+ * not be read; it is not "inactive" (D315).
18004
+ */
18005
+ var ExtensionSlotActivationSchema = object({
18006
+ state: _enum([
18007
+ "active",
18008
+ "inactive",
18009
+ "unknown"
18010
+ ]),
18011
+ authority: string(),
18012
+ detail: string().optional()
18013
+ });
18014
+ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
18015
+ state: literal("ok"),
18016
+ /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
18017
+ slotRef: string(),
18018
+ slot: ExtensionSlotIdSchema,
18019
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
18020
+ authority: string(),
18021
+ activation: ExtensionSlotActivationSchema.nullable(),
18022
+ value: ExtensionSlotValueSchema,
18023
+ sources: array(ExtensionSlotSourceSchema),
18024
+ /**
18025
+ * Devices the owner links to the host ON ITS OWN, by structure, and that
18026
+ * are therefore not in `value` (D655): a camera's device-tree children, and
18027
+ * the child a picked container stands for (D356 — a container is never a
18028
+ * pick, its children are). Read-only; the editor shows them and may offer
18029
+ * per-device options on them (the linked-devices tracked-event opt-in).
18030
+ * Absent when the owner links nothing beyond the picks, and from an owner
18031
+ * older than D655.
18032
+ */
18033
+ impliedSources: array(ExtensionSlotSourceSchema).optional()
18034
+ }), object({
18035
+ state: literal("unreachable"),
18036
+ addonId: string(),
18037
+ reason: string()
18038
+ })]);
18039
+ var ExtensionCandidatesSchema = object({ candidates: array(object({
18040
+ deviceId: number().int(),
18041
+ name: string(),
18042
+ type: string(),
18043
+ location: string().nullable(),
18044
+ /** The container a child belongs to — "Spotlight 2" alone does not say which. */
18045
+ parentName: string().nullable(),
18046
+ /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
18047
+ suggested: boolean(),
18048
+ /** Why it is suggested ("same location"), null when it is not. */
18049
+ reason: string().nullable()
18050
+ })) });
18051
+ /**
18052
+ * What a write did. `none` — applied in place. `host` / `parent` — the owner
18053
+ * reloaded the host (or, for an accessory child, its parent) because the
18054
+ * write changed what the host REGISTERS. A slot never adds a capability to its
18055
+ * host — that is a composition (D663) — so today's owners answer `none`.
18056
+ */
18057
+ var ExtensionSetResultSchema = object({
18058
+ applied: literal(true),
18059
+ reload: _enum([
18060
+ "none",
18061
+ "host",
18062
+ "parent"
18063
+ ])
18064
+ });
18065
+ var ExtensionSlotRefInputSchema = object({
18066
+ deviceId: number().int(),
18067
+ slotRef: string().min(1)
18068
+ });
18069
+ var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
18070
+ method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)), method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)), method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema), method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
18071
+ kind: "mutation",
18072
+ auth: "admin"
18073
+ });
17884
18074
  /**
17885
18075
  * Per-field ownership (D663). A composition may CLAIM fields of a native
17886
18076
  * cap's runtime-state slice on an existing device: `replace` overlays the
@@ -21250,17 +21440,25 @@ var NcScheduleSchema = object({
21250
21440
  */
21251
21441
  var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
21252
21442
  /**
21443
+ * How a summary window is SENT (D698): `rule` (absent) = ONE notification with
21444
+ * every section; `section` = one notification per section with something to
21445
+ * say (an unavailable section included), each titled by its section.
21446
+ */
21447
+ var NcStateSummarySplitSchema = _enum(["rule", "section"]);
21448
+ /**
21253
21449
  * `summary`: the rule sends NOTHING per event. When a window of `schedule`
21254
21450
  * CLOSES, it sends one notification with the current state of its kinds
21255
21451
  * (low batteries, consumables to replace, devices offline, updates
21256
- * available, ...), after its own filters. Nothing to report sends nothing.
21452
+ * available, ...), after its own filters — or, with `split: 'section'`
21453
+ * (D698), one notification per section. Nothing to report sends nothing.
21257
21454
  * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
21258
21455
  * midnight crossing); the send time is each window's END. Refused on the
21259
21456
  * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
21260
21457
  */
21261
21458
  var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
21262
21459
  mode: literal("summary"),
21263
- schedule: NcScheduleSchema
21460
+ schedule: NcScheduleSchema,
21461
+ split: NcStateSummarySplitSchema.optional()
21264
21462
  })]);
21265
21463
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
21266
21464
  var NcPlateMatcherSchema = object({
@@ -21911,10 +22109,12 @@ var NcMediaPolicySchema = object({
21911
22109
  /**
21912
22110
  * Cooldown GRANULARITY over the subject's class — how much a fired
21913
22111
  * notification suppresses.
21914
- * - `shared` (default, and the absent value) — one window for the whole
21915
- * rule/scope: a cat silences the next dog for `cooldownSec`.
21916
- * - `per-class` — an independent window per detected class, so cat→dog fires
21917
- * at once and cat→cat still waits.
22112
+ * - `per-class` (default, and the absent value since 2026-10-01 — D36) — an
22113
+ * independent window per detected class: cat→dog fires at once, cat→cat
22114
+ * still waits.
22115
+ * - `shared` — one window for the whole rule/scope: a cat silences the next
22116
+ * dog for `cooldownSec`. Only written when an operator picks it; it is no
22117
+ * longer what an unset rule means.
21918
22118
  *
21919
22119
  * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
21920
22120
  * must not be swallowed by a bark's window (the precedent this generalizes —
@@ -21929,8 +22129,11 @@ var NcThrottleSchema = object({
21929
22129
  /**
21930
22130
  * Class granularity of the cooldown key. Optional rather than defaulted:
21931
22131
  * a Zod default does NOT run on the addon→addon cap path, so a persisted
21932
- * rule authored before this field simply carries none — and the engine
21933
- * reads absent as `shared`, the pre-existing behaviour.
22132
+ * rule authored before this field simply carries none. The engine used to
22133
+ * read absent as `shared` (D36); since 2026-10-01 it reads absent as
22134
+ * `per-class` — the operator's default — and only an explicit `shared`
22135
+ * still means `shared`. See `cooldownKey` / `keysPerClass` in the rule
22136
+ * engine, and D36's dated note.
21934
22137
  */
21935
22138
  granularity: NcThrottleGranularitySchema.optional()
21936
22139
  });
@@ -22254,11 +22457,23 @@ var NcStateSummaryPreviewSectionSchema = object({
22254
22457
  reason: string().optional()
22255
22458
  });
22256
22459
  /**
22460
+ * One notification a previewed summary would send (D698): the whole summary on
22461
+ * a `rule` split (no `section`), one section's on a `section` split.
22462
+ */
22463
+ var NcStateSummaryPreviewRowSchema = object({
22464
+ section: string().optional(),
22465
+ /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
22466
+ title: string(),
22467
+ text: string(),
22468
+ count: number().int().nonnegative()
22469
+ });
22470
+ /**
22257
22471
  * The summary a rule would send now. `empty` = nothing to report and every
22258
22472
  * section read: such a summary is NOT sent. `dropped` counts the open states
22259
22473
  * the rule's own filters (or a camera mute) left out, by reason.
22260
22474
  * `excludedGroups` names the kind groups selected that have no state to report
22261
- * (backups & exports).
22475
+ * (backups & exports). `split` and `rows` (D698) say how it would be sent —
22476
+ * optional, so an older hub's answer still parses.
22262
22477
  */
22263
22478
  var NcStateSummaryPreviewSchema = object({
22264
22479
  empty: boolean(),
@@ -22266,7 +22481,9 @@ var NcStateSummaryPreviewSchema = object({
22266
22481
  text: string(),
22267
22482
  sections: array(NcStateSummaryPreviewSectionSchema),
22268
22483
  dropped: record(string(), number().int().nonnegative()),
22269
- excludedGroups: array(string())
22484
+ excludedGroups: array(string()),
22485
+ split: NcStateSummarySplitSchema.optional(),
22486
+ rows: array(NcStateSummaryPreviewRowSchema).optional()
22270
22487
  });
22271
22488
  var NcTestResultSchema = object({
22272
22489
  recordId: string(),
@@ -23280,12 +23497,26 @@ var CameraSensorRefSchema = object({
23280
23497
  function cameraSensorRefKey(ref) {
23281
23498
  return `${ref.addonId}/${ref.stableId}`;
23282
23499
  }
23283
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
23284
- var CameraSensorsModeSchema = _enum(["in-addition"]);
23285
- var CameraSensorsConfigSchema = object({
23286
- mode: CameraSensorsModeSchema,
23287
- sensors: array(CameraSensorRefSchema).min(1).max(8)
23288
- }).superRefine((value, ctx) => {
23500
+ /**
23501
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
23502
+ * `replace`: its motion is the camera's, and while one is READY the camera's
23503
+ * own motion no longer counts on its own. `confirm`: while one is READY the
23504
+ * camera's own motion counts only while a confirm sensor is active — with or
23505
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
23506
+ */
23507
+ var CameraSensorModeSchema = _enum([
23508
+ "in-addition",
23509
+ "confirm",
23510
+ "replace"
23511
+ ]);
23512
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
23513
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
23514
+ /**
23515
+ * A camera's attached sensors. A CS-1 row carried an envelope
23516
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
23517
+ * what the write stores (D672 rule 3), so it is never written again.
23518
+ */
23519
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
23289
23520
  const seen = /* @__PURE__ */ new Set();
23290
23521
  value.sensors.forEach((ref, index) => {
23291
23522
  const key = cameraSensorRefKey(ref);
@@ -23297,39 +23528,41 @@ var CameraSensorsConfigSchema = object({
23297
23528
  seen.add(key);
23298
23529
  });
23299
23530
  });
23300
- var RefFields = {
23531
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
23532
+ var AttachmentFields = {
23301
23533
  addonId: string(),
23302
- stableId: string()
23534
+ stableId: string(),
23535
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
23303
23536
  };
23304
23537
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
23305
23538
  object({
23306
- ...RefFields,
23539
+ ...AttachmentFields,
23307
23540
  state: literal("ready"),
23308
23541
  deviceId: number(),
23309
23542
  name: string(),
23310
23543
  caps: array(string()).readonly()
23311
23544
  }),
23312
23545
  object({
23313
- ...RefFields,
23546
+ ...AttachmentFields,
23314
23547
  state: literal("offline"),
23315
23548
  deviceId: number(),
23316
23549
  name: string(),
23317
23550
  reason: _enum(["offline", "disabled"])
23318
23551
  }),
23319
23552
  object({
23320
- ...RefFields,
23553
+ ...AttachmentFields,
23321
23554
  state: literal("missing"),
23322
23555
  reason: string()
23323
23556
  }),
23324
23557
  object({
23325
- ...RefFields,
23558
+ ...AttachmentFields,
23326
23559
  state: literal("unsupported"),
23327
23560
  deviceId: number(),
23328
23561
  name: string(),
23329
23562
  reason: string()
23330
23563
  }),
23331
23564
  object({
23332
- ...RefFields,
23565
+ ...AttachmentFields,
23333
23566
  state: literal("unreadable"),
23334
23567
  reason: string()
23335
23568
  })
@@ -23342,17 +23575,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
23342
23575
  * say.
23343
23576
  */
23344
23577
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
23578
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
23579
+ var CameraOwnMotionSchema = _enum([
23580
+ "always",
23581
+ "when-confirmed",
23582
+ "never"
23583
+ ]);
23584
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
23585
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
23586
+ /**
23587
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
23588
+ * never the effective value. The camera's `motion` slice is the effective one,
23589
+ * so a view of the camera's own row reads this.
23590
+ * - `atMs`: when the bridge received the camera's last own edge.
23591
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
23592
+ * AND it is younger than the window the bridge counts the camera's own motion
23593
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
23594
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
23595
+ * within the window; a status read is a snapshot, so a live view re-asks.
23596
+ * Absent = this orchestrator has not received the camera's own motion since it
23597
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
23598
+ */
23599
+ var CameraRawLevelSchema = object({
23600
+ detected: boolean(),
23601
+ atMs: number()
23602
+ });
23345
23603
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
23346
- object({ state: literal("not-configured") }),
23604
+ object({
23605
+ state: literal("not-configured"),
23606
+ cameraLevel: CameraRawLevelSchema.optional()
23607
+ }),
23347
23608
  object({
23348
23609
  state: literal("unreadable"),
23349
- reason: string()
23610
+ reason: string(),
23611
+ cameraLevel: CameraRawLevelSchema.optional()
23350
23612
  }),
23351
23613
  object({
23352
23614
  state: literal("configured"),
23353
- mode: CameraSensorsModeSchema,
23615
+ cameraLevel: CameraRawLevelSchema.optional(),
23354
23616
  sensors: array(CameraSensorAttachmentSchema).readonly(),
23355
- notForwarded: CameraSensorsNotForwardedSchema.optional()
23617
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
23618
+ cameraMotion: CameraOwnMotionSchema.optional(),
23619
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
23620
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
23356
23621
  })
23357
23622
  ]);
23358
23623
  Object.freeze({
@@ -26786,8 +27051,10 @@ var RunnerCameraConfigSchema = object({
26786
27051
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
26787
27052
  * analyzer providers) running on decoded frames in this runner.
26788
27053
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
26789
- * analytics). The provider emits ProviderMotion
26790
- * events; the orchestrator forwards to `reportMotion`.
27054
+ * analytics). The provider emits its raw push as `ProviderMotion`
27055
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
27056
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
27057
+ * `reportMotion`.
26791
27058
  */
26792
27059
  motionSources: MotionSourcesSchema.default(["analyzer"]),
26793
27060
  /**
@@ -26964,7 +27231,15 @@ var RunnerLocalLoadSchema = object({
26964
27231
  hasGpu: boolean(),
26965
27232
  inferenceBackend: string().optional(),
26966
27233
  cpuCores: number().optional()
26967
- })
27234
+ }),
27235
+ /**
27236
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
27237
+ * runner never writes it. ABSENT = a runner older than D696, which still
27238
+ * writes the slice from its phase machine; the orchestrator does not write
27239
+ * while any runner node is absent-or-unanswered, so one slice never has two
27240
+ * writers.
27241
+ */
27242
+ motionSliceWriter: literal("orchestrator").optional()
26968
27243
  });
26969
27244
  /**
26970
27245
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -30476,138 +30751,6 @@ var videoclipsCapability = {
30476
30751
  })
30477
30752
  }
30478
30753
  };
30479
- object({
30480
- /** Ms epoch of the last press. null = never observed since this provider started. */
30481
- lastPressedAt: number().nullable(),
30482
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
30483
- pressCountSinceStart: number()
30484
- });
30485
- object({
30486
- deviceId: number(),
30487
- timestamp: number()
30488
- });
30489
- /**
30490
- * The `doorbell-source` extension slot's editable value (D648): the camera's
30491
- * virtual-doorbell trigger devices, exactly what the wrapper's
30492
- * `doorbellSources` persists (as `{ deviceId }` records).
30493
- */
30494
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
30495
- DeviceType.Camera;
30496
- DeviceType.Button, DeviceType.Camera;
30497
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
30498
- DeviceType.Camera, DeviceType.Camera;
30499
- /** A slot's editable value, discriminated on `slot`. */
30500
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
30501
- DeviceType.Camera;
30502
- var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
30503
- state: literal("unreachable"),
30504
- reason: string()
30505
- })]);
30506
- /** One provider of slots — the D554 source-listing row. */
30507
- var ExtensionSourceSchema = object({
30508
- /** The routing prefix of every `slotRef` this provider serves. */
30509
- source: string(),
30510
- label: string(),
30511
- /** Stamped by the dispatcher from the registry (D557). */
30512
- addonId: string().optional(),
30513
- availability: ExtensionSourceAvailabilitySchema
30514
- });
30515
- /**
30516
- * How one picked source stands. `missing` — no such device (deleted, or an id
30517
- * that never existed); `foreign` — a device the slot's owner cannot read from
30518
- * (a source owned by another addon); `unavailable` — it exists but is not a
30519
- * valid source now (a container, which is never a pick). A source in any of
30520
- * these states is SHOWN, never silently dropped or re-pointed.
30521
- */
30522
- var ExtensionSourceHealthSchema = _enum([
30523
- "ok",
30524
- "missing",
30525
- "foreign",
30526
- "unavailable"
30527
- ]);
30528
- var ExtensionSlotSourceSchema = object({
30529
- deviceId: number().int(),
30530
- /** The device's name, or null when it could not be resolved (`missing`). */
30531
- name: string().nullable(),
30532
- health: ExtensionSourceHealthSchema,
30533
- detail: string().optional()
30534
- });
30535
- /**
30536
- * A DIFFERENT authority that gates whether the slot's sources do anything —
30537
- * reported read-only, named, never written through this contract (D62). The
30538
- * virtual doorbell's sources ring only while the wrapper is bound to the
30539
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
30540
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
30541
- */
30542
- var ExtensionSlotActivationSchema = object({
30543
- state: _enum([
30544
- "active",
30545
- "inactive",
30546
- "unknown"
30547
- ]),
30548
- authority: string(),
30549
- detail: string().optional()
30550
- });
30551
- var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
30552
- state: literal("ok"),
30553
- /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
30554
- slotRef: string(),
30555
- slot: ExtensionSlotIdSchema,
30556
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
30557
- authority: string(),
30558
- activation: ExtensionSlotActivationSchema.nullable(),
30559
- value: ExtensionSlotValueSchema,
30560
- sources: array(ExtensionSlotSourceSchema),
30561
- /**
30562
- * Devices the owner links to the host ON ITS OWN, by structure, and that
30563
- * are therefore not in `value` (D655): a camera's device-tree children, and
30564
- * the child a picked container stands for (D356 — a container is never a
30565
- * pick, its children are). Read-only; the editor shows them and may offer
30566
- * per-device options on them (the linked-devices tracked-event opt-in).
30567
- * Absent when the owner links nothing beyond the picks, and from an owner
30568
- * older than D655.
30569
- */
30570
- impliedSources: array(ExtensionSlotSourceSchema).optional()
30571
- }), object({
30572
- state: literal("unreachable"),
30573
- addonId: string(),
30574
- reason: string()
30575
- })]);
30576
- var ExtensionCandidatesSchema = object({ candidates: array(object({
30577
- deviceId: number().int(),
30578
- name: string(),
30579
- type: string(),
30580
- location: string().nullable(),
30581
- /** The container a child belongs to — "Spotlight 2" alone does not say which. */
30582
- parentName: string().nullable(),
30583
- /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
30584
- suggested: boolean(),
30585
- /** Why it is suggested ("same location"), null when it is not. */
30586
- reason: string().nullable()
30587
- })) });
30588
- /**
30589
- * What a write did. `none` — applied in place. `host` / `parent` — the owner
30590
- * reloaded the host (or, for an accessory child, its parent) because the
30591
- * write changed what the host REGISTERS. A slot never adds a capability to its
30592
- * host — that is a composition (D663) — so today's owners answer `none`.
30593
- */
30594
- var ExtensionSetResultSchema = object({
30595
- applied: literal(true),
30596
- reload: _enum([
30597
- "none",
30598
- "host",
30599
- "parent"
30600
- ])
30601
- });
30602
- var ExtensionSlotRefInputSchema = object({
30603
- deviceId: number().int(),
30604
- slotRef: string().min(1)
30605
- });
30606
- var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
30607
- method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)), method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)), method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema), method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
30608
- kind: "mutation",
30609
- auth: "admin"
30610
- });
30611
30754
  /**
30612
30755
  * Optional client-side hints sent at session creation to help the provider
30613
30756
  * pick the best native source. All fields optional — a viewer that knows
@@ -32604,6 +32747,17 @@ object({
32604
32747
  deviceId: number(),
32605
32748
  status: DeviceStatusSchema
32606
32749
  });
32750
+ object({
32751
+ /** Ms epoch of the last press. null = never observed since this provider started. */
32752
+ lastPressedAt: number().nullable(),
32753
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
32754
+ pressCountSinceStart: number()
32755
+ });
32756
+ object({
32757
+ deviceId: number(),
32758
+ timestamp: number()
32759
+ });
32760
+ DeviceType.Button, DeviceType.Camera;
32607
32761
  /**
32608
32762
  * Enum-state sensor — a string value picked from a finite option set.
32609
32763
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -34243,39 +34397,14 @@ authKey: string().optional() }), object({
34243
34397
  /** Human-readable error when `ok: false`. */
34244
34398
  error: string().optional()
34245
34399
  }), { kind: "mutation" });
34246
- /**
34247
- * Hardware / firmware motion sensor cap — binary detected state plus
34248
- * a timestamp of the last observation. Distinct from
34249
- * `motion-detection.cap.ts` which owns the LOCAL ML motion pipeline;
34250
- * `motion` is the lightweight readout from on-camera motion (Reolink
34251
- * `GetMdState`, Baichuan push `type: motion`, ONVIF analytics).
34252
- *
34253
- * Native-motion providers also fan out to `detection.camera-native`
34254
- * with `source: 'onboard'` so cross-cutting system services
34255
- * (alert-center, advanced-notifier) can subscribe once and receive
34256
- * motion from every camera.
34257
- */
34258
- /**
34259
- * How long after a camera's last rising edge `pipeline-analytics` closes the
34260
- * motion EPISODE row it kept open for it (D475) — "il delay della cap", in
34261
- * the operator's words. One authority for the whole system: every provider
34262
- * that seeds `autoClearAfterMs` on a `Camera` device writes this SAME
34263
- * constant while `detected: true` (never its own number — Hikvision used to
34264
- * seed its internal 3 s inactivity timer here, which is a different clock for
34265
- * a different purpose), and `MotionEpisodeTracker` imports it directly rather
34266
- * than reading the live cap value on the hot path. A `Sensor` device (HA
34267
- * binary sensor, Homematic) gets a genuine push both ways and has no episode
34268
- * to close on a timer, so it writes `null`, never this constant.
34269
- */
34270
- var MOTION_CLOSE_AFTER_MS = 15e3;
34271
34400
  object({
34272
34401
  detected: boolean(),
34273
34402
  /** Ms epoch of the last detected-true observation. Null if never detected. */
34274
34403
  lastDetectedAt: number().nullable(),
34275
34404
  /**
34276
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
34277
- * `null` while false and on every `Sensor` device (D475) — see that
34278
- * constant's doc for the one-authority rule.
34405
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
34406
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
34407
+ * device (D475) — see that constant's doc for the one-authority rule.
34279
34408
  *
34280
34409
  * ## Reading this field still arms nothing
34281
34410
  *
@@ -34293,38 +34422,41 @@ object({
34293
34422
  * `addon-export-alexa/src/motion-clear-hold.ts` and
34294
34423
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
34295
34424
  *
34296
- * ## Who writes it
34297
- *
34298
- * - **Cameras** — the runner's phase machine, `active → watching` on
34299
- * `cooldown_expired`, which then writes this slice with
34300
- * `detected: false` (`handlePhaseChanged` in
34301
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
34302
- * matters most for the sources that only ever push a rising one:
34303
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
34304
- * a false.
34305
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
34306
- * provider pushes the false itself, from the upstream system's own
34307
- * state change. No phase machine is involved.
34308
- *
34309
- * ### The phase machine is CANONICAL, not sole — and that is a defect
34310
- *
34311
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
34312
- * phase machine is the sole writer for a camera. It is not.
34313
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
34314
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
34315
- * Hikvision's comment says why: it read THIS docblock, agreed the
34316
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
34317
- * two authorities can disagree about one slice, which this repo
34318
- * forbids, and the doc said otherwise — which is worse than saying
34319
- * nothing, because it reads as verification.
34320
- *
34321
- * This predates D475 and is not fixed there: the fix touches every
34322
- * camera provider. Recorded in D475's Consequences. Do not restore the
34323
- * "sole writer" wording without also removing the other writers.
34425
+ * ## Who writes it (D696)
34426
+ *
34427
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
34428
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
34429
+ * `camera-sensors/motion-slice.ts`), alongside the effective
34430
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
34431
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
34432
+ * device activity, as the camera's `motionSources` counts them. A source
34433
+ * that never sends a clear falls one window after its last `true`.
34434
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
34435
+ * writes this slice (not even a boot seed), and neither does the runner's
34436
+ * phase machine: its phase is "a session is attached" for every on-motion
34437
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
34438
+ * A provider's `isDetected` answers from the hub's slice.
34439
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
34440
+ * imports) — the provider pushes both edges itself, from the upstream
34441
+ * system's own state change. That is the sensor's own fact.
34442
+ *
34443
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
34444
+ * `motion` slice; each sensor writer is a named exemption.
34445
+ *
34446
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
34447
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
34448
+ * it, every provider seeded it at boot, and every session left it
34449
+ * `detected: true` after its detach.
34324
34450
  */
34325
34451
  autoClearAfterMs: number().nullable()
34326
34452
  });
34327
- object({
34453
+ /**
34454
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
34455
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
34456
+ * — both the cap event surface and the bus payload type alias to this
34457
+ * schema.
34458
+ */
34459
+ var MotionOnMotionChangedDataSchema = object({
34328
34460
  deviceId: number(),
34329
34461
  detected: boolean(),
34330
34462
  timestamp: number(),
@@ -42450,6 +42582,18 @@ Object.freeze({
42450
42582
  addonId: null,
42451
42583
  access: "view"
42452
42584
  },
42585
+ "databaseSnapshot.listDatabases": {
42586
+ capName: "database-snapshot",
42587
+ capScope: "system",
42588
+ addonId: null,
42589
+ access: "view"
42590
+ },
42591
+ "databaseSnapshot.writeSnapshot": {
42592
+ capName: "database-snapshot",
42593
+ capScope: "system",
42594
+ addonId: null,
42595
+ access: "create"
42596
+ },
42453
42597
  "dataStoreProvider.aggregate": {
42454
42598
  capName: "data-store-provider",
42455
42599
  capScope: "system",
@@ -51028,6 +51172,18 @@ var STATE_SUMMARY_VARS = [
51028
51172
  systemEventKinds: [...SUMMARISABLE],
51029
51173
  systemEventDeliveries: ["summary"]
51030
51174
  },
51175
+ {
51176
+ name: "summarySection",
51177
+ label: "Summary: this section",
51178
+ description: "The section a per-section summary row is about (D698); on a summary sent as one message, every section it reports, comma-separated.",
51179
+ example: "Batterie scariche",
51180
+ group: "system",
51181
+ families: ["rule"],
51182
+ fields: [...RULE_TEXT],
51183
+ kinds: ["system"],
51184
+ systemEventKinds: [...SUMMARISABLE],
51185
+ systemEventDeliveries: ["summary"]
51186
+ },
51031
51187
  ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
51032
51188
  name: v.name,
51033
51189
  label: v.label,
@@ -51366,6 +51522,10 @@ TimelapseRuleInputSchema.extend({
51366
51522
  createdAt: number(),
51367
51523
  updatedAt: number()
51368
51524
  });
51525
+ function cameraOwnsMotionViaSensor(motionSources) {
51526
+ return motionSources.some((s) => s !== "analyzer");
51527
+ }
51528
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
51369
51529
  /** True when `model` ships a build for `format`. */
51370
51530
  function hasBuild(model, format) {
51371
51531
  return Boolean(model.formats[format]);
@@ -52581,4 +52741,4 @@ function enumerateInferenceDevices(hw) {
52581
52741
  return out;
52582
52742
  }
52583
52743
  //#endregion
52584
- export { declareLogChannel as $, stripRetiredBandPreBufferSec as $t, OpsLogEntrySchema as A, sleep as An, overlayClusterStepSettings as At, VIDEOCLIPS_MAX_OFFER_BYTES as B, record as Bn, recordingExportCapability as Bt, ExportRecordSchema as C, isEvent as Cn, maskUrlCredentials as Ct, MOTION_CLOSE_AFTER_MS as D, parseJsonUnknown as Dn, migrateLegacyDeviceSignalConfig as Dt, HF_BASE_URL as E, nodePin as En, memoizePinnedFfmpeg as Et, RECORDING_SOURCE_CAMSTACK as F, lazy as Fn, pipelineExecutorCapability as Ft, audioAnalyzerCapability as G, resolveLocationMode as Gt, YAMNET_TO_MACRO as H, union as Hn, resolveClusterStepModelId as Ht, RECORDING_SOURCE_CAMSTACK_ADDON as I, literal as In, pipelineRunnerCapability as It, cameraStreamsCapability as J, runtimeDevices as Jt, audioKindId as K, resolvePoolMemoryPolicy as Kt, RecordingConfigSchema as L, number as Ln, rankStreams as Lt, RATE_CONTROL_RELAXED as M, array as Mn, pickClusterStepSettings as Mt, RATE_CONTROL_TIGHT as N, boolean as Nn, pickDetailCropConvention as Nt, NativeLeaseAdmissionSchema as O, parseProfileBrokerId as On, modeMayWrite as Ot, RECORDING_EXPORT_MAX_READ_BYTES as P, discriminatedUnion as Pn, pickNativeLeaseOverride as Pt, createLogChannelsProvider as Q, streamBrokerCapability as Qt, RecordingSignalStatusSchema as R, object as Rn, recordingArchiveCapability as Rt, EncodeProfileSchema as S, hydrateSchema as Sn, mapAudioLabelToMacro as St, G711_SCALE_CORRECTION_DB as T, makeSourceBrokerId as Tn, mayWriteToLocation as Tt, addonWidgetsSourceCapability as U, EventCategory as Un, resolveCreateExportSubject as Ut, VISIT_MERGE_GAP_MS as V, string as Vn, recordingSignalCapability as Vt, audioAnalysisCapability as W, resolveEgressDecodeHwAccel as Wt, clipExportFailureOf as X, storageEvictableCapability as Xt, clipExportFailure as Y, stepModelRunsOnFormat as Yt, createHwAccelCache as Z, storageOccupancyCapability as Zt, DEFAULT_EVENTS_BAND_BUFFER_SEC as _, audioChunkBytesPerSample as _n, hfModelUrl as _t, BackgroundRunRegistry as a, Fmp4BoxSplitter as an, egressTranscodeSharingKey as at, EVENT_OWNER_TYPES as b, defineCustomActions as bn, loadContributionCapability as bt, CLIP_STILL_REASON_CODE_HEADER as c, invocationFromEncodeProfile as cn, evaluateZoneRules as ct, CLUSTER_MODEL_SCOPED_STEPS as d, DeviceType as dn, exportSubjectOf as dt, supportedRuntimes as en, defaultDeviceFor as et, COCO_80_LABELS as f, errMsg as fn, failureContributionCapability as ft, DEFAULT_DETAIL_CROP_CONVENTION as g, CamProfileSchema as gn, gifFromMp4Args as gt, DEFAULT_CLUSTER_STEP_SETTINGS as h, CAM_PROFILE_ORDER as hn, gifDeriveSpeed as ht, AUDIO_MACRO_LABELS as i, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as in, detectionPipelineCapability as it, PoolMemoryWatchdog as j, _enum as jn, parseProcStatus as jt, NativeLeaseSettingsSchema as k, selectAssignedProfileSlots as kn, motionDetectionCapability as kt, CLIP_STILL_REASON_HEADER as l, isSoftwareDecode as ln, evictionPolicyForMode as lt, DEFAULT_AUDIO_ANALYZER_CONFIG as m, BaseAddon as mn, gbToBytes as mt, APPLE_SA_TO_MACRO as n, webrtcSessionCapability as nn, deriveDetailCropRect as nt, BatteryStatusSchema as o, buildFfmpegArgs as on, egressTransportFromRequest as ot, COCO_TO_MACRO as p, BOOT_RECOVERY_BACKOFF_MS as pn, ffmpegRate as pt, batteryCapability as q, resolveRecordingProfiles as qt, AUDIO_BACKEND_CHOICES as r, AUDIO_PRESETS as rn, deriveRecordingMode as rt, CLIP_BROKER_PACED_RATES as s, evaluateSensorEdge as sn, enumerateInferenceDevices as st, APPEARANCE_BLOCKS as t, videoclipsCapability as tn, deriveBatteryPresence as tt, CLIP_STREAM_TRANSPORT_HEADER as u, DeviceFeature as un, evictionPolicyOfLocation as ut, DEFAULT_NATIVE_LEASE_SETTINGS as v, createEvent as vn, inferModelProvider as vt, FailureCounters as w, makeProfileBrokerId as wn, mayReadLocation as wt, EVENT_PAD_MS as x, expandAudioChunkToF32le as xn, logChannelsCapability as xt, DEVICE_BACKEND_TO_FORMAT as y, customAction as yn, isFirstLevelMacroClass as yt, RingBuffer as z, preprocess as zn, recordingCapability as zt };
52744
+ export { declareLogChannel as $, stripRetiredBandPreBufferSec as $t, PoolMemoryWatchdog as A, sleep as An, overlayClusterStepSettings as At, VISIT_MERGE_GAP_MS as B, record as Bn, recordingExportCapability as Bt, ExportRecordSchema as C, isEvent as Cn, maskUrlCredentials as Ct, NativeLeaseAdmissionSchema as D, parseJsonUnknown as Dn, migrateLegacyDeviceSignalConfig as Dt, HF_BASE_URL as E, nodePin as En, memoizePinnedFfmpeg as Et, RECORDING_SOURCE_CAMSTACK_ADDON as F, lazy as Fn, pipelineExecutorCapability as Ft, audioKindId as G, resolveLocationMode as Gt, addonWidgetsSourceCapability as H, union as Hn, resolveClusterStepModelId as Ht, RecordingConfigSchema as I, literal as In, pipelineRunnerCapability as It, cameraStreamsCapability as J, runtimeDevices as Jt, batteryCapability as K, resolvePoolMemoryPolicy as Kt, RecordingSignalStatusSchema as L, number as Ln, rankStreams as Lt, RATE_CONTROL_TIGHT as M, array as Mn, pickClusterStepSettings as Mt, RECORDING_EXPORT_MAX_READ_BYTES as N, boolean as Nn, pickDetailCropConvention as Nt, NativeLeaseSettingsSchema as O, parseProfileBrokerId as On, modeMayWrite as Ot, RECORDING_SOURCE_CAMSTACK as P, discriminatedUnion as Pn, pickNativeLeaseOverride as Pt, createLogChannelsProvider as Q, streamBrokerCapability as Qt, RingBuffer as R, object as Rn, recordingArchiveCapability as Rt, EncodeProfileSchema as S, hydrateSchema as Sn, mapAudioLabelToMacro as St, G711_SCALE_CORRECTION_DB as T, makeSourceBrokerId as Tn, mayWriteToLocation as Tt, audioAnalysisCapability as U, EventCategory as Un, resolveCreateExportSubject as Ut, YAMNET_TO_MACRO as V, string as Vn, recordingSignalCapability as Vt, audioAnalyzerCapability as W, resolveEgressDecodeHwAccel as Wt, clipExportFailureOf as X, storageEvictableCapability as Xt, clipExportFailure as Y, stepModelRunsOnFormat as Yt, createHwAccelCache as Z, storageOccupancyCapability as Zt, DEFAULT_EVENTS_BAND_BUFFER_SEC as _, audioChunkBytesPerSample as _n, hfModelUrl as _t, BackgroundRunRegistry as a, Fmp4BoxSplitter as an, egressTranscodeSharingKey as at, EVENT_OWNER_TYPES as b, defineCustomActions as bn, loadContributionCapability as bt, CLIP_STILL_REASON_CODE_HEADER as c, invocationFromEncodeProfile as cn, evaluateZoneRules as ct, CLUSTER_MODEL_SCOPED_STEPS as d, DeviceType as dn, exportSubjectOf as dt, supportedRuntimes as en, defaultDeviceFor as et, COCO_80_LABELS as f, errMsg as fn, failureContributionCapability as ft, DEFAULT_DETAIL_CROP_CONVENTION as g, CamProfileSchema as gn, gifFromMp4Args as gt, DEFAULT_CLUSTER_STEP_SETTINGS as h, CAM_PROFILE_ORDER as hn, gifDeriveSpeed as ht, AUDIO_MACRO_LABELS as i, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as in, detectionPipelineCapability as it, RATE_CONTROL_RELAXED as j, _enum as jn, parseProcStatus as jt, OpsLogEntrySchema as k, selectAssignedProfileSlots as kn, motionDetectionCapability as kt, CLIP_STILL_REASON_HEADER as l, isSoftwareDecode as ln, evictionPolicyForMode as lt, DEFAULT_AUDIO_ANALYZER_CONFIG as m, BaseAddon as mn, gbToBytes as mt, APPLE_SA_TO_MACRO as n, webrtcSessionCapability as nn, deriveDetailCropRect as nt, BatteryStatusSchema as o, buildFfmpegArgs as on, egressTransportFromRequest as ot, COCO_TO_MACRO as p, BOOT_RECOVERY_BACKOFF_MS as pn, ffmpegRate as pt, cameraOwnsMotionViaSensor as q, resolveRecordingProfiles as qt, AUDIO_BACKEND_CHOICES as r, AUDIO_PRESETS as rn, deriveRecordingMode as rt, CLIP_BROKER_PACED_RATES as s, evaluateSensorEdge as sn, enumerateInferenceDevices as st, APPEARANCE_BLOCKS as t, videoclipsCapability as tn, deriveBatteryPresence as tt, CLIP_STREAM_TRANSPORT_HEADER as u, DeviceFeature as un, evictionPolicyOfLocation as ut, DEFAULT_NATIVE_LEASE_SETTINGS as v, createEvent as vn, inferModelProvider as vt, FailureCounters as w, makeProfileBrokerId as wn, mayReadLocation as wt, EVENT_PAD_MS as x, expandAudioChunkToF32le as xn, logChannelsCapability as xt, DEVICE_BACKEND_TO_FORMAT as y, customAction as yn, isFirstLevelMacroClass as yt, VIDEOCLIPS_MAX_OFFER_BYTES as z, preprocess as zn, recordingCapability as zt };