@camstack/addon-post-analysis 1.2.180 → 1.2.182

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.
@@ -1,4 +1,4 @@
1
- import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-DEAreP8H.mjs";
1
+ import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg$1, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-DvmLRRIq.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { promises } from "node:fs";
@@ -35955,6 +35955,7 @@ var EMPTY_CASCADE_TALLY = {
35955
35955
  faces: 0,
35956
35956
  plates: 0,
35957
35957
  embeddings: 0,
35958
+ summaries: 0,
35958
35959
  bytesReclaimed: 0
35959
35960
  };
35960
35961
  /** Fold a leaf store's receipts into a running tally. */
@@ -35998,6 +35999,13 @@ function addLeafCounts(tally, counts) {
35998
35999
  bytesReclaimed
35999
36000
  };
36000
36001
  break;
36002
+ case "summaries":
36003
+ next = {
36004
+ ...next,
36005
+ summaries: next.summaries + c.rows,
36006
+ bytesReclaimed
36007
+ };
36008
+ break;
36001
36009
  }
36002
36010
  }
36003
36011
  return next;
@@ -36011,6 +36019,7 @@ function mergeCascadeTallies(a, b) {
36011
36019
  faces: a.faces + b.faces,
36012
36020
  plates: a.plates + b.plates,
36013
36021
  embeddings: a.embeddings + b.embeddings,
36022
+ summaries: a.summaries + b.summaries,
36014
36023
  bytesReclaimed: a.bytesReclaimed + b.bytesReclaimed
36015
36024
  };
36016
36025
  }
@@ -36073,6 +36082,7 @@ function absentCascadeLegs(stores) {
36073
36082
  if (!stores.faceStore) absent.push("faces");
36074
36083
  if (!stores.plateStore) absent.push("plates");
36075
36084
  if (!stores.objectEmbeddingStore) absent.push("embeddings");
36085
+ if (!stores.summaryStore) absent.push("summaries");
36076
36086
  return absent;
36077
36087
  }
