@camstack/addon-post-analysis 1.2.315 → 1.2.319

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_clip_model_registry = require("../clip-model-registry-8Ss3XHG0.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-BjYpZuuI.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -16996,7 +16996,7 @@ var NcStateSummaryTestSchema = require_clip_model_registry.object({
16996
16996
  /** Nothing open and every section read: nothing was enqueued. */
16997
16997
  nothingToReport: require_clip_model_registry.boolean(),
16998
16998
  sections: require_clip_model_registry.array(require_clip_model_registry.object({
16999
- id: require_clip_model_registry.string(),
16999
+ id: require_clip_model_registry.NcStateSummarySectionIdSchema,
17000
17000
  status: require_clip_model_registry._enum(["read", "unavailable"]),
17001
17001
  items: require_clip_model_registry.number().int().nonnegative(),
17002
17002
  reason: require_clip_model_registry.string().optional()
@@ -17004,7 +17004,7 @@ var NcStateSummaryTestSchema = require_clip_model_registry.object({
17004
17004
  rows: require_clip_model_registry.array(require_clip_model_registry.object({
17005
17005
  recordId: require_clip_model_registry.string(),
17006
17006
  /** The section of a per-section row. */
17007
- section: require_clip_model_registry.string().optional(),
17007
+ section: require_clip_model_registry.NcStateSummarySectionIdSchema.optional(),
17008
17008
  enqueued: require_clip_model_registry.number().int().nonnegative(),
17009
17009
  targetIds: require_clip_model_registry.array(require_clip_model_registry.string())
17010
17010
  }))
@@ -20730,17 +20730,8 @@ var NcDeviceStateCache = class {
20730
20730
  * a job that COMPLETED, and the state it leaves behind is not a condition that
20731
20731
  * is still true. The ADR records why it is excluded rather than approximated.
20732
20732
  */
20733
- var NC_STATE_SUMMARY_SECTION_IDS = [
20734
- "device-state",
20735
- "streams",
20736
- "battery",
20737
- "consumables",
20738
- "tanks",
20739
- "detection-blind",
20740
- "nodes",
20741
- "addons",
20742
- "updates"
20743
- ];
20733
+ /** One source: the cap's Zod enum (D698 LOW-6). */
20734
+ var NC_STATE_SUMMARY_SECTION_IDS = require_clip_model_registry.NcStateSummarySectionIdSchema.options;
20744
20735
  /** The kind groups that have no section, and why — said on the preview. */
20745
20736
  var NC_STATE_SUMMARY_EXCLUDED_GROUPS = { "backups-exports": "a backup or an export is a completed job, not a state that is still true" };
20746
20737
  function isStateSectionId(value) {
@@ -35209,10 +35200,10 @@ var NcStateSummaryService = class {
35209
35200
  const rows = [];
35210
35201
  for (const subject of subjects) {
35211
35202
  const enqueued = await this.deps.enqueue(rule, subject, "synthetic");
35212
- const section = subject.systemEvent?.data["section"];
35203
+ const section = stateSummarySectionOf(subject);
35213
35204
  rows.push({
35214
35205
  recordId: subject.recordId,
35215
- ...typeof section === "string" ? { section } : {},
35206
+ ...section !== void 0 ? { section } : {},
35216
35207
  enqueued: enqueued.inserted,
35217
35208
  targetIds: [...enqueued.targetIds]
35218
35209
  });
@@ -51371,6 +51362,11 @@ var MEDIA_ID_SEGMENT = "m";
51371
51362
  /** Path segment that introduces an OWNER question ("best media of"). */
51372
51363
  var MEDIA_OWNER_SEGMENT = "o";
51373
51364
  /**
51365
+ * Path segment that introduces an event-owner GENERATION (D694) —
51366
+ * `/g/<deviceId>/<recordAt>` after the owner id.
51367
+ */
51368
+ var MEDIA_GENERATION_SEGMENT = "g";
51369
+ /**
51374
51370
  * The plane path for one media row.
51375
51371
  *
51376
51372
  * `prefix` is `/addon/<addonId>/event-media` — a pure function of the addon
@@ -51387,6 +51383,23 @@ function mediaFileUrl(prefix, mediaId) {
51387
51383
  return `${prefix}/${MEDIA_ID_SEGMENT}/${mediaId}`;
51388
51384
  }
51389
51385
  /**
51386
+ * The plane path for "whatever media best represents this owner".
51387
+ *
51388
+ * The owner id IS encoded: a trackId is a UUID today but an owner id is an
51389
+ * arbitrary row key (the summary mosaic's is `summary-<ruleId>-<windowEndMs>`),
51390
+ * and one slash in it would silently re-cut the path.
51391
+ *
51392
+ * When `generation` is set and `ownerType` is an event table, the path carries
51393
+ * `/g/<deviceId>/<recordAt>` so a reused rowid cannot resolve another record's
51394
+ * crop. Generation is ignored (and not minted) for non-event owners — a track
51395
+ * id is random and needs none.
51396
+ */
51397
+ function mediaOwnerUrl(prefix, ownerType, ownerId, generation) {
51398
+ const base = `${prefix}/${MEDIA_OWNER_SEGMENT}/${ownerType}/${encodeURIComponent(ownerId)}`;
51399
+ if (generation === void 0 || !require_clip_model_registry.isEventOwnerType(ownerType)) return base;
51400
+ return `${base}/${MEDIA_GENERATION_SEGMENT}/${generation.deviceId}/${generation.recordAt}`;
51401
+ }
51402
+ /**
51390
51403
  * A stored row as the wire sees it: the ref, the address, and — transitionally
51391
51404
  * — the bytes.
51392
51405
  *
@@ -59239,6 +59252,12 @@ function openGzipStream(out) {
59239
59252
  onceDrain: (listener) => {
59240
59253
  gzip.once("drain", listener);
59241
59254
  },
59255
+ flush: () => new Promise((resolve, reject) => {
59256
+ gzip.flush((err) => {
59257
+ if (err !== null && err !== void 0) reject(err);
59258
+ else resolve();
59259
+ });
59260
+ }),
59242
59261
  finish: async () => {
59243
59262
  gzip.end();
59244
59263
  await closed;
@@ -59334,6 +59353,25 @@ var TapeSegmentWriter = class {
59334
59353
  });
59335
59354
  }
59336
59355
  /**
59356
+ * Push the sink's pending gzip output to disk without ending the segment.
59357
+ * A quiet / silent camera otherwise keeps its tail only in the zlib
59358
+ * buffer until the next rotation (D681 follow-up). Never throws into the
59359
+ * caller's frame path: a flush fault is logged and swallowed here.
59360
+ */
59361
+ async flushToDisk() {
59362
+ try {
59363
+ await this.deps.sink.flush();
59364
+ } catch (err) {
59365
+ this.deps.logger.warn("tracking tape segment flush failed", {
59366
+ tags: { deviceId: this.deps.deviceId },
59367
+ meta: {
59368
+ segmentOriginMs: this.deps.segmentOriginMs,
59369
+ error: require_clip_model_registry.errMsg(err)
59370
+ }
59371
+ });
59372
+ }
59373
+ }
59374
+ /**
59337
59375
  * Always ends the segment's records (queue flush, lost-span gap, `end`
59338
59376
  * record) before touching the sink, so a sink that cannot finish still
59339
59377
  * leaves a decodable body behind it. `sink.finish()`'s rejection is caught
@@ -59660,6 +59698,21 @@ var TapeRecorder = class {
59660
59698
  if (cam !== void 0) await this.closeSegment(deviceId, cam, "segment-end");
59661
59699
  }
59662
59700
  /**
59701
+ * Push every open segment's pending gzip output to disk without rotating
59702
+ * (D681 follow-up). A silent / quiet camera otherwise keeps its tail only
59703
+ * in the zlib buffer until the next frame or the 10-minute cut — a crash
59704
+ * loses it, and `bytesLastHour` lags behind what was already recorded.
59705
+ * Never throws: a per-camera flush fault is logged inside the writer.
59706
+ */
59707
+ async flushOpenSegments() {
59708
+ const pending = [];
59709
+ for (const cam of this.cameras.values()) {
59710
+ const writer = cam.segment?.writer;
59711
+ if (writer !== void 0 && writer !== null) pending.push(writer.flushToDisk());
59712
+ }
59713
+ await Promise.all(pending);
59714
+ }
59715
+ /**
59663
59716
  * Close the camera's open segment only if it holds time at or before
59664
59717
  * `atMs` — what a promotion of a window ending at `atMs` needs on disk
59665
59718
  * (Task 12 fix round, H1). A window wholly before the open segment leaves
@@ -60159,6 +60212,29 @@ var TapeShelf = class {
60159
60212
  };
60160
60213
  }
60161
60214
  };
60215
+ /**
60216
+ * Log every refusal the sweep already measured (D391 / D681 follow-up).
60217
+ * `sweep()` itself never throws for `rootUnreadable` / per-camera `failed`:
60218
+ * those land on the report, and a caller that only `.catch`es would drop them
60219
+ * silently even when the shelf already warned at the `readdir` site. One
60220
+ * tagged line per refusal at the sweep boundary closes that gap.
60221
+ */
60222
+ function logTapeSweepReport(report, logger) {
60223
+ if (report.rootUnreadable !== null) logger.warn("tracking tape sweep refused", { meta: {
60224
+ reason: report.rootUnreadable,
60225
+ scope: "root"
60226
+ } });
60227
+ for (const [deviceId, outcome] of report.perDevice) {
60228
+ if (!("failed" in outcome) || outcome.failed !== true) continue;
60229
+ logger.warn("tracking tape sweep refused", {
60230
+ tags: { deviceId },
60231
+ meta: {
60232
+ reason: outcome.reason,
60233
+ scope: "camera"
60234
+ }
60235
+ });
60236
+ }
60237
+ }
60162
60238
  //#endregion
60163
60239
  //#region src/pipeline-analytics/tracking-bench/recorder/tape-operation-queue.ts
60164
60240
  /** Generous: a promotion reads its window's segments and walks back through
@@ -61212,6 +61288,17 @@ function shelfFigures(listing, now) {
61212
61288
  ...last !== void 0 ? { heldToMs: last.originMs } : {}
61213
61289
  };
61214
61290
  }
61291
+ /**
61292
+ * Default `debug.tape.status` (no `deviceIds`): drop the noise. An Off camera
61293
+ * with a measured empty shelf is not a cost row and is not promotable; keep
61294
+ * everything else — Recording / Refused / Failing / Unknown, Off with held
61295
+ * segments (still promotable after the switch went off), and Off whose shelf
61296
+ * could not be read (D393: never silently hide an unreadable camera). Explicit
61297
+ * `deviceIds` still return every asked-about camera, Off included (D62).
61298
+ */
61299
+ function defaultTapeStatusCameras(rows) {
61300
+ return rows.filter((row) => row.state !== "off" || row.segmentsHeld === null || row.segmentsHeld > 0);
61301
+ }
61215
61302
  function describeTapeStatus(input) {
61216
61303
  return input.deviceIds.map((deviceId) => {
61217
61304
  const shelf = {
@@ -66122,6 +66209,51 @@ var EventStore = class {
66122
66209
  return found;
66123
66210
  }
66124
66211
  /**
66212
+ * The GENERATION of each live event in `table` among `eventIds` — its camera
66213
+ * and its own timestamp (D694). Same throw-on-failure contract as
66214
+ * {@link existingEventIdsIn}: a quiet under-report here would let the orphan
66215
+ * audit keep treating another camera's leftovers as owned.
66216
+ */
66217
+ async eventGenerationsIn(table, eventIds) {
66218
+ const collection = EVENT_COLLECTION_BY_TABLE[table];
66219
+ const found = /* @__PURE__ */ new Map();
66220
+ for (let i = 0; i < eventIds.length; i += PRUNE_PAGE_SIZE) {
66221
+ const chunk = eventIds.slice(i, i + PRUNE_PAGE_SIZE);
66222
+ if (chunk.length === 0) continue;
66223
+ const rows = await this.store.query.query({
66224
+ collection,
66225
+ columns: [
66226
+ "id",
66227
+ "deviceId",
66228
+ "timestamp"
66229
+ ],
66230
+ filter: {
66231
+ whereIn: { id: [...chunk] },
66232
+ limit: chunk.length
66233
+ }
66234
+ });
66235
+ for (const row of rows) {
66236
+ const deviceId = row.data["deviceId"];
66237
+ const timestamp = row.data["timestamp"];
66238
+ if (typeof deviceId !== "number" || !Number.isFinite(deviceId)) continue;
66239
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) continue;
66240
+ found.set(row.id, {
66241
+ deviceId,
66242
+ timestamp
66243
+ });
66244
+ }
66245
+ }
66246
+ return found;
66247
+ }
66248
+ /**
66249
+ * One event's generation, or `null` when the id is not live in `table`.
66250
+ * Used by the `/o/<eventOwner>/<id>` plane ladder to scope media to the
66251
+ * current holder (D694).
66252
+ */
66253
+ async eventGenerationOf(table, eventId) {
66254
+ return (await this.eventGenerationsIn(table, [eventId])).get(eventId) ?? null;
66255
+ }
66256
+ /**
66125
66257
  * The SUBSET of `eventIds` that are MOTION events specifically.
66126
66258
  *
66127
66259
  * {@link existingEventIds} answers "is this event alive", which is the
@@ -73076,7 +73208,7 @@ var AnalyticsQueryFacade = class {
73076
73208
  async getMotionEvents(input) {
73077
73209
  const events = await (this.deps.eventStore()?.queryMotion(input) ?? Promise.resolve([]));
73078
73210
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73079
- return this.withMediaUrl(events);
73211
+ return this.withMediaUrl(events, "motion");
73080
73212
  }
73081
73213
  async getEventDensity(input) {
73082
73214
  return this.deps.eventStore()?.densityByKind(input) ?? [];
@@ -73133,12 +73265,12 @@ var AnalyticsQueryFacade = class {
73133
73265
  async getObjectEvents(input) {
73134
73266
  const events = await (this.deps.eventStore()?.queryObject(input) ?? Promise.resolve([]));
73135
73267
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73136
- return this.withMediaUrl(events);
73268
+ return this.withMediaUrl(events, "object");
73137
73269
  }
73138
73270
  async getAudioEvents(input) {
73139
73271
  const events = await (this.deps.eventStore()?.queryAudio(input) ?? Promise.resolve([]));
73140
73272
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73141
- return this.withMediaUrl(events);
73273
+ return this.withMediaUrl(events, "audio");
73142
73274
  }
73143
73275
  async getEventMedia(input) {
73144
73276
  const store = this.deps.mediaStore();
@@ -73405,13 +73537,20 @@ var AnalyticsQueryFacade = class {
73405
73537
  *
73406
73538
  * Only called when `eventMediaBaseUrl()` is non-null (guard sits in
73407
73539
  * the callers), so the template literal is safe.
73540
+ *
73541
+ * The URL names the owner TABLE and the event's GENERATION (D482 + D694):
73542
+ * a bare `${base}/${id}` was both ambiguous across tables and unable to
73543
+ * survive a reused rowid. New stamps always carry `/o/<type>/<id>/g/…`.
73408
73544
  */
73409
- withMediaUrl(events) {
73545
+ withMediaUrl(events, ownerType) {
73410
73546
  const base = this.deps.eventMediaBaseUrl();
73411
73547
  if (base === null) return events;
73412
73548
  return events.map((e) => ({
73413
73549
  ...e,
73414
- mediaUrl: `${base}/${e.id}`
73550
+ mediaUrl: mediaOwnerUrl(base, ownerType, String(e.id), {
73551
+ deviceId: e.deviceId,
73552
+ recordAt: e.timestamp
73553
+ })
73415
73554
  }));
73416
73555
  }
73417
73556
  };
@@ -76243,18 +76382,26 @@ function eventMediaEtag(media) {
76243
76382
  }
76244
76383
  /** Digits only — `Number('')` is 0 and `Number(' ')` is 0, so neither is used. */
76245
76384
  var DIGITS = /^\d+$/;
76385
+ /** One run of digits, for composing the event-owner remainder pattern. */
76386
+ var DIGIT_RUN = "\\d+";
76387
+ /**
76388
+ * Event-owner remainder with an optional generation suffix (D694):
76389
+ * `<ownerId>` or `<ownerId>/g/<deviceId>/<recordAt>`. Owner ids for event
76390
+ * tables are decimal rowids; the generation segment is the literal `g`.
76391
+ */
76392
+ var EVENT_OWNER_REMAINDER = new RegExp(`^(${DIGIT_RUN})(?:/${MEDIA_GENERATION_SEGMENT}/(${DIGIT_RUN})/(${DIGIT_RUN}))?$`);
76246
76393
  /**
76247
76394
  * Read a plane path. `null` for anything that is not one of the two forms —
76248
76395
  * including the BARE id the plane used to accept, which is gone: a bare number
76249
76396
  * could be a media id or an event id, and after D474 an event id could be one
76250
76397
  * of three rows. Ambiguity at the door is what this record removes.
76251
76398
  *
76252
- * The owner id is taken as the whole remainder rather than one segment: the
76253
- * hub's wildcard param is percent-decoded by find-my-way before the sub-path
76254
- * is proxied, so a `%2F` an owner id carried would arrive here as a real
76255
- * slash, and re-cutting on it would silently address a different owner. It is
76256
- * only ever a DB query VALUE (`where: { ownerId }`) — never a filesystem path,
76257
- * which the row itself supplies.
76399
+ * For non-event owners the id is the whole remainder rather than one segment:
76400
+ * the hub's wildcard param is percent-decoded by find-my-way before the
76401
+ * sub-path is proxied, so a `%2F` an owner id carried would arrive here as a
76402
+ * real slash, and re-cutting on it would silently address a different owner.
76403
+ * Event owners (decimal rowids) may end with `/g/<deviceId>/<recordAt>` —
76404
+ * that suffix is the generation, not part of the id (D694).
76258
76405
  */
76259
76406
  function parseMediaAddress(rawPath) {
76260
76407
  const path = rawPath.replace(/^\/+/, "");
@@ -76274,13 +76421,35 @@ function parseMediaAddress(rawPath) {
76274
76421
  const typeSlash = rest.indexOf("/");
76275
76422
  if (typeSlash <= 0) return null;
76276
76423
  const ownerType = rest.slice(0, typeSlash);
76277
- const ownerId = rest.slice(typeSlash + 1);
76278
- if (ownerId.length === 0) return null;
76424
+ const remainder = rest.slice(typeSlash + 1);
76425
+ if (remainder.length === 0) return null;
76279
76426
  if (!require_clip_model_registry.isMediaOwnerType(ownerType)) return null;
76427
+ if (require_clip_model_registry.isEventOwnerType(ownerType)) {
76428
+ const match = EVENT_OWNER_REMAINDER.exec(remainder);
76429
+ if (match === null) return null;
76430
+ const ownerId = match[1];
76431
+ const deviceIdRaw = match[2];
76432
+ const recordAtRaw = match[3];
76433
+ if (ownerId === void 0) return null;
76434
+ if (deviceIdRaw !== void 0 && recordAtRaw !== void 0) return {
76435
+ kind: "owner",
76436
+ ownerType,
76437
+ ownerId,
76438
+ generation: {
76439
+ deviceId: Number(deviceIdRaw),
76440
+ recordAt: Number(recordAtRaw)
76441
+ }
76442
+ };
76443
+ return {
76444
+ kind: "owner",
76445
+ ownerType,
76446
+ ownerId
76447
+ };
76448
+ }
76280
76449
  return {
76281
76450
  kind: "owner",
76282
76451
  ownerType,
76283
- ownerId
76452
+ ownerId: remainder
76284
76453
  };
76285
76454
  }
76286
76455
  /** Set by the HUB for a `csv_*` share token admitted on this plane; never by a client. */
@@ -78957,6 +79126,218 @@ function createRetrainExportHandler(deps) {
78957
79126
  };
78958
79127
  }
78959
79128
  //#endregion
79129
+ //#region src/pipeline-analytics/retrain/retrain-collection-store.ts
79130
+ /**
79131
+ * @durable class=ledger owner=pipeline-analytics
79132
+ * write="one row per named training collection the operator creates. Names are
79133
+ * unique on nameKey (lower(trim)); empty names never become rows."
79134
+ * retention="none — operator-owned corpus metadata. Rows leave only when an
79135
+ * explicit delete API lands (not V1)."
79136
+ */
79137
+ var RETRAIN_COLLECTIONS_COLLECTION = "pipeline-analytics:retrain-collections";
79138
+ var RETRAIN_COLLECTION_COLUMNS = [
79139
+ {
79140
+ name: "id",
79141
+ type: "TEXT",
79142
+ primaryKey: true,
79143
+ notNull: true
79144
+ },
79145
+ {
79146
+ name: "name",
79147
+ type: "TEXT",
79148
+ notNull: true
79149
+ },
79150
+ {
79151
+ name: "nameKey",
79152
+ type: "TEXT",
79153
+ notNull: true
79154
+ },
79155
+ {
79156
+ name: "createdAt",
79157
+ type: "INTEGER",
79158
+ notNull: true
79159
+ },
79160
+ {
79161
+ name: "updatedAt",
79162
+ type: "INTEGER",
79163
+ notNull: true
79164
+ }
79165
+ ];
79166
+ var RETRAIN_COLLECTION_INDEXES = [{
79167
+ name: "idx_retrain_collections_name_key",
79168
+ columns: ["nameKey"],
79169
+ unique: true
79170
+ }, {
79171
+ name: "idx_retrain_collections_created",
79172
+ columns: ["createdAt"]
79173
+ }];
79174
+ function normalizeCollectionName(raw) {
79175
+ const name = raw.trim();
79176
+ if (name.length === 0) return null;
79177
+ return {
79178
+ name,
79179
+ nameKey: name.toLowerCase()
79180
+ };
79181
+ }
79182
+ var RetrainCollectionStore = class {
79183
+ store;
79184
+ logger;
79185
+ now;
79186
+ newId;
79187
+ countFrames;
79188
+ constructor(deps) {
79189
+ this.store = deps.store;
79190
+ this.logger = deps.logger;
79191
+ this.now = deps.now;
79192
+ this.newId = deps.newId;
79193
+ this.countFrames = deps.countFrames;
79194
+ }
79195
+ static async declare(store) {
79196
+ await store.declareCollection.mutate({
79197
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79198
+ columns: [...RETRAIN_COLLECTION_COLUMNS],
79199
+ indexes: [...RETRAIN_COLLECTION_INDEXES]
79200
+ });
79201
+ }
79202
+ async create(input) {
79203
+ const normalized = normalizeCollectionName(input.name);
79204
+ if (normalized === null) return {
79205
+ kind: "refused",
79206
+ reason: "empty-name"
79207
+ };
79208
+ if (await this.findByNameKey(normalized.nameKey) !== null) return {
79209
+ kind: "refused",
79210
+ reason: "duplicate-name"
79211
+ };
79212
+ const createdAt = this.now();
79213
+ const id = this.newId();
79214
+ await this.store.set.mutate({
79215
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79216
+ key: id,
79217
+ value: {
79218
+ name: normalized.name,
79219
+ nameKey: normalized.nameKey,
79220
+ createdAt,
79221
+ updatedAt: createdAt
79222
+ }
79223
+ });
79224
+ return {
79225
+ kind: "created",
79226
+ collection: {
79227
+ id,
79228
+ name: normalized.name,
79229
+ createdAt,
79230
+ frameCount: 0
79231
+ }
79232
+ };
79233
+ }
79234
+ async list() {
79235
+ let rows;
79236
+ try {
79237
+ rows = await this.store.query.query({
79238
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79239
+ filter: { orderBy: {
79240
+ field: "createdAt",
79241
+ direction: "desc"
79242
+ } }
79243
+ });
79244
+ } catch (err) {
79245
+ this.logger.warn("retrain collections list failed", { meta: { error: String(err) } });
79246
+ return [];
79247
+ }
79248
+ const out = [];
79249
+ for (const row of rows) {
79250
+ const name = typeof row.data["name"] === "string" ? row.data["name"] : "";
79251
+ const createdAt = Number(row.data["createdAt"] ?? 0);
79252
+ const frameCount = this.countFrames !== void 0 ? await this.countFrames(row.id) : 0;
79253
+ out.push({
79254
+ id: row.id,
79255
+ name,
79256
+ createdAt,
79257
+ frameCount
79258
+ });
79259
+ }
79260
+ return out;
79261
+ }
79262
+ async getById(id) {
79263
+ try {
79264
+ const row = await this.store.get.query({
79265
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79266
+ key: id
79267
+ });
79268
+ if (row === null || row === void 0) return null;
79269
+ const data = row;
79270
+ return {
79271
+ id,
79272
+ name: typeof data["name"] === "string" ? data["name"] : "",
79273
+ createdAt: Number(data["createdAt"] ?? 0),
79274
+ frameCount: this.countFrames !== void 0 ? await this.countFrames(id) : 0
79275
+ };
79276
+ } catch (err) {
79277
+ this.logger.warn("retrain collection get failed", { meta: {
79278
+ collectionId: id,
79279
+ error: String(err)
79280
+ } });
79281
+ return null;
79282
+ }
79283
+ }
79284
+ /**
79285
+ * Stub until still ingest lands. Validates the collection exists, then
79286
+ * refuses with a named reason so callers can wire UI against a stable shape.
79287
+ */
79288
+ async addRetrainFrameFromRecording(input) {
79289
+ if (await this.getById(input.collectionId) === null) {
79290
+ this.logger.info("retrain collection addFrame refused: unknown-collection", {
79291
+ tags: { deviceId: input.deviceId },
79292
+ meta: {
79293
+ collectionId: input.collectionId,
79294
+ epochMs: input.epochMs
79295
+ }
79296
+ });
79297
+ return {
79298
+ kind: "refused",
79299
+ reason: "unknown-collection"
79300
+ };
79301
+ }
79302
+ this.logger.info("retrain collection addFrame stub: still-capture-unimplemented", {
79303
+ tags: { deviceId: input.deviceId },
79304
+ meta: {
79305
+ collectionId: input.collectionId,
79306
+ epochMs: input.epochMs
79307
+ }
79308
+ });
79309
+ return {
79310
+ kind: "refused",
79311
+ reason: "still-capture-unimplemented",
79312
+ detail: "Still ingest lands in plan Task 3"
79313
+ };
79314
+ }
79315
+ async findByNameKey(nameKey) {
79316
+ try {
79317
+ const row = (await this.store.query.query({
79318
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79319
+ filter: {
79320
+ where: { nameKey },
79321
+ limit: 1
79322
+ }
79323
+ }))[0];
79324
+ if (row === void 0) return null;
79325
+ return {
79326
+ id: row.id,
79327
+ name: typeof row.data["name"] === "string" ? row.data["name"] : "",
79328
+ createdAt: Number(row.data["createdAt"] ?? 0),
79329
+ frameCount: 0
79330
+ };
79331
+ } catch (err) {
79332
+ this.logger.warn("retrain collection findByNameKey failed", { meta: {
79333
+ nameKey,
79334
+ error: String(err)
79335
+ } });
79336
+ return null;
79337
+ }
79338
+ }
79339
+ };
79340
+ //#endregion
78960
79341
  //#region src/pipeline-analytics/retrain/retrain-assist.ts
78961
79342
  /** The whole frame, as a normalised rectangle. */
78962
79343
  var WHOLE_FRAME = {
@@ -82366,7 +82747,8 @@ async function rankKeyEvents(candidates, options, peakLookup) {
82366
82747
  ...t.markForTrain !== void 0 ? { markForTrain: t.markForTrain } : {},
82367
82748
  ...t.retrainStatus !== void 0 ? { retrainStatus: t.retrainStatus } : {},
82368
82749
  ...t.debug !== void 0 ? { debug: t.debug } : {},
82369
- ...t.favourited !== void 0 ? { favourited: t.favourited } : {}
82750
+ ...t.favourited !== void 0 ? { favourited: t.favourited } : {},
82751
+ ...t.audioLabels !== void 0 && t.audioLabels.length > 0 ? { audioLabels: t.audioLabels } : {}
82370
82752
  });
82371
82753
  }
82372
82754
  scored.sort((a, b) => b.importance - a.importance);
@@ -82793,16 +83175,19 @@ var MotionOnsetRegistry = class {
82793
83175
  * - `resolveSearchThumbnailUrl` — the search-hit thumbnail resolver. It points
82794
83176
  * a hit at the CLIP embedding row's OWN crop (the crop captured on the
82795
83177
  * embedding's peak frame → the hit thumbnail IS the embedded crop), and only
82796
- * falls back to the representative event's crop when the embedding row has no
82797
- * crop yet, so a hit never renders blank.
83178
+ * falls back to the representative event's owner ladder when the embedding
83179
+ * row has no crop yet, so a hit never renders blank.
82798
83180
  */
82799
83181
  function isClipObjectEmbedding(t, registry = require_clip_model_registry.BUILTIN_CLIP_MODELS) {
82800
83182
  return Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && registry.resolve(t.embeddingModelId) !== null;
82801
83183
  }
82802
83184
  function resolveSearchThumbnailUrl(input) {
82803
83185
  if (input.baseUrl === null) return void 0;
82804
- const id = input.embeddingMediaKey ?? input.eventId;
82805
- return `${input.baseUrl}/${encodeURIComponent(id)}`;
83186
+ if (input.embeddingMediaId !== void 0) return mediaFileUrl(input.baseUrl, input.embeddingMediaId);
83187
+ return mediaOwnerUrl(input.baseUrl, "object", input.eventId, {
83188
+ deviceId: input.deviceId,
83189
+ recordAt: input.recordAt
83190
+ });
82806
83191
  }
82807
83192
  //#endregion
82808
83193
  //#region src/pipeline-analytics/pipeline/onboard-birth-authority.ts
@@ -93492,6 +93877,7 @@ async function auditScope(scope, deps, limits, cursor) {
93492
93877
  const { trackIds, eventIdsByTable } = questionsFor(refs);
93493
93878
  let liveTracks;
93494
93879
  const liveEvents = /* @__PURE__ */ new Map();
93880
+ const eventGens = /* @__PURE__ */ new Map();
93495
93881
  let keptMedia = /* @__PURE__ */ new Set();
93496
93882
  try {
93497
93883
  liveTracks = trackIds.length > 0 ? await deps.liveTrackIds(trackIds) : /* @__PURE__ */ new Set();
@@ -93499,6 +93885,7 @@ async function auditScope(scope, deps, limits, cursor) {
93499
93885
  for (const [table, ids] of eventIdsByTable) {
93500
93886
  if (ids.length === 0) continue;
93501
93887
  liveEvents.set(table, await deps.liveEventIds(table, ids));
93888
+ if (deps.eventGenerations !== void 0 && scope.collection === MEDIA_COLLECTION) eventGens.set(table, await deps.eventGenerations(table, ids));
93502
93889
  }
93503
93890
  } catch (err) {
93504
93891
  deps.logger.warn("orphan audit: liveness probe failed — scope abandoned for this run", { meta: {
@@ -93527,7 +93914,13 @@ async function auditScope(scope, deps, limits, cursor) {
93527
93914
  tally.unresolvable += 1;
93528
93915
  continue;
93529
93916
  }
93530
- if (ref.kind === "track" ? liveTracks.has(ref.trackId) || keptMedia.has(row.key) : ref.kind === "event" ? liveEvents.get(ref.table)?.has(ref.eventId) ?? false : false) continue;
93917
+ let live = ref.kind === "track" ? liveTracks.has(ref.trackId) || keptMedia.has(row.key) : ref.kind === "event" ? liveEvents.get(ref.table)?.has(ref.eventId) ?? false : false;
93918
+ if (live && ref.kind === "event" && scope.collection === MEDIA_COLLECTION) {
93919
+ const gen = eventGens.get(ref.table)?.get(ref.eventId);
93920
+ const mediaDeviceId = num(row.data, scope.deviceIdField);
93921
+ if (gen !== void 0 && mediaDeviceId !== null && mediaDeviceId !== gen.deviceId) live = false;
93922
+ }
93923
+ if (live) continue;
93531
93924
  const ts = num(row.data, scope.timestampField);
93532
93925
  if (ts !== null && ts > graceFloor) {
93533
93926
  tally.withinGrace += 1;
@@ -93542,7 +93935,7 @@ async function auditScope(scope, deps, limits, cursor) {
93542
93935
  break;
93543
93936
  }
93544
93937
  try {
93545
- const outcome = await deps.reclaim(scope.collection, row.key);
93938
+ const outcome = await deps.reclaim(scope.collection, row.key, row);
93546
93939
  if (outcome.rows === 0) continue;
93547
93940
  tally.reclaimed += 1;
93548
93941
  reclaimedInPage += 1;
@@ -93698,7 +94091,10 @@ var ORPHAN_AUDIT_DEFAULTS = {
93698
94091
  intervalHours: OrphanAuditIntervalHoursSchema.parse(void 0),
93699
94092
  pageSize: OrphanAuditPageSizeSchema.parse(void 0)
93700
94093
  };
93701
- var RAW_DELETE_COLLECTIONS = new Set(["pipeline-analytics:object-events", "pipeline-analytics:plates"]);
94094
+ //#endregion
94095
+ //#region src/pipeline-analytics/retention/orphan-reclaimers.ts
94096
+ var OBJECT_EVENTS_COLLECTION = "pipeline-analytics:object-events";
94097
+ var RAW_DELETE_COLLECTIONS = new Set([OBJECT_EVENTS_COLLECTION, "pipeline-analytics:plates"]);
93702
94098
  /**
93703
94099
  * The `deleteRow` port: ONE row by its primary key `id` (what every
93704
94100
  * raw-delete collection above declares), answering whether a row went.
@@ -93707,12 +94103,19 @@ var RAW_DELETE_COLLECTIONS = new Set(["pipeline-analytics:object-events", "pipel
93707
94103
  * is `z.void()`, and reading it as `!== undefined` answered `false` for every
93708
94104
  * row it really deleted — the audit counted every plates/object-events reclaim
93709
94105
  * as "found nothing", and the per-scope reclaim ceiling never tripped.
94106
+ *
94107
+ * When `assert.trackId` is set, the delete also requires that column: an
94108
+ * object-events rowid alone is not an identity across the row's life (D694).
93710
94109
  */
93711
94110
  function settingsRowDeleter(store) {
93712
- return async (collection, key) => {
94111
+ return async (collection, key, assert) => {
94112
+ const where = assert !== void 0 ? {
94113
+ id: key,
94114
+ trackId: assert.trackId
94115
+ } : { id: key };
93713
94116
  const { deleted } = await store.deleteWhere.mutate({
93714
94117
  collection,
93715
- filter: { where: { id: key } }
94118
+ filter: { where }
93716
94119
  });
93717
94120
  return deleted > 0;
93718
94121
  };
@@ -93727,14 +94130,19 @@ function settingsRowDeleter(store) {
93727
94130
  * `scripts/check-orphan-reclaimers.ts` checks `index.ts` uses it.
93728
94131
  */
93729
94132
  function settingsRowDeleterOf(getStore) {
93730
- return async (collection, key) => {
94133
+ return async (collection, key, assert) => {
93731
94134
  const store = getStore();
93732
94135
  if (store === null) throw new Error("orphan audit: settings store not bound");
93733
- return await settingsRowDeleter(store)(collection, key);
94136
+ return await settingsRowDeleter(store)(collection, key, assert);
93734
94137
  };
93735
94138
  }
94139
+ /** Pull the trackId the raw delete must re-assert, or `null` when absent. */
94140
+ function trackIdAssertOf(data) {
94141
+ const trackId = data["trackId"];
94142
+ return typeof trackId === "string" && trackId.length > 0 ? { trackId } : null;
94143
+ }
93736
94144
  function buildOrphanReclaimer(ports) {
93737
- return async (collection, key) => {
94145
+ return async (collection, key, data = {}) => {
93738
94146
  if (collection === MEDIA_COLLECTION) {
93739
94147
  const mediaId = Number(key);
93740
94148
  if (!Number.isInteger(mediaId) || mediaId <= 0) {
@@ -93760,10 +94168,23 @@ function buildOrphanReclaimer(ports) {
93760
94168
  rows: await ports.deleteTrackAlias(key) ? 1 : 0,
93761
94169
  bytes: null
93762
94170
  };
93763
- if (RAW_DELETE_COLLECTIONS.has(collection)) return {
93764
- rows: await ports.deleteRow(collection, key) ? 1 : 0,
93765
- bytes: null
93766
- };
94171
+ if (RAW_DELETE_COLLECTIONS.has(collection)) {
94172
+ const assert = trackIdAssertOf(data);
94173
+ if (collection === OBJECT_EVENTS_COLLECTION && assert === null) {
94174
+ ports.logger.warn("orphan audit: object-events reclaim without trackId — row kept (id-only delete refused)", { meta: {
94175
+ collection,
94176
+ key
94177
+ } });
94178
+ return {
94179
+ rows: 0,
94180
+ bytes: null
94181
+ };
94182
+ }
94183
+ return {
94184
+ rows: await ports.deleteRow(collection, key, assert === null ? void 0 : assert) ? 1 : 0,
94185
+ bytes: null
94186
+ };
94187
+ }
93767
94188
  throw new Error(`orphan audit: '${collection}' is not declared in RECLAIMED_COLLECTIONS — a collection walked by the audit must say how its rows are deleted, because the raw path leaves behind anything its store owns outside the row`);
93768
94189
  };
93769
94190
  }
@@ -100926,7 +101347,10 @@ function ofGeneration(files, scope) {
100926
101347
  function makeNcOwnerMediaReader(deps) {
100927
101348
  return async (ownerKind, ownerId, eventScope) => {
100928
101349
  if (ownerKind === "track") return deps.media.listByOwner("track", await deps.resolveTrackOwnerId(ownerId));
100929
- if (eventScope === void 0) return deps.media.listByOwner("object", ownerId);
101350
+ if (eventScope === void 0) {
101351
+ deps.logger.error("object media read refused — NcEventMediaScope required", { meta: { ownerId } });
101352
+ return [];
101353
+ }
100930
101354
  const onCamera = await deps.media.listByOwner("object", ownerId, eventScope.deviceId);
100931
101355
  const kept = ofGeneration(onCamera, eventScope);
100932
101356
  if (kept.length < onCamera.length) deps.logger.warn("event media of another record on a reused event id — not used", {
@@ -101163,8 +101587,14 @@ async function buildTrainingExportPlan(readers, input) {
101163
101587
  const eventsByTrack = /* @__PURE__ */ new Map();
101164
101588
  for (const link of eventLinks) {
101165
101589
  const list = eventsByTrack.get(link.trackId);
101166
- if (list) list.push(link.eventId);
101167
- else eventsByTrack.set(link.trackId, [link.eventId]);
101590
+ if (list) list.push({
101591
+ eventId: link.eventId,
101592
+ recordAt: link.recordAt
101593
+ });
101594
+ else eventsByTrack.set(link.trackId, [{
101595
+ eventId: link.eventId,
101596
+ recordAt: link.recordAt
101597
+ }]);
101168
101598
  }
101169
101599
  for (const track of take) {
101170
101600
  const files = [];
@@ -101184,15 +101614,17 @@ async function buildTrainingExportPlan(readers, input) {
101184
101614
  ownerId: `${PLATE_OWNER_PREFIX}${track.trackId}`,
101185
101615
  scope: "plate"
101186
101616
  },
101187
- ...(eventsByTrack.get(track.trackId) ?? []).map((eventId) => ({
101617
+ ...(eventsByTrack.get(track.trackId) ?? []).map((ev) => ({
101188
101618
  ownerKind: "object",
101189
- ownerId: eventId,
101190
- scope: `events/${eventId}`
101619
+ ownerId: ev.eventId,
101620
+ scope: `events/${ev.eventId}`,
101621
+ recordAt: ev.recordAt
101191
101622
  }))
101192
101623
  ];
101193
101624
  for (const owner of owners) {
101194
- const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId);
101195
- for (const info of infos) {
101625
+ const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId, owner.ownerKind === "object" ? track.deviceId : void 0);
101626
+ const kept = owner.recordAt === void 0 ? infos : infos.filter((info) => info.timestamp === owner.recordAt);
101627
+ for (const info of kept) {
101196
101628
  const archivePath = archivePathFor({
101197
101629
  deviceId: track.deviceId,
101198
101630
  trackId: track.trackId,
@@ -102011,6 +102443,10 @@ var NC_ARTIFACT_PRUNE_INTERVAL_MS = 36e5;
102011
102443
  * (every camera switched off): the bound is 24 h, not "24 h after the next
102012
102444
  * close" (D681). */
102013
102445
  var TAPE_SWEEP_INTERVAL_MS = 36e5;
102446
+ /** How often open tape segments push their pending gzip output to disk
102447
+ * without rotating (D681 follow-up): a silent camera otherwise keeps its
102448
+ * tail only in the zlib buffer until the next frame or the 10-minute cut. */
102449
+ var TAPE_FLUSH_INTERVAL_MS = 6e4;
102014
102450
  /** What a publish answers on a node whose artifact plane never came up: no
102015
102451
  * link, no index row — and, exactly as before, bytes-only delivery. */
102016
102452
  var NO_PUBLICATION = {
@@ -102544,6 +102980,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
102544
102980
  ncArtifactReadDataPlane = null;
102545
102981
  ncArtifactPruneTimer = null;
102546
102982
  tapeSweepTimer = null;
102983
+ tapeFlushTimer = null;
102547
102984
  /** Why `startTrackingTapes` threw on a node that was allowed to record —
102548
102985
  * reported `failing` per switched-on camera, never as `recording`. */
102549
102986
  tapeStartFailure = null;
@@ -104348,6 +104785,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
104348
104785
  StationaryObjectRegistry.declare(api.settingsStore),
104349
104786
  RetrainFrameStore.declare(api.settingsStore),
104350
104787
  RetrainAnnotationStore.declare(api.settingsStore),
104788
+ RetrainCollectionStore.declare(api.settingsStore),
104351
104789
  OpsLogStore.declare(api.settingsStore),
104352
104790
  DebugNoteArchiveStore.declare(api.settingsStore),
104353
104791
  TrackingTapeIndexStore.declare(api.settingsStore),
@@ -105703,15 +106141,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
105703
106141
  if (!trackStore || !mediaStore) throw new Error("training export: analytics stores unavailable");
105704
106142
  return buildTrainingExportPlan({
105705
106143
  listMarkedTracks: (deviceId, limit) => trackStore.listStaging(deviceId, limit),
105706
- listMediaInfo: async (ownerKind, ownerId) => {
106144
+ listMediaInfo: async (ownerKind, ownerId, deviceId) => {
105707
106145
  if (!isMediaOwnerKind(ownerKind)) return [];
105708
- return mediaStore.listInfoByOwner(ownerKind, ownerId);
106146
+ return mediaStore.listInfoByOwner(ownerKind, ownerId, deviceId);
105709
106147
  },
105710
106148
  listEventIdsForTracks: async (trackIds) => {
105711
106149
  if (!eventStore || trackIds.length === 0) return [];
105712
106150
  return (await eventStore.queryObjectByTrackIds(trackIds)).flatMap((e) => e.trackId === void 0 ? [] : [{
105713
106151
  eventId: String(e.id),
105714
- trackId: e.trackId
106152
+ trackId: e.trackId,
106153
+ recordAt: e.timestamp
105715
106154
  }]);
105716
106155
  },
105717
106156
  now: () => Date.now()
@@ -106795,6 +107234,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
106795
107234
  clearInterval(this.tapeSweepTimer);
106796
107235
  this.tapeSweepTimer = null;
106797
107236
  }
107237
+ if (this.tapeFlushTimer !== null) {
107238
+ clearInterval(this.tapeFlushTimer);
107239
+ this.tapeFlushTimer = null;
107240
+ }
106798
107241
  if (this.ncArtifactPruneTimer !== null) {
106799
107242
  clearInterval(this.ncArtifactPruneTimer);
106800
107243
  this.ncArtifactPruneTimer = null;
@@ -111912,26 +112355,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
111912
112355
  },
111913
112356
  liveTrackIds: (ids) => this.liveTrackIdsThroughAliases(ids, trackStore),
111914
112357
  liveEventIds: (table, ids) => eventStore.existingEventIdsIn(table, ids),
112358
+ eventGenerations: (table, ids) => eventStore.eventGenerationsIn(table, ids),
111915
112359
  keptMediaKeys: async (keys) => this.keptStationaryMediaKeys(keys),
111916
- reclaim: buildOrphanReclaimer({
111917
- deleteFace: async (faceId) => {
111918
- await this.faceStore?.delete(faceId);
111919
- },
111920
- deleteMediaConfirmed: async (mediaId) => {
111921
- const count = await mediaStore.deleteConfirmed(mediaId);
111922
- return {
111923
- rows: count.rows,
111924
- bytes: count.bytes
111925
- };
111926
- },
111927
- deleteTrackAlias: async (aliasId) => {
111928
- const aliasStore = this.trackAliasStore;
111929
- if (aliasStore === null) throw new Error("track alias store not bound");
111930
- return await aliasStore.deleteAlias(aliasId);
111931
- },
111932
- deleteRow: settingsRowDeleterOf(() => this.settingsStore),
111933
- logger: this.ctx.logger
111934
- }),
112360
+ reclaim: (() => {
112361
+ const reclaimOne = buildOrphanReclaimer({
112362
+ deleteFace: async (faceId) => {
112363
+ await this.faceStore?.delete(faceId);
112364
+ },
112365
+ deleteMediaConfirmed: async (mediaId) => {
112366
+ const count = await mediaStore.deleteConfirmed(mediaId);
112367
+ return {
112368
+ rows: count.rows,
112369
+ bytes: count.bytes
112370
+ };
112371
+ },
112372
+ deleteTrackAlias: async (aliasId) => {
112373
+ const aliasStore = this.trackAliasStore;
112374
+ if (aliasStore === null) throw new Error("track alias store not bound");
112375
+ return await aliasStore.deleteAlias(aliasId);
112376
+ },
112377
+ deleteRow: settingsRowDeleterOf(() => this.settingsStore),
112378
+ logger: this.ctx.logger
112379
+ });
112380
+ return (collection, key, row) => reclaimOne(collection, key, row.data);
112381
+ })(),
111935
112382
  now: () => Date.now(),
111936
112383
  logger: this.ctx.logger,
111937
112384
  breathe: () => new Promise((resolve) => setImmediate(resolve))
@@ -113564,10 +114011,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
113564
114011
  });
113565
114012
  const scored = [];
113566
114013
  for (const { event, score } of bestEventByTrackId.values()) {
114014
+ const embeddingMediaId = event.trackId !== void 0 ? embeddingMediaIdByTrackId.get(event.trackId) : void 0;
113567
114015
  const mediaUrl = resolveSearchThumbnailUrl({
113568
114016
  baseUrl: this.eventMediaBaseUrl,
113569
114017
  eventId: String(event.id),
113570
- ...event.trackId !== void 0 && embeddingMediaIdByTrackId.has(event.trackId) ? { embeddingMediaId: embeddingMediaIdByTrackId.get(event.trackId) } : {}
114018
+ deviceId: event.deviceId,
114019
+ recordAt: event.timestamp,
114020
+ ...embeddingMediaId !== void 0 ? { embeddingMediaId } : {}
113571
114021
  });
113572
114022
  const keyFrameMediaId = event.trackId !== void 0 ? keyFrameMediaIdByTrackId.get(event.trackId) : void 0;
113573
114023
  scored.push({
@@ -113954,7 +114404,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
113954
114404
  logger
113955
114405
  });
113956
114406
  const sweep = (deviceId) => {
113957
- shelf.sweep().catch((err) => {
114407
+ shelf.sweep().then((report) => {
114408
+ logTapeSweepReport(report, logger);
114409
+ }).catch((err) => {
113958
114410
  logger.warn("tracking tape sweep failed", {
113959
114411
  ...deviceId !== null ? { tags: { deviceId } } : {},
113960
114412
  meta: { error: require_clip_model_registry.errMsg(err) }
@@ -113986,6 +114438,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
113986
114438
  sweep(null);
113987
114439
  this.tapeSweepTimer = setInterval(() => sweep(null), TAPE_SWEEP_INTERVAL_MS);
113988
114440
  this.tapeSweepTimer.unref?.();
114441
+ this.tapeFlushTimer = setInterval(() => {
114442
+ this.tapeRecorder?.flushOpenSegments();
114443
+ }, TAPE_FLUSH_INTERVAL_MS);
114444
+ this.tapeFlushTimer.unref?.();
113989
114445
  this.ctx.logger.info("tracking tapes ready", { meta: { seededProcessors: this.processorInputs.size } });
113990
114446
  } catch (err) {
113991
114447
  this.tapeStartFailure = require_clip_model_registry.errMsg(err);
@@ -114178,7 +114634,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114178
114634
  segments.set(deviceId, await this.tapeListingOf(shelf, deviceId));
114179
114635
  }
114180
114636
  const recorder = this.tapeRecorder;
114181
- return { cameras: describeTapeStatus({
114637
+ const cameras = describeTapeStatus({
114182
114638
  deviceIds,
114183
114639
  switchOn,
114184
114640
  switchUnreadable,
@@ -114187,7 +114643,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114187
114643
  startFailure: this.tapeStartFailure,
114188
114644
  segments,
114189
114645
  now: Date.now()
114190
- }) };
114646
+ });
114647
+ return { cameras: input.deviceIds === void 0 ? defaultTapeStatusCameras(cameras) : cameras };
114191
114648
  },
114192
114649
  "debug.tape.promote": async (input) => {
114193
114650
  const deps = this.tapePromotionDeps();
@@ -114636,7 +115093,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114636
115093
  * owner table, so the motion/object coin-flip is gone from here too.
114637
115094
  */
114638
115095
  async readMediaByAddress(address, variant, preferKind) {
114639
- const base = address.kind === "row" ? await this.readMediaById(address.mediaId) : await this.readOwnerThumbnail(address.ownerType, address.ownerId, preferKind);
115096
+ const base = address.kind === "row" ? await this.readMediaById(address.mediaId) : await this.readOwnerThumbnail(address.ownerType, address.ownerId, preferKind, address.generation);
114640
115097
  if (base === null || variant === void 0) return base;
114641
115098
  return this.applyThumbVariant(base, variant);
114642
115099
  }
@@ -114694,9 +115151,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114694
115151
  * ~144 MB and allocated ~190 MB of base64 to deliver ~1.2 MB, all of it on
114695
115152
  * the single-threaded post-analysis runner.
114696
115153
  */
114697
- async readOwnerThumbnail(ownerType, rawOwnerId, preferKind) {
115154
+ async readOwnerThumbnail(ownerType, rawOwnerId, preferKind, addressGeneration) {
114698
115155
  const ownerId = ownerType === "track" ? await resolveMediaOwnerId(ownerType, rawOwnerId, (id) => this.trackAliasStore?.resolve(id) ?? Promise.resolve(null), (failure) => this.aliasResolveFailureLogger?.(failure), "readOwnerThumbnail") : rawOwnerId;
114699
- const listInfo = (ownerKind, id) => this.mediaStore?.listInfoByOwner(ownerKind, id) ?? Promise.resolve([]);
115156
+ const listInfo = (ownerKind, id, deviceId) => this.mediaStore?.listInfoByOwner(ownerKind, id, deviceId) ?? Promise.resolve([]);
114700
115157
  const getTrackIdForEvent = (eventId) => require_clip_model_registry.isEventOwnerType(ownerType) ? this.eventStore?.getTrackIdForEvent(eventId, ownerType) ?? Promise.resolve(null) : Promise.resolve(null);
114701
115158
  const readBlob = async (mediaId) => {
114702
115159
  const file = await (this.mediaStore?.getById(mediaId) ?? Promise.resolve(null));
@@ -114707,7 +115164,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114707
115164
  deviceId: file.deviceId
114708
115165
  };
114709
115166
  };
114710
- const ownFiles = await listInfo(ownerType, ownerId);
115167
+ let ownFiles = await listInfo(ownerType, ownerId);
115168
+ if (require_clip_model_registry.isEventOwnerType(ownerType)) {
115169
+ const gen = addressGeneration !== void 0 ? {
115170
+ deviceId: addressGeneration.deviceId,
115171
+ timestamp: addressGeneration.recordAt
115172
+ } : this.eventStore !== null ? await this.eventStore.eventGenerationOf(ownerType, ownerId) : null;
115173
+ if (gen !== null) {
115174
+ const onCamera = await listInfo(ownerType, ownerId, gen.deviceId);
115175
+ const kept = onCamera.filter((f) => f.timestamp === gen.timestamp);
115176
+ if (kept.length < onCamera.length) this.ctx.logger.warn("event media of another record on a reused event id — not used", {
115177
+ tags: { deviceId: gen.deviceId },
115178
+ meta: {
115179
+ ownerId,
115180
+ ownerType,
115181
+ recordAt: gen.timestamp,
115182
+ refused: onCamera.length - kept.length,
115183
+ kept: kept.length,
115184
+ via: "readOwnerThumbnail",
115185
+ scopedBy: addressGeneration !== void 0 ? "url" : "live-row"
115186
+ }
115187
+ });
115188
+ ownFiles = kept;
115189
+ }
115190
+ }
114711
115191
  if (preferKind !== void 0 && preferKind.length > 0) {
114712
115192
  const layers = await collectCropForcedMedia({
114713
115193
  listByOwner: listInfo,