@camstack/addon-post-analysis 1.2.181 → 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.
@@ -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-BB70nZUm.js");
5
+ const require_dist = require("../dist-_fedHYU3.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -35980,6 +35980,7 @@ var EMPTY_CASCADE_TALLY = {
35980
35980
  faces: 0,
35981
35981
  plates: 0,
35982
35982
  embeddings: 0,
35983
+ summaries: 0,
35983
35984
  bytesReclaimed: 0
35984
35985
  };
35985
35986
  /** Fold a leaf store's receipts into a running tally. */
@@ -36023,6 +36024,13 @@ function addLeafCounts(tally, counts) {
36023
36024
  bytesReclaimed
36024
36025
  };
36025
36026
  break;
36027
+ case "summaries":
36028
+ next = {
36029
+ ...next,
36030
+ summaries: next.summaries + c.rows,
36031
+ bytesReclaimed
36032
+ };
36033
+ break;
36026
36034
  }
36027
36035
  }
36028
36036
  return next;
@@ -36036,6 +36044,7 @@ function mergeCascadeTallies(a, b) {
36036
36044
  faces: a.faces + b.faces,
36037
36045
  plates: a.plates + b.plates,
36038
36046
  embeddings: a.embeddings + b.embeddings,
36047
+ summaries: a.summaries + b.summaries,
36039
36048
  bytesReclaimed: a.bytesReclaimed + b.bytesReclaimed
36040
36049
  };
36041
36050
  }
@@ -36098,6 +36107,7 @@ function absentCascadeLegs(stores) {
36098
36107
  if (!stores.faceStore) absent.push("faces");
36099
36108
  if (!stores.plateStore) absent.push("plates");
36100
36109
  if (!stores.objectEmbeddingStore) absent.push("embeddings");
36110
+ if (!stores.summaryStore) absent.push("summaries");
36101
36111
  return absent;
36102
36112
  }