36078
36088
  function buildAnalyticsCascadeRegistry(stores) {
@@ -36080,6 +36090,7 @@ function buildAnalyticsCascadeRegistry(stores) {
36080
36090
  if (stores.faceStore) registry.push(stores.faceStore);
36081
36091
  if (stores.plateStore) registry.push(stores.plateStore);
36082
36092
  if (stores.objectEmbeddingStore) registry.push(stores.objectEmbeddingStore);
36093
+ if (stores.summaryStore) registry.push(stores.summaryStore);
36083
36094
  return registry;
36084
36095
  }
36085
36096
  /**
@@ -51714,7 +51725,7 @@ async function persistTrackKeyFrame(deps, input) {
51714
51725
  ...drop,
51715
51726
  meta: {
51716
51727
  ...drop.meta,
51717
- error: errMsg(err)
51728
+ error: errMsg$1(err)
51718
51729
  }
51719
51730
  });
51720
51731
  }
@@ -51723,7 +51734,7 @@ async function persistTrackKeyFrame(deps, input) {
51723
51734
  keyFrameKey
51724
51735
  };
51725
51736
  } catch (err) {
51726
- const error = errMsg(err);
51737
+ const error = errMsg$1(err);
51727
51738
  deps.logger.warn("key-frame capture failed", {
51728
51739
  ...drop,
51729
51740
  meta: {
@@ -56240,6 +56251,344 @@ async function reconcileGallerySamples(deps, options) {
56240
56251
  stoppedByCeiling
56241
56252
  };
56242
56253
  }
56254
+ //#endregion
56255
+ //#region src/pipeline-analytics/summary/summary-store.ts
56256
+ var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
56257
+ var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
56258
+ var SUMMARIES_COLUMNS = [
56259
+ {
56260
+ name: "id",
56261
+ type: "TEXT",
56262
+ primaryKey: true,
56263
+ notNull: true
56264
+ },
56265
+ {
56266
+ name: "deviceId",
56267
+ type: "INTEGER",
56268
+ notNull: true
56269
+ },
56270
+ {
56271
+ name: "openedAt",
56272
+ type: "INTEGER",
56273
+ notNull: true
56274
+ },
56275
+ {
56276
+ name: "lastActiveAt",
56277
+ type: "INTEGER",
56278
+ notNull: true
56279
+ },
56280
+ {
56281
+ name: "sealedAt",
56282
+ type: "INTEGER"
56283
+ },
56284
+ (
56285
+ /** `deviceId` while open, NULL once sealed — the unique "one open per camera". */
56286
+ {
56287
+ name: "openDeviceId",
56288
+ type: "INTEGER"
56289
+ }),
56290
+ {
56291
+ name: "memberCount",
56292
+ type: "INTEGER",
56293
+ notNull: true
56294
+ }
56295
+ ];
56296
+ var SUMMARIES_INDEXES = [{
56297
+ name: "summaries_open_device",
56298
+ columns: ["openDeviceId"],
56299
+ unique: true
56300
+ }, {
56301
+ name: "summaries_device_opened",
56302
+ columns: ["deviceId", "openedAt"]
56303
+ }];
56304
+ var SUMMARY_MEMBERS_COLUMNS = [
56305
+ {
56306
+ name: "id",
56307
+ type: "TEXT",
56308
+ primaryKey: true,
56309
+ notNull: true
56310
+ },
56311
+ {
56312
+ name: "summaryId",
56313
+ type: "TEXT",
56314
+ notNull: true
56315
+ },
56316
+ {
56317
+ name: "trackId",
56318
+ type: "TEXT",
56319
+ notNull: true
56320
+ },
56321
+ {
56322
+ name: "deviceId",
56323
+ type: "INTEGER",
56324
+ notNull: true
56325
+ },
56326
+ (
56327
+ /** When the track joined — named `timestamp` so the orphan audit's declared-owner scope reads it. */
56328
+ {
56329
+ name: "timestamp",
56330
+ type: "INTEGER",
56331
+ notNull: true
56332
+ })
56333
+ ];
56334
+ var SUMMARY_MEMBERS_INDEXES = [{
56335
+ name: "summary_members_summary",
56336
+ columns: ["summaryId"]
56337
+ }, {
56338
+ name: "summary_members_track",
56339
+ columns: ["trackId"]
56340
+ }];
56341
+ function summaryId(deviceId, openedAt) {
56342
+ return `sum-${deviceId}-${openedAt}`;
56343
+ }
56344
+ function memberRowId(summaryIdValue, trackId) {
56345
+ return `${summaryIdValue}:${trackId}`;
56346
+ }
56347
+ function num$1(value, fallback = 0) {
56348
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
56349
+ }
56350
+ function toRow$1(id, data) {
56351
+ return {
56352
+ id,
56353
+ deviceId: num$1(data["deviceId"]),
56354
+ openedAt: num$1(data["openedAt"]),
56355
+ lastActiveAt: num$1(data["lastActiveAt"]),
56356
+ sealedAt: typeof data["sealedAt"] === "number" ? data["sealedAt"] : null,
56357
+ memberCount: num$1(data["memberCount"])
56358
+ };
56359
+ }
56360
+ function isUniqueViolation(err) {
56361
+ return err instanceof Error && /UNIQUE constraint failed/i.test(err.message);
56362
+ }
56363
+ var SummaryStore = class {
56364
+ store;
56365
+ logger;
56366
+ constructor(deps) {
56367
+ this.store = deps.store;
56368
+ this.logger = deps.logger;
56369
+ }
56370
+ static async declare(store) {
56371
+ await Promise.all([store.declareCollection.mutate({
56372
+ collection: SUMMARIES_COLLECTION,
56373
+ columns: [...SUMMARIES_COLUMNS],
56374
+ indexes: [...SUMMARIES_INDEXES]
56375
+ }), store.declareCollection.mutate({
56376
+ collection: SUMMARY_MEMBERS_COLLECTION,
56377
+ columns: [...SUMMARY_MEMBERS_COLUMNS],
56378
+ indexes: [...SUMMARY_MEMBERS_INDEXES]
56379
+ })]);
56380
+ }
56381
+ /** The camera's OPEN summary, from the row — `null` when none. */
56382
+ async findOpen(deviceId) {
56383
+ const first = (await this.store.query.query({
56384
+ collection: SUMMARIES_COLLECTION,
56385
+ filter: {
56386
+ where: { openDeviceId: deviceId },
56387
+ limit: 1
56388
+ }
56389
+ }))[0];
56390
+ return first === void 0 ? null : toRow$1(first.id, first.data);
56391
+ }
56392
+ async listOpen() {
56393
+ return (await this.store.query.query({
56394
+ collection: SUMMARIES_COLLECTION,
56395
+ filter: {
56396
+ where: { sealedAt: null },
56397
+ limit: 1e4
56398
+ }
56399
+ })).filter((r) => r.data["openDeviceId"] !== null).map((r) => toRow$1(r.id, r.data));
56400
+ }
56401
+ async membersOf(summaryIdValue) {
56402
+ return (await this.store.query.query({
56403
+ collection: SUMMARY_MEMBERS_COLLECTION,
56404
+ filter: {
56405
+ where: { summaryId: summaryIdValue },
56406
+ limit: 1e4
56407
+ }
56408
+ })).map((r) => ({
56409
+ summaryId: summaryIdValue,
56410
+ trackId: String(r.data["trackId"] ?? ""),
56411
+ deviceId: num$1(r.data["deviceId"]),
56412
+ joinedAt: num$1(r.data["timestamp"])
56413
+ }));
56414
+ }
56415
+ /**
56416
+ * Put a track into the camera's open summary, opening one when there is
56417
+ * none. Racing opens are settled by the store: the loser's insert hits the
56418
+ * unique `openDeviceId` and is re-read as an absorb.
56419
+ */
56420
+ async openOrAbsorb(input) {
56421
+ const existing = await this.findOpen(input.deviceId);
56422
+ if (existing !== null) return this.absorb(existing, input);
56423
+ const id = summaryId(input.deviceId, input.at);
56424
+ try {
56425
+ await this.store.set.mutate({
56426
+ collection: SUMMARIES_COLLECTION,
56427
+ key: id,
56428
+ value: {
56429
+ deviceId: input.deviceId,
56430
+ openedAt: input.at,
56431
+ lastActiveAt: input.at,
56432
+ sealedAt: null,
56433
+ openDeviceId: input.deviceId,
56434
+ memberCount: 1
56435
+ }
56436
+ });
56437
+ } catch (err) {
56438
+ if (!isUniqueViolation(err)) throw err;
56439
+ const winner = await this.findOpen(input.deviceId);
56440
+ if (winner === null) throw err;
56441
+ return this.absorb(winner, input);
56442
+ }
56443
+ await this.addMember(id, input);
56444
+ return {
56445
+ id,
56446
+ deviceId: input.deviceId,
56447
+ openedAt: input.at,
56448
+ lastActiveAt: input.at,
56449
+ sealedAt: null,
56450
+ memberCount: 1
56451
+ };
56452
+ }
56453
+ async absorb(summary, input) {
56454
+ const members = await this.membersOf(summary.id);
56455
+ if (members.some((m) => m.trackId === input.trackId)) return summary;
56456
+ await this.addMember(summary.id, input);
56457
+ const memberCount = members.length + 1;
56458
+ const lastActiveAt = Math.max(summary.lastActiveAt, input.at);
56459
+ await this.store.updateWhere.mutate({
56460
+ collection: SUMMARIES_COLLECTION,
56461
+ filter: { where: {
56462
+ openDeviceId: summary.deviceId,
56463
+ openedAt: summary.openedAt
56464
+ } },
56465
+ data: {
56466
+ memberCount,
56467
+ lastActiveAt
56468
+ }
56469
+ });
56470
+ return {
56471
+ ...summary,
56472
+ memberCount,
56473
+ lastActiveAt
56474
+ };
56475
+ }
56476
+ async addMember(summaryIdValue, input) {
56477
+ await this.store.insertMany.mutate({
56478
+ collection: SUMMARY_MEMBERS_COLLECTION,
56479
+ records: [{
56480
+ id: memberRowId(summaryIdValue, input.trackId),
56481
+ data: {
56482
+ summaryId: summaryIdValue,
56483
+ trackId: input.trackId,
56484
+ deviceId: input.deviceId,
56485
+ timestamp: input.at
56486
+ }
56487
+ }]
56488
+ });
56489
+ }
56490
+ /**
56491
+ * Seal in place. `true` when THIS call sealed it; `false` when the row was
56492
+ * already sealed (or gone) — the second sealer learns, it does not write.
56493
+ */
56494
+ async seal(summary, sealedAt) {
56495
+ const { updated } = await this.store.updateWhere.mutate({
56496
+ collection: SUMMARIES_COLLECTION,
56497
+ filter: { where: {
56498
+ openDeviceId: summary.deviceId,
56499
+ openedAt: summary.openedAt
56500
+ } },
56501
+ data: {
56502
+ sealedAt,
56503
+ openDeviceId: null
56504
+ }
56505
+ });
56506
+ if (updated > 1) this.logger.error("summary seal touched more than one row — the open key is not unique", {
56507
+ tags: { deviceId: summary.deviceId },
56508
+ meta: {
56509
+ summaryId: summary.id,
56510
+ updated
56511
+ }
56512
+ });
56513
+ return updated >= 1;
56514
+ }
56515
+ /**
56516
+ * Track cascade leaf: a member row whose track is gone is gone too, and
56517
+ * the summary's count follows. The envelope itself outlives its members
56518
+ * — it is an age-keyed root (`RETENTION_ROOTS`, `summary`).
56519
+ */
56520
+ async deleteByTracks(trackIds) {
56521
+ if (trackIds.length === 0) return [{
56522
+ family: "summaries",
56523
+ rows: 0,
56524
+ bytes: 0
56525
+ }];
56526
+ const members = await this.store.query.query({
56527
+ collection: SUMMARY_MEMBERS_COLLECTION,
56528
+ filter: {
56529
+ whereIn: { trackId: [...trackIds] },
56530
+ limit: 1e4
56531
+ }
56532
+ });
56533
+ if (members.length === 0) return [{
56534
+ family: "summaries",
56535
+ rows: 0,
56536
+ bytes: 0
56537
+ }];
56538
+ const { deleted } = await this.store.deleteWhere.mutate({
56539
+ collection: SUMMARY_MEMBERS_COLLECTION,
56540
+ filter: { whereIn: { trackId: [...trackIds] } }
56541
+ });
56542
+ const touched = new Set(members.map((m) => String(m.data["summaryId"] ?? "")));
56543
+ for (const summaryIdValue of touched) {
56544
+ const remaining = await this.membersOf(summaryIdValue);
56545
+ const raw = await this.store.get.query({
56546
+ collection: SUMMARIES_COLLECTION,
56547
+ key: summaryIdValue
56548
+ });
56549
+ if (typeof raw !== "object" || raw === null) continue;
56550
+ const row = raw;
56551
+ await this.store.updateWhere.mutate({
56552
+ collection: SUMMARIES_COLLECTION,
56553
+ filter: { where: {
56554
+ deviceId: num$1(row["deviceId"]),
56555
+ openedAt: num$1(row["openedAt"])
56556
+ } },
56557
+ data: { memberCount: remaining.length }
56558
+ });
56559
+ }
56560
+ return [{
56561
+ family: "summaries",
56562
+ rows: deleted,
56563
+ bytes: 0
56564
+ }];
56565
+ }
56566
+ /** Age sweep on the device window: everything opened before `cutoffMs`, members included. */
56567
+ async evictBefore(deviceId, cutoffMs) {
56568
+ const rows = await this.store.query.query({
56569
+ collection: SUMMARIES_COLLECTION,
56570
+ filter: {
56571
+ where: { deviceId },
56572
+ whereBetween: { openedAt: [0, cutoffMs - 1] },
56573
+ limit: 1e4
56574
+ }
56575
+ });
56576
+ if (rows.length === 0) return 0;
56577
+ const ids = rows.map((r) => r.id);
56578
+ await this.store.deleteWhere.mutate({
56579
+ collection: SUMMARY_MEMBERS_COLLECTION,
56580
+ filter: { whereIn: { summaryId: ids } }
56581
+ });
56582
+ const { deleted } = await this.store.deleteWhere.mutate({
56583
+ collection: SUMMARIES_COLLECTION,
56584
+ filter: {
56585
+ where: { deviceId },
56586
+ whereBetween: { openedAt: [0, cutoffMs - 1] }
56587
+ }
56588
+ });
56589
+ return deleted;
56590
+ }
56591
+ };
56243
56592
  /**
56244
56593
  * Where the completion marker lives.
56245
56594
  *
@@ -58535,6 +58884,10 @@ var ANALYTICS_COLLECTIONS = [
58535
58884
  collection: PLATES_COLLECTION,
58536
58885
  classification: derived(PLATE_COLUMNS)
58537
58886
  },
58887
+ {
58888
+ collection: SUMMARY_MEMBERS_COLLECTION,
58889
+ classification: derived(SUMMARY_MEMBERS_COLUMNS)
58890
+ },
58538
58891
  {
58539
58892
  collection: MEDIA_COLLECTION,
58540
58893
  classification: {
@@ -58578,6 +58931,13 @@ var ANALYTICS_COLLECTIONS = [
58578
58931
  rootId: "ops-log"
58579
58932
  }
58580
58933
  },
58934
+ {
58935
+ collection: SUMMARIES_COLLECTION,
58936
+ classification: {
58937
+ kind: "retention-root",
58938
+ rootId: "summary"
58939
+ }
58940
+ },
58581
58941
  {
58582
58942
  collection: IDENTITIES_COLLECTION,
58583
58943
  classification: {
@@ -59138,12 +59498,14 @@ async function sweepTracklessRoots(deps) {
59138
59498
  cutoffMs
59139
59499
  });
59140
59500
  const sensor = deps.evictSensorEvents ? await deps.evictSensorEvents(deviceId, cutoffMs) : 0;
59501
+ const summaries = deps.evictSummaries ? await deps.evictSummaries(deviceId, cutoffMs) : 0;
59141
59502
  const evicted = {
59142
59503
  motion: events.motion,
59143
59504
  audio: events.audio,
59144
- sensor
59505
+ sensor,
59506
+ summaries
59145
59507
  };
59146
- if (evicted.motion.length + evicted.audio.length + evicted.sensor > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
59508
+ if (evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
59147
59509
  } catch (err) {
59148
59510
  deps.onError(deviceId, err);
59149
59511
  }
@@ -60103,7 +60465,7 @@ function createNativeFrameTransport(deps) {
60103
60465
  } catch (err) {
60104
60466
  logger.debug("full-frame jpeg fetch failed", { meta: {
60105
60467
  nodeId: handle.nodeId,
60106
- error: errMsg(err)
60468
+ error: errMsg$1(err)
60107
60469
  } });
60108
60470
  return null;
60109
60471
  }
@@ -60152,7 +60514,7 @@ function createNativeFrameTransport(deps) {
60152
60514
  try {
60153
60515
  return await requestNativeCrop(frameHandle, bbox, maxWidth, normalize);
60154
60516
  } catch (err) {
60155
- cropMetricLogger.debug("native crop request failed — falling back", { meta: { error: errMsg(err) } });
60517
+ cropMetricLogger.debug("native crop request failed — falling back", { meta: { error: errMsg$1(err) } });
60156
60518
  return null;
60157
60519
  }
60158
60520
  };
@@ -60201,7 +60563,7 @@ function createNativeFrameTransport(deps) {
60201
60563
  const tiered = await fetchNativeFullFrameTiered(handle, DISPLAY_FALLBACK_MAX_WIDTH).catch((err) => {
60202
60564
  logger.child("DisplayCrop").debug("fallback full-frame fetch failed — treating as a miss", { meta: {
60203
60565
  nodeId: handle.nodeId,
60204
- error: errMsg(err)
60566
+ error: errMsg$1(err)
60205
60567
  } });
60206
60568
  return null;
60207
60569
  });
@@ -64838,6 +65200,97 @@ function rowFromMetadata(id, metadata) {
64838
65200
  };
64839
65201
  }
64840
65202
  //#endregion
65203
+ //#region src/pipeline-analytics/summary/summary-sealer.ts
65204
+ function errMsg(err) {
65205
+ return err instanceof Error ? err.message : String(err);
65206
+ }
65207
+ var SummarySealer = class {
65208
+ deps;
65209
+ sweepInFlight = false;
65210
+ constructor(deps) {
65211
+ this.deps = deps;
65212
+ }
65213
+ async onTrackOpened(input) {
65214
+ try {
65215
+ await this.deps.summaries.openOrAbsorb({
65216
+ deviceId: input.deviceId,
65217
+ trackId: input.trackId,
65218
+ at: this.deps.now()
65219
+ });
65220
+ } catch (err) {
65221
+ this.deps.logger.warn("summary: could not open or absorb for a track", {
65222
+ tags: { deviceId: input.deviceId },
65223
+ meta: {
65224
+ trackId: input.trackId,
65225
+ error: errMsg(err)
65226
+ }
65227
+ });
65228
+ }
65229
+ }
65230
+ /** One sweep at a time (THE latch). A tick that finds one in flight returns. */
65231
+ async sweep() {
65232
+ if (this.sweepInFlight) return;
65233
+ this.sweepInFlight = true;
65234
+ try {
65235
+ const open = await this.deps.summaries.listOpen();
65236
+ for (const summary of open) await this.sealIfDone(summary);
65237
+ } catch (err) {
65238
+ this.deps.logger.warn("summary sweep: could not list open summaries", { meta: { error: errMsg(err) } });
65239
+ } finally {
65240
+ this.sweepInFlight = false;
65241
+ }
65242
+ }
65243
+ async sealIfDone(summary) {
65244
+ const { id: summaryId, deviceId } = summary;
65245
+ let openTracks;
65246
+ try {
65247
+ openTracks = await this.deps.openTrackIds(deviceId);
65248
+ } catch (err) {
65249
+ this.deps.logger.warn("summary sweep: could not read open tracks — seal aborted this sweep", {
65250
+ tags: { deviceId },
65251
+ meta: {
65252
+ summaryId,
65253
+ error: errMsg(err)
65254
+ }
65255
+ });
65256
+ return;
65257
+ }
65258
+ if (openTracks.length > 0) return;
65259
+ let quiet;
65260
+ try {
65261
+ quiet = await this.deps.isCameraQuiet(deviceId);
65262
+ } catch (err) {
65263
+ this.deps.logger.warn("summary sweep: could not read the stationary registry — seal aborted this sweep", {
65264
+ tags: { deviceId },
65265
+ meta: {
65266
+ summaryId,
65267
+ error: errMsg(err)
65268
+ }
65269
+ });
65270
+ return;
65271
+ }
65272
+ if (!quiet) return;
65273
+ const sealedAt = this.deps.now();
65274
+ try {
65275
+ if (await this.deps.summaries.seal(summary, sealedAt)) this.deps.logger.info("summary sealed", {
65276
+ tags: { deviceId },
65277
+ meta: {
65278
+ summaryId,
65279
+ sealedAt
65280
+ }
65281
+ });
65282
+ } catch (err) {
65283
+ this.deps.logger.warn("summary sweep: seal failed — the row stays open for the next sweep", {
65284
+ tags: { deviceId },
65285
+ meta: {
65286
+ summaryId,
65287
+ error: errMsg(err)
65288
+ }
65289
+ });
65290
+ }
65291
+ }
65292
+ };
65293
+ //#endregion
64841
65294
  //#region src/pipeline-analytics/store/settings-raw-mirror.ts
