@camstack/addon-pipeline 1.2.314 → 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-Es1-dvFU.mjs → default-detection-model-BTlf5MpT.mjs} +1 -1
  4. package/dist/{default-detection-model-BvxtQEcb.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-E1Z_K22_.mjs → dist-C8W_MWSz.mjs} +156 -114
  8. package/dist/{dist-Jvi2SOxI.js → dist-sCjZdzKn.js} +161 -119
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-DHe-9_tm.mjs → node-CnLKPz1A.mjs} +1 -1
  12. package/dist/{node-EjcZQh8D.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-BUtnSPYK.js → process-memory-CMnhjGyE.js} +1 -1
  16. package/dist/{process-memory-IQcwHauP.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-CR58wpzs.mjs → segment-demux-js-CCSsDyMw.mjs} +1 -1
  20. package/dist/{segment-demux-js-Il0EkSOb.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-CBvGGDX_.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BOKNSh-e.mjs} +2 -2
  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-CjvrBwfh.mjs → hostInit-BkvtmJ2D.mjs} +2 -2
  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 +1 -1
  31. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D0BR9vFO.mjs +0 -26
@@ -1,5 +1,5 @@
1
1
  let node_crypto = require("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
  */
@@ -17948,28 +17956,10 @@ method(object({
17948
17956
  deviceIds: array(number()).readonly(),
17949
17957
  caps: array(string()).readonly().optional()
17950
17958
  }), record(string(), record(string(), unknown().nullable())));
17951
- object({
17952
- /** Ms epoch of the last press. null = never observed since this provider started. */
17953
- lastPressedAt: number().nullable(),
17954
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
17955
- pressCountSinceStart: number()
17956
- });
17957
- object({
17958
- deviceId: number(),
17959
- timestamp: number()
17960
- });
17961
- /**
17962
- * The `doorbell-source` extension slot's editable value (D648): the camera's
17963
- * virtual-doorbell trigger devices, exactly what the wrapper's
17964
- * `doorbellSources` persists (as `{ deviceId }` records).
17965
- */
17966
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
17959
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
17967
17960
  DeviceType.Camera;
17968
- DeviceType.Button, DeviceType.Camera;
17969
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
17970
- DeviceType.Camera, DeviceType.Camera;
17971
17961
  /** A slot's editable value, discriminated on `slot`. */
17972
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
17962
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
17973
17963
  DeviceType.Camera;
17974
17964
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
17975
17965
  state: literal("unreachable"),
@@ -18006,10 +17996,11 @@ var ExtensionSlotSourceSchema = object({
18006
17996
  });
18007
17997
  /**
18008
17998
  * A DIFFERENT authority that gates whether the slot's sources do anything —
18009
- * reported read-only, named, never written through this contract (D62). The
18010
- * virtual doorbell's sources ring only while the wrapper is bound to the
18011
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
18012
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
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).
18013
18004
  */
18014
18005
  var ExtensionSlotActivationSchema = object({
18015
18006
  state: _enum([
@@ -18025,7 +18016,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
18025
18016
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
18026
18017
  slotRef: string(),
18027
18018
  slot: ExtensionSlotIdSchema,
18028
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
18019
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
18029
18020
  authority: string(),
18030
18021
  activation: ExtensionSlotActivationSchema.nullable(),
18031
18022
  value: ExtensionSlotValueSchema,
@@ -23506,12 +23497,26 @@ var CameraSensorRefSchema = object({
23506
23497
  function cameraSensorRefKey(ref) {
23507
23498
  return `${ref.addonId}/${ref.stableId}`;
23508
23499
  }
23509
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
23510
- var CameraSensorsModeSchema = _enum(["in-addition"]);
23511
- var CameraSensorsConfigSchema = object({
23512
- mode: CameraSensorsModeSchema,
23513
- sensors: array(CameraSensorRefSchema).min(1).max(8)
23514
- }).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) => {
23515
23520
  const seen = /* @__PURE__ */ new Set();
23516
23521
  value.sensors.forEach((ref, index) => {
23517
23522
  const key = cameraSensorRefKey(ref);
@@ -23523,39 +23528,41 @@ var CameraSensorsConfigSchema = object({
23523
23528
  seen.add(key);
23524
23529
  });
23525
23530
  });
23526
- var RefFields = {
23531
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
23532
+ var AttachmentFields = {
23527
23533
  addonId: string(),
23528
- stableId: string()
23534
+ stableId: string(),
23535
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
23529
23536
  };
23530
23537
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
23531
23538
  object({
23532
- ...RefFields,
23539
+ ...AttachmentFields,
23533
23540
  state: literal("ready"),
23534
23541
  deviceId: number(),
23535
23542
  name: string(),
23536
23543
  caps: array(string()).readonly()
23537
23544
  }),
23538
23545
  object({
23539
- ...RefFields,
23546
+ ...AttachmentFields,
23540
23547
  state: literal("offline"),
23541
23548
  deviceId: number(),
23542
23549
  name: string(),
23543
23550
  reason: _enum(["offline", "disabled"])
23544
23551
  }),
23545
23552
  object({
23546
- ...RefFields,
23553
+ ...AttachmentFields,
23547
23554
  state: literal("missing"),
23548
23555
  reason: string()
23549
23556
  }),
23550
23557
  object({
23551
- ...RefFields,
23558
+ ...AttachmentFields,
23552
23559
  state: literal("unsupported"),
23553
23560
  deviceId: number(),
23554
23561
  name: string(),
23555
23562
  reason: string()
23556
23563
  }),
23557
23564
  object({
23558
- ...RefFields,
23565
+ ...AttachmentFields,
23559
23566
  state: literal("unreadable"),
23560
23567
  reason: string()
23561
23568
  })
@@ -23568,17 +23575,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
23568
23575
  * say.
23569
23576
  */
23570
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
+ });
23571
23603
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
23572
- object({ state: literal("not-configured") }),
23604
+ object({
23605
+ state: literal("not-configured"),
23606
+ cameraLevel: CameraRawLevelSchema.optional()
23607
+ }),
23573
23608
  object({
23574
23609
  state: literal("unreadable"),
23575
- reason: string()
23610
+ reason: string(),
23611
+ cameraLevel: CameraRawLevelSchema.optional()
23576
23612
  }),
23577
23613
  object({
23578
23614
  state: literal("configured"),
23579
- mode: CameraSensorsModeSchema,
23615
+ cameraLevel: CameraRawLevelSchema.optional(),
23580
23616
  sensors: array(CameraSensorAttachmentSchema).readonly(),
23581
- 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()
23582
23621
  })
23583
23622
  ]);
23584
23623
  Object.freeze({
@@ -27012,8 +27051,10 @@ var RunnerCameraConfigSchema = object({
27012
27051
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
27013
27052
  * analyzer providers) running on decoded frames in this runner.
27014
27053
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
27015
- * analytics). The provider emits ProviderMotion
27016
- * 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`.
27017
27058
  */
27018
27059
  motionSources: MotionSourcesSchema.default(["analyzer"]),
27019
27060
  /**
@@ -27190,7 +27231,15 @@ var RunnerLocalLoadSchema = object({
27190
27231
  hasGpu: boolean(),
27191
27232
  inferenceBackend: string().optional(),
27192
27233
  cpuCores: number().optional()
27193
- })
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()
27194
27243
  });
27195
27244
  /**
27196
27245
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -32698,6 +32747,17 @@ object({
32698
32747
  deviceId: number(),
32699
32748
  status: DeviceStatusSchema
32700
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;
32701
32761
  /**
32702
32762
  * Enum-state sensor — a string value picked from a finite option set.
32703
32763
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -34337,39 +34397,14 @@ authKey: string().optional() }), object({
34337
34397
  /** Human-readable error when `ok: false`. */
34338
34398
  error: string().optional()
34339
34399
  }), { kind: "mutation" });
34340
- /**
34341
- * Hardware / firmware motion sensor cap — binary detected state plus
34342
- * a timestamp of the last observation. Distinct from
34343
- * `motion-detection.cap.ts` which owns the LOCAL ML motion pipeline;
34344
- * `motion` is the lightweight readout from on-camera motion (Reolink
34345
- * `GetMdState`, Baichuan push `type: motion`, ONVIF analytics).
34346
- *
34347
- * Native-motion providers also fan out to `detection.camera-native`
34348
- * with `source: 'onboard'` so cross-cutting system services
34349
- * (alert-center, advanced-notifier) can subscribe once and receive
34350
- * motion from every camera.
34351
- */
34352
- /**
34353
- * How long after a camera's last rising edge `pipeline-analytics` closes the
34354
- * motion EPISODE row it kept open for it (D475) — "il delay della cap", in
34355
- * the operator's words. One authority for the whole system: every provider
34356
- * that seeds `autoClearAfterMs` on a `Camera` device writes this SAME
34357
- * constant while `detected: true` (never its own number — Hikvision used to
34358
- * seed its internal 3 s inactivity timer here, which is a different clock for
34359
- * a different purpose), and `MotionEpisodeTracker` imports it directly rather
34360
- * than reading the live cap value on the hot path. A `Sensor` device (HA
34361
- * binary sensor, Homematic) gets a genuine push both ways and has no episode
34362
- * to close on a timer, so it writes `null`, never this constant.
34363
- */
34364
- var MOTION_CLOSE_AFTER_MS = 15e3;
34365
34400
  object({
34366
34401
  detected: boolean(),
34367
34402
  /** Ms epoch of the last detected-true observation. Null if never detected. */
34368
34403
  lastDetectedAt: number().nullable(),
34369
34404
  /**
34370
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
34371
- * `null` while false and on every `Sensor` device (D475) — see that
34372
- * 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.
34373
34408
  *
34374
34409
  * ## Reading this field still arms nothing
34375
34410
  *
@@ -34387,38 +34422,41 @@ object({
34387
34422
  * `addon-export-alexa/src/motion-clear-hold.ts` and
34388
34423
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
34389
34424
  *
34390
- * ## Who writes it
34391
- *
34392
- * - **Cameras** — the runner's phase machine, `active → watching` on
34393
- * `cooldown_expired`, which then writes this slice with
34394
- * `detected: false` (`handlePhaseChanged` in
34395
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
34396
- * matters most for the sources that only ever push a rising one:
34397
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
34398
- * a false.
34399
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
34400
- * provider pushes the false itself, from the upstream system's own
34401
- * state change. No phase machine is involved.
34402
- *
34403
- * ### The phase machine is CANONICAL, not sole — and that is a defect
34404
- *
34405
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
34406
- * phase machine is the sole writer for a camera. It is not.
34407
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
34408
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
34409
- * Hikvision's comment says why: it read THIS docblock, agreed the
34410
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
34411
- * two authorities can disagree about one slice, which this repo
34412
- * forbids, and the doc said otherwise — which is worse than saying
34413
- * nothing, because it reads as verification.
34414
- *
34415
- * This predates D475 and is not fixed there: the fix touches every
34416
- * camera provider. Recorded in D475's Consequences. Do not restore the
34417
- * "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.
34418
34450
  */
34419
34451
  autoClearAfterMs: number().nullable()
34420
34452
  });
34421
- 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({
34422
34460
  deviceId: number(),
34423
34461
  detected: boolean(),
34424
34462
  timestamp: number(),
@@ -51484,6 +51522,10 @@ TimelapseRuleInputSchema.extend({
51484
51522
  createdAt: number(),
51485
51523
  updatedAt: number()
51486
51524
  });
51525
+ function cameraOwnsMotionViaSensor(motionSources) {
51526
+ return motionSources.some((s) => s !== "analyzer");
51527
+ }
51528
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
51487
51529
  /** True when `model` ships a build for `format`. */
51488
51530
  function hasBuild(model, format) {
51489
51531
  return Boolean(model.formats[format]);
@@ -52915,12 +52957,6 @@ Object.defineProperty(exports, "HF_BASE_URL", {
52915
52957
  return HF_BASE_URL;
52916
52958
  }
52917
52959
  });
52918
- Object.defineProperty(exports, "MOTION_CLOSE_AFTER_MS", {
52919
- enumerable: true,
52920
- get: function() {
52921
- return MOTION_CLOSE_AFTER_MS;
52922
- }
52923
- });
52924
52960
  Object.defineProperty(exports, "NativeLeaseAdmissionSchema", {
52925
52961
  enumerable: true,
52926
52962
  get: function() {
@@ -53071,6 +53107,12 @@ Object.defineProperty(exports, "buildFfmpegArgs", {
53071
53107
  return buildFfmpegArgs;
53072
53108
  }
53073
53109
  });
53110
+ Object.defineProperty(exports, "cameraOwnsMotionViaSensor", {
53111
+ enumerable: true,
53112
+ get: function() {
53113
+ return cameraOwnsMotionViaSensor;
53114
+ }
53115
+ });
53074
53116
  Object.defineProperty(exports, "cameraStreamsCapability", {
53075
53117
  enumerable: true,
53076
53118
  get: function() {
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-Jvi2SOxI.js");
5
+ const require_dist = require("../dist-sCjZdzKn.js");
6
6
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Sn as hydrateSchema, ct as evaluateZoneRules, dn as DeviceType, kt as motionDetectionCapability, mn as BaseAddon } from "../dist-E1Z_K22_.mjs";
1
+ import { Sn as hydrateSchema, ct as evaluateZoneRules, dn as DeviceType, kt as motionDetectionCapability, mn as BaseAddon } from "../dist-C8W_MWSz.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -1,4 +1,4 @@
1
- import { an as Fmp4BoxSplitter, fn as errMsg, ln as isSoftwareDecode, on as buildFfmpegArgs } from "./dist-E1Z_K22_.mjs";
1
+ import { an as Fmp4BoxSplitter, fn as errMsg, ln as isSoftwareDecode, on as buildFfmpegArgs } from "./dist-C8W_MWSz.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-Jvi2SOxI.js");
2
+ const require_dist = require("./dist-sCjZdzKn.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs, 1);
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-Jvi2SOxI.js");
7
- const require_node = require("../node-EjcZQh8D.js");
8
- const require_default_detection_model = require("../default-detection-model-BvxtQEcb.js");
6
+ const require_dist = require("../dist-sCjZdzKn.js");
7
+ const require_node = require("../node-D4xdwpPU.js");
8
+ const require_default_detection_model = require("../default-detection-model-CteW4o8U.js");
9
9
  const require_worker_protocol = require("../worker-protocol-C-G8qmye.js");
10
10
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
11
11
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
@@ -7994,29 +7994,6 @@ function shouldStartOnboardAnalyzer(config) {
7994
7994
  if (config.motionSources.includes("analyzer")) return false;
7995
7995
  return true;
7996
7996
  }
7997
- /**
7998
- * Pure decision helper (D475): does this camera OWN its motion state through
7999
- * a sensor — firmware (`onboard`) or the device reporting its own activity
8000
- * (`device-activity`) — rather than the analyzer?
8001
- *
8002
- * `motionSources` is an array and both can be present at once (a camera with
8003
- * a firmware sensor that ALSO runs the analyzer, e.g. for object-detection
8004
- * triggering via the shared decode `handleOnboardMotionAnalyzer` opens). On
8005
- * such a camera the analyzer's `MotionOnMotionChanged` transitions must NOT
8006
- * reach `reportMotion` — the operator's rule is "the pipeline must never
8007
- * overwrite what the sensors do", and `reportMotion` drives the phase
8008
- * machine that is the SOLE writer of the device's `motion` cap-state slice
8009
- * (`handlePhaseChanged`). Letting the analyzer's noise re-describe a
8010
- * sensor-owned camera's motion state would silently re-open or overwrite
8011
- * what the sensor already decided.
8012
- *
8013
- * A camera with `motionSources: ['analyzer']` only (no sensor) is
8014
- * UNAFFECTED — this returns `false` and the analyzer drives the phase
8015
- * machine exactly as before D475.
8016
- */
8017
- function cameraOwnsMotionViaSensor(motionSources) {
8018
- return motionSources.some((s) => s !== "analyzer");
8019
- }
8020
7997
  /** Recursively collects the `addonId` of every ENABLED step in the tree. */
8021
7998
  function collectEnabledStepIds(steps, acc) {
8022
7999
  if (!steps) return;
@@ -8678,7 +8655,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8678
8655
  const source = data.source;
8679
8656
  if (source === "onboard") this.handleOnboardMotionAnalyzer(deviceId, data.detected);
8680
8657
  if (!attachment.config.motionSources.includes(source)) return;
8681
- if (source === "analyzer" && cameraOwnsMotionViaSensor(attachment.config.motionSources)) return;
8658
+ if (source === "analyzer" && require_dist.cameraOwnsMotionViaSensor(attachment.config.motionSources)) return;
8682
8659
  this.runner?.reportMotion(deviceId, data.detected, source, data.regions ? [...data.regions] : void 0);
8683
8660
  });
8684
8661
  if (this.ctx.eventBus) {
@@ -10897,7 +10874,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
10897
10874
  avgInferenceTimeMs: metrics.avgInferenceTimeMs,
10898
10875
  queueDepthTotal: metrics.queueDepth,
10899
10876
  devices,
10900
- hardware: summarizeRunnerHardware(await this.resolveLocalHardware())
10877
+ hardware: summarizeRunnerHardware(await this.resolveLocalHardware()),
10878
+ motionSliceWriter: "orchestrator"
10901
10879
  };
10902
10880
  }
10903
10881
  /**
@@ -11435,40 +11413,23 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
11435
11413
  }
11436
11414
  }
11437
11415
  /**
11438
- * Bridge runner phase transitions to the device's `motion` runtime
11439
- * state + the bus. Single ownership point — every motion source
11440
- * (analyzer, onboard, future variants) funnels through the runner's
11441
- * phase machine and lands here.
11416
+ * Publish a runner phase transition as `DetectionPhaseTransition` (WATCHING
11417
+ * ↔ ACTIVE, after the cooldown debounce), for the UI events panel, analytics
11418
+ * and the alert center.
11442
11419
  *
11443
- * - Cap-state via the unified `device-state.setCapSlice` API.
11444
- * `autoClearAfterMs = MOTION_CLOSE_AFTER_MS` on ON, `null` on OFF (D475)
11445
- * — the SAME system-wide constant `pipeline-analytics`'s motion-episode
11446
- * close uses, one authority, never this camera's own `motionCooldownMs`
11447
- * (a different clock, for a different purpose: gating the runner's own
11448
- * stream subscriptions, not describing how long an episode stays open).
11449
- * `lastDetectedAt` is preserved across OFF using `lastMotionAt`.
11450
- * - Bus event `MotionOnMotionChanged` fires alongside for consumers
11451
- * that prefer event-driven over runtime-state polling.
11420
+ * The phase is NOT the camera's motion and writes no `motion` runtime slice
11421
+ * (D696). Every on-motion detection session attaches `always-on`, so the
11422
+ * phase reads `active` for a whole session, and an always-on camera is
11423
+ * `active` for as long as it is attached; a detach writes nothing at all.
11424
+ * The slice it used to write was therefore "a session is attached", and was
11425
+ * left `detected: true` after every session. The orchestrator is the only
11426
+ * writer of that slice, with the EFFECTIVE motion, and this runner says so
11427
+ * on `getLocalLoad().motionSliceWriter` so that it never writes the slice
11428
+ * while a runner older than D696 still does.
11452
11429
  */
11453
11430
  handlePhaseChanged(deviceId, phase, meta) {
11454
11431
  const detected = phase === "active";
11455
11432
  if (detected) this.lastMotionAt.set(deviceId, meta.timestamp);
11456
- const slice = {
11457
- detected,
11458
- lastDetectedAt: this.lastMotionAt.get(deviceId) ?? null,
11459
- autoClearAfterMs: detected ? require_dist.MOTION_CLOSE_AFTER_MS : null
11460
- };
11461
- (async () => {
11462
- await (await this.ctx.fetchDevice(deviceId)).deviceState.setCapSlice({
11463
- capName: "motion",
11464
- slice
11465
- });
11466
- })().catch((err) => {
11467
- this.ctx.logger.debug("motion cap-state write failed", {
11468
- tags: { deviceId },
11469
- meta: { error: require_dist.errMsg(err) }
11470
- });
11471
- });
11472
11433
  if (this.ctx.eventBus) {
11473
11434
  const payload = {
11474
11435
  deviceId,
@@ -12483,7 +12444,7 @@ exports.PipelineRunner = PipelineRunner;
12483
12444
  exports.PipelineTimingSampler = PipelineTimingSampler;
12484
12445
  exports.Semaphore = Semaphore;
12485
12446
  exports.acquireSessionDecodeRestreamFrom = acquireSessionDecodeRestreamFrom;
12486
- exports.cameraOwnsMotionViaSensor = cameraOwnsMotionViaSensor;
12447
+ exports.cameraOwnsMotionViaSensor = require_dist.cameraOwnsMotionViaSensor;
12487
12448
  exports.customActions = pipelineRunnerBenchActions;
12488
12449
  exports.default = PipelineRunnerAddon;
12489
12450
  exports.deriveDetailSteps = deriveDetailSteps;