@camstack/addon-export-alexa 1.2.123 → 1.2.124

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.
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  //#endregion
27
27
  let node_crypto = require("node:crypto");
28
28
  node_crypto = __toESM(node_crypto, 1);
29
- //#region ../types/dist/event-category-C5xZWqz6.mjs
29
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
30
30
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
31
31
  EventCategory["SystemBoot"] = "system.boot";
32
32
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -559,6 +559,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
559
559
  */
560
560
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
561
561
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
562
+ /**
563
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
564
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
565
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
566
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
567
+ * for a camera with no attached sensor). Providers must not emit
568
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
569
+ * keeps it so (camera-grid is a named, deferred exception).
570
+ */
562
571
  EventCategory["ProviderMotion"] = "provider.motion";
563
572
  EventCategory["ProviderDetection"] = "provider.detection";
564
573
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -5397,7 +5406,7 @@ var ZodIssueCode = {
5397
5406
  var ZodFirstPartyTypeKind;
5398
5407
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5399
5408
  //#endregion
5400
- //#region ../types/dist/sleep-gU6hqv4_.mjs
5409
+ //#region ../types/dist/sleep-CDCONfkt.mjs
5401
5410
  /**
5402
5411
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5403
5412
  * window to float samples (D455).
@@ -7458,7 +7467,7 @@ function errMsg(err) {
7458
7467
  return String(err);
7459
7468
  }
7460
7469
  //#endregion
7461
- //#region ../types/dist/composition-Dq42XWp0.mjs
7470
+ //#region ../types/dist/composition-43aqsZQj.mjs
7462
7471
  /**
7463
7472
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7464
7473
  *
@@ -7515,13 +7524,12 @@ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac
7515
7524
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7516
7525
  * valutatore puro del suo FRONTE.
7517
7526
  *
7518
- * Viveva dentro il builtin virtual-doorbell
7519
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7520
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7521
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7522
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7523
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7524
- * SPOSTA qui e il doorbell la ri-esporta.
7527
+ * UNA tabella per tutti quelli che chiedono "questo device è ALTO?": il
7528
+ * trigger `RecordingTriggers.sensorDeviceIds` del recorder, i marker dei
7529
+ * sensori della post-analysis, il bridge camera-sensors dell'orchestratore e
7530
+ * le sorgenti di un campanello composto (`doorbellFeatureSources`, D671).
7531
+ * Una seconda copia divergerebbe alla prima cap aggiunta, e il sintomo — "il
7532
+ * sensore fa suonare il campanello ma non registra" — è esattamente D62.
7525
7533
  *
7526
7534
  * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7527
7535
  * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
@@ -9425,7 +9433,7 @@ var RecordingTriggersSchema = object({
9425
9433
  *
9426
9434
  * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9427
9435
  * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9428
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9436
+ * la stessa del campanello composto (D671). Ed è il FRONTE, non
9429
9437
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
9430
9438
  * registrare.
9431
9439
  */
@@ -15650,28 +15658,10 @@ method(object({
15650
15658
  deviceIds: array(number()).readonly(),
15651
15659
  caps: array(string()).readonly().optional()
15652
15660
  }), record(string(), record(string(), unknown().nullable())));
15653
- object({
15654
- /** Ms epoch of the last press. null = never observed since this provider started. */
15655
- lastPressedAt: number().nullable(),
15656
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15657
- pressCountSinceStart: number()
15658
- });
15659
- object({
15660
- deviceId: number(),
15661
- timestamp: number()
15662
- });
15663
- /**
15664
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15665
- * virtual-doorbell trigger devices, exactly what the wrapper's
15666
- * `doorbellSources` persists (as `{ deviceId }` records).
15667
- */
15668
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15661
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15669
15662
  DeviceType.Camera;
15670
- DeviceType.Button, DeviceType.Camera;
15671
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15672
- DeviceType.Camera, DeviceType.Camera;
15673
15663
  /** A slot's editable value, discriminated on `slot`. */
15674
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15664
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15675
15665
  DeviceType.Camera;
15676
15666
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15677
15667
  state: literal("unreachable"),
@@ -15708,10 +15698,11 @@ var ExtensionSlotSourceSchema = object({
15708
15698
  });
15709
15699
  /**
15710
15700
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15711
- * reported read-only, named, never written through this contract (D62). The
15712
- * virtual doorbell's sources ring only while the wrapper is bound to the
15713
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15714
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15701
+ * reported read-only, named, never written through this contract (D62) — e.g.
15702
+ * a slot whose sources act only while a wrapper is bound to the host, a
15703
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15704
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15705
+ * not be read; it is not "inactive" (D315).
15715
15706
  */
15716
15707
  var ExtensionSlotActivationSchema = object({
15717
15708
  state: _enum([
@@ -15727,7 +15718,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15727
15718
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15728
15719
  slotRef: string(),
15729
15720
  slot: ExtensionSlotIdSchema,
15730
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15721
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15731
15722
  authority: string(),
15732
15723
  activation: ExtensionSlotActivationSchema.nullable(),
15733
15724
  value: ExtensionSlotValueSchema,
@@ -20827,12 +20818,26 @@ var CameraSensorRefSchema = object({
20827
20818
  function cameraSensorRefKey(ref) {
20828
20819
  return `${ref.addonId}/${ref.stableId}`;
20829
20820
  }
20830
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20831
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20832
- var CameraSensorsConfigSchema = object({
20833
- mode: CameraSensorsModeSchema,
20834
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20835
- }).superRefine((value, ctx) => {
20821
+ /**
20822
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20823
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20824
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20825
+ * camera's own motion counts only while a confirm sensor is active — with or
20826
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20827
+ */
20828
+ var CameraSensorModeSchema = _enum([
20829
+ "in-addition",
20830
+ "confirm",
20831
+ "replace"
20832
+ ]);
20833
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20834
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20835
+ /**
20836
+ * A camera's attached sensors. A CS-1 row carried an envelope
20837
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20838
+ * what the write stores (D672 rule 3), so it is never written again.
20839
+ */
20840
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20836
20841
  const seen = /* @__PURE__ */ new Set();
20837
20842
  value.sensors.forEach((ref, index) => {
20838
20843
  const key = cameraSensorRefKey(ref);
@@ -20844,39 +20849,41 @@ var CameraSensorsConfigSchema = object({
20844
20849
  seen.add(key);
20845
20850
  });
20846
20851
  });
20847
- var RefFields = {
20852
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20853
+ var AttachmentFields = {
20848
20854
  addonId: string(),
20849
- stableId: string()
20855
+ stableId: string(),
20856
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20850
20857
  };
20851
20858
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20852
20859
  object({
20853
- ...RefFields,
20860
+ ...AttachmentFields,
20854
20861
  state: literal("ready"),
20855
20862
  deviceId: number(),
20856
20863
  name: string(),
20857
20864
  caps: array(string()).readonly()
20858
20865
  }),
20859
20866
  object({
20860
- ...RefFields,
20867
+ ...AttachmentFields,
20861
20868
  state: literal("offline"),
20862
20869
  deviceId: number(),
20863
20870
  name: string(),
20864
20871
  reason: _enum(["offline", "disabled"])
20865
20872
  }),
20866
20873
  object({
20867
- ...RefFields,
20874
+ ...AttachmentFields,
20868
20875
  state: literal("missing"),
20869
20876
  reason: string()
20870
20877
  }),
20871
20878
  object({
20872
- ...RefFields,
20879
+ ...AttachmentFields,
20873
20880
  state: literal("unsupported"),
20874
20881
  deviceId: number(),
20875
20882
  name: string(),
20876
20883
  reason: string()
20877
20884
  }),
20878
20885
  object({
20879
- ...RefFields,
20886
+ ...AttachmentFields,
20880
20887
  state: literal("unreadable"),
20881
20888
  reason: string()
20882
20889
  })
@@ -20889,17 +20896,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20889
20896
  * say.
20890
20897
  */
20891
20898
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20899
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20900
+ var CameraOwnMotionSchema = _enum([
20901
+ "always",
20902
+ "when-confirmed",
20903
+ "never"
20904
+ ]);
20905
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20906
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20907
+ /**
20908
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20909
+ * never the effective value. The camera's `motion` slice is the effective one,
20910
+ * so a view of the camera's own row reads this.
20911
+ * - `atMs`: when the bridge received the camera's last own edge.
20912
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20913
+ * AND it is younger than the window the bridge counts the camera's own motion
20914
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20915
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20916
+ * within the window; a status read is a snapshot, so a live view re-asks.
20917
+ * Absent = this orchestrator has not received the camera's own motion since it
20918
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20919
+ */
20920
+ var CameraRawLevelSchema = object({
20921
+ detected: boolean(),
20922
+ atMs: number()
20923
+ });
20892
20924
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20893
- object({ state: literal("not-configured") }),
20925
+ object({
20926
+ state: literal("not-configured"),
20927
+ cameraLevel: CameraRawLevelSchema.optional()
20928
+ }),
20894
20929
  object({
20895
20930
  state: literal("unreadable"),
20896
- reason: string()
20931
+ reason: string(),
20932
+ cameraLevel: CameraRawLevelSchema.optional()
20897
20933
  }),
20898
20934
  object({
20899
20935
  state: literal("configured"),
20900
- mode: CameraSensorsModeSchema,
20936
+ cameraLevel: CameraRawLevelSchema.optional(),
20901
20937
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20902
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20938
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20939
+ cameraMotion: CameraOwnMotionSchema.optional(),
20940
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20941
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20903
20942
  })
20904
20943
  ]);
20905
20944
  Object.freeze({
@@ -24090,8 +24129,10 @@ var RunnerCameraConfigSchema = object({
24090
24129
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
24091
24130
  * analyzer providers) running on decoded frames in this runner.
24092
24131
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
24093
- * analytics). The provider emits ProviderMotion
24094
- * events; the orchestrator forwards to `reportMotion`.
24132
+ * analytics). The provider emits its raw push as `ProviderMotion`
24133
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
24134
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
24135
+ * `reportMotion`.
24095
24136
  */
24096
24137
  motionSources: MotionSourcesSchema.default(["analyzer"]),
24097
24138
  /**
@@ -24268,7 +24309,15 @@ var RunnerLocalLoadSchema = object({
24268
24309
  hasGpu: boolean(),
24269
24310
  inferenceBackend: string().optional(),
24270
24311
  cpuCores: number().optional()
24271
- })
24312
+ }),
24313
+ /**
24314
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24315
+ * runner never writes it. ABSENT = a runner older than D696, which still
24316
+ * writes the slice from its phase machine; the orchestrator does not write
24317
+ * while any runner node is absent-or-unanswered, so one slice never has two
24318
+ * writers.
24319
+ */
24320
+ motionSliceWriter: literal("orchestrator").optional()
24272
24321
  });
24273
24322
  /**
24274
24323
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -29109,6 +29158,17 @@ object({
29109
29158
  deviceId: number(),
29110
29159
  status: DeviceStatusSchema
29111
29160
  });
29161
+ object({
29162
+ /** Ms epoch of the last press. null = never observed since this provider started. */
29163
+ lastPressedAt: number().nullable(),
29164
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
29165
+ pressCountSinceStart: number()
29166
+ });
29167
+ object({
29168
+ deviceId: number(),
29169
+ timestamp: number()
29170
+ });
29171
+ DeviceType.Button, DeviceType.Camera;
29112
29172
  /**
29113
29173
  * Enum-state sensor — a string value picked from a finite option set.
29114
29174
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30753,9 +30813,9 @@ object({
30753
30813
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30754
30814
  lastDetectedAt: number().nullable(),
30755
30815
  /**
30756
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30757
- * `null` while false and on every `Sensor` device (D475) — see that
30758
- * constant's doc for the one-authority rule.
30816
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30817
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30818
+ * device (D475) — see that constant's doc for the one-authority rule.
30759
30819
  *
30760
30820
  * ## Reading this field still arms nothing
30761
30821
  *
@@ -30773,38 +30833,41 @@ object({
30773
30833
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30774
30834
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30775
30835
  *
30776
- * ## Who writes it
30777
- *
30778
- * - **Cameras** — the runner's phase machine, `active → watching` on
30779
- * `cooldown_expired`, which then writes this slice with
30780
- * `detected: false` (`handlePhaseChanged` in
30781
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30782
- * matters most for the sources that only ever push a rising one:
30783
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30784
- * a false.
30785
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30786
- * provider pushes the false itself, from the upstream system's own
30787
- * state change. No phase machine is involved.
30788
- *
30789
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30790
- *
30791
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30792
- * phase machine is the sole writer for a camera. It is not.
30793
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30794
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30795
- * Hikvision's comment says why: it read THIS docblock, agreed the
30796
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30797
- * two authorities can disagree about one slice, which this repo
30798
- * forbids, and the doc said otherwise — which is worse than saying
30799
- * nothing, because it reads as verification.
30800
- *
30801
- * This predates D475 and is not fixed there: the fix touches every
30802
- * camera provider. Recorded in D475's Consequences. Do not restore the
30803
- * "sole writer" wording without also removing the other writers.
30836
+ * ## Who writes it (D696)
30837
+ *
30838
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30839
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30840
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30841
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30842
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30843
+ * device activity, as the camera's `motionSources` counts them. A source
30844
+ * that never sends a clear falls one window after its last `true`.
30845
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30846
+ * writes this slice (not even a boot seed), and neither does the runner's
30847
+ * phase machine: its phase is "a session is attached" for every on-motion
30848
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30849
+ * A provider's `isDetected` answers from the hub's slice.
30850
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30851
+ * imports) — the provider pushes both edges itself, from the upstream
30852
+ * system's own state change. That is the sensor's own fact.
30853
+ *
30854
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30855
+ * `motion` slice; each sensor writer is a named exemption.
30856
+ *
30857
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30858
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30859
+ * it, every provider seeded it at boot, and every session left it
30860
+ * `detected: true` after its detach.
30804
30861
  */
30805
30862
  autoClearAfterMs: number().nullable()
30806
30863
  });
30807
- object({
30864
+ /**
30865
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30866
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30867
+ * — both the cap event surface and the bus payload type alias to this
30868
+ * schema.
30869
+ */
30870
+ var MotionOnMotionChangedDataSchema = object({
30808
30871
  deviceId: number(),
30809
30872
  detected: boolean(),
30810
30873
  timestamp: number(),
@@ -47281,6 +47344,7 @@ TimelapseRuleInputSchema.extend({
47281
47344
  createdAt: number(),
47282
47345
  updatedAt: number()
47283
47346
  });
47347
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47284
47348
  object({
47285
47349
  /**
47286
47350
  * Fraction of the box's own size added on EACH side before cutting.
@@ -51497,7 +51561,7 @@ var ExportAlexaAddon = class extends BaseAddon {
51497
51561
  * One reconcile pass over every exposed device shortly after boot. This is
51498
51562
  * what heals a shape that drifted while the addon was DOWN (or under an
51499
51563
  * older build that had no signature at all) with no operator click — the
51500
- * doorbell wrapper bound on 615 is exactly that case. Deliberately late
51564
+ * doorbell bound on 615 after it was exposed is exactly that case. Deliberately late
51501
51565
  * enough that device providers have registered their caps: a device whose
51502
51566
  * bindings have not landed yet builds a shape we must not push.
51503
51567
  */
@@ -1,5 +1,5 @@
1
1
  import * as crypto$1 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).
@@ -7431,7 +7440,7 @@ function errMsg(err) {
7431
7440
  return String(err);
7432
7441
  }
7433
7442
  //#endregion
7434
- //#region ../types/dist/composition-Dq42XWp0.mjs
7443
+ //#region ../types/dist/composition-43aqsZQj.mjs
7435
7444
  /**
7436
7445
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7437
7446
  *
@@ -7488,13 +7497,12 @@ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac
7488
7497
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7489
7498
  * valutatore puro del suo FRONTE.
7490
7499
  *
7491
- * Viveva dentro il builtin virtual-doorbell
7492
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7493
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7494
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7495
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7496
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7497
- * SPOSTA qui e il doorbell la ri-esporta.
7500
+ * UNA tabella per tutti quelli che chiedono "questo device è ALTO?": il
7501
+ * trigger `RecordingTriggers.sensorDeviceIds` del recorder, i marker dei
7502
+ * sensori della post-analysis, il bridge camera-sensors dell'orchestratore e
7503
+ * le sorgenti di un campanello composto (`doorbellFeatureSources`, D671).
7504
+ * Una seconda copia divergerebbe alla prima cap aggiunta, e il sintomo — "il
7505
+ * sensore fa suonare il campanello ma non registra" — è esattamente D62.
7498
7506
  *
7499
7507
  * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7500
7508
  * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
@@ -9398,7 +9406,7 @@ var RecordingTriggersSchema = object({
9398
9406
  *
9399
9407
  * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9400
9408
  * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9401
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
9409
+ * la stessa del campanello composto (D671). Ed è il FRONTE, non
9402
9410
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
9403
9411
  * registrare.
9404
9412
  */
@@ -15623,28 +15631,10 @@ method(object({
15623
15631
  deviceIds: array(number()).readonly(),
15624
15632
  caps: array(string()).readonly().optional()
15625
15633
  }), record(string(), record(string(), unknown().nullable())));
15626
- object({
15627
- /** Ms epoch of the last press. null = never observed since this provider started. */
15628
- lastPressedAt: number().nullable(),
15629
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
15630
- pressCountSinceStart: number()
15631
- });
15632
- object({
15633
- deviceId: number(),
15634
- timestamp: number()
15635
- });
15636
- /**
15637
- * The `doorbell-source` extension slot's editable value (D648): the camera's
15638
- * virtual-doorbell trigger devices, exactly what the wrapper's
15639
- * `doorbellSources` persists (as `{ deviceId }` records).
15640
- */
15641
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
15634
+ var ExtensionSlotIdSchema = _enum(["linked-devices"]);
15642
15635
  DeviceType.Camera;
15643
- DeviceType.Button, DeviceType.Camera;
15644
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
15645
- DeviceType.Camera, DeviceType.Camera;
15646
15636
  /** A slot's editable value, discriminated on `slot`. */
15647
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15637
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
15648
15638
  DeviceType.Camera;
15649
15639
  var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
15650
15640
  state: literal("unreachable"),
@@ -15681,10 +15671,11 @@ var ExtensionSlotSourceSchema = object({
15681
15671
  });
15682
15672
  /**
15683
15673
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15684
- * reported read-only, named, never written through this contract (D62). The
15685
- * virtual doorbell's sources ring only while the wrapper is bound to the
15686
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15687
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
15674
+ * reported read-only, named, never written through this contract (D62) — e.g.
15675
+ * a slot whose sources act only while a wrapper is bound to the host, a
15676
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
15677
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
15678
+ * not be read; it is not "inactive" (D315).
15688
15679
  */
15689
15680
  var ExtensionSlotActivationSchema = object({
15690
15681
  state: _enum([
@@ -15700,7 +15691,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
15700
15691
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15701
15692
  slotRef: string(),
15702
15693
  slot: ExtensionSlotIdSchema,
15703
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
15694
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15704
15695
  authority: string(),
15705
15696
  activation: ExtensionSlotActivationSchema.nullable(),
15706
15697
  value: ExtensionSlotValueSchema,
@@ -20800,12 +20791,26 @@ var CameraSensorRefSchema = object({
20800
20791
  function cameraSensorRefKey(ref) {
20801
20792
  return `${ref.addonId}/${ref.stableId}`;
20802
20793
  }
20803
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20804
- var CameraSensorsModeSchema = _enum(["in-addition"]);
20805
- var CameraSensorsConfigSchema = object({
20806
- mode: CameraSensorsModeSchema,
20807
- sensors: array(CameraSensorRefSchema).min(1).max(8)
20808
- }).superRefine((value, ctx) => {
20794
+ /**
20795
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
20796
+ * `replace`: its motion is the camera's, and while one is READY the camera's
20797
+ * own motion no longer counts on its own. `confirm`: while one is READY the
20798
+ * camera's own motion counts only while a confirm sensor is active — with or
20799
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
20800
+ */
20801
+ var CameraSensorModeSchema = _enum([
20802
+ "in-addition",
20803
+ "confirm",
20804
+ "replace"
20805
+ ]);
20806
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
20807
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
20808
+ /**
20809
+ * A camera's attached sensors. A CS-1 row carried an envelope
20810
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
20811
+ * what the write stores (D672 rule 3), so it is never written again.
20812
+ */
20813
+ var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
20809
20814
  const seen = /* @__PURE__ */ new Set();
20810
20815
  value.sensors.forEach((ref, index) => {
20811
20816
  const key = cameraSensorRefKey(ref);
@@ -20817,39 +20822,41 @@ var CameraSensorsConfigSchema = object({
20817
20822
  seen.add(key);
20818
20823
  });
20819
20824
  });
20820
- var RefFields = {
20825
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
20826
+ var AttachmentFields = {
20821
20827
  addonId: string(),
20822
- stableId: string()
20828
+ stableId: string(),
20829
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
20823
20830
  };
20824
20831
  var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20825
20832
  object({
20826
- ...RefFields,
20833
+ ...AttachmentFields,
20827
20834
  state: literal("ready"),
20828
20835
  deviceId: number(),
20829
20836
  name: string(),
20830
20837
  caps: array(string()).readonly()
20831
20838
  }),
20832
20839
  object({
20833
- ...RefFields,
20840
+ ...AttachmentFields,
20834
20841
  state: literal("offline"),
20835
20842
  deviceId: number(),
20836
20843
  name: string(),
20837
20844
  reason: _enum(["offline", "disabled"])
20838
20845
  }),
20839
20846
  object({
20840
- ...RefFields,
20847
+ ...AttachmentFields,
20841
20848
  state: literal("missing"),
20842
20849
  reason: string()
20843
20850
  }),
20844
20851
  object({
20845
- ...RefFields,
20852
+ ...AttachmentFields,
20846
20853
  state: literal("unsupported"),
20847
20854
  deviceId: number(),
20848
20855
  name: string(),
20849
20856
  reason: string()
20850
20857
  }),
20851
20858
  object({
20852
- ...RefFields,
20859
+ ...AttachmentFields,
20853
20860
  state: literal("unreadable"),
20854
20861
  reason: string()
20855
20862
  })
@@ -20862,17 +20869,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20862
20869
  * say.
20863
20870
  */
20864
20871
  var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20872
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
20873
+ var CameraOwnMotionSchema = _enum([
20874
+ "always",
20875
+ "when-confirmed",
20876
+ "never"
20877
+ ]);
20878
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
20879
+ var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
20880
+ /**
20881
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
20882
+ * never the effective value. The camera's `motion` slice is the effective one,
20883
+ * so a view of the camera's own row reads this.
20884
+ * - `atMs`: when the bridge received the camera's last own edge.
20885
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
20886
+ * AND it is younger than the window the bridge counts the camera's own motion
20887
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
20888
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
20889
+ * within the window; a status read is a snapshot, so a live view re-asks.
20890
+ * Absent = this orchestrator has not received the camera's own motion since it
20891
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
20892
+ */
20893
+ var CameraRawLevelSchema = object({
20894
+ detected: boolean(),
20895
+ atMs: number()
20896
+ });
20865
20897
  var CameraSensorsStatusSchema = discriminatedUnion("state", [
20866
- object({ state: literal("not-configured") }),
20898
+ object({
20899
+ state: literal("not-configured"),
20900
+ cameraLevel: CameraRawLevelSchema.optional()
20901
+ }),
20867
20902
  object({
20868
20903
  state: literal("unreadable"),
20869
- reason: string()
20904
+ reason: string(),
20905
+ cameraLevel: CameraRawLevelSchema.optional()
20870
20906
  }),
20871
20907
  object({
20872
20908
  state: literal("configured"),
20873
- mode: CameraSensorsModeSchema,
20909
+ cameraLevel: CameraRawLevelSchema.optional(),
20874
20910
  sensors: array(CameraSensorAttachmentSchema).readonly(),
20875
- notForwarded: CameraSensorsNotForwardedSchema.optional()
20911
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
20912
+ cameraMotion: CameraOwnMotionSchema.optional(),
20913
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
20914
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
20876
20915
  })
20877
20916
  ]);
20878
20917
  Object.freeze({
@@ -24063,8 +24102,10 @@ var RunnerCameraConfigSchema = object({
24063
24102
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
24064
24103
  * analyzer providers) running on decoded frames in this runner.
24065
24104
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
24066
- * analytics). The provider emits ProviderMotion
24067
- * events; the orchestrator forwards to `reportMotion`.
24105
+ * analytics). The provider emits its raw push as `ProviderMotion`
24106
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
24107
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
24108
+ * `reportMotion`.
24068
24109
  */
24069
24110
  motionSources: MotionSourcesSchema.default(["analyzer"]),
24070
24111
  /**
@@ -24241,7 +24282,15 @@ var RunnerLocalLoadSchema = object({
24241
24282
  hasGpu: boolean(),
24242
24283
  inferenceBackend: string().optional(),
24243
24284
  cpuCores: number().optional()
24244
- })
24285
+ }),
24286
+ /**
24287
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
24288
+ * runner never writes it. ABSENT = a runner older than D696, which still
24289
+ * writes the slice from its phase machine; the orchestrator does not write
24290
+ * while any runner node is absent-or-unanswered, so one slice never has two
24291
+ * writers.
24292
+ */
24293
+ motionSliceWriter: literal("orchestrator").optional()
24245
24294
  });
24246
24295
  /**
24247
24296
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -29082,6 +29131,17 @@ object({
29082
29131
  deviceId: number(),
29083
29132
  status: DeviceStatusSchema
29084
29133
  });
29134
+ object({
29135
+ /** Ms epoch of the last press. null = never observed since this provider started. */
29136
+ lastPressedAt: number().nullable(),
29137
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
29138
+ pressCountSinceStart: number()
29139
+ });
29140
+ object({
29141
+ deviceId: number(),
29142
+ timestamp: number()
29143
+ });
29144
+ DeviceType.Button, DeviceType.Camera;
29085
29145
  /**
29086
29146
  * Enum-state sensor — a string value picked from a finite option set.
29087
29147
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -30726,9 +30786,9 @@ object({
30726
30786
  /** Ms epoch of the last detected-true observation. Null if never detected. */
30727
30787
  lastDetectedAt: number().nullable(),
30728
30788
  /**
30729
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
30730
- * `null` while false and on every `Sensor` device (D475) — see that
30731
- * constant's doc for the one-authority rule.
30789
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
30790
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
30791
+ * device (D475) — see that constant's doc for the one-authority rule.
30732
30792
  *
30733
30793
  * ## Reading this field still arms nothing
30734
30794
  *
@@ -30746,38 +30806,41 @@ object({
30746
30806
  * `addon-export-alexa/src/motion-clear-hold.ts` and
30747
30807
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
30748
30808
  *
30749
- * ## Who writes it
30750
- *
30751
- * - **Cameras** — the runner's phase machine, `active → watching` on
30752
- * `cooldown_expired`, which then writes this slice with
30753
- * `detected: false` (`handlePhaseChanged` in
30754
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
30755
- * matters most for the sources that only ever push a rising one:
30756
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
30757
- * a false.
30758
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
30759
- * provider pushes the false itself, from the upstream system's own
30760
- * state change. No phase machine is involved.
30761
- *
30762
- * ### The phase machine is CANONICAL, not sole — and that is a defect
30763
- *
30764
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
30765
- * phase machine is the sole writer for a camera. It is not.
30766
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
30767
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
30768
- * Hikvision's comment says why: it read THIS docblock, agreed the
30769
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
30770
- * two authorities can disagree about one slice, which this repo
30771
- * forbids, and the doc said otherwise — which is worse than saying
30772
- * nothing, because it reads as verification.
30773
- *
30774
- * This predates D475 and is not fixed there: the fix touches every
30775
- * camera provider. Recorded in D475's Consequences. Do not restore the
30776
- * "sole writer" wording without also removing the other writers.
30809
+ * ## Who writes it (D696)
30810
+ *
30811
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
30812
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
30813
+ * `camera-sensors/motion-slice.ts`), alongside the effective
30814
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
30815
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
30816
+ * device activity, as the camera's `motionSources` counts them. A source
30817
+ * that never sends a clear falls one window after its last `true`.
30818
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
30819
+ * writes this slice (not even a boot seed), and neither does the runner's
30820
+ * phase machine: its phase is "a session is attached" for every on-motion
30821
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
30822
+ * A provider's `isDetected` answers from the hub's slice.
30823
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
30824
+ * imports) — the provider pushes both edges itself, from the upstream
30825
+ * system's own state change. That is the sensor's own fact.
30826
+ *
30827
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
30828
+ * `motion` slice; each sensor writer is a named exemption.
30829
+ *
30830
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
30831
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
30832
+ * it, every provider seeded it at boot, and every session left it
30833
+ * `detected: true` after its detach.
30777
30834
  */
30778
30835
  autoClearAfterMs: number().nullable()
30779
30836
  });
30780
- object({
30837
+ /**
30838
+ * Payload of `motion.onMotionChanged` event + the corresponding bus
30839
+ * event `EventCategory.MotionOnMotionChanged`. Single source of truth
30840
+ * — both the cap event surface and the bus payload type alias to this
30841
+ * schema.
30842
+ */
30843
+ var MotionOnMotionChangedDataSchema = object({
30781
30844
  deviceId: number(),
30782
30845
  detected: boolean(),
30783
30846
  timestamp: number(),
@@ -47254,6 +47317,7 @@ TimelapseRuleInputSchema.extend({
47254
47317
  createdAt: number(),
47255
47318
  updatedAt: number()
47256
47319
  });
47320
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
47257
47321
  object({
47258
47322
  /**
47259
47323
  * Fraction of the box's own size added on EACH side before cutting.
@@ -51470,7 +51534,7 @@ var ExportAlexaAddon = class extends BaseAddon {
51470
51534
  * One reconcile pass over every exposed device shortly after boot. This is
51471
51535
  * what heals a shape that drifted while the addon was DOWN (or under an
51472
51536
  * older build that had no signature at all) with no operator click — the
51473
- * doorbell wrapper bound on 615 is exactly that case. Deliberately late
51537
+ * doorbell bound on 615 after it was exposed is exactly that case. Deliberately late
51474
51538
  * enough that device providers have registered their caps: a device whose
51475
51539
  * bindings have not landed yet builds a shape we must not push.
51476
51540
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-alexa",
3
- "version": "1.2.123",
3
+ "version": "1.2.124",
4
4
  "description": "Alexa Smart Home Skill export — hub-side OAuth provider + directive handler. The companion AWS Lambda forwards Alexa directives to this addon's /addon/export-alexa/directive endpoint.",
5
5
  "keywords": [
6
6
  "camstack",