64842
65295
  /** How long a mirrored entry is served before a refresh is started behind the
64843
65296
  * next reader. See the module doc for why this matches the per-kind caches'
@@ -66150,6 +66603,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
66150
66603
  storageMigrationLeaseId = null;
66151
66604
  /** Per-camera history of LINKED-device sensor state changes (Part B). */
66152
66605
  sensorEventStore = null;
66606
+ /** The per-camera session envelope (D359). Bound with the other stores at boot. */
66607
+ summaryStore = null;
66608
+ /** The ONLY writer of a summary's lifecycle (D359). */
66609
+ summarySealer = null;
66153
66610
  /** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
66154
66611
  * so the DeviceStateChanged handler stays cheap. */
66155
66612
  linkedCamerasCache = null;
@@ -66429,7 +66886,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
66429
66886
  };
66430
66887
  this.ctx.logger.info("nc-action data-plane served", { meta: { served: served !== null } });
66431
66888
  } catch (err) {
66432
- this.ctx.logger.warn("nc-action data-plane failed to serve", { meta: { error: errMsg(err) } });
66889
+ this.ctx.logger.warn("nc-action data-plane failed to serve", { meta: { error: errMsg$1(err) } });
66433
66890
  }
66434
66891
  }
66435
66892
  /** Mints a button's callback URL once the action plane is served; null until
@@ -66463,7 +66920,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
66463
66920
  await store.sweep();
66464
66921
  await index.prune(this.isPostProcessingNode ? await store.keptIds() : null);
66465
66922
  } catch (err) {
66466
- this.ctx.logger.warn("nc artifact prune failed", { meta: { error: errMsg(err) } });
66923
+ this.ctx.logger.warn("nc artifact prune failed", { meta: { error: errMsg$1(err) } });
66467
66924
  }
66468
66925
  };
66469
66926
  run();
@@ -66523,7 +66980,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
66523
66980
  retentionDays: Math.round(store.retentionMs / 864e5)
66524
66981
  } });
66525
66982
  } catch (err) {
66526
- this.ctx.logger.warn("nc-artifact data-plane failed to serve", { meta: { error: errMsg(err) } });
66983
+ this.ctx.logger.warn("nc-artifact data-plane failed to serve", { meta: { error: errMsg$1(err) } });
66527
66984
  }
66528
66985
  }
66529
66986
  /** Public base URL for event thumbnails: `/addon/<addonId>/event-media`.
@@ -66767,7 +67224,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
66767
67224
  tags: { deviceId },
66768
67225
  meta: {
66769
67226
  deviceId,
66770
- error: errMsg(error)
67227
+ error: errMsg$1(error)
66771
67228
  }
66772
67229
  });
66773
67230
  }
@@ -66796,7 +67253,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
66796
67253
  tags: { deviceId },
66797
67254
  meta: {
66798
67255
  deviceId,
66799
- error: errMsg(error)
67256
+ error: errMsg$1(error)
66800
67257
  }
66801
67258
  });
66802
67259
  }
@@ -67257,7 +67714,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67257
67714
  */