36103
36113
  function buildAnalyticsCascadeRegistry(stores) {
@@ -36105,6 +36115,7 @@ function buildAnalyticsCascadeRegistry(stores) {
36105
36115
  if (stores.faceStore) registry.push(stores.faceStore);
36106
36116
  if (stores.plateStore) registry.push(stores.plateStore);
36107
36117
  if (stores.objectEmbeddingStore) registry.push(stores.objectEmbeddingStore);
36118
+ if (stores.summaryStore) registry.push(stores.summaryStore);
36108
36119
  return registry;
36109
36120
  }
36110
36121
  /**
@@ -56314,6 +56325,344 @@ async function reconcileGallerySamples(deps, options) {
56314
56325
  stoppedByCeiling
56315
56326
  };
56316
56327
  }
56328
+ //#endregion
56329
+ //#region src/pipeline-analytics/summary/summary-store.ts
56330
+ var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
56331
+ var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
56332
+ var SUMMARIES_COLUMNS = [
56333
+ {
56334
+ name: "id",
56335
+ type: "TEXT",
56336
+ primaryKey: true,
56337
+ notNull: true
56338
+ },
56339
+ {
56340
+ name: "deviceId",
56341
+ type: "INTEGER",
56342
+ notNull: true
56343
+ },
56344
+ {
56345
+ name: "openedAt",
56346
+ type: "INTEGER",
56347
+ notNull: true
56348
+ },
56349
+ {
56350
+ name: "lastActiveAt",
56351
+ type: "INTEGER",
56352
+ notNull: true
56353
+ },
56354
+ {
56355
+ name: "sealedAt",
56356
+ type: "INTEGER"
56357
+ },
56358
+ (
56359
+ /** `deviceId` while open, NULL once sealed — the unique "one open per camera". */
56360
+ {
56361
+ name: "openDeviceId",
56362
+ type: "INTEGER"
56363
+ }),
56364
+ {
56365
+ name: "memberCount",
56366
+ type: "INTEGER",
56367
+ notNull: true
56368
+ }
56369
+ ];
56370
+ var SUMMARIES_INDEXES = [{
56371
+ name: "summaries_open_device",
56372
+ columns: ["openDeviceId"],
56373
+ unique: true
56374
+ }, {
56375
+ name: "summaries_device_opened",
56376
+ columns: ["deviceId", "openedAt"]
56377
+ }];
56378
+ var SUMMARY_MEMBERS_COLUMNS = [
56379
+ {
56380
+ name: "id",
56381
+ type: "TEXT",
56382
+ primaryKey: true,
56383
+ notNull: true
56384
+ },
56385
+ {
56386
+ name: "summaryId",
56387
+ type: "TEXT",
56388
+ notNull: true
56389
+ },
56390
+ {
56391
+ name: "trackId",
56392
+ type: "TEXT",
56393
+ notNull: true
56394
+ },
56395
+ {
56396
+ name: "deviceId",
56397
+ type: "INTEGER",
56398
+ notNull: true
56399
+ },
56400
+ (
56401
+ /** When the track joined — named `timestamp` so the orphan audit's declared-owner scope reads it. */
56402
+ {
56403
+ name: "timestamp",
56404
+ type: "INTEGER",
56405
+ notNull: true
56406
+ })
56407
+ ];
56408
+ var SUMMARY_MEMBERS_INDEXES = [{
56409
+ name: "summary_members_summary",
56410
+ columns: ["summaryId"]
56411
+ }, {
56412
+ name: "summary_members_track",
56413
+ columns: ["trackId"]
56414
+ }];
56415
+ function summaryId(deviceId, openedAt) {
56416
+ return `sum-${deviceId}-${openedAt}`;
56417
+ }
56418
+ function memberRowId(summaryIdValue, trackId) {
56419
+ return `${summaryIdValue}:${trackId}`;
56420
+ }
56421
+ function num$1(value, fallback = 0) {
56422
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
56423
+ }
56424
+ function toRow$1(id, data) {
56425
+ return {
56426
+ id,
56427
+ deviceId: num$1(data["deviceId"]),
56428
+ openedAt: num$1(data["openedAt"]),
56429
+ lastActiveAt: num$1(data["lastActiveAt"]),
56430
+ sealedAt: typeof data["sealedAt"] === "number" ? data["sealedAt"] : null,
56431
+ memberCount: num$1(data["memberCount"])
56432
+ };
56433
+ }
56434
+ function isUniqueViolation(err) {
56435
+ return err instanceof Error && /UNIQUE constraint failed/i.test(err.message);
56436
+ }
56437
+ var SummaryStore = class {
56438
+ store;
56439
+ logger;
56440
+ constructor(deps) {
56441
+ this.store = deps.store;
56442
+ this.logger = deps.logger;
56443
+ }
56444
+ static async declare(store) {
56445
+ await Promise.all([store.declareCollection.mutate({
56446
+ collection: SUMMARIES_COLLECTION,
56447
+ columns: [...SUMMARIES_COLUMNS],
56448
+ indexes: [...SUMMARIES_INDEXES]
56449
+ }), store.declareCollection.mutate({
56450
+ collection: SUMMARY_MEMBERS_COLLECTION,
56451
+ columns: [...SUMMARY_MEMBERS_COLUMNS],
56452
+ indexes: [...SUMMARY_MEMBERS_INDEXES]
56453
+ })]);
56454
+ }
56455
+ /** The camera's OPEN summary, from the row — `null` when none. */
56456
+ async findOpen(deviceId) {
56457
+ const first = (await this.store.query.query({
56458
+ collection: SUMMARIES_COLLECTION,
56459
+ filter: {
56460
+ where: { openDeviceId: deviceId },
56461
+ limit: 1
56462
+ }
56463
+ }))[0];
56464
+ return first === void 0 ? null : toRow$1(first.id, first.data);
56465
+ }
56466
+ async listOpen() {
56467
+ return (await this.store.query.query({
56468
+ collection: SUMMARIES_COLLECTION,
56469
+ filter: {
56470
+ where: { sealedAt: null },
56471
+ limit: 1e4
56472
+ }
56473
+ })).filter((r) => r.data["openDeviceId"] !== null).map((r) => toRow$1(r.id, r.data));
56474
+ }
56475
+ async membersOf(summaryIdValue) {
56476
+ return (await this.store.query.query({
56477
+ collection: SUMMARY_MEMBERS_COLLECTION,
56478
+ filter: {
56479
+ where: { summaryId: summaryIdValue },
56480
+ limit: 1e4
56481
+ }
56482
+ })).map((r) => ({
56483
+ summaryId: summaryIdValue,
56484
+ trackId: String(r.data["trackId"] ?? ""),
56485
+ deviceId: num$1(r.data["deviceId"]),
56486
+ joinedAt: num$1(r.data["timestamp"])
56487
+ }));
56488
+ }
56489
+ /**
56490
+ * Put a track into the camera's open summary, opening one when there is
56491
+ * none. Racing opens are settled by the store: the loser's insert hits the
56492
+ * unique `openDeviceId` and is re-read as an absorb.
56493
+ */
56494
+ async openOrAbsorb(input) {
56495
+ const existing = await this.findOpen(input.deviceId);
56496
+ if (existing !== null) return this.absorb(existing, input);
56497
+ const id = summaryId(input.deviceId, input.at);
56498
+ try {
56499
+ await this.store.set.mutate({
56500
+ collection: SUMMARIES_COLLECTION,
56501
+ key: id,
56502
+ value: {
56503
+ deviceId: input.deviceId,
56504
+ openedAt: input.at,
56505
+ lastActiveAt: input.at,
56506
+ sealedAt: null,
56507
+ openDeviceId: input.deviceId,
56508
+ memberCount: 1
56509
+ }
56510
+ });
56511
+ } catch (err) {
56512
+ if (!isUniqueViolation(err)) throw err;
56513
+ const winner = await this.findOpen(input.deviceId);
56514
+ if (winner === null) throw err;
56515
+ return this.absorb(winner, input);
56516
+ }
56517
+ await this.addMember(id, input);
56518
+ return {
56519
+ id,
56520
+ deviceId: input.deviceId,
56521
+ openedAt: input.at,
56522
+ lastActiveAt: input.at,
56523
+ sealedAt: null,
56524
+ memberCount: 1
56525
+ };
56526
+ }
56527
+ async absorb(summary, input) {
56528
+ const members = await this.membersOf(summary.id);
56529
+ if (members.some((m) => m.trackId === input.trackId)) return summary;
56530
+ await this.addMember(summary.id, input);
56531
+ const memberCount = members.length + 1;
56532
+ const lastActiveAt = Math.max(summary.lastActiveAt, input.at);
56533
+ await this.store.updateWhere.mutate({
56534
+ collection: SUMMARIES_COLLECTION,
56535
+ filter: { where: {
56536
+ openDeviceId: summary.deviceId,
56537
+ openedAt: summary.openedAt
56538
+ } },
56539
+ data: {
56540
+ memberCount,
56541
+ lastActiveAt
56542
+ }
56543
+ });
56544
+ return {
56545
+ ...summary,
56546
+ memberCount,
56547
+ lastActiveAt
56548
+ };
56549
+ }
56550
+ async addMember(summaryIdValue, input) {
56551
+ await this.store.insertMany.mutate({
56552
+ collection: SUMMARY_MEMBERS_COLLECTION,
56553
+ records: [{
56554
+ id: memberRowId(summaryIdValue, input.trackId),
56555
+ data: {
56556
+ summaryId: summaryIdValue,
56557
+ trackId: input.trackId,
56558
+ deviceId: input.deviceId,
56559
+ timestamp: input.at
56560
+ }
56561
+ }]
56562
+ });
56563
+ }
56564
+ /**
56565
+ * Seal in place. `true` when THIS call sealed it; `false` when the row was
56566
+ * already sealed (or gone) — the second sealer learns, it does not write.
56567
+ */
56568
+ async seal(summary, sealedAt) {
56569
+ const { updated } = await this.store.updateWhere.mutate({
56570
+ collection: SUMMARIES_COLLECTION,
56571
+ filter: { where: {
56572
+ openDeviceId: summary.deviceId,
56573
+ openedAt: summary.openedAt
56574
+ } },
56575
+ data: {
56576
+ sealedAt,
56577
+ openDeviceId: null
56578
+ }
56579
+ });
56580
+ if (updated > 1) this.logger.error("summary seal touched more than one row — the open key is not unique", {
56581
+ tags: { deviceId: summary.deviceId },
56582
+ meta: {
56583
+ summaryId: summary.id,
56584
+ updated
56585
+ }
56586
+ });
56587
+ return updated >= 1;
56588
+ }
56589
+ /**
56590
+ * Track cascade leaf: a member row whose track is gone is gone too, and
56591
+ * the summary's count follows. The envelope itself outlives its members
56592
+ * — it is an age-keyed root (`RETENTION_ROOTS`, `summary`).
56593
+ */
56594
+ async deleteByTracks(trackIds) {
56595
+ if (trackIds.length === 0) return [{
56596
+ family: "summaries",
56597
+ rows: 0,
56598
+ bytes: 0
56599
+ }];
56600
+ const members = await this.store.query.query({
56601
+ collection: SUMMARY_MEMBERS_COLLECTION,
56602
+ filter: {
56603
+ whereIn: { trackId: [...trackIds] },
56604
+ limit: 1e4
56605
+ }
56606
+ });
56607
+ if (members.length === 0) return [{
56608
+ family: "summaries",
56609
+ rows: 0,
56610
+ bytes: 0
56611
+ }];
56612
+ const { deleted } = await this.store.deleteWhere.mutate({
56613
+ collection: SUMMARY_MEMBERS_COLLECTION,
56614
+ filter: { whereIn: { trackId: [...trackIds] } }
56615
+ });
56616
+ const touched = new Set(members.map((m) => String(m.data["summaryId"] ?? "")));
56617
+ for (const summaryIdValue of touched) {
56618
+ const remaining = await this.membersOf(summaryIdValue);
56619
+ const raw = await this.store.get.query({
56620
+ collection: SUMMARIES_COLLECTION,
56621
+ key: summaryIdValue
56622
+ });
56623
+ if (typeof raw !== "object" || raw === null) continue;
56624
+ const row = raw;
56625
+ await this.store.updateWhere.mutate({
56626
+ collection: SUMMARIES_COLLECTION,
56627
+ filter: { where: {
56628
+ deviceId: num$1(row["deviceId"]),
56629
+ openedAt: num$1(row["openedAt"])
56630
+ } },
56631
+ data: { memberCount: remaining.length }
56632
+ });
56633
+ }
56634
+ return [{
56635
+ family: "summaries",
56636
+ rows: deleted,
56637
+ bytes: 0
56638
+ }];
56639
+ }
56640
+ /** Age sweep on the device window: everything opened before `cutoffMs`, members included. */
56641
+ async evictBefore(deviceId, cutoffMs) {
56642
+ const rows = await this.store.query.query({
56643
+ collection: SUMMARIES_COLLECTION,
56644
+ filter: {
56645
+ where: { deviceId },
56646
+ whereBetween: { openedAt: [0, cutoffMs - 1] },
56647
+ limit: 1e4
56648
+ }
56649
+ });
56650
+ if (rows.length === 0) return 0;
56651
+ const ids = rows.map((r) => r.id);
56652
+ await this.store.deleteWhere.mutate({
56653
+ collection: SUMMARY_MEMBERS_COLLECTION,
56654
+ filter: { whereIn: { summaryId: ids } }
56655
+ });
56656
+ const { deleted } = await this.store.deleteWhere.mutate({
56657
+ collection: SUMMARIES_COLLECTION,
56658
+ filter: {
56659
+ where: { deviceId },
56660
+ whereBetween: { openedAt: [0, cutoffMs - 1] }
56661
+ }
56662
+ });
56663
+ return deleted;
56664
+ }
56665
+ };
56317
56666
  /**
56318
56667
  * Where the completion marker lives.
56319
56668
  *
@@ -58609,6 +58958,10 @@ var ANALYTICS_COLLECTIONS = [
58609
58958
  collection: PLATES_COLLECTION,
58610
58959
  classification: derived(PLATE_COLUMNS)
58611
58960
  },
58961
+ {
58962
+ collection: SUMMARY_MEMBERS_COLLECTION,
58963
+ classification: derived(SUMMARY_MEMBERS_COLUMNS)
58964
+ },
58612
58965
  {
58613
58966
  collection: MEDIA_COLLECTION,
58614
58967
  classification: {
@@ -58652,6 +59005,13 @@ var ANALYTICS_COLLECTIONS = [
58652
59005
  rootId: "ops-log"
58653
59006
  }
58654
59007
  },
59008
+ {
59009
+ collection: SUMMARIES_COLLECTION,
59010
+ classification: {
59011
+ kind: "retention-root",
59012
+ rootId: "summary"
59013
+ }
59014
+ },
58655
59015
  {
58656
59016
  collection: IDENTITIES_COLLECTION,
58657
59017
  classification: {
@@ -59212,12 +59572,14 @@ async function sweepTracklessRoots(deps) {
59212
59572
  cutoffMs
59213
59573
  });
59214
59574
  const sensor = deps.evictSensorEvents ? await deps.evictSensorEvents(deviceId, cutoffMs) : 0;
59575
+ const summaries = deps.evictSummaries ? await deps.evictSummaries(deviceId, cutoffMs) : 0;
59215
59576
  const evicted = {
59216
59577
  motion: events.motion,
59217
59578
  audio: events.audio,
59218
- sensor
59579
+ sensor,
59580
+ summaries
59219
59581
  };
59220
- if (evicted.motion.length + evicted.audio.length + evicted.sensor > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
59582
+ if (evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
59221
59583
  } catch (err) {
59222
59584
  deps.onError(deviceId, err);
59223
59585
  }
@@ -64912,6 +65274,97 @@ function rowFromMetadata(id, metadata) {
64912
65274
  };
64913
65275
  }
64914
65276
  //#endregion
65277
+ //#region src/pipeline-analytics/summary/summary-sealer.ts
65278
+ function errMsg(err) {
65279
+ return err instanceof Error ? err.message : String(err);
65280
+ }
65281
+ var SummarySealer = class {
65282
+ deps;
65283
+ sweepInFlight = false;
65284
+ constructor(deps) {
65285
+ this.deps = deps;
65286
+ }
65287
+ async onTrackOpened(input) {
65288
+ try {
65289
+ await this.deps.summaries.openOrAbsorb({
65290
+ deviceId: input.deviceId,
65291
+ trackId: input.trackId,
65292
+ at: this.deps.now()
65293
+ });
65294
+ } catch (err) {
65295
+ this.deps.logger.warn("summary: could not open or absorb for a track", {
65296
+ tags: { deviceId: input.deviceId },
65297
+ meta: {
65298
+ trackId: input.trackId,
65299
+ error: errMsg(err)
65300
+ }
65301
+ });
65302
+ }
65303
+ }
65304
+ /** One sweep at a time (THE latch). A tick that finds one in flight returns. */
65305
+ async sweep() {
65306
+ if (this.sweepInFlight) return;
65307
+ this.sweepInFlight = true;
65308
+ try {
65309
+ const open = await this.deps.summaries.listOpen();
65310
+ for (const summary of open) await this.sealIfDone(summary);
65311
+ } catch (err) {
65312
+ this.deps.logger.warn("summary sweep: could not list open summaries", { meta: { error: errMsg(err) } });
65313
+ } finally {
65314
+ this.sweepInFlight = false;
65315
+ }
65316
+ }
65317
+ async sealIfDone(summary) {
65318
+ const { id: summaryId, deviceId } = summary;
65319
+ let openTracks;
65320
+ try {
65321
+ openTracks = await this.deps.openTrackIds(deviceId);
65322
+ } catch (err) {
65323
+ this.deps.logger.warn("summary sweep: could not read open tracks — seal aborted this sweep", {
65324
+ tags: { deviceId },
65325
+ meta: {
65326
+ summaryId,
65327
+ error: errMsg(err)
65328
+ }
65329
+ });
65330
+ return;
65331
+ }
65332
+ if (openTracks.length > 0) return;
65333
+ let quiet;
65334
+ try {
65335
+ quiet = await this.deps.isCameraQuiet(deviceId);
65336
+ } catch (err) {
65337
+ this.deps.logger.warn("summary sweep: could not read the stationary registry — seal aborted this sweep", {
65338
+ tags: { deviceId },
65339
+ meta: {
65340
+ summaryId,
65341
+ error: errMsg(err)
65342
+ }
65343
+ });
65344
+ return;
65345
+ }
65346
+ if (!quiet) return;
65347
+ const sealedAt = this.deps.now();
65348
+ try {
65349
+ if (await this.deps.summaries.seal(summary, sealedAt)) this.deps.logger.info("summary sealed", {
65350
+ tags: { deviceId },
65351
+ meta: {
65352
+ summaryId,
65353
+ sealedAt
65354
+ }
65355
+ });
65356
+ } catch (err) {
65357
+ this.deps.logger.warn("summary sweep: seal failed — the row stays open for the next sweep", {
65358
+ tags: { deviceId },
65359
+ meta: {
65360
+ summaryId,
65361
+ error: errMsg(err)
65362
+ }
65363
+ });
65364
+ }
65365
+ }
65366
+ };
65367
+ //#endregion
64915
65368
  //#region src/pipeline-analytics/store/settings-raw-mirror.ts
