camstack 1.2.142 → 1.2.144

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.
@@ -15389,6 +15389,25 @@ import { createHash } from "crypto";
15389
15389
  var DEFAULT_MAX_UNIT_BYTES = 16 * 1024 * 1024;
15390
15390
 
15391
15391
  // ../types/dist/index.mjs
15392
+ var MEDIA_OWNER_TYPES = [
15393
+ "track",
15394
+ "summary",
15395
+ "face",
15396
+ "identity",
15397
+ "plate",
15398
+ "vehicle",
15399
+ "scene",
15400
+ "motion",
15401
+ "object",
15402
+ "audio"
15403
+ ];
15404
+ var OWNER_TYPES = new Set(MEDIA_OWNER_TYPES);
15405
+ var EVENT_OWNER_TYPES = [
15406
+ "motion",
15407
+ "object",
15408
+ "audio"
15409
+ ];
15410
+ var EVENT_OWNER_TYPE_SET = new Set(EVENT_OWNER_TYPES);
15392
15411
  var VideoEncodeSchema = external_exports.object({
15393
15412
  codec: external_exports.enum([
15394
15413
  "h264",
@@ -19991,8 +20010,18 @@ var settingsStoreCapability = {
19991
20010
  insert: method(external_exports.object({
19992
20011
  namespace: external_exports.string().optional(),
19993
20012
  collection: external_exports.string(),
19994
- record: SettingsRecordSchema
19995
- }), external_exports.void(), { kind: "mutation" }),
20013
+ record: external_exports.object({
20014
+ id: external_exports.string().optional(),
20015
+ data: external_exports.record(external_exports.string(), external_exports.unknown())
20016
+ })
20017
+ }), external_exports.object({
20018
+ /**
20019
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
20020
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
20021
+ * primary key — which is the only place an auto key is knowable.
20022
+ */
20023
+ id: external_exports.union([external_exports.string(), external_exports.number()])
20024
+ }), { kind: "mutation" }),
19996
20025
  /**
19997
20026
  * Insert MANY records in ONE transaction, returning how many landed.
19998
20027
  *
@@ -20205,8 +20234,18 @@ var dataStoreProviderCapability = {
20205
20234
  insert: method(external_exports.object({
20206
20235
  namespace: external_exports.string().optional(),
20207
20236
  collection: external_exports.string(),
20208
- record: SettingsRecordSchema
20209
- }), external_exports.void(), {
20237
+ record: external_exports.object({
20238
+ id: external_exports.string().optional(),
20239
+ data: external_exports.record(external_exports.string(), external_exports.unknown())
20240
+ })
20241
+ }), external_exports.object({
20242
+ /**
20243
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
20244
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
20245
+ * primary key — which is the only place an auto key is knowable.
20246
+ */
20247
+ id: external_exports.union([external_exports.string(), external_exports.number()])
20248
+ }), {
20210
20249
  kind: "mutation",
20211
20250
  auth: "admin"
20212
20251
  }),
@@ -26794,7 +26833,20 @@ var TrackSchema = external_exports.object({
26794
26833
  ...TrackRetrainFields
26795
26834
  });
26796
26835
  var BaseEventFields = {
26797
- id: external_exports.string(),
26836
+ /**
26837
+ * A SQLite ROWID, assigned by the database (D474).
26838
+ *
26839
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
26840
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
26841
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
26842
+ * the rowid: the table itself is that B-tree, so the index stops existing
26843
+ * rather than getting smaller. No shorter string does that.
26844
+ *
26845
+ * Defined once here for all three event kinds, which is why they move
26846
+ * together: a per-table migration would have forked this and
26847
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
26848
+ */
26849
+ id: external_exports.number().int(),
26798
26850
  deviceId: external_exports.number(),
26799
26851
  timestamp: external_exports.number()
26800
26852
  };
@@ -26813,7 +26865,34 @@ var MotionEventSchema = external_exports.object({
26813
26865
  /** Omitted in slim projection. */
26814
26866
  frameHeight: external_exports.number().optional(),
26815
26867
  /** Populated by B5 (recording playback URL for this event). */
26816
- mediaUrl: external_exports.string().optional()
26868
+ mediaUrl: external_exports.string().optional(),
26869
+ /**
26870
+ * One row per motion EPISODE, not one per push (D475). `null` while the
26871
+ * episode is still open — a further rising edge extends it in place rather
26872
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
26873
+ * (the span from the first rising edge to the LAST one, deliberately NOT
26874
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
26875
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
26876
+ * motion for an instantaneous trigger).
26877
+ *
26878
+ * **Absent** (not merely `null`) on a row written before D475 — that means
26879
+ * "closed the old way, before this column existed", never "still open".
26880
+ * Nothing in this codebase may read an absent `durationMs` as an open
26881
+ * episode; only `null` means open.
26882
+ */
26883
+ durationMs: external_exports.number().nullable().optional(),
26884
+ /**
26885
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
26886
+ * first entry is always `0`) of every genuine off→on transition the
26887
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
26888
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
26889
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
26890
+ * explicit `false` mid-episode and then resumes before the quiet window
26891
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
26892
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
26893
+ * which must never be read as "no episode happened here".
26894
+ */
26895
+ edges: external_exports.array(external_exports.number()).readonly().optional()
26817
26896
  });
26818
26897
  var DetectionSourceSchema = external_exports.enum(["pipeline", "onboard"]);
26819
26898
  var ZoneCrossingDirectionSchema = external_exports.enum(["enter", "exit"]);