67258
67715
  armAlarmPanelDeclaration(api) {
67259
67716
  this.declareAlarmPanel(api).catch((err) => {
67260
- this.ctx.logger.warn("alarm panel declaration could not be armed — the Alarm tab stays empty until this addon restarts", { meta: { error: errMsg(err) } });
67717
+ this.ctx.logger.warn("alarm panel declaration could not be armed — the Alarm tab stays empty until this addon restarts", { meta: { error: errMsg$1(err) } });
67261
67718
  });
67262
67719
  }
67263
67720
  async declareAlarmPanel(api) {
@@ -67388,6 +67845,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67388
67845
  MediaStore.declare(api.settingsStore),
67389
67846
  EventStore.declare(api.settingsStore),
67390
67847
  SensorEventStore.declare(api.settingsStore),
67848
+ SummaryStore.declare(api.settingsStore),
67391
67849
  IdentityStore.declare(api.settingsStore),
67392
67850
  FaceStore.declare(api.settingsStore),
67393
67851
  PlateStore.declare(api.settingsStore),
@@ -67409,7 +67867,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67409
67867
  logger: this.ctx.logger.child("viewer-settings")
67410
67868
  });
67411
67869
  } catch (err) {
67412
- this.ctx.logger.warn("viewer-settings legacy adoption threw", { meta: { error: errMsg(err) } });
67870
+ this.ctx.logger.warn("viewer-settings legacy adoption threw", { meta: { error: errMsg$1(err) } });
67413
67871
  }