64916
65369
  /** How long a mirrored entry is served before a refresh is started behind the
64917
65370
  * next reader. See the module doc for why this matches the per-kind caches'
@@ -66224,6 +66677,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
66224
66677
  storageMigrationLeaseId = null;
66225
66678
  /** Per-camera history of LINKED-device sensor state changes (Part B). */
66226
66679
  sensorEventStore = null;
66680
+ /** The per-camera session envelope (D359). Bound with the other stores at boot. */
66681
+ summaryStore = null;
66682
+ /** The ONLY writer of a summary's lifecycle (D359). */
66683
+ summarySealer = null;
66227
66684
  /** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
66228
66685
  * so the DeviceStateChanged handler stays cheap. */
66229
66686
  linkedCamerasCache = null;
@@ -67462,6 +67919,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
67462
67919
  MediaStore.declare(api.settingsStore),
67463
67920
  EventStore.declare(api.settingsStore),
67464
67921
  SensorEventStore.declare(api.settingsStore),
67922
+ SummaryStore.declare(api.settingsStore),
67465
67923
  IdentityStore.declare(api.settingsStore),
67466
67924
  FaceStore.declare(api.settingsStore),
67467
67925
  PlateStore.declare(api.settingsStore),
@@ -67723,6 +68181,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
67723
68181
  store: api.settingsStore,
