@camstack/addon-export-alexa 1.2.103 → 1.2.104

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.
@@ -7326,8 +7326,23 @@ function errMsg(err) {
7326
7326
  if (typeof err === "string") return err;
7327
7327
  return String(err);
7328
7328
  }
7329
- //#endregion
7330
- //#region ../types/dist/index.mjs
7329
+ new Set([
7330
+ "track",
7331
+ "summary",
7332
+ "face",
7333
+ "identity",
7334
+ "plate",
7335
+ "vehicle",
7336
+ "scene",
7337
+ "motion",
7338
+ "object",
7339
+ "audio"
7340
+ ]);
7341
+ new Set([
7342
+ "motion",
7343
+ "object",
7344
+ "audio"
7345
+ ]);
7331
7346
  /**
7332
7347
  * Build an `IAddonRouteProvider` from a list of routes. Implements
7333
7348
  * both the operator-facing `getRoutes` (returning route descriptors
@@ -12671,8 +12686,17 @@ method(object({
12671
12686
  }), array(SettingsRecordSchema).readonly()), method(object({
12672
12687
  namespace: string().optional(),
12673
12688
  collection: string(),
12674
- record: SettingsRecordSchema
12675
- }), _void(), { kind: "mutation" }), method(object({
12689
+ record: object({
12690
+ id: string().optional(),
12691
+ data: record(string(), unknown())
12692
+ })
12693
+ }), object({
12694
+ /**
12695
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12696
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12697
+ * primary key — which is the only place an auto key is knowable.
12698
+ */
12699
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
12676
12700
  namespace: string().optional(),
12677
12701
  collection: string(),
12678
12702
  records: array(BulkRecordSchema).readonly()
@@ -12781,8 +12805,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12781
12805
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
12782
12806
  namespace: string().optional(),
12783
12807
  collection: string(),