67414
67872
  runLabelTierMigration({
67415
67873
  store: api.settingsStore,
@@ -67481,7 +67939,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67481
67939
  try {
67482
67940
  locations = await api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION });
67483
67941
  } catch (err) {
67484
- logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
67942
+ logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg$1(err) } });
67485
67943
  this.eventMediaWriteLocationCache = DEFAULT_MEDIA_LOCATION;
67486
67944
  return DEFAULT_MEDIA_LOCATION;
67487
67945
  }
@@ -67649,6 +68107,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67649
68107
  store: api.settingsStore,
67650
68108
  logger: logger.child("SensorEventStore")
67651
68109
  });
68110
+ this.summaryStore = new SummaryStore({
68111
+ store: api.settingsStore,
68112
+ logger: this.ctx.logger.child("summary")
68113
+ });
68114
+ this.summarySealer = new SummarySealer({
68115
+ summaries: this.summaryStore,
68116
+ openTrackIds: async (deviceId) => this.residents.trackIdsForDevice(deviceId),
68117
+ isCameraQuiet: async (deviceId) => (this.stationaryRegistry?.count(deviceId) ?? 0) === 0,
68118
+ now: () => Date.now(),
68119
+ logger: this.ctx.logger.child("summary-sealer")
68120
+ });
67652
68121
  this.packageDropDetector = new PackageDropDetector({
67653
68122
  events: eventStore,
67654
68123
  emit: {
@@ -67686,7 +68155,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67686
68155
  onPersisted: (ev, phase) => this.notificationCenter?.onPackageEventPersisted(ev, phase),
67687
68156
  onError: (scope, err) => logger.warn("package-drop detector error", { meta: {
67688
68157
  scope,
67689
- error: errMsg(err)
68158
+ error: errMsg$1(err)
67690
68159
  } })
67691
68160
  });
67692
68161
  this.linkedCamerasCache = new LinkedCamerasCache({
@@ -67700,7 +68169,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67700
68169
  linkedDevicesBatch: { getLinkedDevicesBatch: (input) => api.deviceManager.getLinkedDevicesBatch.query(input) },
67701
68170
  onError: (scope, err) => logger.warn("linked-cameras cache refresh failed", { meta: {
67702
68171
  scope,
67703
- error: errMsg(err)
68172
+ error: errMsg$1(err)
67704
68173
  } })
67705
68174
  });
67706
68175
  const syntheticTracks = new SyntheticTrackMaterializer({
@@ -67709,7 +68178,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67709
68178
  tracks: trackStore,
67710
68179
  onError: (scope, err) => logger.warn("synthetic track error", { meta: {
67711
68180
  scope,
67712
- error: errMsg(err)
68181
+ error: errMsg$1(err)
67713
68182
  } })
67714
68183
  });
67715
68184
  this.syntheticTracks = syntheticTracks;
@@ -67721,7 +68190,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67721
68190
  ...deviceId !== void 0 ? { tags: { deviceId } } : {},
67722
68191
  meta: {
67723
68192
  scope,
67724
- error: errMsg(err)
68193
+ error: errMsg$1(err)
67725
68194
  }
67726
68195
  })
