@camstack/addon-post-analysis 1.2.133 → 1.2.135

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-Dqa0bdV6.js");
5
+ const require_dist = require("../dist-kn_4KEmG.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");
@@ -29021,9 +29021,18 @@ function pairRiders(detections) {
29021
29021
  }
29022
29022
  //#endregion
29023
29023
  //#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
29024
- /** How far the grown box's centroid may sit, as a multiple of the LARGER
29025
- * diagonal. 1.35 covers the live 4285605a bird→dog jump (~75px / 59px). */
29026
- var GROWTH_CENTROID_FACTOR = 1.35;
29024
+ /**
29025
+ * How far the grown box's centroid may sit, as a multiple of the **TRACK's own**
29026
+ * diagonal. It was a multiple of the LARGER of the two diagonals until
29027
+ * 2026-08-25, which meant the bigger the INTRUDING detection, the further it
29028
+ * could reach to steal an identity. Live track `59dfb972` lost its car that
29029
+ * way: a 386×752 box 574 px away out-reached a 364×385 track purely because it
29030
+ * was 2.07× the area. The reach is now a property of the track alone.
29031
+ *
29032
+ * 2.3 covers the live `4285605a` bird→dog jump (74.7 px over a 35.8 px crop
29033
+ * diagonal = 2.085) with the same ~10% headroom the old 1.35 gave it.
29034
+ */
29035
+ var GROWTH_CENTROID_FACTOR = 2.3;
29027
29036
  function intersectionArea(a, b) {
29028
29037
  const x1 = Math.max(a.x, b.x);
29029
29038
  const y1 = Math.max(a.y, b.y);
@@ -29032,11 +29041,30 @@ function intersectionArea(a, b) {
29032
29041
  if (x2 <= x1 || y2 <= y1) return 0;
29033
29042
  return (x2 - x1) * (y2 - y1);
29034
29043
  }
29035
- /** Same-class partial crop growing into (or collapsing from) a full-body box
29036
- * with IoU 0. Used by rescue and spawn-dedup so we do not mint a second id.
29037
- * A SMALL box nested inside a LARGE one is occlusion, not growth — those
29038
- * stay two tracks (sort-tracker occlusion suite). */
29039
- function growthAdjacentMatch(track, det) {
29044
+ /**
29045
+ * Same-class partial crop growing into (or collapsing from) a full-body box
29046
+ * with IoU 0. Used by rescue and spawn-dedup so we do not mint a second id.
29047
+ * A SMALL box nested inside a LARGE one is occlusion, not growth — those
29048
+ * stay two tracks (sort-tracker occlusion suite).
29049
+ *
29050
+ * **Geometry alone cannot make this gate safe, and that is measured, not
29051
+ * assumed.** The two live cases:
29052
+ *
29053
+ * | case | dist | trackDiag | dist/trackDiag | dist/maxDiag | dt | trackDiag/s |
29054
+ * | --- | ---: | ---: | ---: | ---: | ---: | ---: |
29055
+ * | `4285605a` bird→dog (KEEP) | 74.7 | 35.8 | 2.085 | 1.238 | 170 ms | 12.26 |
29056
+ * | `59dfb972` car A→car B (REJECT) | 573.8 | 529.8 | 1.083 | 0.679 | 106 ms | 10.22 |
29057
+ *
29058
+ * The theft is a *milder* jump than the case the gate exists for, in EVERY
29059
+ * scale-relative measure — and still milder once divided by elapsed time. A
29060
+ * scale-invariant geometric predicate that accepts the bird therefore MUST
29061
+ * accept the car; so must a speed cap expressed in track-diagonals per second.
29062
+ * The separator has to be something other than the shape of the two boxes, and
29063
+ * the one that holds is track MATURITY ({@link GROWTH_MAX_TRACK_AGE_MS}).
29064
+ */
29065
+ function growthAdjacentMatch(track, det, ctx) {
29066
+ if (ctx.trackAgeMs > 1500) return false;
29067
+ if (ctx.gapMs > 1e3) return false;
29040
29068
  const trackArea = track.w * track.h;
29041
29069
  const detArea = det.w * det.h;
29042
29070
  if (trackArea <= 0 || detArea <= 0) return false;
@@ -29046,8 +29074,24 @@ function growthAdjacentMatch(track, det) {
29046
29074
  if (intersectionArea(track, det) / smaller >= .5) return false;
29047
29075
  const tc = bboxCentroid(track);
29048
29076
  const dc = bboxCentroid(det);
29049
- return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.max(Math.hypot(track.w, track.h), Math.hypot(det.w, det.h));
29077
+ return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.hypot(track.w, track.h);
29050
29078
  }
29079
+ /** Per-(stage, gate) counter + sampling decision. One instance per tracker, so
29080
+ * the counts are per camera and per detection source. */
29081
+ var LooseBindSampler = class {
29082
+ counts = /* @__PURE__ */ new Map();
29083
+ /** Increment the (stage, gate) counter and answer with the running total when
29084
+ * this decision should be reported, or `undefined` when it is sampled out. */
29085
+ next(stage, gate) {
29086
+ const key = `${stage}|${gate}`;
29087
+ const count = (this.counts.get(key) ?? 0) + 1;
29088
+ this.counts.set(key, count);
29089
+ if (count === 1 || count % 50 === 0) return count;
29090
+ }
29091
+ reset() {
29092
+ this.counts.clear();
29093
+ }
29094
+ };
29051
29095
  //#endregion
29052
29096
  //#region src/pipeline-analytics/pipeline/tracker/sort-tracker.ts
29053
29097
  var DEFAULT_TRACKER_CONFIG = {
@@ -29125,12 +29169,62 @@ var SortTracker = class SortTracker {
29125
29169
  * moment the pair stops overlapping and are pruned when either track dies.
29126
29170
  */
29127
29171
  dupStreaks = /* @__PURE__ */ new Map();
29172
+ /** Per-(stage, gate) counters behind the sampled salvage-bind diagnostics. */
29173
+ bindSampler = new LooseBindSampler();
29174
+ /** Sink for salvage-bind diagnostics. Null until an owner that knows the
29175
+ * `deviceId` wires one — see {@link setBindObserver}. */
29176
+ bindObserver = null;
29128
29177
  constructor(config = {}) {
29129
29178
  this.config = {
29130
29179
  ...DEFAULT_TRACKER_CONFIG,
29131
29180
  ...config
29132
29181
  };
29133
29182
  }
29183
+ /**
29184
+ * Wire the diagnostics sink for the salvage branches (rescue, resurrection,
29185
+ * growth spawn-dedup). The tracker has no `deviceId`; the caller that does
29186
+ * stamps it on the log line. Stage 1 is never reported — it runs per track
29187
+ * per frame at frame-rate.
29188
+ */
29189
+ setBindObserver(observer) {
29190
+ this.bindObserver = observer;
29191
+ }
29192
+ /** Emit one salvage decision, subject to {@link LOOSE_BIND_LOG_SAMPLE_EVERY}.
29193
+ * Nothing is computed when no observer is wired. */
29194
+ reportBind(stage, gate, track, det, timestamp) {
29195
+ const observer = this.bindObserver;
29196
+ if (!observer) return;
29197
+ const bindCount = this.bindSampler.next(stage, gate);
29198
+ if (bindCount === void 0) return;
29199
+ const tc = bboxCentroid(track.bbox);
29200
+ const dc = bboxCentroid(det.bbox);
29201
+ const trackArea = track.bbox.w * track.bbox.h;
29202
+ const detArea = det.bbox.w * det.bbox.h;
29203
+ const smaller = Math.min(trackArea, detArea);
29204
+ observer({
29205
+ stage,
29206
+ gate,
29207
+ trackId: track.id,
29208
+ trackClass: track.class,
29209
+ detClass: det.class,
29210
+ detScore: det.score,
29211
+ iou: iou$2(track.bbox, det.bbox),
29212
+ centroidDistPx: Math.hypot(tc.x - dc.x, tc.y - dc.y),
29213
+ trackDiagPx: Math.hypot(track.bbox.w, track.bbox.h),
29214
+ areaRatio: smaller > 0 ? Math.max(trackArea, detArea) / smaller : 0,
29215
+ dtMs: timestamp - track.lastSeen,
29216
+ trackAgeMs: timestamp - track.firstSeen,
29217
+ bindCount,
29218
+ sampledEvery: 50
29219
+ });
29220
+ }
29221
+ /** When the pair is being judged, relative to `track`. */
29222
+ static growthContext(track, timestamp) {
29223
+ return {
29224
+ trackAgeMs: timestamp - track.firstSeen,
29225
+ gapMs: timestamp - track.lastSeen
29226
+ };
29227
+ }
29134
29228
  /** Whether two macro-classes may be collapsed as the SAME subject. Same class
29135
29229
  * always; the {person,animal} pair only when `personAnimalDedup` is on. */
29136
29230
  dedupCompatible(a, b) {
@@ -29175,8 +29269,15 @@ var SortTracker = class SortTracker {
29175
29269
  * set of tracks already surviving this frame (matched, coasting, resurrected,
29176
29270
  * and earlier same-frame spawns).
29177
29271
  */
29178
- isDuplicateSpawn(det, live) {
29179
- for (const t of live) if (this.dedupCompatible(t.class, det.class) && (iou$2(det.bbox, t.bbox) >= this.config.dedupSpawnIou || growthAdjacentMatch(t.bbox, det.bbox))) return true;
29272
+ isDuplicateSpawn(det, live, timestamp) {
29273
+ for (const t of live) {
29274
+ if (!this.dedupCompatible(t.class, det.class)) continue;
29275
+ if (iou$2(det.bbox, t.bbox) >= this.config.dedupSpawnIou) return true;
29276
+ if (growthAdjacentMatch(t.bbox, det.bbox, SortTracker.growthContext(t, timestamp))) {
29277
+ this.reportBind("spawn-dedup", "growth", t, det, timestamp);
29278
+ return true;
29279
+ }
29280
+ }
29180
29281
  if (this.config.personAnimalDedup && det.class === ANIMAL_CLASS && det.score < this.config.animalOverPersonMaxScore) {
29181
29282
  for (const t of live) if (t.class === PERSON_CLASS && iou$2(det.bbox, t.bbox) >= this.config.animalOverPersonIou) return true;
29182
29283
  }
@@ -29263,15 +29364,36 @@ var SortTracker = class SortTracker {
29263
29364
  * crouch→animal flip when `classGroupAssoc` is on, but never by an unrelated
29264
29365
  * class.
29265
29366
  */
29266
- looseMatch(track, det) {
29267
- if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return false;
29268
- if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return true;
29367
+ looseMatch(track, det, timestamp) {
29368
+ if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return null;
29369
+ if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
29269
29370
  const tc = bboxCentroid(track.bbox);
29270
29371
  const dc = bboxCentroid(det.bbox);
29271
29372
  const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
29272
29373
  const diag = Math.hypot(track.bbox.w, track.bbox.h);
29273
- if (dist <= this.config.rescueCentroidFactor * diag) return true;
29274
- return growthAdjacentMatch(track.bbox, det.bbox);
29374
+ if (dist <= this.config.rescueCentroidFactor * diag) return "centroid";
29375
+ if (growthAdjacentMatch(track.bbox, det.bbox, SortTracker.growthContext(track, timestamp))) return "growth";
29376
+ return null;
29377
+ }
29378
+ /**
29379
+ * Whether `det` would mint a track of its own if no salvage branch claimed it:
29380
+ * it clears BOTH spawn floors (per-class score, per-class bbox-area fraction).
29381
+ *
29382
+ * A rescue that rests on PROXIMITY ALONE — the `centroid` clause, reached only
29383
+ * when the overlap is already below `rescueIouThreshold` — must lose to such a
29384
+ * detection. Live track `27855a56` bound a second car 452 px away at IoU 0.058
29385
+ * purely because 452 ≤ 0.75 × the 589×445 track diagonal; the box it stole was
29386
+ * a full-size, floor-clearing vehicle that would have spawned perfectly well
29387
+ * on its own. **A spare track costs media; a stolen track corrupts the plate,
29388
+ * the travelled distance and the last frame of the track it was taken from.**
29389
+ *
29390
+ * The `iou` clause is untouched (real overlap IS evidence), and so is `growth`
29391
+ * — that one is bounded by track maturity inside `growthAdjacentMatch`, and a
29392
+ * partial crop is by construction the same subject, so refusing it here would
29393
+ * re-fragment `4285605a`.
29394
+ */
29395
+ isPlausibleBirth(det, frameArea) {
29396
+ return this.meetsClassScoreFloor(det) && this.meetsSpawnAreaFloor(det, frameArea);
29275
29397
  }
29276
29398
  /** Fold a matched detection's class + score into a track's lifetime vote
29277
29399
  * tally. A non-positive score still counts as an infinitesimal vote so a
@@ -29385,20 +29507,25 @@ var SortTracker = class SortTracker {
29385
29507
  for (let di = 0; di < detections.length; di++) {
29386
29508
  if (used.has(di) || lowSet.has(di)) continue;
29387
29509
  const det = detections[di];
29388
- if (!this.looseMatch(track, det)) continue;
29510
+ const gate = this.looseMatch(track, det, timestamp);
29511
+ if (gate === null) continue;
29512
+ if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
29389
29513
  rescuePairs.push({
29390
29514
  track,
29391
29515
  detIdx: di,
29392
- score: iou$2(track.bbox, det.bbox)
29516
+ score: iou$2(track.bbox, det.bbox),
29517
+ gate
29393
29518
  });
29394
29519
  }
29395
29520
  }
29396
29521
  rescuePairs.sort((a, b) => b.score - a.score);
29397
29522
  for (const pair of rescuePairs) {
29398
29523
  if (matchedTracks.has(pair.track) || used.has(pair.detIdx)) continue;
29399
- matched.set(pair.track, detections[pair.detIdx]);
29524
+ const det = detections[pair.detIdx];
29525
+ matched.set(pair.track, det);
29400
29526
  matchedTracks.add(pair.track);
29401
29527
  used.add(pair.detIdx);
29528
+ this.reportBind("rescue", pair.gate, pair.track, det, timestamp);
29402
29529
  }
29403
29530
  if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, used, matchedTracks, matched);
29404
29531
  for (const [track, det] of matched) {
@@ -29452,17 +29579,22 @@ var SortTracker = class SortTracker {
29452
29579
  const det = detections[di];
29453
29580
  let best;
29454
29581
  let bestScore = -1;
29582
+ let bestGate = "iou";
29455
29583
  for (const lost of this.lostTracks) {
29456
29584
  if (!lost.resurrectable) continue;
29457
29585
  if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
29458
- if (!this.looseMatch(lost, det)) continue;
29586
+ const gate = this.looseMatch(lost, det, timestamp);
29587
+ if (gate === null) continue;
29588
+ if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
29459
29589
  const score = iou$2(lost.bbox, det.bbox);
29460
29590
  if (score > bestScore) {
29461
29591
  best = lost;
29462
29592
  bestScore = score;
29593
+ bestGate = gate;
29463
29594
  }
29464
29595
  }
29465
29596
  if (!best) continue;
29597
+ this.reportBind("resurrection", bestGate, best, det, timestamp);
29466
29598
  this.lostTracks = this.lostTracks.filter((t) => t !== best);
29467
29599
  best.bbox = det.bbox;
29468
29600
  best.class = det.class;
@@ -29502,7 +29634,7 @@ var SortTracker = class SortTracker {
29502
29634
  continue;
29503
29635
  }
29504
29636
  if (this.config.occlusionEnabled && occluderBoxes.some((ob) => containment(det.bbox, ob) >= this.config.occlusionContainment)) continue;
29505
- if (this.config.dedupEnabled && this.isDuplicateSpawn(det, surviving)) {
29637
+ if (this.config.dedupEnabled && this.isDuplicateSpawn(det, surviving, timestamp)) {
29506
29638
  used.add(di);
29507
29639
  continue;
29508
29640
  }
@@ -29561,6 +29693,7 @@ var SortTracker = class SortTracker {
29561
29693
  this.tracks = [];
29562
29694
  this.lostTracks = [];
29563
29695
  this.dupStreaks.clear();
29696
+ this.bindSampler.reset();
29564
29697
  }
29565
29698
  };
29566
29699
  //#endregion
@@ -29977,6 +30110,16 @@ var FrameProcessor = class {
29977
30110
  setStationaryGate(gate) {
29978
30111
  this.stationaryGate = gate;
29979
30112
  }
30113
+ /**
30114
+ * Wire (or clear) the tracker's salvage-bind diagnostics sink. The tracker
30115
+ * itself knows no `deviceId`; the addon that owns this processor stamps
30116
+ * `tags: { deviceId }` on the line — see the wiring in `pipeline-analytics`.
30117
+ * Only the rescue / resurrection / growth branches report, and they report
30118
+ * sampled: stage 1 runs per track per frame at frame-rate.
30119
+ */
30120
+ setBindObserver(observer) {
30121
+ this.tracker.setBindObserver(observer);
30122
+ }
29980
30123
  /** Forget a track in the underlying tracker (used when the addon promotes it
29981
30124
  * to a stationary-object registry entry). */
29982
30125
  dropTrack(trackId) {
@@ -34936,6 +35079,70 @@ async function makeSquareThumb(bytes, size) {
34936
35079
  }).jpeg({ quality: THUMB_QUALITY }).toBuffer();
34937
35080
  }
34938
35081
  //#endregion
35082
+ //#region src/pipeline-analytics/track-media-scope.ts
35083
+ /** The stored kind a detail owner's crop is written with. */
35084
+ var STORED_DETAIL_CROP_KIND = "crop";
35085
+ /** The kinds only a detail owner can produce. */
35086
+ var DETAIL_CROP_KINDS = ["faceCrop", "plateCrop"];
35087
+ /**
35088
+ * Every owner that holds media for `trackId`, in publication order (the track's
35089
+ * own rows first, then face, then plate).
35090
+ */
35091
+ function trackMediaOwnerScopes(trackId) {
35092
+ return [
35093
+ {
35094
+ ownerKind: "track",
35095
+ ownerId: trackId,
35096
+ publishCropAs: null
35097
+ },
35098
+ {
35099
+ ownerKind: "face",
35100
+ ownerId: `${FACE_MEDIA_OWNER_PREFIX}${trackId}`,
35101
+ publishCropAs: "faceCrop"
35102
+ },
35103
+ {
35104
+ ownerKind: "plate",
35105
+ ownerId: `${PLATE_MEDIA_OWNER_PREFIX}${trackId}`,
35106
+ publishCropAs: "plateCrop"
35107
+ }
35108
+ ];
35109
+ }
35110
+ /**
35111
+ * The scopes worth READING for a `kinds`-filtered request.
35112
+ *
35113
+ * A detail owner can only ever publish `faceCrop` / `plateCrop`, so a request
35114
+ * that names neither is answered without opening those two owners — which is
35115
+ * what keeps the common per-entry fetch (`kinds: ['keyFrameSmall']`) at exactly
35116
+ * its previous cost. The track scope is never skipped: it is the only owner
35117
+ * whose kinds are open-ended, and dropping it on a guess is how a listing
35118
+ * starts lying about what a track has.
35119
+ */
35120
+ function trackMediaScopesForKinds(trackId, kinds) {
35121
+ const scopes = trackMediaOwnerScopes(trackId);
35122
+ if (kinds === void 0 || kinds.length === 0) return scopes;
35123
+ return kinds.some((kind) => DETAIL_CROP_KINDS.includes(kind)) ? scopes : scopes.filter((scope) => scope.publishCropAs === null);
35124
+ }
35125
+ /**
35126
+ * Fold every scope's rows into ONE track media listing: detail crops
35127
+ * relabelled, everything filtered to `kinds`, ordered oldest first.
35128
+ *
35129
+ * `relabel` is injected rather than spread inline so the caller keeps its own
35130
+ * concrete row type (`MediaFile` with bytes, `MediaFileInfo` without) — there
35131
+ * is no cast and no third shape in between.
35132
+ */
35133
+ function collectTrackMedia(scoped, relabel, kinds) {
35134
+ const out = [];
35135
+ for (const { scope, rows } of scoped) for (const row of rows) {
35136
+ if (scope.publishCropAs === null) {
35137
+ out.push(row);
35138
+ continue;
35139
+ }
35140
+ if (row.kind !== STORED_DETAIL_CROP_KIND) continue;
35141
+ out.push(relabel(row, scope.publishCropAs));
35142
+ }
35143
+ return (kinds === void 0 || kinds.length === 0 ? out : out.filter((row) => kinds.includes(row.kind))).toSorted((a, b) => a.timestamp - b.timestamp);
35144
+ }
35145
+ //#endregion
34939
35146
  //#region src/pipeline-analytics/zone-name-stamp.ts
34940
35147
  /**
34941
35148
  * Copy each track with `zoneNames` stamped on.
@@ -35055,10 +35262,25 @@ var AnalyticsQueryFacade = class {
35055
35262
  const all = await (this.deps.mediaStore()?.listByOwner("event", input.eventId, input.deviceId) ?? Promise.resolve([]));
35056
35263
  return input.kind ? all.filter((m) => m.kind === input.kind) : all;
35057
35264
  }
35265
+ /**
35266
+ * A track's media WITH the bytes, restricted to `kinds` when given.
35267
+ *
35268
+ * Reads every owner a track's media lives under — its own rows plus the face
35269
+ * and plate detail crops, whose stored `kind: 'crop'` is published as
35270
+ * `faceCrop` / `plateCrop`. See `track-media-scope.ts` for why the rename
35271
+ * happens on the way out and not in the caller's question.
35272
+ */
35058
35273
  async getTrackMedia(input) {
35059
- const all = await (this.deps.mediaStore()?.listByOwner("track", input.trackId, input.deviceId) ?? Promise.resolve([]));
35060
- const kinds = input.kinds;
35061
- return kinds && kinds.length > 0 ? all.filter((m) => kinds.includes(m.kind)) : all;
35274
+ const store = this.deps.mediaStore();
35275
+ if (store === null) return [];
35276
+ const scopes = trackMediaScopesForKinds(input.trackId, input.kinds);
35277
+ return collectTrackMedia(await Promise.all(scopes.map(async (scope) => ({
35278
+ scope,
35279
+ rows: await store.listByOwner(scope.ownerKind, scope.ownerId, input.deviceId)
35280
+ }))), (row, kind) => ({
35281
+ ...row,
35282
+ kind
35283
+ }), input.kinds);
35062
35284
  }
35063
35285
  /**
35064
35286
  * A track's media WITHOUT the bytes.
@@ -35068,9 +35290,21 @@ var AnalyticsQueryFacade = class {
35068
35290
  * that path reads every blob off disk and base64-encodes it first, so the
35069
35291
  * response would shrink while the work did not. Measured: 7.67 MB read and
35070
35292
  * ~10 MB of string allocated, to answer a question the index already holds.
35293
+ *
35294
+ * Covers the same owners as {@link getTrackMedia} — a manifest that listed
35295
+ * fewer keys than the byte read can serve (or more) is the disagreement the
35296
+ * viewer's per-entry loader turns into a red triangle.
35071
35297
  */
35072
35298
  async listTrackMedia(input) {
35073
- return this.deps.mediaStore()?.listInfoByOwner("track", input.trackId, input.deviceId) ?? Promise.resolve([]);
35299
+ const store = this.deps.mediaStore();
35300
+ if (store === null) return [];
35301
+ return collectTrackMedia(await Promise.all(trackMediaOwnerScopes(input.trackId).map(async (scope) => ({
35302
+ scope,
35303
+ rows: await store.listInfoByOwner(scope.ownerKind, scope.ownerId, input.deviceId)
35304
+ }))), (row, kind) => ({
35305
+ ...row,
35306
+ kind
35307
+ }), void 0);
35074
35308
  }
35075
35309
  async pruneEventsBefore(input) {
35076
35310
  const eventStore = this.deps.eventStore();
@@ -64782,6 +65016,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
64782
65016
  cooldownSec,
64783
65017
  minTrackAgeMs: trk.minTrackAgeMs
64784
65018
  }, source);
65019
+ p.setBindObserver((record) => {
65020
+ this.ctx.logger.debug("tracker: salvage bind", {
65021
+ tags: { deviceId },
65022
+ meta: {
65023
+ source,
65024
+ ...record
65025
+ }
65026
+ });
65027
+ });
64785
65028
  p.setTrackLabelState((trackId) => {
64786
65029
  const active = this.trackStore?.getActiveByTrack(trackId);
64787
65030
  return active !== null && active !== void 0 ? {