67724
68182
  logger: logger.child("SensorEventStore")
67725
68183
  });
68184
+ this.summaryStore = new SummaryStore({
68185
+ store: api.settingsStore,
68186
+ logger: this.ctx.logger.child("summary")
68187
+ });
68188
+ this.summarySealer = new SummarySealer({
68189
+ summaries: this.summaryStore,
68190
+ openTrackIds: async (deviceId) => this.residents.trackIdsForDevice(deviceId),
68191
+ isCameraQuiet: async (deviceId) => (this.stationaryRegistry?.count(deviceId) ?? 0) === 0,
68192
+ now: () => Date.now(),
68193
+ logger: this.ctx.logger.child("summary-sealer")
68194
+ });
67726
68195
  this.packageDropDetector = new PackageDropDetector({
67727
68196
  events: eventStore,
67728
68197
  emit: {
@@ -69935,6 +70404,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
69935
70404
  className: t.className
69936
70405
  }
69937
70406
  });
70407
+ this.summarySealer?.onTrackOpened({
70408
+ deviceId,
70409
+ trackId: id
70410
+ });
69938
70411
  const startPayload = buildTrackLifecyclePayload({
69939
70412
  deviceId,
69940
70413
  trackId: id,
@@ -71959,6 +72432,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71959
72432
  async sweepExpiredTracks() {
71960
72433
  if (this.shuttingDown || !this.trackStore) return;
71961
72434
  await this.trackCloser.sweep();
72435
+ await this.summarySealer?.sweep();
71962
72436
  }
71963
72437
  /** Unified retention (Phase 1): earliest retained footage per device — the
71964
72438
  * follow-recordings cutoff source. Cached 10 min: availability answers in
@@ -72055,6 +72529,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
72055
72529
  await this.runTrackRetentionSweep(deviceIds, now);
72056
72530
  const eventStore = this.eventStore;
72057
72531
  const sensorEventStore = this.sensorEventStore;
72532
+ const summaryStore = this.summaryStore;
72058
72533
  await sweepTracklessRoots({
72059
72534
  deviceIds,
72060
72535
  nowMs: now,
@@ -72064,15 +72539,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
72064
72539
  cutoffMs
72065
72540
  }),
72066
72541
  evictSensorEvents: sensorEventStore ? (deviceId, cutoffMs) => sensorEventStore.evictBefore(deviceId, cutoffMs) : null,
72542
+ evictSummaries: summaryStore ? (deviceId, cutoffMs) => summaryStore.evictBefore(deviceId, cutoffMs) : null,
72067
72543
  onEvicted: async (deviceId, evicted, cutoffs) => {
72068
- const total = evicted.motion.length + evicted.audio.length + evicted.sensor;
72544
+ const total = evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries;
72069
72545
  await this.eventsOpsLog?.append({
72070
72546
  op: "prune",
72071
72547
  reason: "retention",
72072
72548
  deviceId,
72073
72549
  itemsAffected: total,
72074
72550
  bytesReclaimed: 0,
72075
- detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor`
72551
+ detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor, ${evicted.summaries} summaries`
72076
72552
  });
72077
72553
  this.ctx.logger.info("analytics trackless event eviction (age sweep)", {
72078
72554
  tags: { deviceId },
@@ -72080,6 +72556,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
72080
72556
  motion: evicted.motion.length,
72081
72557
  audio: evicted.audio.length,
72082
72558
  sensor: evicted.sensor,
72559
+ summaries: evicted.summaries,
72083
72560
  mode: cutoffs.effectiveMode,
72084
72561
  cutoffMs: cutoffs.cutoffMs
72085
72562
  }
@@ -74559,7 +75036,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
74559
75036
  mediaStore,
74560
75037
  faceStore: this.faceStore,
74561
75038
  plateStore: this.plateStore,
74562
- objectEmbeddingStore: this.objectEmbeddingStore
75039
+ objectEmbeddingStore: this.objectEmbeddingStore,
75040
+ summaryStore: this.summaryStore
74563
75041
  };
74564
75042
  const absent = absentCascadeLegs(stores);
74565
75043
  if (absent.length > 0) this.ctx.logger.warn("track cascade is missing a leg — its rows will be left behind", { meta: {