67727
68196
  });
@@ -67731,7 +68200,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67731
68200
  logger: logger.child("AudioMarkers"),
67732
68201
  onError: (scope, err) => logger.warn("audio marker error", { meta: {
67733
68202
  scope,
67734
- error: errMsg(err)
68203
+ error: errMsg$1(err)
67735
68204
  } })
67736
68205
  });
67737
68206
  const galleryVectorStore = api.vectorStore;
@@ -68052,7 +68521,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68052
68521
  } catch (err) {
68053
68522
  this.ctx.logger.debug("zone ownership read failed", {
68054
68523
  tags: { deviceId },
68055
- meta: { error: errMsg(err) }
68524
+ meta: { error: errMsg$1(err) }
68056
68525
  });
68057
68526
  }
68058
68527
  return out;
@@ -68112,7 +68581,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68112
68581
  } catch (err) {
68113
68582
  return {
68114
68583
  ok: false,
68115
- error: errMsg(err)
68584
+ error: errMsg$1(err)
68116
68585
  };
68117
68586
  }
68118
68587
  },
@@ -68132,7 +68601,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68132
68601
  } catch (err) {
68133
68602
  return {
68134
68603
  ok: false,
68135
- error: errMsg(err)
68604
+ error: errMsg$1(err)
68136
68605
  };
68137
68606
  }
68138
68607
  },
@@ -68488,7 +68957,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68488
68957
  } catch (err) {
68489
68958
  this.ctx.logger.warn("readEventThumbnail failed", { meta: {
68490
68959
  eventId: id,
68491
- error: errMsg(err)
68960
+ error: errMsg$1(err)
68492
68961
  } });
68493
68962
  throw err;
68494
68963
  }
@@ -68508,7 +68977,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68508
68977
  access: planeAccess("event-media")
68509
68978
  } });
68510
68979
  } catch (err) {
68511
- this.ctx.logger.warn("event-media data-plane failed to serve", { meta: { error: errMsg(err) } });
68980
+ this.ctx.logger.warn("event-media data-plane failed to serve", { meta: { error: errMsg$1(err) } });
68512
68981
  }
68513
68982
  }
68514
68983
  /**
@@ -68534,7 +69003,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68534
69003
  handler
68535
69004
  }) ?? null;
68536
69005
  } catch (err) {
68537
- this.ctx.logger.warn("training-export data-plane failed to serve", { meta: { error: errMsg(err) } });
69006
+ this.ctx.logger.warn("training-export data-plane failed to serve", { meta: { error: errMsg$1(err) } });
68538
69007
  }
68539
69008
  }
68540
69009
  /**
@@ -68835,7 +69304,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68835
69304
  handler
68836
69305
  }) ?? null;
68837
69306
  } catch (err) {
68838
- this.ctx.logger.warn("debug-export data-plane failed to serve", { meta: { error: errMsg(err) } });
69307
+ this.ctx.logger.warn("debug-export data-plane failed to serve", { meta: { error: errMsg$1(err) } });
68839
69308
  }
68840
69309
  }
68841
69310
  /**
@@ -68864,7 +69333,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68864
69333
  handler
68865
69334
  }) ?? null;
68866
69335
  } catch (err) {
68867
- this.ctx.logger.warn("retrain-export data-plane failed to serve", { meta: { error: errMsg(err) } });
69336
+ this.ctx.logger.warn("retrain-export data-plane failed to serve", { meta: { error: errMsg$1(err) } });
68868
69337
  }
68869
69338
  }
68870
69339
  /**
@@ -68929,7 +69398,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68929
69398
  tags: { deviceId },
68930
69399
  meta: {
68931
69400
  deviceId,
68932
- error: errMsg(err)
69401
+ error: errMsg$1(err)
68933
69402
  }
68934
69403
  });
68935
69404
  return null;
@@ -69041,7 +69510,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69041
69510
  }, RETENTION_SWEEP_INTERVAL_MS);
69042
69511
  this.ltsFlushTimer = setInterval(() => {
69043
69512
  this.lts?.flushDue().catch((err) => {
69044
- this.ctx.logger.warn("lts flush failed", { meta: { error: errMsg(err) } });
69513
+ this.ctx.logger.warn("lts flush failed", { meta: { error: errMsg$1(err) } });
69045
69514
  });
69046
69515
  }, LTS_FLUSH_INTERVAL_MS);
69047
69516
  this.startOrphanAuditTimer();
@@ -69062,7 +69531,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69062
69531
  } catch (err) {
69063
69532
  this.ctx.logger.error("artifacts storage location is NOT available — enrolling a face, a plate or a scene reference will FAIL until it is", { meta: {
69064
69533
  location: ARTIFACTS_MEDIA_LOCATION,
69065
- error: errMsg(err),
69534
+ error: errMsg$1(err),
69066
69535
  hint: "the declaration is seeded when a storage-provider registers; restart this node so the manifest is re-read, or create an artifacts location. Refusing the write is deliberate — the footage volume is where the last gallery was lost (D197), and the addon data dir carries no media (D341)."
69067
69536
  } });
69068
69537
  return;
@@ -69079,7 +69548,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69079
69548
  hint: "the pixels are gone for good; re-enrol these people from a photo"
69080
69549
  } });
69081
69550
  } catch (err) {
69082
- this.ctx.logger.warn("enrolled gallery media reconcile failed", { meta: { error: errMsg(err) } });
69551
+ this.ctx.logger.warn("enrolled gallery media reconcile failed", { meta: { error: errMsg$1(err) } });
69083
69552
  }
69084
69553
  try {
69085
69554
  const identityStore = this.identityStore;
@@ -69111,7 +69580,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69111
69580
  maxReclaimPerRun: 16
69112
69581
  })).reclaimed > 0) await this.faceRecognizer?.refreshGallery();
69113
69582
  } catch (err) {
69114
- this.ctx.logger.warn("gallery sample reconcile failed", { meta: { error: errMsg(err) } });
69583
+ this.ctx.logger.warn("gallery sample reconcile failed", { meta: { error: errMsg$1(err) } });
69115
69584
  }
69116
69585
  }
69117
69586
  /** Schedule the orphan audit on the operator's interval (hours). */
