@camstack/addon-post-analysis 1.2.316 → 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.
@@ -1,4 +1,4 @@
1
- import { $ as NC_ALARM_SYSTEM_EVENT_KINDS, $n as notificationRulesCapability, $r as object, $t as SCENE_DEFAULT_UNCOVERED_POLICY, A as EMBEDDING_REQUIREMENTS_FAILED, Ar as canonicalHash, At as NcScheduleSchema, B as ListTrackRelationsInputSchema, Bn as isAudioClassName, Br as hydrateSchema, Bt as OPS_LOG_DEFAULT_LIMIT, C as DEFAULT_EVENT_COLOR, Cn as buildEventKindDescriptor, Cr as systemEventSummaryScheduleOf, Ct as NcConditionDescriptorSchema, D as DeclaredDevices, Dn as describeSystemEventDeliveryProblem, Dr as vectorDimFromBase64, Dt as NcRuleSchema, E as DETECTION_PIPELINE_CAP_NAME, En as deriveRecordingMode, Er as templateVarsFor, Et as NcRulePatchSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as evictionPolicyOfLocation, Fr as BaseAddon, Ft as NcSystemEventKindSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as isRetiredClearKey, Gr as ZodObject, Gt as RebuildObjectEmbeddingsRefusalSchema, H as LockStateSchema, Hn as isEventOwnerType, Hr as nodePin, I as FIRST_LEVEL_MACRO_CLASSES, In as faceGalleryCapability, Ir as CamProfileSchema, It as NcTaxonomySchema, J as MOTION_CLOSE_AFTER_MS, Jn as isSystemEventSummaryRule, Jr as boolean, Jt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, K as MAX_TRACK_PURITIES, Kn as isScheduleActive, Kr as _enum, Kt as RetrainStatusSchema, L as FULL_IMAGE_BBOX, Ln as failureContributionCapability, Lr as createEvent, Lt as NcTemplatePreviewInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mr as isSourceCap, Mt as NcSnoozeSchema, N as EVENT_KIND_BY_CAP, Nr as DeviceType, Nt as NcSnoozeSuppressedSchema, O as DetectionSourceSchema, On as describeSystemEventFamilyProblem, Or as zoneAnalyticsCapability, Ot as NcRuleTargetSchema, P as EVENT_OWNER_TYPES, Pn as encodeVectorBase64, Pr as errMsg, Pt as NcStateSummarySectionIdSchema, Q as NC_ALARM_DELAY_MAX_SEC, Qn as memoizePinnedFfmpeg, Qr as number, Qt as SCENE_DEFAULT_QUIET_SECONDS, R as FailureCounters, Rn as gifDeriveSpeed, Rr as customAction, Rt as NcTemplatePreviewSchema, S as CoverStateSchema, Sn as audioModeOf, Sr as systemEventFilterApplies, St as NcAlarmSettingsPatchSchema, T as DETECTION_MACRO_CLASSES, Tn as cosineSimilarity$1, Tr as templateCarriesSummary, Tt as NcRuleInputSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as isKnownTemplateVar, Ut as RECORDING_EXPORT_MAX_READ_BYTES, V as ListTrackRelationsResultSchema, Vn as isDetectionMacroClass, Vr as isDeviceScopedCap, Vt as OpsLogEntrySchema, W as MAX_BIRTH_DECISION_RECORDS, Wn as isMediaOwnerType, Wr as sleep$1, Wt as RECORDING_SOURCE_CAMSTACK_ADDON, X as MotionTriggeredBySchema, Xn as kebabToCamel, Xr as literal, Xt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, Y as MediaFileKindEnum, Yn as isVectorIndexDimConflict, Yr as discriminatedUnion, Yt as SCENE_DEFAULT_ANCHOR_THRESHOLD, Z as NC_ALARM_DELAY_FIELDS, Zn as mayWriteToLocation, Zr as looseObject, Zt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, _ as BaseDevice, _n as alarmPanelCapability, _r as servedTemplateVars, _t as NC_TAXONOMY, a as CLIP_EMBEDDING_STEP_ID, ai as tuple, an as SetTrackRelationInputSchema, ar as readMotionTriggeredBy, at as NC_CLIP_RETENTION_MAX_SEC, b as ClearTrackRelationInputSchema, bn as assertTimelapseCadences, br as systemEventDeliveryProblems, bt as NcAlarmSensorsInputSchema, c as resolveClusterModelPin, ci as EventCategory, cn as TimelapseRuleInputSchema, cr as resolveLocationMode, ct as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as TrackPuritySchema, dr as ruleBelongsToAlarm$1, dt as NC_GROUPING_MAX_SEC, ei as partialRecord, en as SCENE_DIVERGED, er as occupancyScope, et as NC_ALARM_TRIGGERED_MAX_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as TrackRelationKindSchema, fr as ruleDeviceCompatCapQueries, ft as NC_GROUPING_MIN_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as addonWidgetsSourceCapability, gr as sensorRaisedOf, gt as NC_TANK_SYSTEM_EVENT_KINDS, h as ArchivedDebugNoteSchema, hn as TrackSourceSchema, hr as sceneMonitorCapability, ht as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, ii as templateLiteral, in as SetTrackPurityResultSchema, ir as readDeviceStateFrom, it as NC_CLIP_PRE_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as embeddingRefusalOf, jr as evaluateSensorEdge, jt as NcSnoozeInputSchema, k as EMBEDDING_MODEL_FAILED, kr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, kt as NcRuleUpdateInputSchema, l as APPEARANCE_BIN_COUNT, ln as TimelapseRulePatchSchema, lr as resolveModeDelays, lt as NC_DEFAULT_SNOOZE_MINUTES, m as AlarmArmModeSchema, mn as TrackSideSchema, mr as ruleKindOf, mt as NC_SNOOZE_MAX_MINUTES, ni as strictObject, nn as SceneMonitorSchema, nr as pipelineAnalyticsCapability, nt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as union, on as TIMELAPSE_DENSE_FLOOR_SEC, or as readTimelapseGeneratedAt, ot as NC_CONDITION_CATALOG, p as AUDIO_MACRO_LABELS, pn as TrackRelationSchema, pr as ruleGroupingApplies, pt as NC_SENSOR_TRANSITION_KINDS, q as MAX_TRACK_RELATIONS, qn as isSystemDelivery, qr as array, qt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, ri as string, rn as SetTrackPurityInputSchema, rr as plateGalleryCapability, rt as NC_CLIP_POST_ROLL_DEFAULT_SEC, s as readClusterModelPinStrict, si as unknown, sn as TRACK_RELATIONS_DEFAULT_LIMIT, sr as resolveClipSearchMinScore, st as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as record, tn as SCENE_RESET_RECAPTURES, tt as NC_AUDIO_CONFIRM_HITS_DEFAULT, u as APPEARANCE_BLOCKS, un as TimelapseRuleSchema, ut as NC_GROUPING_DEFAULT_SEC, v as BirthDecisionRecordSchema, vn as applyModeDelaysEdits, vr as storageOccupancyCapability, vt as NC_TEMPLATE_VARS, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as consumableLowPercentOf, wr as systemEventSummarySplitOf, wt as NcConditionsSchema, x as ClearTrackRelationResultSchema, xn as audioMetricsCapability, xr as systemEventFamilyProblems, xt as NcAlarmSensorsSchema, y as COCO_TO_MACRO, yn as applyNonBlockingEdit, yr as subKindsOf, yt as NcAlarmConfigSchema, z as LabelAttributionSchema, zn as gifFromMp4Args, zr as defineCustomActions, zt as NcTemplateVarDescriptorSchema } from "../clip-model-registry-Dkv8MUCO.mjs";
1
+ import { $ as NC_ALARM_SYSTEM_EVENT_KINDS, $n as notificationRulesCapability, $r as object, $t as SCENE_DEFAULT_UNCOVERED_POLICY, A as EMBEDDING_REQUIREMENTS_FAILED, Ar as canonicalHash, At as NcScheduleSchema, B as ListTrackRelationsInputSchema, Bn as isAudioClassName, Br as hydrateSchema, Bt as OPS_LOG_DEFAULT_LIMIT, C as DEFAULT_EVENT_COLOR, Cn as buildEventKindDescriptor, Cr as systemEventSummaryScheduleOf, Ct as NcConditionDescriptorSchema, D as DeclaredDevices, Dn as describeSystemEventDeliveryProblem, Dr as vectorDimFromBase64, Dt as NcRuleSchema, E as DETECTION_PIPELINE_CAP_NAME, En as deriveRecordingMode, Er as templateVarsFor, Et as NcRulePatchSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as evictionPolicyOfLocation, Fr as BaseAddon, Ft as NcSystemEventKindSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as isRetiredClearKey, Gr as ZodObject, Gt as RebuildObjectEmbeddingsRefusalSchema, H as LockStateSchema, Hn as isEventOwnerType, Hr as nodePin, I as FIRST_LEVEL_MACRO_CLASSES, In as faceGalleryCapability, Ir as CamProfileSchema, It as NcTaxonomySchema, J as MOTION_CLOSE_AFTER_MS, Jn as isSystemEventSummaryRule, Jr as boolean, Jt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, K as MAX_TRACK_PURITIES, Kn as isScheduleActive, Kr as _enum, Kt as RetrainStatusSchema, L as FULL_IMAGE_BBOX, Ln as failureContributionCapability, Lr as createEvent, Lt as NcTemplatePreviewInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mr as isSourceCap, Mt as NcSnoozeSchema, N as EVENT_KIND_BY_CAP, Nr as DeviceType, Nt as NcSnoozeSuppressedSchema, O as DetectionSourceSchema, On as describeSystemEventFamilyProblem, Or as zoneAnalyticsCapability, Ot as NcRuleTargetSchema, P as EVENT_OWNER_TYPES, Pn as encodeVectorBase64, Pr as errMsg, Pt as NcStateSummarySectionIdSchema, Q as NC_ALARM_DELAY_MAX_SEC, Qn as memoizePinnedFfmpeg, Qr as number, Qt as SCENE_DEFAULT_QUIET_SECONDS, R as FailureCounters, Rn as gifDeriveSpeed, Rr as customAction, Rt as NcTemplatePreviewSchema, S as CoverStateSchema, Sn as audioModeOf, Sr as systemEventFilterApplies, St as NcAlarmSettingsPatchSchema, T as DETECTION_MACRO_CLASSES, Tn as cosineSimilarity$1, Tr as templateCarriesSummary, Tt as NcRuleInputSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as isKnownTemplateVar, Ut as RECORDING_EXPORT_MAX_READ_BYTES, V as ListTrackRelationsResultSchema, Vn as isDetectionMacroClass, Vr as isDeviceScopedCap, Vt as OpsLogEntrySchema, W as MAX_BIRTH_DECISION_RECORDS, Wn as isMediaOwnerType, Wr as sleep$1, Wt as RECORDING_SOURCE_CAMSTACK_ADDON, X as MotionTriggeredBySchema, Xn as kebabToCamel, Xr as literal, Xt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, Y as MediaFileKindEnum, Yn as isVectorIndexDimConflict, Yr as discriminatedUnion, Yt as SCENE_DEFAULT_ANCHOR_THRESHOLD, Z as NC_ALARM_DELAY_FIELDS, Zn as mayWriteToLocation, Zr as looseObject, Zt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, _ as BaseDevice, _n as alarmPanelCapability, _r as servedTemplateVars, _t as NC_TAXONOMY, a as CLIP_EMBEDDING_STEP_ID, ai as tuple, an as SetTrackRelationInputSchema, ar as readMotionTriggeredBy, at as NC_CLIP_RETENTION_MAX_SEC, b as ClearTrackRelationInputSchema, bn as assertTimelapseCadences, br as systemEventDeliveryProblems, bt as NcAlarmSensorsInputSchema, c as resolveClusterModelPin, ci as EventCategory, cn as TimelapseRuleInputSchema, cr as resolveLocationMode, ct as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as TrackPuritySchema, dr as ruleBelongsToAlarm$1, dt as NC_GROUPING_MAX_SEC, ei as partialRecord, en as SCENE_DIVERGED, er as occupancyScope, et as NC_ALARM_TRIGGERED_MAX_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as TrackRelationKindSchema, fr as ruleDeviceCompatCapQueries, ft as NC_GROUPING_MIN_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as addonWidgetsSourceCapability, gr as sensorRaisedOf, gt as NC_TANK_SYSTEM_EVENT_KINDS, h as ArchivedDebugNoteSchema, hn as TrackSourceSchema, hr as sceneMonitorCapability, ht as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, ii as templateLiteral, in as SetTrackPurityResultSchema, ir as readDeviceStateFrom, it as NC_CLIP_PRE_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as embeddingRefusalOf, jr as evaluateSensorEdge, jt as NcSnoozeInputSchema, k as EMBEDDING_MODEL_FAILED, kr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, kt as NcRuleUpdateInputSchema, l as APPEARANCE_BIN_COUNT, ln as TimelapseRulePatchSchema, lr as resolveModeDelays, lt as NC_DEFAULT_SNOOZE_MINUTES, m as AlarmArmModeSchema, mn as TrackSideSchema, mr as ruleKindOf, mt as NC_SNOOZE_MAX_MINUTES, ni as strictObject, nn as SceneMonitorSchema, nr as pipelineAnalyticsCapability, nt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as union, on as TIMELAPSE_DENSE_FLOOR_SEC, or as readTimelapseGeneratedAt, ot as NC_CONDITION_CATALOG, p as AUDIO_MACRO_LABELS, pn as TrackRelationSchema, pr as ruleGroupingApplies, pt as NC_SENSOR_TRANSITION_KINDS, q as MAX_TRACK_RELATIONS, qn as isSystemDelivery, qr as array, qt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, ri as string, rn as SetTrackPurityInputSchema, rr as plateGalleryCapability, rt as NC_CLIP_POST_ROLL_DEFAULT_SEC, s as readClusterModelPinStrict, si as unknown, sn as TRACK_RELATIONS_DEFAULT_LIMIT, sr as resolveClipSearchMinScore, st as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as record, tn as SCENE_RESET_RECAPTURES, tt as NC_AUDIO_CONFIRM_HITS_DEFAULT, u as APPEARANCE_BLOCKS, un as TimelapseRuleSchema, ut as NC_GROUPING_DEFAULT_SEC, v as BirthDecisionRecordSchema, vn as applyModeDelaysEdits, vr as storageOccupancyCapability, vt as NC_TEMPLATE_VARS, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as consumableLowPercentOf, wr as systemEventSummarySplitOf, wt as NcConditionsSchema, x as ClearTrackRelationResultSchema, xn as audioMetricsCapability, xr as systemEventFamilyProblems, xt as NcAlarmSensorsSchema, y as COCO_TO_MACRO, yn as applyNonBlockingEdit, yr as subKindsOf, yt as NcAlarmConfigSchema, z as LabelAttributionSchema, zn as gifFromMp4Args, zr as defineCustomActions, zt as NcTemplateVarDescriptorSchema } from "../clip-model-registry-DSqobJfx.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs$1 from "node:fs";
4
4
  import { createWriteStream, promises } from "node:fs";