12784
- record: SettingsRecordSchema
12785
- }), _void(), {
12808
+ record: object({
12809
+ id: string().optional(),
12810
+ data: record(string(), unknown())
12811
+ })
12812
+ }), object({
12813
+ /**
12814
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12815
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12816
+ * primary key — which is the only place an auto key is knowable.
12817
+ */
12818
+ id: union([string(), number()]) }), {
12786
12819
  kind: "mutation",
12787
12820
  auth: "admin"
12788
12821
  }), method(object({
@@ -19465,7 +19498,20 @@ var TrackSchema = object({
19465
19498
  ...TrackRetrainFields
19466
19499
  });
19467
19500
  var BaseEventFields = {
19468
- id: string(),
19501
+ /**
19502
+ * A SQLite ROWID, assigned by the database (D474).
19503
+ *
19504
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
19505
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
19506
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
19507
+ * the rowid: the table itself is that B-tree, so the index stops existing
19508
+ * rather than getting smaller. No shorter string does that.
19509
+ *
19510
+ * Defined once here for all three event kinds, which is why they move
19511
+ * together: a per-table migration would have forked this and
19512
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
19513
+ */
19514
+ id: number().int(),
19469
19515
  deviceId: number(),
19470
19516
  timestamp: number()
19471
19517
  };
@@ -19484,7 +19530,34 @@ var MotionEventSchema = object({
19484
19530
  /** Omitted in slim projection. */
19485
19531
  frameHeight: number().optional(),
19486
19532
  /** Populated by B5 (recording playback URL for this event). */
19487
- mediaUrl: string().optional()
19533
+ mediaUrl: string().optional(),
19534
+ /**
19535
+ * One row per motion EPISODE, not one per push (D475). `null` while the
19536
+ * episode is still open — a further rising edge extends it in place rather
19537
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
19538
+ * (the span from the first rising edge to the LAST one, deliberately NOT
19539
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
19540
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
19541
+ * motion for an instantaneous trigger).
19542
+ *
19543
+ * **Absent** (not merely `null`) on a row written before D475 — that means
19544
+ * "closed the old way, before this column existed", never "still open".
19545
+ * Nothing in this codebase may read an absent `durationMs` as an open
19546
+ * episode; only `null` means open.
19547
+ */
19548
+ durationMs: number().nullable().optional(),
19549
+ /**
19550
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
19551
+ * first entry is always `0`) of every genuine off→on transition the
19552
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
19553
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
19554
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
19555
+ * explicit `false` mid-episode and then resumes before the quiet window
19556
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
19557
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
19558
+ * which must never be read as "no episode happened here".
19559
+ */
19560
+ edges: array(number()).readonly().optional()
19488
19561
  });
19489
19562
  /**
19490
19563
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -19539,6 +19612,23 @@ var ObjectEventSchema = object({
19539
19612
  * includes it (it is light). Absent on rows written before this field.
19540
19613
  */
19541
19614
  frameId: string().optional(),
19615
+ /**
19616
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
19617
+ * (D474).
19618
+ *
19619
+ * Only the package detector writes it, and it exists because the event id
19620
+ * stopped being choosable: the delivery and pick-up rows used to BE their
19621
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
19622
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
19623
+ * assigned by SQLite, so that key had to move off the primary key rather
19624
+ * than be dropped — a detector that cannot recognise its own row re-delivers
19625
+ * every parcel on every boot.
19626
+ *
19627
+ * Absent on every other object event, and on every row written before this
19628
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
19629
+ * not per row, and nothing addresses a row by it.
19630
+ */
19631
+ idempotencyKey: string().optional(),
19542
19632
  /** Omitted in slim projection. */
19543
19633
  trackId: string().optional(),
19544
19634
  className: string(),
@@ -26605,9 +26695,54 @@ object({
26605
26695
  /** Ms epoch of the last detected-true observation. Null if never detected. */
26606
26696
  lastDetectedAt: number().nullable(),
26607
26697
  /**
26608
- * Ms after which `detected` auto-reverts to false if no fresh push
26609
- * arrives. Null means the provider leaves detected state until a
26610
- * native "clear" event.
26698
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
26699
+ * `null` while false and on every `Sensor` device (D475) — see that
26700
+ * constant's doc for the one-authority rule.
26701
+ *
26702
+ * ## Reading this field still arms nothing
26703
+ *
26704
+ * It reads like an instruction to the consumer ("revert after N ms if
26705
+ * no fresh push arrives") and it is not one: nothing in this repo reads
26706
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
26707
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
26708
+ * imported constant, not as a read of `device.state.motion.value`, so this
26709
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
26710
+ * computed elsewhere. Building a self-clear timer out of a READ of this
26711
+ * field would add a second falling-edge authority beside whichever one
26712
+ * already owns the device, and two that can disagree are worse than one.
26713
+ * Consumers that need a falling edge SHAPED differently — held open across
26714
+ * a flapping source — debounce on their own side and say so, as
26715
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
26716
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
26717
+ *
26718
+ * ## Who writes it
26719
+ *
26720
+ * - **Cameras** — the runner's phase machine, `active → watching` on
26721
+ * `cooldown_expired`, which then writes this slice with
26722
+ * `detected: false` (`handlePhaseChanged` in
26723
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
26724
+ * matters most for the sources that only ever push a rising one:
26725
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
26726
+ * a false.
26727
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
26728
+ * provider pushes the false itself, from the upstream system's own
26729
+ * state change. No phase machine is involved.
26730
+ *
26731
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
26732
+ *
26733
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
26734
+ * phase machine is the sole writer for a camera. It is not.
26735
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
26736
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
26737
+ * Hikvision's comment says why: it read THIS docblock, agreed the
26738
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
26739
+ * two authorities can disagree about one slice, which this repo
26740
+ * forbids, and the doc said otherwise — which is worse than saying
26741
+ * nothing, because it reads as verification.
26742
+ *
26743
+ * This predates D475 and is not fixed there: the fix touches every
26744
+ * camera provider. Recorded in D475's Consequences. Do not restore the
26745
+ * "sole writer" wording without also removing the other writers.
26611
26746
  */
26612
26747
  autoClearAfterMs: number().nullable()
26613
26748
  });
@@ -7299,8 +7299,23 @@ function errMsg(err) {
7299
7299
  if (typeof err === "string") return err;
7300
7300
  return String(err);
7301
7301
  }
7302
- //#endregion
7303
- //#region ../types/dist/index.mjs
7302
+ new Set([
7303
+ "track",
7304
+ "summary",
7305
+ "face",
7306
+ "identity",
7307
+ "plate",
7308
+ "vehicle",
7309
+ "scene",
7310
+ "motion",
7311
+ "object",
7312
+ "audio"
7313
+ ]);
7314
+ new Set([
7315
+ "motion",
7316
+ "object",
7317
+ "audio"
7318
+ ]);
7304
7319
  /**
7305
7320
  * Build an `IAddonRouteProvider` from a list of routes. Implements
7306
7321
  * both the operator-facing `getRoutes` (returning route descriptors
@@ -12644,8 +12659,17 @@ method(object({
12644
12659
  }), array(SettingsRecordSchema).readonly()), method(object({
12645
12660
  namespace: string().optional(),
12646
12661
  collection: string(),
12647
- record: SettingsRecordSchema
12648
- }), _void(), { kind: "mutation" }), method(object({
12662
+ record: object({
12663
+ id: string().optional(),
12664
+ data: record(string(), unknown())
12665
+ })
12666
+ }), object({
12667
+ /**
12668
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12669
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12670
+ * primary key — which is the only place an auto key is knowable.
12671
+ */
12672
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
12649
12673
  namespace: string().optional(),
12650
12674
  collection: string(),
12651
12675
  records: array(BulkRecordSchema).readonly()
@@ -12754,8 +12778,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12754
12778
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
12755
12779
  namespace: string().optional(),
12756
12780
  collection: string(),
12757
- record: SettingsRecordSchema
12758
- }), _void(), {
12781
+ record: object({
12782
+ id: string().optional(),
12783
+ data: record(string(), unknown())
12784
+ })
12785
+ }), object({
12786
+ /**
12787
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12788
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12789
+ * primary key — which is the only place an auto key is knowable.
12790
+ */
12791
+ id: union([string(), number()]) }), {
12759
12792
  kind: "mutation",
12760
12793
  auth: "admin"
12761
12794
  }), method(object({
@@ -19438,7 +19471,20 @@ var TrackSchema = object({
19438
19471
  ...TrackRetrainFields
19439
19472
  });
19440
19473
  var BaseEventFields = {
19441
- id: string(),
19474
+ /**
19475
+ * A SQLite ROWID, assigned by the database (D474).
19476
+ *
19477
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
19478
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
19479
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
19480
+ * the rowid: the table itself is that B-tree, so the index stops existing
19481
+ * rather than getting smaller. No shorter string does that.
19482
+ *
19483
+ * Defined once here for all three event kinds, which is why they move
19484
+ * together: a per-table migration would have forked this and
19485
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
19486
+ */
19487
+ id: number().int(),
19442
19488
  deviceId: number(),
19443
19489
  timestamp: number()
19444
19490
  };
@@ -19457,7 +19503,34 @@ var MotionEventSchema = object({
19457
19503
  /** Omitted in slim projection. */
19458
19504
  frameHeight: number().optional(),
19459
19505
  /** Populated by B5 (recording playback URL for this event). */
19460
- mediaUrl: string().optional()
19506
+ mediaUrl: string().optional(),
19507
+ /**
19508
+ * One row per motion EPISODE, not one per push (D475). `null` while the
19509
+ * episode is still open — a further rising edge extends it in place rather
19510
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
19511
+ * (the span from the first rising edge to the LAST one, deliberately NOT
19512
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
19513
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
19514
+ * motion for an instantaneous trigger).
19515
+ *
19516
+ * **Absent** (not merely `null`) on a row written before D475 — that means
19517
+ * "closed the old way, before this column existed", never "still open".
19518
+ * Nothing in this codebase may read an absent `durationMs` as an open
19519
+ * episode; only `null` means open.
19520
+ */
19521
+ durationMs: number().nullable().optional(),
19522
+ /**
19523
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
19524
+ * first entry is always `0`) of every genuine off→on transition the
19525
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
19526
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
19527
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
19528
+ * explicit `false` mid-episode and then resumes before the quiet window
19529
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
19530
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
19531
+ * which must never be read as "no episode happened here".
19532
+ */
19533
+ edges: array(number()).readonly().optional()
19461
19534
  });
19462
19535
  /**
19463
19536
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -19512,6 +19585,23 @@ var ObjectEventSchema = object({
19512
19585
  * includes it (it is light). Absent on rows written before this field.
19513
19586
  */
19514
19587
  frameId: string().optional(),
19588
+ /**
19589
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
19590
+ * (D474).
19591
+ *
19592
+ * Only the package detector writes it, and it exists because the event id
19593
+ * stopped being choosable: the delivery and pick-up rows used to BE their
19594
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
19595
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
19596
+ * assigned by SQLite, so that key had to move off the primary key rather
19597
+ * than be dropped — a detector that cannot recognise its own row re-delivers
19598
+ * every parcel on every boot.
19599
+ *
19600
+ * Absent on every other object event, and on every row written before this
19601
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
19602
+ * not per row, and nothing addresses a row by it.
19603
+ */
19604
+ idempotencyKey: string().optional(),
19515
19605
  /** Omitted in slim projection. */
19516
19606
  trackId: string().optional(),
19517
19607
  className: string(),
@@ -26578,9 +26668,54 @@ object({
26578
26668
  /** Ms epoch of the last detected-true observation. Null if never detected. */
26579
26669
  lastDetectedAt: number().nullable(),
26580
26670
  /**
26581
- * Ms after which `detected` auto-reverts to false if no fresh push
26582
- * arrives. Null means the provider leaves detected state until a
26583
- * native "clear" event.
26671
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
26672
+ * `null` while false and on every `Sensor` device (D475) — see that
26673
+ * constant's doc for the one-authority rule.
26674
+ *
26675
+ * ## Reading this field still arms nothing
26676
+ *
26677
+ * It reads like an instruction to the consumer ("revert after N ms if
26678
+ * no fresh push arrives") and it is not one: nothing in this repo reads
26679
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
26680
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
26681
+ * imported constant, not as a read of `device.state.motion.value`, so this
26682
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
26683
+ * computed elsewhere. Building a self-clear timer out of a READ of this
26684
+ * field would add a second falling-edge authority beside whichever one
26685
+ * already owns the device, and two that can disagree are worse than one.
26686
+ * Consumers that need a falling edge SHAPED differently — held open across
26687
+ * a flapping source — debounce on their own side and say so, as
26688
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
26689
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
26690
+ *
26691
+ * ## Who writes it
26692
+ *
26693
+ * - **Cameras** — the runner's phase machine, `active → watching` on
26694
+ * `cooldown_expired`, which then writes this slice with
26695
+ * `detected: false` (`handlePhaseChanged` in
26696
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
26697
+ * matters most for the sources that only ever push a rising one:
26698
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
26699
+ * a false.
26700
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
26701
+ * provider pushes the false itself, from the upstream system's own
26702
+ * state change. No phase machine is involved.
26703
+ *
26704
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
26705
+ *
26706
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
26707
+ * phase machine is the sole writer for a camera. It is not.
26708
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
26709
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
26710
+ * Hikvision's comment says why: it read THIS docblock, agreed the
26711
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
26712
+ * two authorities can disagree about one slice, which this repo
26713
+ * forbids, and the doc said otherwise — which is worse than saying
26714
+ * nothing, because it reads as verification.
26715
+ *
26716
+ * This predates D475 and is not fixed there: the fix touches every
26717
+ * camera provider. Recorded in D475's Consequences. Do not restore the
26718
+ * "sole writer" wording without also removing the other writers.
26584
26719
  */
26585
26720
  autoClearAfterMs: number().nullable()
26586
26721
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-alexa",
3
- "version": "1.2.103",
3
+ "version": "1.2.104",
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",