@@ -26844,6 +26923,23 @@ var ObjectEventSchema = external_exports.object({
26844
26923
  * includes it (it is light). Absent on rows written before this field.
26845
26924
  */
26846
26925
  frameId: external_exports.string().optional(),
26926
+ /**
26927
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
26928
+ * (D474).
26929
+ *
26930
+ * Only the package detector writes it, and it exists because the event id
26931
+ * stopped being choosable: the delivery and pick-up rows used to BE their
26932
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
26933
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
26934
+ * assigned by SQLite, so that key had to move off the primary key rather
26935
+ * than be dropped — a detector that cannot recognise its own row re-delivers
26936
+ * every parcel on every boot.
26937
+ *
26938
+ * Absent on every other object event, and on every row written before this
26939
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
26940
+ * not per row, and nothing addresses a row by it.
26941
+ */
26942
+ idempotencyKey: external_exports.string().optional(),
26847
26943
  /** Omitted in slim projection. */
26848
26944
  trackId: external_exports.string().optional(),
26849
26945
  className: external_exports.string(),
@@ -35859,9 +35955,54 @@ var MotionStatusSchema = external_exports.object({
35859
35955
  /** Ms epoch of the last detected-true observation. Null if never detected. */
35860
35956
  lastDetectedAt: external_exports.number().nullable(),
35861
35957
  /**
35862
- * Ms after which `detected` auto-reverts to false if no fresh push
35863
- * arrives. Null means the provider leaves detected state until a
35864
- * native "clear" event.
35958
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
35959
+ * `null` while false and on every `Sensor` device (D475) — see that
35960
+ * constant's doc for the one-authority rule.
35961
+ *
35962
+ * ## Reading this field still arms nothing
35963
+ *
35964
+ * It reads like an instruction to the consumer ("revert after N ms if
35965
+ * no fresh push arrives") and it is not one: nothing in this repo reads
35966
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
35967
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
35968
+ * imported constant, not as a read of `device.state.motion.value`, so this
35969
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
35970
+ * computed elsewhere. Building a self-clear timer out of a READ of this
35971
+ * field would add a second falling-edge authority beside whichever one
35972
+ * already owns the device, and two that can disagree are worse than one.
35973
+ * Consumers that need a falling edge SHAPED differently — held open across
35974
+ * a flapping source — debounce on their own side and say so, as
35975
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
35976
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
35977
+ *
35978
+ * ## Who writes it
35979
+ *
35980
+ * - **Cameras** — the runner's phase machine, `active → watching` on
35981
+ * `cooldown_expired`, which then writes this slice with
35982
+ * `detected: false` (`handlePhaseChanged` in
35983
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
35984
+ * matters most for the sources that only ever push a rising one:
35985
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
35986
+ * a false.
35987
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
35988
+ * provider pushes the false itself, from the upstream system's own
35989
+ * state change. No phase machine is involved.
35990
+ *
35991
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
35992
+ *
35993
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
35994
+ * phase machine is the sole writer for a camera. It is not.
35995
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
35996
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
35997
+ * Hikvision's comment says why: it read THIS docblock, agreed the
35998
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
35999
+ * two authorities can disagree about one slice, which this repo
36000
+ * forbids, and the doc said otherwise — which is worse than saying
36001
+ * nothing, because it reads as verification.
36002
+ *
36003
+ * This predates D475 and is not fixed there: the fix touches every
36004
+ * camera provider. Recorded in D475's Consequences. Do not restore the
36005
+ * "sole writer" wording without also removing the other writers.
35865
36006
  */
35866
36007
  autoClearAfterMs: external_exports.number().nullable()
35867
36008
  });
@@ -35927,7 +36068,11 @@ var motionCapability = {
35927
36068
  */
35928
36069
  runtimeState: MotionStatusSchema,
35929
36070
  /**
35930
- * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
36071
+ * Runtime-state durability: **session** — every writer of this slice
36072
+ * writes only on an EDGE, so a restored `detected: true` would stay
36073
+ * frozen until the next one instead of being corrected. The next edge
36074
+ * re-publishes the real state. (On who the writers are, and why there
36075
+ * is more than one, see `autoClearAfterMs` above.)
35931
36076
  *
35932
36077
  * See `RuntimeStateDurability`. Enforced by
35933
36078
  * `scripts/check-runtime-state-durability.ts`.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-I7QELQM5.js";
4
+ } from "./chunk-E3UKNBI7.js";
5
5
  import "./chunk-LMMQX4CK.js";
6
6
 
7
7
  // src/cli.ts
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-7S4JDW34.js");
41
+ await import("./launcher-25V2QKN6.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-7S4JDW34.js");
86
+ await import("./launcher-25V2QKN6.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -1140,7 +1140,7 @@ function isUnknown(_value) {
1140
1140
  return true;
1141
1141
  }
1142
1142
  async function resolveServerInteractive(presetNamespace) {
1143
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-ELE6KHU4.js");
1143
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-YH7C75RI.js");
1144
1144
  if (presetNamespace) {
1145
1145
  const spinner4 = clack.spinner();
1146
1146
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -5,7 +5,7 @@ import {
5
5
  filterHubNodes,
6
6
  resolveHubFromDiscovered,
7
7
  runDiscover
8
- } from "./chunk-I7QELQM5.js";
8
+ } from "./chunk-E3UKNBI7.js";
9
9
  import "./chunk-LMMQX4CK.js";
10
10
  export {
11
11
  DEFAULT_HUB_HTTPS_PORT,
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-0sWJNwme.js
23637
- var require_dist_0sWJNwme = __commonJS({
23638
- "../system/dist/dist-0sWJNwme.js"(exports) {
23636
+ // ../system/dist/dist-DdRxWA7g.js
23637
+ var require_dist_DdRxWA7g = __commonJS({
23638
+ "../system/dist/dist-DdRxWA7g.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -30743,8 +30743,18 @@ var require_dist_0sWJNwme = __commonJS({
30743
30743
  insert: method(zod.z.object({
30744
30744
  namespace: zod.z.string().optional(),
30745
30745
  collection: zod.z.string(),
30746
- record: SettingsRecordSchema
30747
- }), zod.z.void(), { kind: "mutation" }),
30746
+ record: zod.z.object({
30747
+ id: zod.z.string().optional(),
30748
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
30749
+ })
30750
+ }), zod.z.object({
30751
+ /**
30752
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
30753
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
30754
+ * primary key — which is the only place an auto key is knowable.
30755
+ */
30756
+ id: zod.z.union([zod.z.string(), zod.z.number()])
30757
+ }), { kind: "mutation" }),
30748
30758
  /**
30749
30759
  * Insert MANY records in ONE transaction, returning how many landed.
30750
30760
  *
@@ -30957,8 +30967,18 @@ var require_dist_0sWJNwme = __commonJS({
30957
30967
  insert: method(zod.z.object({
30958
30968
  namespace: zod.z.string().optional(),
30959
30969
  collection: zod.z.string(),
30960
- record: SettingsRecordSchema
30961
- }), zod.z.void(), {
30970
+ record: zod.z.object({
30971
+ id: zod.z.string().optional(),
30972
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
30973
+ })
30974
+ }), zod.z.object({
30975
+ /**
30976
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
30977
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
30978
+ * primary key — which is the only place an auto key is knowable.
30979
+ */
30980
+ id: zod.z.union([zod.z.string(), zod.z.number()])
30981
+ }), {
30962
30982
  kind: "mutation",
30963
30983
  auth: "admin"
30964
30984
  }),
@@ -37573,7 +37593,20 @@ var require_dist_0sWJNwme = __commonJS({
37573
37593
  ...TrackRetrainFields
37574
37594
  });
37575
37595
  var BaseEventFields = {
37576
- id: zod.z.string(),
37596
+ /**
37597
+ * A SQLite ROWID, assigned by the database (D474).
37598
+ *
37599
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
37600
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
37601
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
37602
+ * the rowid: the table itself is that B-tree, so the index stops existing
37603
+ * rather than getting smaller. No shorter string does that.
37604
+ *
37605
+ * Defined once here for all three event kinds, which is why they move
37606
+ * together: a per-table migration would have forked this and
37607
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
37608
+ */
37609
+ id: zod.z.number().int(),
37577
37610
  deviceId: zod.z.number(),
37578
37611
  timestamp: zod.z.number()
37579
37612
  };
@@ -37592,7 +37625,34 @@ var require_dist_0sWJNwme = __commonJS({
37592
37625
  /** Omitted in slim projection. */
37593
37626
  frameHeight: zod.z.number().optional(),
37594
37627
  /** Populated by B5 (recording playback URL for this event). */
37595
- mediaUrl: zod.z.string().optional()
37628
+ mediaUrl: zod.z.string().optional(),
37629
+ /**
37630
+ * One row per motion EPISODE, not one per push (D475). `null` while the
37631
+ * episode is still open — a further rising edge extends it in place rather
37632
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
37633
+ * (the span from the first rising edge to the LAST one, deliberately NOT
37634
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
37635
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
37636
+ * motion for an instantaneous trigger).
37637
+ *
37638
+ * **Absent** (not merely `null`) on a row written before D475 — that means
37639
+ * "closed the old way, before this column existed", never "still open".
37640
+ * Nothing in this codebase may read an absent `durationMs` as an open
37641
+ * episode; only `null` means open.
37642
+ */
37643
+ durationMs: zod.z.number().nullable().optional(),
37644
+ /**
37645
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
37646
+ * first entry is always `0`) of every genuine off→on transition the
37647
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
37648
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
37649
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
37650
+ * explicit `false` mid-episode and then resumes before the quiet window
37651
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
37652
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
37653
+ * which must never be read as "no episode happened here".
37654
+ */
37655
+ edges: zod.z.array(zod.z.number()).readonly().optional()
37596
37656
  });
37597
37657
  var DetectionSourceSchema = zod.z.enum(["pipeline", "onboard"]);