@@ -51320,6 +51320,11 @@ var MEDIA_ID_SEGMENT = "m";
51320
51320
  /** Path segment that introduces an OWNER question ("best media of"). */
51321
51321
  var MEDIA_OWNER_SEGMENT = "o";
51322
51322
  /**
51323
+ * Path segment that introduces an event-owner GENERATION (D694) —
51324
+ * `/g/<deviceId>/<recordAt>` after the owner id.
51325
+ */
51326
+ var MEDIA_GENERATION_SEGMENT = "g";
51327
+ /**
51323
51328
  * The plane path for one media row.
51324
51329
  *
51325
51330
  * `prefix` is `/addon/<addonId>/event-media` — a pure function of the addon
@@ -51336,6 +51341,23 @@ function mediaFileUrl(prefix, mediaId) {
51336
51341
  return `${prefix}/${MEDIA_ID_SEGMENT}/${mediaId}`;
51337
51342
  }
51338
51343
  /**
51344
+ * The plane path for "whatever media best represents this owner".
51345
+ *
51346
+ * The owner id IS encoded: a trackId is a UUID today but an owner id is an
51347
+ * arbitrary row key (the summary mosaic's is `summary-<ruleId>-<windowEndMs>`),
51348
+ * and one slash in it would silently re-cut the path.
51349
+ *
51350
+ * When `generation` is set and `ownerType` is an event table, the path carries
51351
+ * `/g/<deviceId>/<recordAt>` so a reused rowid cannot resolve another record's
51352
+ * crop. Generation is ignored (and not minted) for non-event owners — a track
51353
+ * id is random and needs none.
51354
+ */
51355
+ function mediaOwnerUrl(prefix, ownerType, ownerId, generation) {
51356
+ const base = `${prefix}/${MEDIA_OWNER_SEGMENT}/${ownerType}/${encodeURIComponent(ownerId)}`;
51357
+ if (generation === void 0 || !isEventOwnerType(ownerType)) return base;
51358
+ return `${base}/${MEDIA_GENERATION_SEGMENT}/${generation.deviceId}/${generation.recordAt}`;
51359
+ }
51360
+ /**
51339
51361
  * A stored row as the wire sees it: the ref, the address, and — transitionally
51340
51362
  * — the bytes.
51341
51363
  *
@@ -59186,6 +59208,12 @@ function openGzipStream(out) {
59186
59208
  onceDrain: (listener) => {
59187
59209
  gzip.once("drain", listener);
59188
59210
  },
59211
+ flush: () => new Promise((resolve, reject) => {
59212
+ gzip.flush((err) => {
59213
+ if (err !== null && err !== void 0) reject(err);
59214
+ else resolve();
59215
+ });
59216
+ }),
59189
59217
  finish: async () => {
59190
59218
  gzip.end();
59191
59219
  await closed;
@@ -59281,6 +59309,25 @@ var TapeSegmentWriter = class {
59281
59309
  });
59282
59310
  }
59283
59311
  /**
59312
+ * Push the sink's pending gzip output to disk without ending the segment.
59313
+ * A quiet / silent camera otherwise keeps its tail only in the zlib
59314
+ * buffer until the next rotation (D681 follow-up). Never throws into the
59315
+ * caller's frame path: a flush fault is logged and swallowed here.
59316
+ */
59317
+ async flushToDisk() {
59318
+ try {
59319
+ await this.deps.sink.flush();
59320
+ } catch (err) {
59321
+ this.deps.logger.warn("tracking tape segment flush failed", {
59322
+ tags: { deviceId: this.deps.deviceId },
59323
+ meta: {
59324
+ segmentOriginMs: this.deps.segmentOriginMs,
59325
+ error: errMsg(err)
59326
+ }
59327
+ });
59328
+ }
59329
+ }
59330
+ /**
59284
59331
  * Always ends the segment's records (queue flush, lost-span gap, `end`
59285
59332
  * record) before touching the sink, so a sink that cannot finish still
59286
59333
  * leaves a decodable body behind it. `sink.finish()`'s rejection is caught
@@ -59607,6 +59654,21 @@ var TapeRecorder = class {
59607
59654
  if (cam !== void 0) await this.closeSegment(deviceId, cam, "segment-end");
59608
59655
  }
59609
59656
  /**
59657
+ * Push every open segment's pending gzip output to disk without rotating
59658
+ * (D681 follow-up). A silent / quiet camera otherwise keeps its tail only
59659
+ * in the zlib buffer until the next frame or the 10-minute cut — a crash
59660
+ * loses it, and `bytesLastHour` lags behind what was already recorded.
59661
+ * Never throws: a per-camera flush fault is logged inside the writer.
59662
+ */
59663
+ async flushOpenSegments() {
59664
+ const pending = [];
59665
+ for (const cam of this.cameras.values()) {
59666
+ const writer = cam.segment?.writer;
59667
+ if (writer !== void 0 && writer !== null) pending.push(writer.flushToDisk());
59668
+ }
59669
+ await Promise.all(pending);
59670
+ }
59671
+ /**
59610
59672
  * Close the camera's open segment only if it holds time at or before
59611
59673
  * `atMs` — what a promotion of a window ending at `atMs` needs on disk
59612
59674
  * (Task 12 fix round, H1). A window wholly before the open segment leaves
@@ -60106,6 +60168,29 @@ var TapeShelf = class {
60106
60168
  };
60107
60169
  }
60108
60170
  };
60171
+ /**
60172
+ * Log every refusal the sweep already measured (D391 / D681 follow-up).
60173
+ * `sweep()` itself never throws for `rootUnreadable` / per-camera `failed`:
60174
+ * those land on the report, and a caller that only `.catch`es would drop them
60175
+ * silently even when the shelf already warned at the `readdir` site. One
60176
+ * tagged line per refusal at the sweep boundary closes that gap.
60177
+ */
60178
+ function logTapeSweepReport(report, logger) {
60179
+ if (report.rootUnreadable !== null) logger.warn("tracking tape sweep refused", { meta: {
60180
+ reason: report.rootUnreadable,
60181
+ scope: "root"
60182
+ } });
60183
+ for (const [deviceId, outcome] of report.perDevice) {
60184
+ if (!("failed" in outcome) || outcome.failed !== true) continue;
60185
+ logger.warn("tracking tape sweep refused", {
60186
+ tags: { deviceId },
60187
+ meta: {
60188
+ reason: outcome.reason,
60189
+ scope: "camera"
60190
+ }
60191
+ });
60192
+ }
60193
+ }
60109
60194
  //#endregion
