@camstack/addon-post-analysis 1.2.223 → 1.2.225

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.
@@ -7491,6 +7491,34 @@ function errMsg(err) {
7491
7491
  if (typeof err === "string") return err;
7492
7492
  return String(err);
7493
7493
  }
7494
+ new Set([
7495
+ "track",
7496
+ "summary",
7497
+ "face",
7498
+ "identity",
7499
+ "plate",
7500
+ "vehicle",
7501
+ "scene",
7502
+ "motion",
7503
+ "object",
7504
+ "audio"
7505
+ ]);
7506
+ /** Build a media key. The timestamp segment is omitted when there is none. */
7507
+ function formatMediaOwnerKey(key) {
7508
+ const head = `${key.ownerType}:${key.ownerId}:${key.fileKind}`;
7509
+ return key.timestampMs === void 0 ? head : `${head}:${key.timestampMs}`;
7510
+ }
7511
+ /**
7512
+ * The owner types that are event tables, in the order the event-media resolver
7513
+ * should consider them. Exported so a consumer asking "is this key an event?"
7514
+ * does not re-spell the list and drift from it.
7515
+ */
7516
+ var EVENT_OWNER_TYPES = [
7517
+ "motion",
7518
+ "object",
7519
+ "audio"
7520
+ ];
7521
+ new Set(EVENT_OWNER_TYPES);
7494
7522
  var EncodeProfileSchema = object({
7495
7523
  video: object({
7496
7524
  codec: _enum([
@@ -13096,8 +13124,17 @@ method(object({
13096
13124
  }), array(SettingsRecordSchema).readonly()), method(object({
13097
13125
  namespace: string().optional(),
13098
13126
  collection: string(),
13099
- record: SettingsRecordSchema
13100
- }), _void(), { kind: "mutation" }), method(object({
13127
+ record: object({
13128
+ id: string().optional(),
13129
+ data: record(string(), unknown())
13130
+ })
13131
+ }), object({
13132
+ /**
13133
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13134
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13135
+ * primary key — which is the only place an auto key is knowable.
13136
+ */
13137
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
13101
13138
  namespace: string().optional(),
13102
13139
  collection: string(),
13103
13140
  records: array(BulkRecordSchema).readonly()
@@ -13206,8 +13243,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13206
13243
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
13207
13244
  namespace: string().optional(),
13208
13245
  collection: string(),
13209
- record: SettingsRecordSchema
13210
- }), _void(), {
13246
+ record: object({
13247
+ id: string().optional(),
13248
+ data: record(string(), unknown())
13249
+ })
13250
+ }), object({
13251
+ /**
13252
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13253
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13254
+ * primary key — which is the only place an auto key is knowable.
13255
+ */
13256
+ id: union([string(), number()]) }), {
13211
13257
  kind: "mutation",
13212
13258
  auth: "admin"
13213
13259
  }), method(object({
@@ -20701,7 +20747,20 @@ var TrackSchema = object({
20701
20747
  ...TrackRetrainFields
20702
20748
  });
20703
20749
  var BaseEventFields = {
20704
- id: string(),
20750
+ /**
20751
+ * A SQLite ROWID, assigned by the database (D474).
20752
+ *
20753
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
20754
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
20755
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
20756
+ * the rowid: the table itself is that B-tree, so the index stops existing
20757
+ * rather than getting smaller. No shorter string does that.
20758
+ *
20759
+ * Defined once here for all three event kinds, which is why they move
20760
+ * together: a per-table migration would have forked this and
20761
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
20762
+ */
20763
+ id: number().int(),
20705
20764
  deviceId: number(),
20706
20765
  timestamp: number()
20707
20766
  };
@@ -20720,7 +20779,34 @@ var MotionEventSchema = object({
20720
20779
  /** Omitted in slim projection. */
20721
20780
  frameHeight: number().optional(),
20722
20781
  /** Populated by B5 (recording playback URL for this event). */
20723
- mediaUrl: string().optional()
20782
+ mediaUrl: string().optional(),
20783
+ /**
20784
+ * One row per motion EPISODE, not one per push (D475). `null` while the
20785
+ * episode is still open — a further rising edge extends it in place rather
20786
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
20787
+ * (the span from the first rising edge to the LAST one, deliberately NOT
20788
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
20789
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
20790
+ * motion for an instantaneous trigger).
20791
+ *
20792
+ * **Absent** (not merely `null`) on a row written before D475 — that means
20793
+ * "closed the old way, before this column existed", never "still open".
20794
+ * Nothing in this codebase may read an absent `durationMs` as an open
20795
+ * episode; only `null` means open.
20796
+ */
20797
+ durationMs: number().nullable().optional(),
20798
+ /**
20799
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
20800
+ * first entry is always `0`) of every genuine off→on transition the
20801
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
20802
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
20803
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
20804
+ * explicit `false` mid-episode and then resumes before the quiet window
20805
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
20806
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
20807
+ * which must never be read as "no episode happened here".
20808
+ */
20809
+ edges: array(number()).readonly().optional()
20724
20810
  });
20725
20811
  /**
20726
20812
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -20775,6 +20861,23 @@ var ObjectEventSchema = object({
20775
20861
  * includes it (it is light). Absent on rows written before this field.
20776
20862
  */
20777
20863
  frameId: string().optional(),
20864
+ /**
20865
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
20866
+ * (D474).
20867
+ *
20868
+ * Only the package detector writes it, and it exists because the event id
20869
+ * stopped being choosable: the delivery and pick-up rows used to BE their
20870
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
20871
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
20872
+ * assigned by SQLite, so that key had to move off the primary key rather
20873
+ * than be dropped — a detector that cannot recognise its own row re-delivers
20874
+ * every parcel on every boot.
20875
+ *
20876
+ * Absent on every other object event, and on every row written before this
20877
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
20878
+ * not per row, and nothing addresses a row by it.
20879
+ */
20880
+ idempotencyKey: string().optional(),
20778
20881
  /** Omitted in slim projection. */
20779
20882
  trackId: string().optional(),
20780
20883
  className: string(),
@@ -29734,14 +29837,72 @@ authKey: string().optional() }), object({
29734
29837
  * (alert-center, advanced-notifier) can subscribe once and receive
29735
29838
  * motion from every camera.
29736
29839
  */
29840
+ /**
29841
+ * How long after a camera's last rising edge `pipeline-analytics` closes the
29842
+ * motion EPISODE row it kept open for it (D475) — "il delay della cap", in
29843
+ * the operator's words. One authority for the whole system: every provider
29844
+ * that seeds `autoClearAfterMs` on a `Camera` device writes this SAME
29845
+ * constant while `detected: true` (never its own number — Hikvision used to
29846
+ * seed its internal 3 s inactivity timer here, which is a different clock for
29847
+ * a different purpose), and `MotionEpisodeTracker` imports it directly rather
29848
+ * than reading the live cap value on the hot path. A `Sensor` device (HA
29849
+ * binary sensor, Homematic) gets a genuine push both ways and has no episode
29850
+ * to close on a timer, so it writes `null`, never this constant.
29851
+ */
29852
+ var MOTION_CLOSE_AFTER_MS = 15e3;
29737
29853
  var MotionStatusSchema = object({
29738
29854
  detected: boolean(),
29739
29855
  /** Ms epoch of the last detected-true observation. Null if never detected. */
29740
29856
  lastDetectedAt: number().nullable(),
29741
29857
  /**
29742
- * Ms after which `detected` auto-reverts to false if no fresh push
29743
- * arrives. Null means the provider leaves detected state until a
29744
- * native "clear" event.
29858
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
29859
+ * `null` while false and on every `Sensor` device (D475) — see that
29860
+ * constant's doc for the one-authority rule.
29861
+ *
29862
+ * ## Reading this field still arms nothing
29863
+ *
29864
+ * It reads like an instruction to the consumer ("revert after N ms if
29865
+ * no fresh push arrives") and it is not one: nothing in this repo reads
29866
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
29867
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
29868
+ * imported constant, not as a read of `device.state.motion.value`, so this
29869
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
29870
+ * computed elsewhere. Building a self-clear timer out of a READ of this
29871
+ * field would add a second falling-edge authority beside whichever one
29872
+ * already owns the device, and two that can disagree are worse than one.
29873
+ * Consumers that need a falling edge SHAPED differently — held open across
29874
+ * a flapping source — debounce on their own side and say so, as
29875
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
29876
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
29877
+ *
29878
+ * ## Who writes it
29879
+ *
29880
+ * - **Cameras** — the runner's phase machine, `active → watching` on
29881
+ * `cooldown_expired`, which then writes this slice with
29882
+ * `detected: false` (`handlePhaseChanged` in
29883
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
29884
+ * matters most for the sources that only ever push a rising one:
29885
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
29886
+ * a false.
29887
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
29888
+ * provider pushes the false itself, from the upstream system's own
29889
+ * state change. No phase machine is involved.
29890
+ *
29891
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
29892
+ *
29893
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
29894
+ * phase machine is the sole writer for a camera. It is not.
29895
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
29896
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
29897
+ * Hikvision's comment says why: it read THIS docblock, agreed the
29898
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
29899
+ * two authorities can disagree about one slice, which this repo
29900
+ * forbids, and the doc said otherwise — which is worse than saying
29901
+ * nothing, because it reads as verification.
29902
+ *
29903
+ * This predates D475 and is not fixed there: the fix touches every
29904
+ * camera provider. Recorded in D475's Consequences. Do not restore the
29905
+ * "sole writer" wording without also removing the other writers.
29745
29906
  */
29746
29907
  autoClearAfterMs: number().nullable()
29747
29908
  });
@@ -29811,7 +29972,11 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
29811
29972
  */
29812
29973
  runtimeState: MotionStatusSchema,
29813
29974
  /**
29814
- * 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.
29975
+ * Runtime-state durability: **session** — every writer of this slice
29976
+ * writes only on an EDGE, so a restored `detected: true` would stay
29977
+ * frozen until the next one instead of being corrected. The next edge
29978
+ * re-publishes the real state. (On who the writers are, and why there
29979
+ * is more than one, see `autoClearAfterMs` above.)
29815
29980
  *
29816
29981
  * See `RuntimeStateDurability`. Enforced by
29817
29982
  * `scripts/check-runtime-state-durability.ts`.
@@ -45672,4 +45837,4 @@ function vectorDimFromBase64(encoded) {
45672
45837
  return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
45673
45838
  }
45674
45839
  //#endregion
45675
- export { VISIT_MERGE_GAP_MS as $, object as $t, NcRulePatchSchema as A, resolvePoolMemoryPolicy as At, PoolMemoryWatchdog as B, CamProfileSchema as Bt, NC_ALARM_SYSTEM_EVENT_KINDS as C, parseProcStatus as Ct, NC_TAXONOMY as D, readDeviceStateFrom as Dt, NC_SNOOZE_MAX_MINUTES as E, plateGalleryCapability as Et, NcSnoozeSchema as F, vectorDimFromBase64 as Ft, SCENE_DEFAULT_UNCOVERED_POLICY as G, nodePin as Gt, RetrainStatusSchema as H, createEvent as Ht, NcSnoozeSuppressedSchema as I, videoclipsCapability as It, TIMELAPSE_DENSE_FLOOR_SEC as J, array as Jt, SCENE_DIVERGED as K, sleep as Kt, NcSystemEventKindSchema as L, zoneAnalyticsCapability as Lt, NcRuleTargetSchema as M, storageOccupancyCapability as Mt, NcScheduleSchema as N, subKindsOf as Nt, NcConditionDescriptorSchema as O, readTimelapseGeneratedAt as Ot, NcSnoozeInputSchema as P, systemEventFilterApplies as Pt, TrackSourceSchema as Q, number as Qt, NcTaxonomySchema as R, errMsg as Rt, MediaFileKindEnum as S, notificationRulesCapability as St, NC_DEFAULT_SNOOZE_MINUTES as T, pipelineAnalyticsCapability as Tt, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as U, hydrateSchema as Ut, RECORDING_EXPORT_MAX_READ_BYTES as V, DeviceType as Vt, SCENE_DEFAULT_ANCHOR_THRESHOLD as W, isDeviceScopedCap as Wt, TimelapseRulePatchSchema as X, discriminatedUnion as Xt, TimelapseRuleInputSchema as Y, boolean as Yt, TimelapseRuleSchema as Z, literal as Zt, FailureCounters as _, isDetectionMacroClass as _t, CLUSTER_MODEL_SCOPED_STEPS as a, buildEventKindDescriptor as at, MAX_BIRTH_DECISION_RECORDS as b, kebabToCamel as bt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as c, defineCustomActions as ct, DETECTION_PIPELINE_CAP_NAME as d, encodeVectorBase64 as dt, partialRecord as en, addonWidgetsSourceCapability as et, DeclaredDevices as f, evaluateSensorEdge as ft, FULL_IMAGE_BBOX as g, hfModelUrl as gt, FIRST_LEVEL_MACRO_CLASSES as h, failureContributionCapability as ht, BirthDecisionRecordSchema as i, EventCategory as in, audioModeOf as it, NcRuleSchema as j, sceneMonitorCapability as jt, NcRuleInputSchema as k, resolveLocationMode as kt, DEFAULT_TIMELAPSE_PREVIEW_TEXT as l, deriveRecordingMode as lt, EVENT_PAD_MS as m, faceGalleryCapability as mt, ArchivedDebugNoteSchema as n, string as nn, assertTimelapseCadences as nt, COCO_TO_MACRO as o, cosineSimilarity as ot, EVENT_KIND_BY_CAP as p, evictionPolicyOfLocation as pt, SceneMonitorSchema as q, _enum as qt, BaseDevice as r, unknown as rn, audioMetricsCapability as rt, DEFAULT_EVENT_COLOR as s, customAction as st, AUDIO_MACRO_LABELS as t, record as tn, alarmPanelCapability as tt, DETECTION_MACRO_CLASSES as u, embeddingEncoderCapability as ut, LabelAttributionSchema as v, isScheduleActive as vt, NC_CONDITION_CATALOG as w, pickClusterStepModels as wt, MAX_BIRTH_LATENCY_PROXY_MS as x, mayWriteToLocation as xt, MAX_ARCHIVED_DEBUG_NOTES as y, isSourceCap as yt, OpsLogEntrySchema as z, BaseAddon as zt };
45840
+ export { TimelapseRuleSchema as $, discriminatedUnion as $t, NcConditionDescriptorSchema as A, readDeviceStateFrom as At, NcTaxonomySchema as B, zoneAnalyticsCapability as Bt, MOTION_CLOSE_AFTER_MS as C, kebabToCamel as Ct, NC_DEFAULT_SNOOZE_MINUTES as D, pickClusterStepModels as Dt, NC_CONDITION_CATALOG as E, parseProcStatus as Et, NcScheduleSchema as F, storageOccupancyCapability as Ft, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as G, createEvent as Gt, PoolMemoryWatchdog as H, BaseAddon as Ht, NcSnoozeInputSchema as I, subKindsOf as It, SCENE_DIVERGED as J, nodePin as Jt, SCENE_DEFAULT_ANCHOR_THRESHOLD as K, hydrateSchema as Kt, NcSnoozeSchema as L, systemEventFilterApplies as Lt, NcRulePatchSchema as M, resolveLocationMode as Mt, NcRuleSchema as N, resolvePoolMemoryPolicy as Nt, NC_SNOOZE_MAX_MINUTES as O, pipelineAnalyticsCapability as Ot, NcRuleTargetSchema as P, sceneMonitorCapability as Pt, TimelapseRulePatchSchema as Q, boolean as Qt, NcSnoozeSuppressedSchema as R, vectorDimFromBase64 as Rt, MAX_BIRTH_LATENCY_PROXY_MS as S, isSourceCap as St, NC_ALARM_SYSTEM_EVENT_KINDS as T, notificationRulesCapability as Tt, RECORDING_EXPORT_MAX_READ_BYTES as U, CamProfileSchema as Ut, OpsLogEntrySchema as V, errMsg as Vt, RetrainStatusSchema as W, DeviceType as Wt, TIMELAPSE_DENSE_FLOOR_SEC as X, _enum as Xt, SceneMonitorSchema as Y, sleep as Yt, TimelapseRuleInputSchema as Z, array as Zt, FULL_IMAGE_BBOX as _, failureContributionCapability as _t, CLUSTER_MODEL_SCOPED_STEPS as a, string as an, audioMetricsCapability as at, MAX_ARCHIVED_DEBUG_NOTES as b, isDetectionMacroClass as bt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as c, cosineSimilarity as ct, DETECTION_PIPELINE_CAP_NAME as d, deriveRecordingMode as dt, literal as en, TrackSourceSchema as et, DeclaredDevices as f, embeddingEncoderCapability as ft, FIRST_LEVEL_MACRO_CLASSES as g, faceGalleryCapability as gt, EVENT_PAD_MS as h, evictionPolicyOfLocation as ht, BirthDecisionRecordSchema as i, record as in, assertTimelapseCadences as it, NcRuleInputSchema as j, readTimelapseGeneratedAt as jt, NC_TAXONOMY as k, plateGalleryCapability as kt, DEFAULT_TIMELAPSE_PREVIEW_TEXT as l, customAction as lt, EVENT_OWNER_TYPES as m, evaluateSensorEdge as mt, ArchivedDebugNoteSchema as n, object as nn, addonWidgetsSourceCapability as nt, COCO_TO_MACRO as o, unknown as on, audioModeOf as ot, EVENT_KIND_BY_CAP as p, encodeVectorBase64 as pt, SCENE_DEFAULT_UNCOVERED_POLICY as q, isDeviceScopedCap as qt, BaseDevice as r, partialRecord as rn, alarmPanelCapability as rt, DEFAULT_EVENT_COLOR as s, EventCategory as sn, buildEventKindDescriptor as st, AUDIO_MACRO_LABELS as t, number as tn, VISIT_MERGE_GAP_MS as tt, DETECTION_MACRO_CLASSES as u, defineCustomActions as ut, FailureCounters as v, formatMediaOwnerKey as vt, MediaFileKindEnum as w, mayWriteToLocation as wt, MAX_BIRTH_DECISION_RECORDS as x, isScheduleActive as xt, LabelAttributionSchema as y, hfModelUrl as yt, NcSystemEventKindSchema as z, videoclipsCapability as zt };
@@ -7522,6 +7522,34 @@ function errMsg(err) {
7522
7522
  if (typeof err === "string") return err;
7523
7523
  return String(err);
7524
7524
  }
7525
+ new Set([
7526
+ "track",
7527
+ "summary",
7528
+ "face",
7529
+ "identity",
7530
+ "plate",
7531
+ "vehicle",
7532
+ "scene",
7533
+ "motion",
7534
+ "object",
7535
+ "audio"
7536
+ ]);
7537
+ /** Build a media key. The timestamp segment is omitted when there is none. */
7538
+ function formatMediaOwnerKey(key) {
7539
+ const head = `${key.ownerType}:${key.ownerId}:${key.fileKind}`;
7540
+ return key.timestampMs === void 0 ? head : `${head}:${key.timestampMs}`;
7541
+ }
7542
+ /**
7543
+ * The owner types that are event tables, in the order the event-media resolver
7544
+ * should consider them. Exported so a consumer asking "is this key an event?"
7545
+ * does not re-spell the list and drift from it.
7546
+ */
7547
+ var EVENT_OWNER_TYPES = [
7548
+ "motion",
7549
+ "object",
7550
+ "audio"
7551
+ ];
7552
+ new Set(EVENT_OWNER_TYPES);
7525
7553
  var EncodeProfileSchema = object({
7526
7554
  video: object({
7527
7555
  codec: _enum([
@@ -13127,8 +13155,17 @@ method(object({
13127
13155
  }), array(SettingsRecordSchema).readonly()), method(object({
13128
13156
  namespace: string().optional(),
13129
13157
  collection: string(),
13130
- record: SettingsRecordSchema
13131
- }), _void(), { kind: "mutation" }), method(object({
13158
+ record: object({
13159
+ id: string().optional(),
13160
+ data: record(string(), unknown())
13161
+ })
13162
+ }), object({
13163
+ /**
13164
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13165
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13166
+ * primary key — which is the only place an auto key is knowable.
13167
+ */
13168
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
13132
13169
  namespace: string().optional(),
13133
13170
  collection: string(),
13134
13171
  records: array(BulkRecordSchema).readonly()
@@ -13237,8 +13274,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13237
13274
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
13238
13275
  namespace: string().optional(),
13239
13276
  collection: string(),
13240
- record: SettingsRecordSchema
13241
- }), _void(), {
13277
+ record: object({
13278
+ id: string().optional(),
13279
+ data: record(string(), unknown())
13280
+ })
13281
+ }), object({
13282
+ /**
13283
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13284
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13285
+ * primary key — which is the only place an auto key is knowable.
13286
+ */
13287
+ id: union([string(), number()]) }), {
13242
13288
  kind: "mutation",
13243
13289
  auth: "admin"
13244
13290
  }), method(object({
@@ -20732,7 +20778,20 @@ var TrackSchema = object({
20732
20778
  ...TrackRetrainFields
20733
20779
  });
20734
20780
  var BaseEventFields = {
20735
- id: string(),
20781
+ /**
20782
+ * A SQLite ROWID, assigned by the database (D474).
20783
+ *
20784
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
20785
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
20786
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
20787
+ * the rowid: the table itself is that B-tree, so the index stops existing
20788
+ * rather than getting smaller. No shorter string does that.
20789
+ *
20790
+ * Defined once here for all three event kinds, which is why they move
20791
+ * together: a per-table migration would have forked this and
20792
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
20793
+ */
20794
+ id: number().int(),
20736
20795
  deviceId: number(),
20737
20796
  timestamp: number()
20738
20797
  };
@@ -20751,7 +20810,34 @@ var MotionEventSchema = object({
20751
20810
  /** Omitted in slim projection. */
20752
20811
  frameHeight: number().optional(),
20753
20812
  /** Populated by B5 (recording playback URL for this event). */
20754
- mediaUrl: string().optional()
20813
+ mediaUrl: string().optional(),
20814
+ /**
20815
+ * One row per motion EPISODE, not one per push (D475). `null` while the
20816
+ * episode is still open — a further rising edge extends it in place rather
20817
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
20818
+ * (the span from the first rising edge to the LAST one, deliberately NOT
20819
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
20820
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
20821
+ * motion for an instantaneous trigger).
20822
+ *
20823
+ * **Absent** (not merely `null`) on a row written before D475 — that means
20824
+ * "closed the old way, before this column existed", never "still open".
20825
+ * Nothing in this codebase may read an absent `durationMs` as an open
20826
+ * episode; only `null` means open.
20827
+ */
20828
+ durationMs: number().nullable().optional(),
20829
+ /**
20830
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
20831
+ * first entry is always `0`) of every genuine off→on transition the
20832
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
20833
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
20834
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
20835
+ * explicit `false` mid-episode and then resumes before the quiet window
20836
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
20837
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
20838
+ * which must never be read as "no episode happened here".
20839
+ */
20840
+ edges: array(number()).readonly().optional()
20755
20841
  });
20756
20842
  /**
20757
20843
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -20806,6 +20892,23 @@ var ObjectEventSchema = object({
20806
20892
  * includes it (it is light). Absent on rows written before this field.
20807
20893
  */
20808
20894
  frameId: string().optional(),
20895
+ /**
20896
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
20897
+ * (D474).
20898
+ *
20899
+ * Only the package detector writes it, and it exists because the event id
20900
+ * stopped being choosable: the delivery and pick-up rows used to BE their
20901
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
20902
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
20903
+ * assigned by SQLite, so that key had to move off the primary key rather
20904
+ * than be dropped — a detector that cannot recognise its own row re-delivers
20905
+ * every parcel on every boot.
20906
+ *
20907
+ * Absent on every other object event, and on every row written before this
20908
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
20909
+ * not per row, and nothing addresses a row by it.
20910
+ */
20911
+ idempotencyKey: string().optional(),
20809
20912
  /** Omitted in slim projection. */
20810
20913
  trackId: string().optional(),
20811
20914
  className: string(),
@@ -29765,14 +29868,72 @@ authKey: string().optional() }), object({
29765
29868
  * (alert-center, advanced-notifier) can subscribe once and receive
29766
29869
  * motion from every camera.
29767
29870
  */
29871
+ /**
29872
+ * How long after a camera's last rising edge `pipeline-analytics` closes the
29873
+ * motion EPISODE row it kept open for it (D475) — "il delay della cap", in
29874
+ * the operator's words. One authority for the whole system: every provider
29875
+ * that seeds `autoClearAfterMs` on a `Camera` device writes this SAME
29876
+ * constant while `detected: true` (never its own number — Hikvision used to
29877
+ * seed its internal 3 s inactivity timer here, which is a different clock for
29878
+ * a different purpose), and `MotionEpisodeTracker` imports it directly rather
29879
+ * than reading the live cap value on the hot path. A `Sensor` device (HA
29880
+ * binary sensor, Homematic) gets a genuine push both ways and has no episode
29881
+ * to close on a timer, so it writes `null`, never this constant.
29882
+ */
29883
+ var MOTION_CLOSE_AFTER_MS = 15e3;
29768
29884
  var MotionStatusSchema = object({
29769
29885
  detected: boolean(),
29770
29886
  /** Ms epoch of the last detected-true observation. Null if never detected. */
29771
29887
  lastDetectedAt: number().nullable(),
29772
29888
  /**
29773
- * Ms after which `detected` auto-reverts to false if no fresh push
29774
- * arrives. Null means the provider leaves detected state until a
29775
- * native "clear" event.
29889
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
29890
+ * `null` while false and on every `Sensor` device (D475) — see that
29891
+ * constant's doc for the one-authority rule.
29892
+ *
29893
+ * ## Reading this field still arms nothing
29894
+ *
29895
+ * It reads like an instruction to the consumer ("revert after N ms if
29896
+ * no fresh push arrives") and it is not one: nothing in this repo reads
29897
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
29898
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
29899
+ * imported constant, not as a read of `device.state.motion.value`, so this
29900
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
29901
+ * computed elsewhere. Building a self-clear timer out of a READ of this
29902
+ * field would add a second falling-edge authority beside whichever one
29903
+ * already owns the device, and two that can disagree are worse than one.
29904
+ * Consumers that need a falling edge SHAPED differently — held open across
29905
+ * a flapping source — debounce on their own side and say so, as
29906
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
29907
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
29908
+ *
29909
+ * ## Who writes it
29910
+ *
29911
+ * - **Cameras** — the runner's phase machine, `active → watching` on
29912
+ * `cooldown_expired`, which then writes this slice with
29913
+ * `detected: false` (`handlePhaseChanged` in
29914
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
29915
+ * matters most for the sources that only ever push a rising one:
29916
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
29917
+ * a false.
29918
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
29919
+ * provider pushes the false itself, from the upstream system's own
29920
+ * state change. No phase machine is involved.
29921
+ *
29922
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
29923
+ *
29924
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
29925
+ * phase machine is the sole writer for a camera. It is not.
29926
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
29927
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
29928
+ * Hikvision's comment says why: it read THIS docblock, agreed the
29929
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
29930
+ * two authorities can disagree about one slice, which this repo
29931
+ * forbids, and the doc said otherwise — which is worse than saying
29932
+ * nothing, because it reads as verification.
29933
+ *
29934
+ * This predates D475 and is not fixed there: the fix touches every
29935
+ * camera provider. Recorded in D475's Consequences. Do not restore the
29936
+ * "sole writer" wording without also removing the other writers.
29776
29937
  */
29777
29938
  autoClearAfterMs: number().nullable()
29778
29939
  });
@@ -29842,7 +30003,11 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
29842
30003
  */
29843
30004
  runtimeState: MotionStatusSchema,
29844
30005
  /**
29845
- * 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.
30006
+ * Runtime-state durability: **session** — every writer of this slice
30007
+ * writes only on an EDGE, so a restored `detected: true` would stay
30008
+ * frozen until the next one instead of being corrected. The next edge
30009
+ * re-publishes the real state. (On who the writers are, and why there
30010
+ * is more than one, see `autoClearAfterMs` above.)
29846
30011
  *
29847
30012
  * See `RuntimeStateDurability`. Enforced by
29848
30013
  * `scripts/check-runtime-state-durability.ts`.
@@ -45799,6 +45964,12 @@ Object.defineProperty(exports, "EVENT_KIND_BY_CAP", {
45799
45964
  return EVENT_KIND_BY_CAP;
45800
45965
  }
45801
45966
  });
45967
+ Object.defineProperty(exports, "EVENT_OWNER_TYPES", {
45968
+ enumerable: true,
45969
+ get: function() {
45970
+ return EVENT_OWNER_TYPES;
45971
+ }
45972
+ });
45802
45973
  Object.defineProperty(exports, "EVENT_PAD_MS", {
45803
45974
  enumerable: true,
45804
45975
  get: function() {
@@ -45853,6 +46024,12 @@ Object.defineProperty(exports, "MAX_BIRTH_LATENCY_PROXY_MS", {
45853
46024
  return MAX_BIRTH_LATENCY_PROXY_MS;
45854
46025
  }
45855
46026
  });
46027
+ Object.defineProperty(exports, "MOTION_CLOSE_AFTER_MS", {
46028
+ enumerable: true,
46029
+ get: function() {
46030
+ return MOTION_CLOSE_AFTER_MS;
46031
+ }
46032
+ });
45856
46033
  Object.defineProperty(exports, "MediaFileKindEnum", {
45857
46034
  enumerable: true,
45858
46035
  get: function() {
@@ -46189,6 +46366,12 @@ Object.defineProperty(exports, "failureContributionCapability", {
46189
46366
  return failureContributionCapability;
46190
46367
  }
46191
46368
  });
46369
+ Object.defineProperty(exports, "formatMediaOwnerKey", {
46370
+ enumerable: true,
46371
+ get: function() {
46372
+ return formatMediaOwnerKey;
46373
+ }
46374
+ });
46192
46375
  Object.defineProperty(exports, "hfModelUrl", {
46193
46376
  enumerable: true,
46194
46377
  get: function() {
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-iJA2F7_d.js");
5
+ const require_dist = require("../dist-BypQW8jz.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs);
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { At as resolvePoolMemoryPolicy, B as PoolMemoryWatchdog, Ct as parseProcStatus, gt as hfModelUrl, ut as embeddingEncoderCapability, zt as BaseAddon } from "../dist-CxPGPwcy.mjs";
1
+ import { Et as parseProcStatus, H as PoolMemoryWatchdog, Ht as BaseAddon, Nt as resolvePoolMemoryPolicy, ft as embeddingEncoderCapability, yt as hfModelUrl } from "../dist-B-cXIX_e.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.100",
6
+ version: "1.2.101",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_pipeline_analytics_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.180",
21
+ version: "1.2.181",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.148",
36
+ version: "1.2.149",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.180",
39
+ version: "1.2.181",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.2.100",
48
+ version: "1.2.101",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.148",
84
+ version: "1.2.149",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,