@@ -69253,7 +69722,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69253
69722
  engine.start();
69254
69723
  this.sceneEngine = engine;
69255
69724
  this.sceneReloadTimer = setInterval(() => {
69256
- store.load().then(() => store.pruneLatches()).catch((err) => log.debug("scene reload failed", { meta: { error: errMsg(err) } }));
69725
+ store.load().then(() => store.pruneLatches()).catch((err) => log.debug("scene reload failed", { meta: { error: errMsg$1(err) } }));
69257
69726
  }, SCENE_RELOAD_INTERVAL_MS);
69258
69727
  this.sceneReloadTimer.unref?.();
69259
69728
  }
@@ -69297,7 +69766,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69297
69766
  lng: status.location.longitude
69298
69767
  };
69299
69768
  } catch (err) {
69300
- this.ctx.logger.debug("site location read failed", { meta: { error: errMsg(err) } });
69769
+ this.ctx.logger.debug("site location read failed", { meta: { error: errMsg$1(err) } });
69301
69770
  }
69302
69771
  this.siteCoordsCache = {
69303
69772
  value,
@@ -69316,7 +69785,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69316
69785
  } catch (err) {
69317
69786
  this.ctx.logger.debug("scene slice mirror failed", {
69318
69787
  tags: { deviceId },
69319
- meta: { error: errMsg(err) }
69788
+ meta: { error: errMsg$1(err) }
69320
69789
  });
69321
69790
  }
69322
69791
  }
@@ -69861,6 +70330,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
69861
70330
  className: t.className
69862
70331
  }
69863
70332
  });
70333
+ this.summarySealer?.onTrackOpened({
70334
+ deviceId,
70335
+ trackId: id
70336
+ });
69864
70337
  const startPayload = buildTrackLifecyclePayload({
69865
70338
  deviceId,
69866
70339
  trackId: id,
@@ -70389,7 +70862,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70389
70862
  } catch (err) {
70390
70863
  this.ctx.logger.debug("confirmation gate: getGlobalSteps failed", { meta: {
70391
70864
  nodeId,
70392
- error: errMsg(err)
70865
+ error: errMsg$1(err)
70393
70866
  } });
70394
70867
  }
70395
70868
  this.confirmationDetectorStepByNode.set(nodeId, {
@@ -70424,7 +70897,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70424
70897
  } catch (err) {
70425
70898
  this.ctx.logger.debug("confirmation gate: re-detection failed — fail-open", {
70426
70899
  tags: { deviceId },
70427
- meta: { error: errMsg(err) }
70900
+ meta: { error: errMsg$1(err) }
70428
70901
  });
70429
70902
  return null;
70430
70903
  }
@@ -70728,7 +71201,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70728
71201
  meta: {
70729
71202
  trackId,
70730
71203
  stepId: d.stepId,
70731
- error: errMsg(err)
71204
+ error: errMsg$1(err)
70732
71205
  }
70733
71206
  });
70734
71207
  }
@@ -70834,7 +71307,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70834
71307
  try {
70835
71308
  value = (await this.ctx.api.embeddingEncoder.getInfo.query())?.modelId ?? null;
70836
71309
  } catch (err) {
70837
- this.ctx.logger.debug("resolveClipModelId: getInfo failed", { meta: { error: errMsg(err) } });
71310
+ this.ctx.logger.debug("resolveClipModelId: getInfo failed", { meta: { error: errMsg$1(err) } });
70838
71311
  }
70839
71312
  this.clipModelIdCache = {
70840
71313
  value,
@@ -70892,7 +71365,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70892
71365
  tags: { deviceId },
70893
71366
  meta: {
70894
71367
  trackId: t.trackId,
70895
- error: errMsg(err)
71368
+ error: errMsg$1(err)
70896
71369
  }
70897
71370
  });
70898
71371
  }
@@ -71045,7 +71518,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71045
71518
  meta: {
71046
71519
  trackId,
71047
71520
  keyFrameKey,
71048
- error: errMsg(err)
71521
+ error: errMsg$1(err)
71049
71522
  }
71050
71523
  });
71051
71524
  }
@@ -71531,7 +72004,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71531
72004
  tags: { deviceId: input.deviceId },
71532
72005
  meta: {
71533
72006
  className: ref.className,
71534
- error: errMsg(err)
72007
+ error: errMsg$1(err)
71535
72008
  }
71536
72009
  });
71537
72010
  }
@@ -71563,7 +72036,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71563
72036
  tags: { deviceId: track.deviceId },
71564
72037
  meta: {
71565
72038
  episodeId: ref.episodeId,
71566
- error: errMsg(err)
72039
+ error: errMsg$1(err)
71567
72040
  }
71568
72041
  });
71569
72042
  }
@@ -71885,6 +72358,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71885
72358
  async sweepExpiredTracks() {
71886
72359
  if (this.shuttingDown || !this.trackStore) return;
71887
72360
  await this.trackCloser.sweep();
72361
+ await this.summarySealer?.sweep();
71888
72362
  }