60110
60195
  //#region src/pipeline-analytics/tracking-bench/recorder/tape-operation-queue.ts
60111
60196
  /** Generous: a promotion reads its window's segments and walks back through
@@ -61159,6 +61244,17 @@ function shelfFigures(listing, now) {
61159
61244
  ...last !== void 0 ? { heldToMs: last.originMs } : {}
61160
61245
  };
61161
61246
  }
61247
+ /**
61248
+ * Default `debug.tape.status` (no `deviceIds`): drop the noise. An Off camera
61249
+ * with a measured empty shelf is not a cost row and is not promotable; keep
61250
+ * everything else — Recording / Refused / Failing / Unknown, Off with held
61251
+ * segments (still promotable after the switch went off), and Off whose shelf
61252
+ * could not be read (D393: never silently hide an unreadable camera). Explicit
61253
+ * `deviceIds` still return every asked-about camera, Off included (D62).
61254
+ */
61255
+ function defaultTapeStatusCameras(rows) {
61256
+ return rows.filter((row) => row.state !== "off" || row.segmentsHeld === null || row.segmentsHeld > 0);
61257
+ }
61162
61258
  function describeTapeStatus(input) {
61163
61259
  return input.deviceIds.map((deviceId) => {
61164
61260
  const shelf = {
@@ -73017,7 +73113,7 @@ var AnalyticsQueryFacade = class {
73017
73113
  async getMotionEvents(input) {
73018
73114
  const events = await (this.deps.eventStore()?.queryMotion(input) ?? Promise.resolve([]));
73019
73115
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73020
- return this.withMediaUrl(events);
73116
+ return this.withMediaUrl(events, "motion");
73021
73117
  }
73022
73118
  async getEventDensity(input) {
73023
73119
  return this.deps.eventStore()?.densityByKind(input) ?? [];
@@ -73074,12 +73170,12 @@ var AnalyticsQueryFacade = class {
73074
73170
  async getObjectEvents(input) {
73075
73171
  const events = await (this.deps.eventStore()?.queryObject(input) ?? Promise.resolve([]));
73076
73172
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73077
- return this.withMediaUrl(events);
73173
+ return this.withMediaUrl(events, "object");
73078
73174
  }
73079
73175
  async getAudioEvents(input) {
73080
73176
  const events = await (this.deps.eventStore()?.queryAudio(input) ?? Promise.resolve([]));
73081
73177
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73082
- return this.withMediaUrl(events);
73178
+ return this.withMediaUrl(events, "audio");
73083
73179
  }
73084
73180
  async getEventMedia(input) {
73085
73181
  const store = this.deps.mediaStore();
@@ -73346,13 +73442,20 @@ var AnalyticsQueryFacade = class {
73346
73442
  *
73347
73443
  * Only called when `eventMediaBaseUrl()` is non-null (guard sits in
73348
73444
  * the callers), so the template literal is safe.
73445
+ *
73446
+ * The URL names the owner TABLE and the event's GENERATION (D482 + D694):
73447
+ * a bare `${base}/${id}` was both ambiguous across tables and unable to
73448
+ * survive a reused rowid. New stamps always carry `/o/<type>/<id>/g/…`.
73349
73449
  */
73350
- withMediaUrl(events) {
73450
+ withMediaUrl(events, ownerType) {
73351
73451
  const base = this.deps.eventMediaBaseUrl();
73352
73452
  if (base === null) return events;
73353
73453
  return events.map((e) => ({
73354
73454
  ...e,
73355
- mediaUrl: `${base}/${e.id}`
73455
+ mediaUrl: mediaOwnerUrl(base, ownerType, String(e.id), {
73456
+ deviceId: e.deviceId,
73457
+ recordAt: e.timestamp
73458
+ })
73356
73459
  }));
73357
73460
  }
73358
73461
  };
@@ -76184,18 +76287,26 @@ function eventMediaEtag(media) {
76184
76287
  }
76185
76288
  /** Digits only — `Number('')` is 0 and `Number(' ')` is 0, so neither is used. */
76186
76289
  var DIGITS = /^\d+$/;
76290
+ /** One run of digits, for composing the event-owner remainder pattern. */
76291
+ var DIGIT_RUN = "\\d+";
76292
+ /**
76293
+ * Event-owner remainder with an optional generation suffix (D694):
76294
+ * `<ownerId>` or `<ownerId>/g/<deviceId>/<recordAt>`. Owner ids for event
76295
+ * tables are decimal rowids; the generation segment is the literal `g`.
76296
+ */
76297
+ var EVENT_OWNER_REMAINDER = new RegExp(`^(${DIGIT_RUN})(?:/${MEDIA_GENERATION_SEGMENT}/(${DIGIT_RUN})/(${DIGIT_RUN}))?$`);
76187
76298
  /**
76188
76299
  * Read a plane path. `null` for anything that is not one of the two forms —
76189
76300
  * including the BARE id the plane used to accept, which is gone: a bare number
76190
76301
  * could be a media id or an event id, and after D474 an event id could be one
76191
76302
  * of three rows. Ambiguity at the door is what this record removes.
76192
76303
  *
76193
- * The owner id is taken as the whole remainder rather than one segment: the
76194
- * hub's wildcard param is percent-decoded by find-my-way before the sub-path
76195
- * is proxied, so a `%2F` an owner id carried would arrive here as a real
76196
- * slash, and re-cutting on it would silently address a different owner. It is
76197
- * only ever a DB query VALUE (`where: { ownerId }`) — never a filesystem path,
76198
- * which the row itself supplies.
76304
+ * For non-event owners the id is the whole remainder rather than one segment:
76305
+ * the hub's wildcard param is percent-decoded by find-my-way before the
76306
+ * sub-path is proxied, so a `%2F` an owner id carried would arrive here as a
76307
+ * real slash, and re-cutting on it would silently address a different owner.
76308
+ * Event owners (decimal rowids) may end with `/g/<deviceId>/<recordAt>` —
76309
+ * that suffix is the generation, not part of the id (D694).
76199
76310
  */
76200
76311
  function parseMediaAddress(rawPath) {
76201
76312
  const path = rawPath.replace(/^\/+/, "");
@@ -76215,13 +76326,35 @@ function parseMediaAddress(rawPath) {
76215
76326
  const typeSlash = rest.indexOf("/");
76216
76327
  if (typeSlash <= 0) return null;
76217
76328
  const ownerType = rest.slice(0, typeSlash);
76218
- const ownerId = rest.slice(typeSlash + 1);
76219
- if (ownerId.length === 0) return null;
76329
+ const remainder = rest.slice(typeSlash + 1);
76330
+ if (remainder.length === 0) return null;
76220
76331
  if (!isMediaOwnerType(ownerType)) return null;
76332
+ if (isEventOwnerType(ownerType)) {
76333
+ const match = EVENT_OWNER_REMAINDER.exec(remainder);
76334
+ if (match === null) return null;
76335
+ const ownerId = match[1];
76336
+ const deviceIdRaw = match[2];
76337
+ const recordAtRaw = match[3];
76338
+ if (ownerId === void 0) return null;
76339
+ if (deviceIdRaw !== void 0 && recordAtRaw !== void 0) return {
76340
+ kind: "owner",
76341
+ ownerType,
76342
+ ownerId,
76343
+ generation: {
76344
+ deviceId: Number(deviceIdRaw),
76345
+ recordAt: Number(recordAtRaw)
76346
+ }
76347
+ };
76348
+ return {
76349
+ kind: "owner",
76350
+ ownerType,
76351
+ ownerId
76352
+ };
76353
+ }
76221
76354
  return {
76222
76355
  kind: "owner",
76223
76356
  ownerType,
76224
- ownerId
76357
+ ownerId: remainder
76225
76358
  };
76226
76359
  }
76227
76360
  /** Set by the HUB for a `csv_*` share token admitted on this plane; never by a client. */
@@ -78898,6 +79031,218 @@ function createRetrainExportHandler(deps) {
78898
79031
  };
78899
79032
  }
78900
79033
  //#endregion
79034
+ //#region src/pipeline-analytics/retrain/retrain-collection-store.ts
79035
+ /**
79036
+ * @durable class=ledger owner=pipeline-analytics
79037
+ * write="one row per named training collection the operator creates. Names are
79038
+ * unique on nameKey (lower(trim)); empty names never become rows."
79039
+ * retention="none — operator-owned corpus metadata. Rows leave only when an
79040
+ * explicit delete API lands (not V1)."
79041
+ */
79042
+ var RETRAIN_COLLECTIONS_COLLECTION = "pipeline-analytics:retrain-collections";
79043
+ var RETRAIN_COLLECTION_COLUMNS = [
79044
+ {
79045
+ name: "id",
79046
+ type: "TEXT",
79047
+ primaryKey: true,
79048
+ notNull: true
79049
+ },
79050
+ {
79051
+ name: "name",
79052
+ type: "TEXT",
79053
+ notNull: true
79054
+ },
79055
+ {
79056
+ name: "nameKey",
79057
+ type: "TEXT",
79058
+ notNull: true
79059
+ },
79060
+ {
79061
+ name: "createdAt",
79062
+ type: "INTEGER",
79063
+ notNull: true
79064
+ },
79065
+ {
79066
+ name: "updatedAt",
79067
+ type: "INTEGER",
79068
+ notNull: true
79069
+ }
79070
+ ];
79071
+ var RETRAIN_COLLECTION_INDEXES = [{
79072
+ name: "idx_retrain_collections_name_key",
79073
+ columns: ["nameKey"],
79074
+ unique: true
79075
+ }, {
79076
+ name: "idx_retrain_collections_created",
79077
+ columns: ["createdAt"]
79078
+ }];
79079
+ function normalizeCollectionName(raw) {
79080
+ const name = raw.trim();
79081
+ if (name.length === 0) return null;
79082
+ return {
79083
+ name,
79084
+ nameKey: name.toLowerCase()
79085
+ };
79086
+ }
79087
+ var RetrainCollectionStore = class {
79088
+ store;
79089
+ logger;
79090
+ now;
79091
+ newId;
79092
+ countFrames;
79093
+ constructor(deps) {
79094
+ this.store = deps.store;
79095
+ this.logger = deps.logger;
79096
+ this.now = deps.now;
79097
+ this.newId = deps.newId;
79098
+ this.countFrames = deps.countFrames;
79099
+ }
79100
+ static async declare(store) {
79101
+ await store.declareCollection.mutate({
79102
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79103
+ columns: [...RETRAIN_COLLECTION_COLUMNS],
79104
+ indexes: [...RETRAIN_COLLECTION_INDEXES]
79105
+ });
79106
+ }
79107
+ async create(input) {
79108
+ const normalized = normalizeCollectionName(input.name);
79109
+ if (normalized === null) return {
79110
+ kind: "refused",
79111
+ reason: "empty-name"
79112
+ };
79113
+ if (await this.findByNameKey(normalized.nameKey) !== null) return {
79114
+ kind: "refused",
79115
+ reason: "duplicate-name"
79116
+ };
79117
+ const createdAt = this.now();
79118
+ const id = this.newId();
79119
+ await this.store.set.mutate({
79120
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79121
+ key: id,
79122
+ value: {
79123
+ name: normalized.name,
79124
+ nameKey: normalized.nameKey,
79125
+ createdAt,
79126
+ updatedAt: createdAt
79127
+ }
79128
+ });
79129
+ return {
79130
+ kind: "created",
79131
+ collection: {
79132
+ id,
79133
+ name: normalized.name,
79134
+ createdAt,
79135
+ frameCount: 0
79136
+ }
79137
+ };
79138
+ }
79139
+ async list() {
79140
+ let rows;
79141
+ try {
79142
+ rows = await this.store.query.query({
79143
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79144
+ filter: { orderBy: {
79145
+ field: "createdAt",
79146
+ direction: "desc"
79147
+ } }
79148
+ });
79149
+ } catch (err) {
79150
+ this.logger.warn("retrain collections list failed", { meta: { error: String(err) } });
79151
+ return [];
79152
+ }
79153
+ const out = [];
79154
+ for (const row of rows) {
79155
+ const name = typeof row.data["name"] === "string" ? row.data["name"] : "";
79156
+ const createdAt = Number(row.data["createdAt"] ?? 0);
79157
+ const frameCount = this.countFrames !== void 0 ? await this.countFrames(row.id) : 0;
79158
+ out.push({
79159
+ id: row.id,
79160
+ name,
79161
+ createdAt,
79162
+ frameCount
79163
+ });
79164
+ }
79165
+ return out;
79166
+ }
79167
+ async getById(id) {
79168
+ try {
79169
+ const row = await this.store.get.query({
79170
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79171
+ key: id
79172
+ });
79173
+ if (row === null || row === void 0) return null;
79174
+ const data = row;
79175
+ return {
79176
+ id,
79177
+ name: typeof data["name"] === "string" ? data["name"] : "",
79178
+ createdAt: Number(data["createdAt"] ?? 0),
79179
+ frameCount: this.countFrames !== void 0 ? await this.countFrames(id) : 0
79180
+ };
79181
+ } catch (err) {
79182
+ this.logger.warn("retrain collection get failed", { meta: {
79183
+ collectionId: id,
79184
+ error: String(err)
79185
+ } });
79186
+ return null;
79187
+ }
79188
+ }
79189
+ /**
79190
+ * Stub until still ingest lands. Validates the collection exists, then
79191
+ * refuses with a named reason so callers can wire UI against a stable shape.
79192
+ */
79193
+ async addRetrainFrameFromRecording(input) {
79194
+ if (await this.getById(input.collectionId) === null) {
79195
+ this.logger.info("retrain collection addFrame refused: unknown-collection", {
79196
+ tags: { deviceId: input.deviceId },
79197
+ meta: {
79198
+ collectionId: input.collectionId,
79199
+ epochMs: input.epochMs
79200
+ }
79201
+ });
79202
+ return {
79203
+ kind: "refused",
79204
+ reason: "unknown-collection"
79205
+ };
79206
+ }
79207
+ this.logger.info("retrain collection addFrame stub: still-capture-unimplemented", {
79208
+ tags: { deviceId: input.deviceId },
79209
+ meta: {
79210
+ collectionId: input.collectionId,
79211
+ epochMs: input.epochMs
79212
+ }
79213
+ });
79214
+ return {
79215
+ kind: "refused",
79216
+ reason: "still-capture-unimplemented",
79217
+ detail: "Still ingest lands in plan Task 3"
79218
+ };
79219
+ }
79220
+ async findByNameKey(nameKey) {
79221
+ try {
79222
+ const row = (await this.store.query.query({
79223
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
79224
+ filter: {
79225
+ where: { nameKey },
79226
+ limit: 1
79227
+ }
79228
+ }))[0];
79229
+ if (row === void 0) return null;
79230
+ return {
79231
+ id: row.id,
79232
+ name: typeof row.data["name"] === "string" ? row.data["name"] : "",
79233
+ createdAt: Number(row.data["createdAt"] ?? 0),
79234
+ frameCount: 0
79235
+ };
79236
+ } catch (err) {
79237
+ this.logger.warn("retrain collection findByNameKey failed", { meta: {
79238
+ nameKey,
79239
+ error: String(err)
79240
+ } });
79241
+ return null;
79242
+ }
79243
+ }
79244
+ };
79245
+ //#endregion
78901
79246
  //#region src/pipeline-analytics/retrain/retrain-assist.ts
78902
79247
  /** The whole frame, as a normalised rectangle. */
78903
79248
  var WHOLE_FRAME = {
@@ -82307,7 +82652,8 @@ async function rankKeyEvents(candidates, options, peakLookup) {
82307
82652
  ...t.markForTrain !== void 0 ? { markForTrain: t.markForTrain } : {},
82308
82653
  ...t.retrainStatus !== void 0 ? { retrainStatus: t.retrainStatus } : {},
82309
82654
  ...t.debug !== void 0 ? { debug: t.debug } : {},
82310
- ...t.favourited !== void 0 ? { favourited: t.favourited } : {}
82655
+ ...t.favourited !== void 0 ? { favourited: t.favourited } : {},
82656
+ ...t.audioLabels !== void 0 && t.audioLabels.length > 0 ? { audioLabels: t.audioLabels } : {}
82311
82657
  });
82312
82658
  }
82313
82659
  scored.sort((a, b) => b.importance - a.importance);
@@ -82734,16 +83080,19 @@ var MotionOnsetRegistry = class {
82734
83080
  * - `resolveSearchThumbnailUrl` — the search-hit thumbnail resolver. It points
82735
83081
  * a hit at the CLIP embedding row's OWN crop (the crop captured on the
82736
83082
  * embedding's peak frame → the hit thumbnail IS the embedded crop), and only
82737
- * falls back to the representative event's crop when the embedding row has no
82738
- * crop yet, so a hit never renders blank.
83083
+ * falls back to the representative event's owner ladder when the embedding
83084
+ * row has no crop yet, so a hit never renders blank.
82739
83085
  */
82740
83086
  function isClipObjectEmbedding(t, registry = BUILTIN_CLIP_MODELS) {
82741
83087
  return Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && registry.resolve(t.embeddingModelId) !== null;
82742
83088
  }
82743
83089
  function resolveSearchThumbnailUrl(input) {
82744
83090
  if (input.baseUrl === null) return void 0;
82745
- const id = input.embeddingMediaKey ?? input.eventId;
82746
- return `${input.baseUrl}/${encodeURIComponent(id)}`;
83091
+ if (input.embeddingMediaId !== void 0) return mediaFileUrl(input.baseUrl, input.embeddingMediaId);
83092
+ return mediaOwnerUrl(input.baseUrl, "object", input.eventId, {
83093
+ deviceId: input.deviceId,
83094
+ recordAt: input.recordAt
83095
+ });
82747
83096
  }
82748
83097
  //#endregion
82749
83098
  //#region src/pipeline-analytics/pipeline/onboard-birth-authority.ts
@@ -100903,7 +101252,10 @@ function ofGeneration(files, scope) {
100903
101252
  function makeNcOwnerMediaReader(deps) {
100904
101253
  return async (ownerKind, ownerId, eventScope) => {
100905
101254
  if (ownerKind === "track") return deps.media.listByOwner("track", await deps.resolveTrackOwnerId(ownerId));
100906
- if (eventScope === void 0) return deps.media.listByOwner("object", ownerId);
101255
+ if (eventScope === void 0) {
101256
+ deps.logger.error("object media read refused — NcEventMediaScope required", { meta: { ownerId } });
101257
+ return [];
101258
+ }
100907
101259
  const onCamera = await deps.media.listByOwner("object", ownerId, eventScope.deviceId);
100908
101260
  const kept = ofGeneration(onCamera, eventScope);
100909
101261
  if (kept.length < onCamera.length) deps.logger.warn("event media of another record on a reused event id — not used", {
@@ -101996,6 +102348,10 @@ var NC_ARTIFACT_PRUNE_INTERVAL_MS = 36e5;
101996
102348
  * (every camera switched off): the bound is 24 h, not "24 h after the next
101997
102349
  * close" (D681). */
101998
102350
  var TAPE_SWEEP_INTERVAL_MS = 36e5;
102351
+ /** How often open tape segments push their pending gzip output to disk
102352
+ * without rotating (D681 follow-up): a silent camera otherwise keeps its
102353
+ * tail only in the zlib buffer until the next frame or the 10-minute cut. */
102354
+ var TAPE_FLUSH_INTERVAL_MS = 6e4;
101999
102355
  /** What a publish answers on a node whose artifact plane never came up: no
102000
102356
  * link, no index row — and, exactly as before, bytes-only delivery. */
102001
102357
  var NO_PUBLICATION = {
@@ -102529,6 +102885,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102529
102885
  ncArtifactReadDataPlane = null;
102530
102886
  ncArtifactPruneTimer = null;
102531
102887
  tapeSweepTimer = null;
102888
+ tapeFlushTimer = null;
102532
102889
  /** Why `startTrackingTapes` threw on a node that was allowed to record —
102533
102890
  * reported `failing` per switched-on camera, never as `recording`. */
102534
102891
  tapeStartFailure = null;
@@ -104333,6 +104690,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
104333
104690
  StationaryObjectRegistry.declare(api.settingsStore),
104334
104691
  RetrainFrameStore.declare(api.settingsStore),
104335
104692
  RetrainAnnotationStore.declare(api.settingsStore),
104693
+ RetrainCollectionStore.declare(api.settingsStore),
104336
104694
  OpsLogStore.declare(api.settingsStore),
104337
104695
  DebugNoteArchiveStore.declare(api.settingsStore),
104338
104696
  TrackingTapeIndexStore.declare(api.settingsStore),
@@ -106781,6 +107139,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
106781
107139
  clearInterval(this.tapeSweepTimer);
106782
107140
  this.tapeSweepTimer = null;
106783
107141
  }
107142
+ if (this.tapeFlushTimer !== null) {
107143
+ clearInterval(this.tapeFlushTimer);
107144
+ this.tapeFlushTimer = null;
107145
+ }
106784
107146
  if (this.ncArtifactPruneTimer !== null) {
106785
107147
  clearInterval(this.ncArtifactPruneTimer);
106786
107148
  this.ncArtifactPruneTimer = null;
@@ -113554,10 +113916,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113554
113916
  });
113555
113917
  const scored = [];
113556
113918
  for (const { event, score } of bestEventByTrackId.values()) {
113919
+ const embeddingMediaId = event.trackId !== void 0 ? embeddingMediaIdByTrackId.get(event.trackId) : void 0;
113557
113920
  const mediaUrl = resolveSearchThumbnailUrl({
113558
113921
  baseUrl: this.eventMediaBaseUrl,
113559
113922
  eventId: String(event.id),
113560
- ...event.trackId !== void 0 && embeddingMediaIdByTrackId.has(event.trackId) ? { embeddingMediaId: embeddingMediaIdByTrackId.get(event.trackId) } : {}
113923
+ deviceId: event.deviceId,
113924
+ recordAt: event.timestamp,
113925
+ ...embeddingMediaId !== void 0 ? { embeddingMediaId } : {}
113561
113926
  });
113562
113927
  const keyFrameMediaId = event.trackId !== void 0 ? keyFrameMediaIdByTrackId.get(event.trackId) : void 0;
113563
113928
  scored.push({
@@ -113944,7 +114309,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113944
114309
  logger
113945
114310
  });
113946
114311
  const sweep = (deviceId) => {
113947
- shelf.sweep().catch((err) => {
114312
+ shelf.sweep().then((report) => {
114313
+ logTapeSweepReport(report, logger);
114314
+ }).catch((err) => {
113948
114315
  logger.warn("tracking tape sweep failed", {
113949
114316
  ...deviceId !== null ? { tags: { deviceId } } : {},
113950
114317
  meta: { error: errMsg(err) }
@@ -113976,6 +114343,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113976
114343
  sweep(null);
113977
114344
  this.tapeSweepTimer = setInterval(() => sweep(null), TAPE_SWEEP_INTERVAL_MS);
113978
114345
  this.tapeSweepTimer.unref?.();
114346
+ this.tapeFlushTimer = setInterval(() => {
114347
+ this.tapeRecorder?.flushOpenSegments();
114348
+ }, TAPE_FLUSH_INTERVAL_MS);
114349
+ this.tapeFlushTimer.unref?.();
113979
114350
  this.ctx.logger.info("tracking tapes ready", { meta: { seededProcessors: this.processorInputs.size } });
113980
114351
  } catch (err) {
113981
114352
  this.tapeStartFailure = errMsg(err);
@@ -114168,7 +114539,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114168
114539
  segments.set(deviceId, await this.tapeListingOf(shelf, deviceId));
114169
114540
  }
114170
114541
  const recorder = this.tapeRecorder;
114171
- return { cameras: describeTapeStatus({
114542
+ const cameras = describeTapeStatus({
114172
114543
  deviceIds,
114173
114544
  switchOn,
114174
114545
  switchUnreadable,
@@ -114177,7 +114548,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114177
114548
  startFailure: this.tapeStartFailure,
114178
114549
  segments,
114179
114550
  now: Date.now()
114180
- }) };
114551
+ });
114552
+ return { cameras: input.deviceIds === void 0 ? defaultTapeStatusCameras(cameras) : cameras };
114181
114553
  },
114182
114554
  "debug.tape.promote": async (input) => {
114183
114555
  const deps = this.tapePromotionDeps();
@@ -114626,7 +114998,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114626
114998
  * owner table, so the motion/object coin-flip is gone from here too.
114627
114999
  */
114628
115000
  async readMediaByAddress(address, variant, preferKind) {
114629
- const base = address.kind === "row" ? await this.readMediaById(address.mediaId) : await this.readOwnerThumbnail(address.ownerType, address.ownerId, preferKind);
115001
+ const base = address.kind === "row" ? await this.readMediaById(address.mediaId) : await this.readOwnerThumbnail(address.ownerType, address.ownerId, preferKind, address.generation);
114630
115002
  if (base === null || variant === void 0) return base;
114631
115003
  return this.applyThumbVariant(base, variant);
114632
115004
  }
@@ -114684,7 +115056,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114684
115056
  * ~144 MB and allocated ~190 MB of base64 to deliver ~1.2 MB, all of it on
114685
115057
  * the single-threaded post-analysis runner.
114686
115058
  */
114687
- async readOwnerThumbnail(ownerType, rawOwnerId, preferKind) {
115059
+ async readOwnerThumbnail(ownerType, rawOwnerId, preferKind, addressGeneration) {
114688
115060
  const ownerId = ownerType === "track" ? await resolveMediaOwnerId(ownerType, rawOwnerId, (id) => this.trackAliasStore?.resolve(id) ?? Promise.resolve(null), (failure) => this.aliasResolveFailureLogger?.(failure), "readOwnerThumbnail") : rawOwnerId;
114689
115061
  const listInfo = (ownerKind, id, deviceId) => this.mediaStore?.listInfoByOwner(ownerKind, id, deviceId) ?? Promise.resolve([]);
114690
115062
  const getTrackIdForEvent = (eventId) => isEventOwnerType(ownerType) ? this.eventStore?.getTrackIdForEvent(eventId, ownerType) ?? Promise.resolve(null) : Promise.resolve(null);
@@ -114698,8 +115070,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114698
115070
  };
114699
115071
  };
114700
115072
  let ownFiles = await listInfo(ownerType, ownerId);
114701
- if (isEventOwnerType(ownerType) && this.eventStore !== null) {
114702
- const gen = await this.eventStore.eventGenerationOf(ownerType, ownerId);
115073
+ if (isEventOwnerType(ownerType)) {
115074
+ const gen = addressGeneration !== void 0 ? {
115075
+ deviceId: addressGeneration.deviceId,
115076
+ timestamp: addressGeneration.recordAt
115077
+ } : this.eventStore !== null ? await this.eventStore.eventGenerationOf(ownerType, ownerId) : null;
114703
115078
  if (gen !== null) {
114704
115079
  const onCamera = await listInfo(ownerType, ownerId, gen.deviceId);
114705
115080
  const kept = onCamera.filter((f) => f.timestamp === gen.timestamp);
@@ -114711,7 +115086,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114711
115086
  recordAt: gen.timestamp,
114712
115087
  refused: onCamera.length - kept.length,
114713
115088
  kept: kept.length,
114714
- via: "readOwnerThumbnail"
115089
+ via: "readOwnerThumbnail",
115090
+ scopedBy: addressGeneration !== void 0 ? "url" : "live-row"
114715
115091
  }
114716
115092
  });
114717
115093
  ownFiles = kept;