37598
37658
  var ZoneCrossingDirectionSchema = zod.z.enum(["enter", "exit"]);
@@ -37623,6 +37683,23 @@ var require_dist_0sWJNwme = __commonJS({
37623
37683
  * includes it (it is light). Absent on rows written before this field.
37624
37684
  */
37625
37685
  frameId: zod.z.string().optional(),
37686
+ /**
37687
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
37688
+ * (D474).
37689
+ *
37690
+ * Only the package detector writes it, and it exists because the event id
37691
+ * stopped being choosable: the delivery and pick-up rows used to BE their
37692
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
37693
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
37694
+ * assigned by SQLite, so that key had to move off the primary key rather
37695
+ * than be dropped — a detector that cannot recognise its own row re-delivers
37696
+ * every parcel on every boot.
37697
+ *
37698
+ * Absent on every other object event, and on every row written before this
37699
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
37700
+ * not per row, and nothing addresses a row by it.
37701
+ */
37702
+ idempotencyKey: zod.z.string().optional(),
37626
37703
  /** Omitted in slim projection. */
37627
37704
  trackId: zod.z.string().optional(),
37628
37705
  className: zod.z.string(),
@@ -46462,9 +46539,54 @@ var require_dist_0sWJNwme = __commonJS({
46462
46539
  /** Ms epoch of the last detected-true observation. Null if never detected. */
46463
46540
  lastDetectedAt: zod.z.number().nullable(),
46464
46541
  /**
46465
- * Ms after which `detected` auto-reverts to false if no fresh push
46466
- * arrives. Null means the provider leaves detected state until a
46467
- * native "clear" event.
46542
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
46543
+ * `null` while false and on every `Sensor` device (D475) — see that
46544
+ * constant's doc for the one-authority rule.
46545
+ *
46546
+ * ## Reading this field still arms nothing
46547
+ *
46548
+ * It reads like an instruction to the consumer ("revert after N ms if
46549
+ * no fresh push arrives") and it is not one: nothing in this repo reads
46550
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
46551
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
46552
+ * imported constant, not as a read of `device.state.motion.value`, so this
46553
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
46554
+ * computed elsewhere. Building a self-clear timer out of a READ of this
46555
+ * field would add a second falling-edge authority beside whichever one
46556
+ * already owns the device, and two that can disagree are worse than one.
46557
+ * Consumers that need a falling edge SHAPED differently — held open across
46558
+ * a flapping source — debounce on their own side and say so, as
46559
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
46560
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
46561
+ *
46562
+ * ## Who writes it
46563
+ *
46564
+ * - **Cameras** — the runner's phase machine, `active → watching` on
46565
+ * `cooldown_expired`, which then writes this slice with
46566
+ * `detected: false` (`handlePhaseChanged` in
46567
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
46568
+ * matters most for the sources that only ever push a rising one:
46569
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
46570
+ * a false.
46571
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
46572
+ * provider pushes the false itself, from the upstream system's own
46573
+ * state change. No phase machine is involved.
46574
+ *
46575
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
46576
+ *
46577
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
46578
+ * phase machine is the sole writer for a camera. It is not.
46579
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
46580
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
46581
+ * Hikvision's comment says why: it read THIS docblock, agreed the
46582
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
46583
+ * two authorities can disagree about one slice, which this repo
46584
+ * forbids, and the doc said otherwise — which is worse than saying
46585
+ * nothing, because it reads as verification.
46586
+ *
46587
+ * This predates D475 and is not fixed there: the fix touches every
46588
+ * camera provider. Recorded in D475's Consequences. Do not restore the
46589
+ * "sole writer" wording without also removing the other writers.
46468
46590
  */
46469
46591
  autoClearAfterMs: zod.z.number().nullable()
46470
46592
  });
@@ -46530,7 +46652,11 @@ var require_dist_0sWJNwme = __commonJS({
46530
46652
  */
46531
46653
  runtimeState: MotionStatusSchema,
46532
46654
  /**
46533
- * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
46655
+ * Runtime-state durability: **session** — every writer of this slice
46656
+ * writes only on an EDGE, so a restored `detected: true` would stay
46657
+ * frozen until the next one instead of being corrected. The next edge
46658
+ * re-publishes the real state. (On who the writers are, and why there
46659
+ * is more than one, see `autoClearAfterMs` above.)
46534
46660
  *
46535
46661
  * See `RuntimeStateDurability`. Enforced by
46536
46662
  * `scripts/check-runtime-state-durability.ts`.
@@ -61739,7 +61865,7 @@ var require_alerts_addon = __commonJS({
61739
61865
  [Symbol.toStringTag]: { value: "Module" }
61740
61866
  });
61741
61867
  require_chunk_Cek0wNdY();
61742
- var require_dist10 = require_dist_0sWJNwme();
61868
+ var require_dist10 = require_dist_DdRxWA7g();
61743
61869
  function selectExpired(alerts, cutoffMs) {
61744
61870
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
61745
61871
  }
@@ -62558,7 +62684,7 @@ var require_console_logging = __commonJS({
62558
62684
  [Symbol.toStringTag]: { value: "Module" }
62559
62685
  });
62560
62686
  require_chunk_Cek0wNdY();
62561
- var require_dist10 = require_dist_0sWJNwme();
62687
+ var require_dist10 = require_dist_DdRxWA7g();
62562
62688
  var require_formatter = require_formatter_DqAKDlvN();
62563
62689
  var LEVEL_RANK = {
62564
62690
  debug: 0,
@@ -62652,7 +62778,7 @@ var require_core_blocks_addon = __commonJS({
62652
62778
  "use strict";
62653
62779
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
62654
62780
  var require_chunk = require_chunk_Cek0wNdY();
62655
- var require_dist10 = require_dist_0sWJNwme();
62781
+ var require_dist10 = require_dist_DdRxWA7g();
62656
62782
  var node_crypto = __require("crypto");
62657
62783
  var node_fs_promises = __require("fs/promises");
62658
62784
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -63549,11 +63675,11 @@ var require_core_blocks = __commonJS({
63549
63675
  }
63550
63676
  });
63551
63677
 
63552
- // ../system/dist/retired-settings-keys-Dh-XMfFx.js
63553
- var require_retired_settings_keys_Dh_XMfFx = __commonJS({
63554
- "../system/dist/retired-settings-keys-Dh-XMfFx.js"(exports) {
63678
+ // ../system/dist/retired-settings-keys-C-jOWHI0.js
63679
+ var require_retired_settings_keys_C_jOWHI0 = __commonJS({
63680
+ "../system/dist/retired-settings-keys-C-jOWHI0.js"(exports) {
63555
63681
  "use strict";
63556
- var require_dist10 = require_dist_0sWJNwme();
63682
+ var require_dist10 = require_dist_DdRxWA7g();
63557
63683
  function settingsStoreIsAuthoritativeHere(env) {
63558
63684
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
63559
63685
  return raw === "" || raw === "hub";
@@ -65788,8 +65914,8 @@ var require_device_manager_addon = __commonJS({
65788
65914
  [Symbol.toStringTag]: { value: "Module" }
65789
65915
  });
65790
65916
  require_chunk_Cek0wNdY();
65791
- var require_dist10 = require_dist_0sWJNwme();
65792
- var require_retired_settings_keys = require_retired_settings_keys_Dh_XMfFx();
65917
+ var require_dist10 = require_dist_DdRxWA7g();
65918
+ var require_retired_settings_keys = require_retired_settings_keys_C_jOWHI0();
65793
65919
  var node_crypto = __require("crypto");
65794
65920
  var _camstack_types_node = require_node();
65795
65921
  var JOB_HISTORY = 20;
@@ -71414,7 +71540,7 @@ var require_hub_forwarder = __commonJS({
71414
71540
  [Symbol.toStringTag]: { value: "Module" }
71415
71541
  });
71416
71542
  require_chunk_Cek0wNdY();
71417
- var require_dist10 = require_dist_0sWJNwme();
71543
+ var require_dist10 = require_dist_DdRxWA7g();
71418
71544
  var require_formatter = require_formatter_DqAKDlvN();
71419
71545
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
71420
71546
  var HubForwarderDestination = class {
@@ -71551,7 +71677,7 @@ var require_liveness_monitor_addon = __commonJS({
71551
71677
  "use strict";
71552
71678
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
71553
71679
  require_chunk_Cek0wNdY();
71554
- var require_dist10 = require_dist_0sWJNwme();
71680
+ var require_dist10 = require_dist_DdRxWA7g();
71555
71681
  var NO_DEVICES = "liveness:no-devices";
71556
71682
  var ALL_OFFLINE = "liveness:all-devices-offline";
71557
71683
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -71747,7 +71873,7 @@ var require_local_auth_addon = __commonJS({
71747
71873
  [Symbol.toStringTag]: { value: "Module" }
71748
71874
  });
71749
71875
  var require_chunk = require_chunk_Cek0wNdY();
71750
- var require_dist10 = require_dist_0sWJNwme();
71876
+ var require_dist10 = require_dist_DdRxWA7g();
71751
71877
  var node_crypto = __require("crypto");
71752
71878
  node_crypto = require_chunk.__toESM(node_crypto);
71753
71879
  var crypto$1 = __require("crypto");
@@ -79560,7 +79686,7 @@ var require_loki_logging = __commonJS({
79560
79686
  [Symbol.toStringTag]: { value: "Module" }
79561
79687
  });
79562
79688
  require_chunk_Cek0wNdY();
79563
- var require_dist10 = require_dist_0sWJNwme();
79689
+ var require_dist10 = require_dist_DdRxWA7g();
79564
79690
  function sanitizeLabelName(raw) {
79565
79691
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
79566
79692
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -80147,7 +80273,7 @@ var require_native_metrics_addon = __commonJS({
80147
80273
  [Symbol.toStringTag]: { value: "Module" }
80148
80274
  });
80149
80275
  var require_chunk = require_chunk_Cek0wNdY();
80150
- var require_dist10 = require_dist_0sWJNwme();
80276
+ var require_dist10 = require_dist_DdRxWA7g();
80151
80277
  var node_fs_promises = __require("fs/promises");
80152
80278
  var node_fs = __require("fs");
80153
80279
  node_fs = require_chunk.__toESM(node_fs);
@@ -82900,7 +83026,7 @@ var require_filesystem_storage_addon = __commonJS({
82900
83026
  [Symbol.toStringTag]: { value: "Module" }
82901
83027
  });
82902
83028
  var require_chunk = require_chunk_Cek0wNdY();
82903
- var require_dist10 = require_dist_0sWJNwme();
83029
+ var require_dist10 = require_dist_DdRxWA7g();
82904
83030
  var node_crypto = __require("crypto");
82905
83031
  var node_fs_promises = __require("fs/promises");
82906
83032
  var node_path = __require("path");
@@ -84127,8 +84253,8 @@ var require_sqlite_settings_addon = __commonJS({
84127
84253
  [Symbol.toStringTag]: { value: "Module" }
84128
84254
  });
84129
84255
  var require_chunk = require_chunk_Cek0wNdY();
84130
- var require_dist10 = require_dist_0sWJNwme();
84131
- var require_retired_settings_keys = require_retired_settings_keys_Dh_XMfFx();
84256
+ var require_dist10 = require_dist_DdRxWA7g();
84257
+ var require_retired_settings_keys = require_retired_settings_keys_C_jOWHI0();
84132
84258
  var require_wal_checkpoint_policy = require_wal_checkpoint_policy_CCJZngds();
84133
84259
  var node_crypto = __require("crypto");
84134
84260
  var node_path = __require("path");
@@ -84948,11 +85074,12 @@ var require_sqlite_settings_addon = __commonJS({
84948
85074
  async insert({ namespace, collection, record }) {
84949
85075
  const scoped = this.scopedName(namespace, collection);
84950
85076
  const decl = this.requireDeclared(scoped);
84951
- const id = record.id || (0, node_crypto.randomUUID)();
84952
- const row = { [decl.primaryKey]: id };
85077
+ const suppliedOrMinted = decl.autoPrimaryKey ? null : record.id || (0, node_crypto.randomUUID)();
85078
+ const row = decl.autoPrimaryKey ? {} : { [decl.primaryKey]: suppliedOrMinted };
84953
85079
  if (decl.columns.size === 1 && decl.columns.has("data")) row["data"] = JSON.stringify(record.data);
84954
85080
  else for (const [k, v] of Object.entries(record.data)) if (decl.columns.has(k)) row[k] = this.serializeColumnValue(v);
84955
- await this.tableInsert(scoped, row);
85081
+ const rowid = await this.tableInsert(scoped, row);
85082
+ return { id: suppliedOrMinted ?? Number(rowid) };
84956
85083
  }
84957
85084
  /**
84958
85085
  * Insert a batch in ONE transaction, on ONE prepared statement.
@@ -85829,6 +85956,8 @@ var require_sqlite_settings_addon = __commonJS({
85829
85956
  this.declaredCollections.delete(table);
85830
85957
  return { rows };
85831
85958
  }
85959
+ /** Returns SQLite's `lastInsertRowid` — the only place an auto-assigned key
85960
+ * is knowable (D473). */
85832
85961
  async tableInsert(table, row) {
85833
85962
  const keys = Object.keys(row);
85834
85963
  const cols = keys.map((k) => `"${k}"`).join(", ");
@@ -85837,7 +85966,7 @@ var require_sqlite_settings_addon = __commonJS({
85837
85966
  const v = row[k];
85838
85967
  return typeof v === "object" && v !== null ? JSON.stringify(v) : v;
85839
85968
  });
85840
- this.getDb().prepare(`INSERT INTO "${table}" (${cols}) VALUES (${placeholders})`).run(...values);
85969
+ return this.getDb().prepare(`INSERT INTO "${table}" (${cols}) VALUES (${placeholders})`).run(...values).lastInsertRowid;
85841
85970
  }
85842
85971
  async tableUpdate(table, filter, updates) {
85843
85972
  const setClauses = [];
@@ -86864,7 +86993,7 @@ var require_storage_orchestrator_addon = __commonJS({
86864
86993
  [Symbol.toStringTag]: { value: "Module" }
86865
86994
  });
86866
86995
  var require_chunk = require_chunk_Cek0wNdY();
86867
- var require_dist10 = require_dist_0sWJNwme();
86996
+ var require_dist10 = require_dist_DdRxWA7g();
86868
86997
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
86869
86998
  var zod = require_zod();
86870
86999
  var node_crypto = __require("crypto");
@@ -90514,7 +90643,7 @@ var require_system_config_addon = __commonJS({
90514
90643
  [Symbol.toStringTag]: { value: "Module" }
90515
90644
  });
90516
90645
  require_chunk_Cek0wNdY();
90517
- var require_dist10 = require_dist_0sWJNwme();
90646
+ var require_dist10 = require_dist_DdRxWA7g();
90518
90647
  var SECTION_TITLES = {
90519
90648
  server: "Server",
90520
90649
  auth: "Authentication"
@@ -108575,7 +108704,7 @@ var require_winston_logging = __commonJS({
108575
108704
  [Symbol.toStringTag]: { value: "Module" }
108576
108705
  });
108577
108706
  var require_chunk = require_chunk_Cek0wNdY();
108578
- var require_dist10 = require_dist_0sWJNwme();
108707
+ var require_dist10 = require_dist_DdRxWA7g();
108579
108708
  var require_formatter = require_formatter_DqAKDlvN();
108580
108709
  var node_path = __require("path");
108581
108710
  node_path = require_chunk.__toESM(node_path);
@@ -121311,9 +121440,9 @@ var require_dist2 = __commonJS({
121311
121440
  }
121312
121441
  });
121313
121442
 
121314
- // ../system/dist/manifest-system-deps-YYKKCKSF.js
121315
- var require_manifest_system_deps_YYKKCKSF = __commonJS({
121316
- "../system/dist/manifest-system-deps-YYKKCKSF.js"(exports) {
121443
+ // ../system/dist/manifest-system-deps-8x1jgPz_.js
121444
+ var require_manifest_system_deps_8x1jgPz = __commonJS({
121445
+ "../system/dist/manifest-system-deps-8x1jgPz_.js"(exports) {
121317
121446
  "use strict";
121318
121447
  var require_chunk = require_chunk_Cek0wNdY();
121319
121448
  require_hub_cap_forward_DmHNjbB0();
@@ -121812,7 +121941,7 @@ var require_manifest_system_deps_YYKKCKSF = __commonJS({
121812
121941
  }
121813
121942
  function formatMeshQueue(peers) {
121814
121943
  if (peers === void 0) return "";
121815
- const total = peers.reduce((sum, p) => sum + p.queuedBytes, 0);
121944
+ const total = peers.reduce((sum2, p) => sum2 + p.queuedBytes, 0);
121816
121945
  const top = peers.filter((p) => p.queuedBytes > 0).toSorted((a, b) => b.queuedBytes - a.queuedBytes || a.nodeId.localeCompare(b.nodeId)).slice(0, 3).map((p) => `${p.nodeId}:${kb(p.queuedBytes)}`).join(",");
121817
121946
  return ` meshQueued=${kb(total)}kB` + (top.length === 0 ? "" : ` meshQueuedTop=${top}`);
121818
121947
  }
@@ -122165,6 +122294,98 @@ var require_manifest_system_deps_YYKKCKSF = __commonJS({
122165
122294
  const perMin = (n) => window2.windowMs <= 0 ? "?" : (n / (window2.windowMs / 6e4)).toFixed(1);
122166
122295
  return ` gc=maj${window2.majors}/min${window2.minors}/inc${window2.incrementals}@${Math.round(window2.windowMs / 1e3)}s gcMajPerMin=${perMin(window2.majors)} gcPauseMs=${Math.round(window2.pauseMs)}`;
122167
122296
  }
122297
+ function createProcessCpuCensus(options) {
122298
+ let windowStart = options.startedAt;
122299
+ let cpuAtStart = options.cpuUsageUs();
122300
+ let tasksAtStart = options.readTasks();
122301
+ return { read: (at) => {
122302
+ const windowMs = at - windowStart;
122303
+ if (windowMs < 1e3) return null;
122304
+ const cpuNow = options.cpuUsageUs();
122305
+ const tasksNow = options.readTasks();
122306
+ const cpuUs = cpuNow - cpuAtStart;
122307
+ const percentOfCore = cpuUs <= 0 ? 0 : round1(cpuUs / 1e3 / windowMs * 100);
122308
+ const reading = {
122309
+ percentOfCore,
122310
+ windowMs,
122311
+ v8Workers: shareOfWorkers(tasksAtStart, tasksNow, percentOfCore)
122312
+ };
122313
+ windowStart = at;
122314
+ cpuAtStart = cpuNow;
122315
+ tasksAtStart = tasksNow;
122316
+ return reading;
122317
+ } };
122318
+ }
122319
+ function shareOfWorkers(before, after, percentOfCore) {
122320
+ if (before === null || after === null) return null;
122321
+ const b = sum(before);
122322
+ const a = sum(after);
122323
+ const workerTicks = a.worker - b.worker;
122324
+ const totalTicks = a.total - b.total;
122325
+ if (workerTicks < 0 || totalTicks <= 0 || workerTicks > totalTicks) return null;
122326
+ return {
122327
+ percentOfCore: round1(percentOfCore * (workerTicks / totalTicks)),
122328
+ threads: a.workerThreads
122329
+ };
122330
+ }
122331
+ var V8_WORKER_COMM = "V8Worker";
122332
+ function sum(tasks) {
122333
+ let worker2 = 0;
122334
+ let total = 0;
122335
+ let workerThreads = 0;
122336
+ for (const t of tasks) {
122337
+ total += t.ticks;
122338
+ if (t.comm === V8_WORKER_COMM) {
122339
+ worker2 += t.ticks;
122340
+ workerThreads += 1;
122341
+ }
122342
+ }
122343
+ return {
122344
+ worker: worker2,
122345
+ total,
122346
+ workerThreads
122347
+ };
122348
+ }
122349
+ function round1(n) {
122350
+ return Math.round(n * 10) / 10;
122351
+ }
122352
+ function readSelfTaskTicks() {
122353
+ try {
122354
+ const tids = (0, node_fs.readdirSync)("/proc/self/task");
122355
+ const out = [];
122356
+ for (const tid of tids) {
122357
+ const parsed = parseTaskStat(tid);
122358
+ if (parsed !== null) out.push(parsed);
122359
+ }
122360
+ return out.length === 0 ? null : out;
122361
+ } catch {
122362
+ return null;
122363
+ }
122364
+ }
122365
+ function parseTaskStat(tid) {
122366
+ try {
122367
+ const raw = (0, node_fs.readFileSync)(`/proc/self/task/${tid}/stat`, "utf8");
122368
+ const close = raw.lastIndexOf(") ");
122369
+ const open = raw.indexOf(" (");
122370
+ if (close < 0 || open < 0 || close <= open) return null;
122371
+ const comm = raw.slice(open + 2, close);
122372
+ const fields = raw.slice(close + 2).split(" ");
122373
+ const utime = Number(fields[11]);
122374
+ const stime = Number(fields[12]);
122375
+ if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
122376
+ return {
122377
+ comm,
122378
+ ticks: utime + stime
122379
+ };
122380
+ } catch {
122381
+ return null;
122382
+ }
122383
+ }
122384
+ function formatProcessCpu(reading) {
122385
+ if (reading === null || reading === void 0) return "";
122386
+ const workers = reading.v8Workers;
122387
+ return ` procCpu=${reading.percentOfCore.toFixed(1)}%` + (workers === null ? "" : ` v8WorkerCpu=${workers.percentOfCore.toFixed(1)}%/${workers.threads}thr`);
122388
+ }
122168
122389
  var HUB_MAIN_HEAP_WATCH_LABEL = "hub-main";
122169
122390
  var HEAP_WATCH_INTERVAL_MS = 6e4;
122170
122391
  var HEAP_WATCH_WARN_RATIO = 0.8;
@@ -122335,6 +122556,15 @@ var require_manifest_system_deps_YYKKCKSF = __commonJS({
122335
122556
  const now = reclaimOptions?.now ?? (() => Date.now());
122336
122557
  const gcObserver = probes?.gcObserver ?? nodeGcObserver();
122337
122558
  const gcCensus = createGcCensus(gcObserver.install, gcObserver.kinds, now());
122559
+ const cpuProbe = probes?.processCpu;
122560
+ const processCpu = createProcessCpuCensus({
122561
+ cpuUsageUs: cpuProbe?.cpuUsageUs ?? (() => {
122562
+ const u = process.cpuUsage();
122563
+ return u.user + u.system;
122564
+ }),
122565
+ readTasks: cpuProbe?.readTasks ?? readSelfTaskTicks,
122566
+ startedAt: now()
122567
+ });
122338
122568
  const triggerMb = reclaimOptions?.triggerMb ?? 1024;
122339
122569
  const minIntervalMs = reclaimOptions?.minIntervalMs ?? 12e4;
122340
122570
  const steadyStateIntervalMs = reclaimOptions?.steadyStateIntervalMs ?? 6e5;
@@ -122410,7 +122640,7 @@ var require_manifest_system_deps_YYKKCKSF = __commonJS({
122410
122640
  const due = at - lastLoggedAt >= intervalMs;
122411
122641
  if (mode === "escalated" || due) {
122412
122642
  lastLoggedAt = at;
122413
- const line = format2(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()) + formatMeshQueue(meshQueue?.()) + formatGcCensus(gcCensus?.read(at)));
122643
+ const line = format2(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()) + formatMeshQueue(meshQueue?.()) + formatGcCensus(gcCensus?.read(at)) + formatProcessCpu(processCpu.read(at)));
122414
122644
  if (sample.nearLimit) sink.warn(`${line} \u2014 APPROACHING HEAP LIMIT`);
122415
122645
  else if (mode === "escalated") sink.warn(`${line} \u2014 heap elevated, sampling every ${probeIntervalMs}ms`);
122416
122646
  else sink.info(line);
@@ -130734,11 +130964,11 @@ var require_manifest_system_deps_YYKKCKSF = __commonJS({
130734
130964
  }
130735
130965
  });
130736
130966
 
130737
- // ../system/dist/custom-action-registry-DvVMHLfJ.js
130738
- var require_custom_action_registry_DvVMHLfJ = __commonJS({
130739
- "../system/dist/custom-action-registry-DvVMHLfJ.js"(exports) {
130967
+ // ../system/dist/custom-action-registry-BTB-LgGN.js
130968
+ var require_custom_action_registry_BTB_LgGN = __commonJS({
130969
+ "../system/dist/custom-action-registry-BTB-LgGN.js"(exports) {
130740
130970
  "use strict";
130741
- var require_dist10 = require_dist_0sWJNwme();
130971
+ var require_dist10 = require_dist_DdRxWA7g();
130742
130972
  var CustomActionRegistry = class {
130743
130973
  byAddon = /* @__PURE__ */ new Map();
130744
130974
  registerAddon(addonId, catalog, handler) {
@@ -134552,7 +134782,7 @@ var require_dist3 = __commonJS({
134552
134782
  "use strict";
134553
134783
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
134554
134784
  var require_chunk = require_chunk_Cek0wNdY();
134555
- var require_dist10 = require_dist_0sWJNwme();
134785
+ var require_dist10 = require_dist_DdRxWA7g();
134556
134786
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
134557
134787
  require_alerts();
134558
134788
  var require_formatter = require_formatter_DqAKDlvN();
@@ -134579,9 +134809,9 @@ var require_dist3 = __commonJS({
134579
134809
  var require_builtins_winston_logging_index = require_winston_logging();
134580
134810
  var require_event_loop_stall_monitor = require_event_loop_stall_monitor_CAsHdMwP();
134581
134811
  var require_tls$1 = require_tls_BFs4PzoW();
134582
- var require_manifest_system_deps = require_manifest_system_deps_YYKKCKSF();
134812
+ var require_manifest_system_deps = require_manifest_system_deps_8x1jgPz();
134583
134813
  var require_resource_monitor = require_resource_monitor_LVE1BoLs();
134584
- var require_custom_action_registry = require_custom_action_registry_DvVMHLfJ();
134814
+ var require_custom_action_registry = require_custom_action_registry_BTB_LgGN();
134585
134815
  var zod = require_zod();
134586
134816
  var node_crypto = __require("crypto");
134587
134817
  var _camstack_types_node = require_node();
@@ -215695,6 +215925,56 @@ var require_dist4 = __commonJS({
215695
215925
  var require_enums2 = require_enums();
215696
215926
  var require_err_msg = require_err_msg_COpsHMw2();
215697
215927
  var zod = require_zod();
215928
+ var MEDIA_OWNER_TYPES = [
215929
+ "track",
215930
+ "summary",
215931
+ "face",
215932
+ "identity",
215933
+ "plate",
215934
+ "vehicle",
215935
+ "scene",
215936
+ "motion",
215937
+ "object",
215938
+ "audio"
215939
+ ];
215940
+ var OWNER_TYPES = new Set(MEDIA_OWNER_TYPES);
215941
+ var DIGITS = /^\d+$/;
215942
+ function isOwnerType(value) {
215943
+ return OWNER_TYPES.has(value);
215944
+ }
215945
+ function parseMediaOwnerKey(key) {
215946
+ const parts = key.split(":");
215947
+ if (parts.length !== 3 && parts.length !== 4) return null;
215948
+ const [ownerType, ownerId, fileKind, timestamp] = parts;
215949
+ if (ownerType === void 0 || !isOwnerType(ownerType)) return null;
215950
+ if (ownerId === void 0 || ownerId.length === 0) return null;
215951
+ if (fileKind === void 0 || fileKind.length === 0) return null;
215952
+ if (timestamp === void 0) return {
215953
+ ownerType,
215954
+ ownerId,
215955
+ fileKind
215956
+ };
215957
+ if (!DIGITS.test(timestamp)) return null;
215958
+ return {
215959
+ ownerType,
215960
+ ownerId,
215961
+ fileKind,
215962
+ timestampMs: Number(timestamp)
215963
+ };
215964
+ }
215965
+ function formatMediaOwnerKey(key) {
215966
+ const head = `${key.ownerType}:${key.ownerId}:${key.fileKind}`;
215967
+ return key.timestampMs === void 0 ? head : `${head}:${key.timestampMs}`;
215968
+ }
215969
+ var EVENT_OWNER_TYPES = [
215970
+ "motion",
215971
+ "object",
215972
+ "audio"
215973
+ ];
215974
+ var EVENT_OWNER_TYPE_SET = new Set(EVENT_OWNER_TYPES);
215975
+ function isEventOwnerType(ownerType) {
215976
+ return EVENT_OWNER_TYPE_SET.has(ownerType);
215977
+ }
215698
215978
  function buildAddonRouteProvider(id, routes) {
215699
215979
  return {
215700
215980
  id,
@@ -222599,8 +222879,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222599
222879
  insert: require_sleep.method(zod.z.object({
222600
222880
  namespace: zod.z.string().optional(),
222601
222881
  collection: zod.z.string(),
222602
- record: SettingsRecordSchema
222603
- }), zod.z.void(), { kind: "mutation" }),
222882
+ record: zod.z.object({
222883
+ id: zod.z.string().optional(),
222884
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
222885
+ })
222886
+ }), zod.z.object({
222887
+ /**
222888
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
222889
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
222890
+ * primary key — which is the only place an auto key is knowable.
222891
+ */
222892
+ id: zod.z.union([zod.z.string(), zod.z.number()])
222893
+ }), { kind: "mutation" }),
222604
222894
  /**
222605
222895
  * Insert MANY records in ONE transaction, returning how many landed.
222606
222896
  *
@@ -222813,8 +223103,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222813
223103
  insert: require_sleep.method(zod.z.object({
222814
223104
  namespace: zod.z.string().optional(),
222815
223105
  collection: zod.z.string(),
222816
- record: SettingsRecordSchema
222817
- }), zod.z.void(), {
223106
+ record: zod.z.object({
223107
+ id: zod.z.string().optional(),
223108
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
223109
+ })
223110
+ }), zod.z.object({
223111
+ /**
223112
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
223113
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
223114
+ * primary key — which is the only place an auto key is knowable.
223115
+ */
223116
+ id: zod.z.union([zod.z.string(), zod.z.number()])
223117
+ }), {
222818
223118
  kind: "mutation",
222819
223119
  auth: "admin"
222820
223120
  }),
@@ -230330,7 +230630,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
230330
230630
  ...TrackRetrainFields
230331
230631
  });
230332
230632
  var BaseEventFields = {
230333
- id: zod.z.string(),
230633
+ /**
230634
+ * A SQLite ROWID, assigned by the database (D474).
230635
+ *
230636
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
230637
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
230638
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
230639
+ * the rowid: the table itself is that B-tree, so the index stops existing
230640
+ * rather than getting smaller. No shorter string does that.
230641
+ *
230642
+ * Defined once here for all three event kinds, which is why they move
230643
+ * together: a per-table migration would have forked this and
230644
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
230645
+ */
230646
+ id: zod.z.number().int(),
230334
230647
  deviceId: zod.z.number(),
230335
230648
  timestamp: zod.z.number()
230336
230649
  };
@@ -230349,7 +230662,34 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
230349
230662
  /** Omitted in slim projection. */
230350
230663
  frameHeight: zod.z.number().optional(),
230351
230664
  /** Populated by B5 (recording playback URL for this event). */
230352
- mediaUrl: zod.z.string().optional()
230665
+ mediaUrl: zod.z.string().optional(),
230666
+ /**
230667
+ * One row per motion EPISODE, not one per push (D475). `null` while the
230668
+ * episode is still open — a further rising edge extends it in place rather
230669
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
230670
+ * (the span from the first rising edge to the LAST one, deliberately NOT
230671
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
230672
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
230673
+ * motion for an instantaneous trigger).
230674
+ *
230675
+ * **Absent** (not merely `null`) on a row written before D475 — that means
230676
+ * "closed the old way, before this column existed", never "still open".
230677
+ * Nothing in this codebase may read an absent `durationMs` as an open
230678
+ * episode; only `null` means open.
230679
+ */
230680
+ durationMs: zod.z.number().nullable().optional(),
230681
+ /**
230682
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
230683
+ * first entry is always `0`) of every genuine off→on transition the
230684
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
230685
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
230686
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
230687
+ * explicit `false` mid-episode and then resumes before the quiet window
230688
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
230689
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
230690
+ * which must never be read as "no episode happened here".
230691
+ */
230692
+ edges: zod.z.array(zod.z.number()).readonly().optional()
230353
230693
  });
230354
230694
  var DetectionSourceSchema = zod.z.enum(["pipeline", "onboard"]);
230355
230695
  var ZoneCrossingDirectionSchema = zod.z.enum(["enter", "exit"]);
@@ -230380,6 +230720,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
230380
230720
  * includes it (it is light). Absent on rows written before this field.
230381
230721
  */
230382
230722
  frameId: zod.z.string().optional(),
230723
+ /**
230724
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
230725
+ * (D474).
230726
+ *
230727
+ * Only the package detector writes it, and it exists because the event id
230728
+ * stopped being choosable: the delivery and pick-up rows used to BE their
230729
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
230730
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
230731
+ * assigned by SQLite, so that key had to move off the primary key rather
230732
+ * than be dropped — a detector that cannot recognise its own row re-delivers
230733
+ * every parcel on every boot.
230734
+ *
230735
+ * Absent on every other object event, and on every row written before this
230736
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
230737
+ * not per row, and nothing addresses a row by it.
230738
+ */
230739
+ idempotencyKey: zod.z.string().optional(),
230383
230740
  /** Omitted in slim projection. */
230384
230741
  trackId: zod.z.string().optional(),
230385
230742
  className: zod.z.string(),
@@ -239415,14 +239772,60 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239415
239772
  }), { kind: "mutation" })
239416
239773
  }
239417
239774
  };
239775
+ var MOTION_CLOSE_AFTER_MS = 15e3;
239418
239776
  var MotionStatusSchema = zod.z.object({
239419
239777
  detected: zod.z.boolean(),
239420
239778
  /** Ms epoch of the last detected-true observation. Null if never detected. */
239421
239779
  lastDetectedAt: zod.z.number().nullable(),
239422
239780
  /**
239423
- * Ms after which `detected` auto-reverts to false if no fresh push
239424
- * arrives. Null means the provider leaves detected state until a
239425
- * native "clear" event.
239781
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
239782
+ * `null` while false and on every `Sensor` device (D475) — see that
239783
+ * constant's doc for the one-authority rule.
239784
+ *
239785
+ * ## Reading this field still arms nothing
239786
+ *
239787
+ * It reads like an instruction to the consumer ("revert after N ms if
239788
+ * no fresh push arrives") and it is not one: nothing in this repo reads
239789
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
239790
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
239791
+ * imported constant, not as a read of `device.state.motion.value`, so this
239792
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
239793
+ * computed elsewhere. Building a self-clear timer out of a READ of this
239794
+ * field would add a second falling-edge authority beside whichever one
239795
+ * already owns the device, and two that can disagree are worse than one.
239796
+ * Consumers that need a falling edge SHAPED differently — held open across
239797
+ * a flapping source — debounce on their own side and say so, as
239798
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
239799
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
239800
+ *
239801
+ * ## Who writes it
239802
+ *
239803
+ * - **Cameras** — the runner's phase machine, `active → watching` on
239804
+ * `cooldown_expired`, which then writes this slice with
239805
+ * `detected: false` (`handlePhaseChanged` in
239806
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
239807
+ * matters most for the sources that only ever push a rising one:
239808
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
239809
+ * a false.
239810
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
239811
+ * provider pushes the false itself, from the upstream system's own
239812
+ * state change. No phase machine is involved.
239813
+ *
239814
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
239815
+ *
239816
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
239817
+ * phase machine is the sole writer for a camera. It is not.
239818
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
239819
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
239820
+ * Hikvision's comment says why: it read THIS docblock, agreed the
239821
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
239822
+ * two authorities can disagree about one slice, which this repo
239823
+ * forbids, and the doc said otherwise — which is worse than saying
239824
+ * nothing, because it reads as verification.
239825
+ *
239826
+ * This predates D475 and is not fixed there: the fix touches every
239827
+ * camera provider. Recorded in D475's Consequences. Do not restore the
239828
+ * "sole writer" wording without also removing the other writers.
239426
239829
  */
239427
239830
  autoClearAfterMs: zod.z.number().nullable()
239428
239831
  });
@@ -239488,7 +239891,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239488
239891
  */
239489
239892
  runtimeState: MotionStatusSchema,
239490
239893
  /**
239491
- * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
239894
+ * Runtime-state durability: **session** — every writer of this slice
239895
+ * writes only on an EDGE, so a restored `detected: true` would stay
239896
+ * frozen until the next one instead of being corrected. The next edge
239897
+ * re-publishes the real state. (On who the writers are, and why there
239898
+ * is more than one, see `autoClearAfterMs` above.)
239492
239899
  *
239493
239900
  * See `RuntimeStateDurability`. Enforced by
239494
239901
  * `scripts/check-runtime-state-durability.ts`.
@@ -261124,6 +261531,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
261124
261531
  exports.EVENTFUL_CAP_NAMES = EVENTFUL_CAP_NAMES;
261125
261532
  exports.EVENT_DENSITY_BATCH_MAX = EVENT_DENSITY_BATCH_MAX;
261126
261533
  exports.EVENT_KIND_BY_CAP = EVENT_KIND_BY_CAP;
261534
+ exports.EVENT_OWNER_TYPES = EVENT_OWNER_TYPES;
261127
261535
  exports.EVENT_PAD_MS = EVENT_PAD_MS;
261128
261536
  exports.EVENT_TAXONOMY = EVENT_TAXONOMY;
261129
261537
  exports.EXPORT_DENSE_MAX_RANGES = EXPORT_DENSE_MAX_RANGES;
@@ -261314,10 +261722,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
261314
261722
  exports.MAX_REASONS_PER_KEY = MAX_REASONS_PER_KEY;
261315
261723
  exports.MAX_SENSOR_TRIGGER_DEVICES = MAX_SENSOR_TRIGGER_DEVICES;
261316
261724
  exports.MAX_TRACK_DEBUG_NOTE_LEN = MAX_TRACK_DEBUG_NOTE_LEN;
261725
+ exports.MEDIA_OWNER_TYPES = MEDIA_OWNER_TYPES;
261317
261726
  exports.METHOD_ACCESS_MAP = METHOD_ACCESS_MAP;
261318
261727
  exports.METHOD_DEVICE_SELECTORS = METHOD_DEVICE_SELECTORS;
261319
261728
  exports.MODEL_FORMATS = MODEL_FORMATS;
261320
261729
  exports.MODEL_PROVIDER_IDS = MODEL_PROVIDER_IDS;
261730
+ exports.MOTION_CLOSE_AFTER_MS = MOTION_CLOSE_AFTER_MS;
261321
261731
  exports.MOTION_TRIGGER_FEATURE = MOTION_TRIGGER_FEATURE;
261322
261732
  exports.ManagedModelCatalogEntrySchema = ManagedModelCatalogEntrySchema;
261323
261733
  exports.ManagedModelExtraFileSchema = ManagedModelExtraFileSchema;
@@ -262089,6 +262499,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
262089
262499
  exports.foldSnapshotByFunction = foldSnapshotByFunction;
262090
262500
  exports.formatForBackend = formatForBackend;
262091
262501
  exports.formatForRuntime = formatForRuntime;
262502
+ exports.formatMediaOwnerKey = formatMediaOwnerKey;
262092
262503
  exports.gasCapability = gasCapability;
262093
262504
  exports.gbToBytes = gbToBytes;
262094
262505
  exports.generateAutomationBlock = generateAutomationBlock;
@@ -262124,6 +262535,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
262124
262535
  exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
262125
262536
  exports.isDeviceScopedCap = require_sleep.isDeviceScopedCap;
262126
262537
  exports.isEvent = require_sleep.isEvent;
262538
+ exports.isEventOwnerType = isEventOwnerType;
262127
262539
  exports.isFirstLevelMacroClass = isFirstLevelMacroClass;
262128
262540
  exports.isIsolatedBuiltin = isIsolatedBuiltin;
262129
262541
  exports.isLocationEnabled = isLocationEnabled;
@@ -262211,6 +262623,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
262211
262623
  exports.parseJsonArray = require_sleep.parseJsonArray;
262212
262624
  exports.parseJsonObject = require_sleep.parseJsonObject;
262213
262625
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
262626
+ exports.parseMediaOwnerKey = parseMediaOwnerKey;
262214
262627
  exports.parseProcStatus = parseProcStatus;
262215
262628
  exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
262216
262629
  exports.parseRuleSection = parseRuleSection;
@@ -262981,6 +263394,7 @@ var require_server_root = __commonJS({
262981
263394
  devUploadsDir: /* @__PURE__ */ __name(() => devUploadsDir, "devUploadsDir"),
262982
263395
  emptyServerRootState: /* @__PURE__ */ __name(() => emptyServerRootState, "emptyServerRootState"),
262983
263396
  findMissingNativePrebuilds: /* @__PURE__ */ __name(() => findMissingNativePrebuilds, "findMissingNativePrebuilds"),
263397
+ findUnappliedClosurePatches: /* @__PURE__ */ __name(() => findUnappliedClosurePatches, "findUnappliedClosurePatches"),
262984
263398
  isDevChannelVersion: /* @__PURE__ */ __name(() => isDevChannelVersion, "isDevChannelVersion"),
262985
263399
  isHostExternal: /* @__PURE__ */ __name(() => isHostExternal, "isHostExternal"),
262986
263400
  isServerRootState: /* @__PURE__ */ __name(() => isServerRootState, "isServerRootState"),
@@ -263385,6 +263799,58 @@ var require_server_root = __commonJS({
263385
263799
  return missing;
263386
263800
  }
263387
263801
  __name(findMissingNativePrebuilds, "findMissingNativePrebuilds");
263802
+ var CLOSURE_PATCH_PAYLOAD = [
263803
+ "node_modules",
263804
+ "@camstack",
263805
+ "server",
263806
+ "dist",
263807
+ "closure-patches"
263808
+ ];
263809
+ var CLOSURE_PATCH_MANIFEST = "patch-reach.json";
263810
+ function fileContains(file, marker) {
263811
+ try {
263812
+ return fs3.readFileSync(file, "utf-8").includes(marker);
263813
+ } catch {
263814
+ return false;
263815
+ }
263816
+ }
263817
+ __name(fileContains, "fileContains");
263818
+ function findUnappliedClosurePatches(closureDir) {
263819
+ const payloadDir = path3.join(closureDir, ...CLOSURE_PATCH_PAYLOAD);
263820
+ let manifest;
263821
+ try {
263822
+ const raw = JSON.parse(fs3.readFileSync(path3.join(payloadDir, CLOSURE_PATCH_MANIFEST), "utf-8"));
263823
+ if (typeof raw !== "object" || raw === null) return [];
263824
+ manifest = raw;
263825
+ } catch {
263826
+ return [];
263827
+ }
263828
+ if (!Array.isArray(manifest.patches)) return [];
263829
+ const nm = path3.join(closureDir, "node_modules");
263830
+ const missing = [];
263831
+ for (const raw of manifest.patches) {
263832
+ if (raw.reach !== "closure-postinstall") continue;
263833
+ const pkg = raw.package;
263834
+ const markers = raw.markers;
263835
+ if (typeof pkg !== "string" || typeof markers !== "object" || markers === null) continue;
263836
+ const pkgDir = path3.join(nm, pkg);
263837
+ if (!fs3.existsSync(pkgDir)) {
263838
+ continue;
263839
+ }
263840
+ for (const [rel, list] of Object.entries(markers)) {
263841
+ if (!Array.isArray(list)) continue;
263842
+ const target = path3.join(pkgDir, ...rel.split("/"));
263843
+ for (const marker of list) {
263844
+ if (typeof marker !== "string") continue;
263845
+ if (!fileContains(target, marker)) {
263846
+ missing.push(`${pkg}/${rel}: ${marker}`);
263847
+ }
263848
+ }
263849
+ }
263850
+ }
263851
+ return missing;
263852
+ }
263853
+ __name(findUnappliedClosurePatches, "findUnappliedClosurePatches");
263388
263854
  function rmrf(target) {
263389
263855
  try {
263390
263856
  fs3.rmSync(target, {
@@ -264321,6 +264787,10 @@ var require_server_root = __commonJS({
264321
264787
  if (missingNatives.length > 0) {
264322
264788
  throw new Error(`staged closure missing native prebuilds: ${missingNatives.join(", ")} \u2014 refusing to arm a swap that would break the forked runners`);
264323
264789
  }
264790
+ const unpatched = findUnappliedClosurePatches(stagingDir);
264791
+ if (unpatched.length > 0) {
264792
+ throw new Error(`staged closure did not apply its shipped patches: ${unpatched.join(", ")} \u2014 refusing to arm a swap into an unpatched closure`);
264793
+ }
264324
264794
  return stagingDir;
264325
264795
  } catch (err) {
264326
264796
  await fs6.promises.rm(stagingDir, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.142",
3
+ "version": "1.2.144",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",