71889
72363
  /** Unified retention (Phase 1): earliest retained footage per device — the
71890
72364
  * follow-recordings cutoff source. Cached 10 min: availability answers in
@@ -71935,7 +72409,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71935
72409
  listTrackedDeviceIds: async () => await this.trackStore?.listDeviceIds() ?? []
71936
72410
  }, (half, err) => this.ctx.logger.warn("retention scope half unavailable — sweeping on the other", { meta: {
71937
72411
  half,
71938
- error: errMsg(err)
72412
+ error: errMsg$1(err)
71939
72413
  } }));
71940
72414
  }
71941
72415
  /**
@@ -71981,6 +72455,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71981
72455
  await this.runTrackRetentionSweep(deviceIds, now);
71982
72456
  const eventStore = this.eventStore;
71983
72457
  const sensorEventStore = this.sensorEventStore;
72458
+ const summaryStore = this.summaryStore;
71984
72459
  await sweepTracklessRoots({
71985
72460
  deviceIds,
71986
72461
  nowMs: now,
@@ -71990,15 +72465,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71990
72465
  cutoffMs
71991
72466
  }),
71992
72467
  evictSensorEvents: sensorEventStore ? (deviceId, cutoffMs) => sensorEventStore.evictBefore(deviceId, cutoffMs) : null,
72468
+ evictSummaries: summaryStore ? (deviceId, cutoffMs) => summaryStore.evictBefore(deviceId, cutoffMs) : null,
71993
72469
  onEvicted: async (deviceId, evicted, cutoffs) => {
71994
- const total = evicted.motion.length + evicted.audio.length + evicted.sensor;
72470
+ const total = evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries;
71995
72471
  await this.eventsOpsLog?.append({
71996
72472
  op: "prune",
71997
72473
  reason: "retention",
71998
72474
  deviceId,
71999
72475
  itemsAffected: total,
72000
72476
  bytesReclaimed: 0,
72001
- detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor`
72477
+ detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor, ${evicted.summaries} summaries`
72002
72478
  });
72003
72479
  this.ctx.logger.info("analytics trackless event eviction (age sweep)", {
72004
72480
  tags: { deviceId },
@@ -72006,6 +72482,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
72006
72482
  motion: evicted.motion.length,
72007
72483
  audio: evicted.audio.length,
72008
72484
  sensor: evicted.sensor,
72485
+ summaries: evicted.summaries,
72009
72486
  mode: cutoffs.effectiveMode,
72010
72487
  cutoffMs: cutoffs.cutoffMs
72011
72488
  }
@@ -72825,7 +73302,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
72825
73302
  const status = await api.pipelineOrchestrator.getCameraStatus.query({ deviceId: input.deviceId }).catch((err) => {
72826
73303
  this.ctx.logger.warn("listEventKinds: getCameraStatus failed", {
72827
73304
  tags: { deviceId: input.deviceId },
72828
- meta: { error: errMsg(err) }
73305
+ meta: { error: errMsg$1(err) }
72829
73306
  });
72830
73307
  return null;
72831
73308
  });
@@ -72844,7 +73321,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
72844
73321
  tags: { deviceId: input.deviceId },
72845
73322
  meta: {
72846
73323
  scope,
72847
- error: errMsg(err)
73324
+ error: errMsg$1(err)
72848
73325
  }
72849
73326
  })
72850
73327
  }, input.deviceId);
@@ -72877,7 +73354,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
72877
73354
  const statuses = await api.pipelineOrchestrator.getCameraStatuses.query({ deviceIds }).catch((err) => {
72878
73355
  this.ctx.logger.warn("listEventKindsBatch: getCameraStatuses failed", { meta: {
72879
73356
  deviceCount: deviceIds.length,
72880
- error: errMsg(err)
73357
+ error: errMsg$1(err)
72881
73358
  } });
72882
73359
  return [];
72883
73360
  });
@@ -72889,7 +73366,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
72889
73366
  }, deviceIds).catch((err) => {
72890
73367
  this.ctx.logger.warn("listEventKindsBatch: batched device topology unavailable — per-device fallback", { meta: {
72891
73368
  deviceCount: deviceIds.length,
72892
- error: errMsg(err)
73369
+ error: errMsg$1(err)
72893
73370
  } });
72894
73371
  return null;
72895
73372
  });
@@ -72909,13 +73386,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
72909
73386
  tags: { deviceId },
72910
73387
  meta: {
72911
73388
  scope,
72912
- error: errMsg(err)
73389
+ error: errMsg$1(err)
72913
73390
  }
72914
73391
  })
72915
73392
  }, deviceId);
72916
73393
  }, (deviceId, err) => this.ctx.logger.warn("listEventKindsBatch: compose threw — empty vocabulary", {
72917
73394
  tags: { deviceId },
72918
- meta: { error: errMsg(err) }
73395
+ meta: { error: errMsg$1(err) }
72919
73396
  }));
72920
73397
  }
72921
73398
  /** Per-camera sensor-event history (state changes of linked devices). */
@@ -72961,7 +73438,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
72961
73438
  tags: { deviceId: data.deviceId },
72962
73439
  meta: {
72963
73440
  capName: data.capName,
72964
- error: errMsg(err)
73441
+ error: errMsg$1(err)
72965
73442
  }
72966
73443
  });
72967
73444
  }
@@ -73004,7 +73481,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
73004
73481
  tags: { deviceId: data.deviceId },
73005
73482
  meta: {
73006
73483
  capName: data.capName,
73007
- error: errMsg(err)
73484
+ error: errMsg$1(err)
73008
73485
  }
73009
73486
  });
73010
73487
  }
@@ -73102,7 +73579,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
73102
73579
  } catch (err) {
73103
73580
  this.ctx?.logger?.debug("resolveProducingDeviceName failed", {
73104
73581
  tags: { deviceId },
73105
- meta: { error: errMsg(err) }
73582
+ meta: { error: errMsg$1(err) }
73106
73583
  });
73107
73584
  return;
73108
73585
  }
@@ -73771,7 +74248,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
73771
74248
  nodeId
73772
74249
  };
73773
74250
  } catch (err) {
73774
- const reason = errMsg(err);
74251
+ const reason = errMsg$1(err);
73775
74252
  failures.push(`${nodeId}: ${reason}`);
73776
74253
  this.ctx.logger.warn("photo enrollment: a node could not analyse the photograph", { meta: {
73777
74254
  nodeId,
@@ -74485,7 +74962,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
74485
74962
  mediaStore,
74486
74963
  faceStore: this.faceStore,
74487
74964
  plateStore: this.plateStore,
74488
- objectEmbeddingStore: this.objectEmbeddingStore
74965
+ objectEmbeddingStore: this.objectEmbeddingStore,
74966
+ summaryStore: this.summaryStore
74489
74967
  };
74490
74968
  const absent = absentCascadeLegs(stores);
74491
74969
  if (absent.length > 0) this.ctx.logger.warn("track cascade is missing a leg — its rows will be left behind", { meta: {
@@ -74991,7 +75469,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
74991
75469
  this.ctx.logger.debug("event media: thumb variant failed — serving full", { meta: {
74992
75470
  key: media.key,
74993
75471
  size: variant.size,
74994
- error: errMsg(err)
75472
+ error: errMsg$1(err)
74995
75473
  } });
74996
75474
  return media;
74997
75475
  }