@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.
@@ -1,4 +1,4 @@
1
- import { $ as NC_ALARM_SYSTEM_EVENT_KINDS, $n as occupancyScope, $r as partialRecord, $t as SCENE_DIVERGED, A as EMBEDDING_REQUIREMENTS_FAILED, An as embeddingRefusalOf, Ar as evaluateSensorEdge, At as NcScheduleSchema, B as ListTrackRelationsInputSchema, Bn as isDetectionMacroClass, Br as isDeviceScopedCap, Bt as OpsLogEntrySchema, C as DEFAULT_EVENT_COLOR, Cn as consumableLowPercentOf, Cr as systemEventSummarySplitOf, Ct as NcConditionDescriptorSchema, D as DeclaredDevices, Dn as describeSystemEventFamilyProblem, Dr as zoneAnalyticsCapability, Dt as NcRuleSchema, E as DETECTION_PIPELINE_CAP_NAME, En as describeSystemEventDeliveryProblem, Er as vectorDimFromBase64, Et as NcRulePatchSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as faceGalleryCapability, Fr as CamProfileSchema, Ft as NcTaxonomySchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as isScheduleActive, Gr as _enum, Gt as RetrainStatusSchema, H as LockStateSchema, Hn as isKnownTemplateVar, Ht as RECORDING_EXPORT_MAX_READ_BYTES, I as FIRST_LEVEL_MACRO_CLASSES, In as failureContributionCapability, Ir as createEvent, It as NcTemplatePreviewInputSchema, J as MOTION_CLOSE_AFTER_MS, Jn as isVectorIndexDimConflict, Jr as discriminatedUnion, Jt as SCENE_DEFAULT_ANCHOR_THRESHOLD, K as MAX_TRACK_PURITIES, Kn as isSystemDelivery, Kr as array, Kt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, L as FULL_IMAGE_BBOX, Ln as gifDeriveSpeed, Lr as customAction, Lt as NcTemplatePreviewSchema, M as EVENT_DENSITY_BATCH_MAX, Mr as DeviceType, Mt as NcSnoozeSchema, N as EVENT_KIND_BY_CAP, Nn as encodeVectorBase64, Nr as errMsg, Nt as NcSnoozeSuppressedSchema, O as DetectionSourceSchema, Or as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Ot as NcRuleTargetSchema, P as EVENT_OWNER_TYPES, Pn as evictionPolicyOfLocation, Pr as BaseAddon, Pt as NcSystemEventKindSchema, Q as NC_ALARM_DELAY_MAX_SEC, Qn as notificationRulesCapability, Qr as object, Qt as SCENE_DEFAULT_UNCOVERED_POLICY, R as FailureCounters, Rn as gifFromMp4Args, Rr as defineCustomActions, Rt as NcTemplateVarDescriptorSchema, S as CoverStateSchema, Sn as buildEventKindDescriptor, Sr as systemEventSummaryScheduleOf, St as NcAlarmSettingsPatchSchema, T as DETECTION_MACRO_CLASSES, Tn as deriveRecordingMode, Tr as templateVarsFor, Tt as NcRuleInputSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as isMediaOwnerType, Ur as sleep$1, Ut as RECORDING_SOURCE_CAMSTACK_ADDON, V as ListTrackRelationsResultSchema, Vn as isEventOwnerType, Vr as nodePin, W as MAX_BIRTH_DECISION_RECORDS, Wn as isRetiredClearKey, Wr as ZodObject, Wt as RebuildObjectEmbeddingsRefusalSchema, X as MotionTriggeredBySchema, Xn as mayWriteToLocation, Xr as looseObject, Xt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, Y as MediaFileKindEnum, Yn as kebabToCamel, Yr as literal, Yt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, Z as NC_ALARM_DELAY_FIELDS, Zn as memoizePinnedFfmpeg, Zr as number, Zt as SCENE_DEFAULT_QUIET_SECONDS, _ as BaseDevice, _n as applyModeDelaysEdits, _r as storageOccupancyCapability, _t as NC_TAXONOMY, a as CLIP_EMBEDDING_STEP_ID, ai as union, an as TIMELAPSE_DENSE_FLOOR_SEC, ar as readTimelapseGeneratedAt, at as NC_CLIP_RETENTION_MAX_SEC, b as ClearTrackRelationInputSchema, bn as audioMetricsCapability, br as systemEventFamilyProblems, bt as NcAlarmSensorsInputSchema, c as resolveClusterModelPin, cn as TimelapseRulePatchSchema, cr as resolveModeDelays, ct as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as TrackRelationKindSchema, dr as ruleDeviceCompatCapQueries, dt as NC_GROUPING_MAX_SEC, ei as record, en as SCENE_RESET_RECAPTURES, et as NC_ALARM_TRIGGERED_MAX_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as TrackRelationSchema, fr as ruleGroupingApplies, ft as NC_GROUPING_MIN_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as alarmPanelCapability, gr as servedTemplateVars, gt as NC_TANK_SYSTEM_EVENT_KINDS, h as ArchivedDebugNoteSchema, hn as addonWidgetsSourceCapability, hr as sensorRaisedOf, ht as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, ii as tuple, in as SetTrackRelationInputSchema, ir as readMotionTriggeredBy, it as NC_CLIP_PRE_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jr as isSourceCap, jt as NcSnoozeInputSchema, k as EMBEDDING_MODEL_FAILED, kr as canonicalHash, kt as NcRuleUpdateInputSchema, l as APPEARANCE_BIN_COUNT, ln as TimelapseRuleSchema, lt as NC_DEFAULT_SNOOZE_MINUTES, m as AlarmArmModeSchema, mn as TrackSourceSchema, mr as sceneMonitorCapability, mt as NC_SNOOZE_MAX_MINUTES, ni as string, nn as SetTrackPurityInputSchema, nr as plateGalleryCapability, nt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as unknown, on as TRACK_RELATIONS_DEFAULT_LIMIT, or as resolveClipSearchMinScore, ot as NC_CONDITION_CATALOG, p as AUDIO_MACRO_LABELS, pn as TrackSideSchema, pr as ruleKindOf, pt as NC_SENSOR_TRANSITION_KINDS, q as MAX_TRACK_RELATIONS, qn as isSystemEventSummaryRule, qr as boolean, qt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ri as templateLiteral, rn as SetTrackPurityResultSchema, rr as readDeviceStateFrom, rt as NC_CLIP_POST_ROLL_DEFAULT_SEC, s as readClusterModelPinStrict, si as EventCategory, sn as TimelapseRuleInputSchema, sr as resolveLocationMode, st as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as strictObject, tn as SceneMonitorSchema, tr as pipelineAnalyticsCapability, tt as NC_AUDIO_CONFIRM_HITS_DEFAULT, u as APPEARANCE_BLOCKS, un as TrackPuritySchema, ur as ruleBelongsToAlarm$1, ut as NC_GROUPING_DEFAULT_SEC, v as BirthDecisionRecordSchema, vn as applyNonBlockingEdit, vr as subKindsOf, vt as NC_TEMPLATE_VARS, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as cosineSimilarity$1, wr as templateCarriesSummary, wt as NcConditionsSchema, x as ClearTrackRelationResultSchema, xn as audioModeOf, xr as systemEventFilterApplies, xt as NcAlarmSensorsSchema, y as COCO_TO_MACRO, yn as assertTimelapseCadences, yr as systemEventDeliveryProblems, yt as NcAlarmConfigSchema, z as LabelAttributionSchema, zn as isAudioClassName, zr as hydrateSchema, zt as OPS_LOG_DEFAULT_LIMIT } from "../clip-model-registry-60cLfjwb.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";
@@ -16988,7 +16988,7 @@ var NcStateSummaryTestSchema = object({
16988
16988
  /** Nothing open and every section read: nothing was enqueued. */
16989
16989
  nothingToReport: boolean(),
16990
16990
  sections: array(object({
16991
- id: string(),
16991
+ id: NcStateSummarySectionIdSchema,
16992
16992
  status: _enum(["read", "unavailable"]),
16993
16993
  items: number().int().nonnegative(),
16994
16994
  reason: string().optional()
@@ -16996,7 +16996,7 @@ var NcStateSummaryTestSchema = object({
16996
16996
  rows: array(object({
16997
16997
  recordId: string(),
16998
16998
  /** The section of a per-section row. */
16999
- section: string().optional(),
16999
+ section: NcStateSummarySectionIdSchema.optional(),
17000
17000
  enqueued: number().int().nonnegative(),
17001
17001
  targetIds: array(string())
17002
17002
  }))
@@ -20707,17 +20707,8 @@ var NcDeviceStateCache = class {
20707
20707
  * a job that COMPLETED, and the state it leaves behind is not a condition that
20708
20708
  * is still true. The ADR records why it is excluded rather than approximated.
20709
20709
  */
20710
- var NC_STATE_SUMMARY_SECTION_IDS = [
20711
- "device-state",
20712
- "streams",
20713
- "battery",
20714
- "consumables",
20715
- "tanks",
20716
- "detection-blind",
20717
- "nodes",
20718
- "addons",
20719
- "updates"
20720
- ];
20710
+ /** One source: the cap's Zod enum (D698 LOW-6). */
20711
+ var NC_STATE_SUMMARY_SECTION_IDS = NcStateSummarySectionIdSchema.options;
20721
20712
  /** The kind groups that have no section, and why — said on the preview. */
20722
20713
  var NC_STATE_SUMMARY_EXCLUDED_GROUPS = { "backups-exports": "a backup or an export is a completed job, not a state that is still true" };
20723
20714
  function isStateSectionId(value) {
@@ -35186,10 +35177,10 @@ var NcStateSummaryService = class {
35186
35177
  const rows = [];
35187
35178
  for (const subject of subjects) {
35188
35179
  const enqueued = await this.deps.enqueue(rule, subject, "synthetic");
35189
- const section = subject.systemEvent?.data["section"];
35180
+ const section = stateSummarySectionOf(subject);
35190
35181
  rows.push({
35191
35182
  recordId: subject.recordId,
35192
- ...typeof section === "string" ? { section } : {},
35183
+ ...section !== void 0 ? { section } : {},
35193
35184
  enqueued: enqueued.inserted,
35194
35185
  targetIds: [...enqueued.targetIds]
35195
35186
  });
@@ -51329,6 +51320,11 @@ var MEDIA_ID_SEGMENT = "m";
51329
51320
  /** Path segment that introduces an OWNER question ("best media of"). */
51330
51321
  var MEDIA_OWNER_SEGMENT = "o";
51331
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
+ /**
51332
51328
  * The plane path for one media row.
51333
51329
  *
51334
51330
  * `prefix` is `/addon/<addonId>/event-media` — a pure function of the addon
@@ -51345,6 +51341,23 @@ function mediaFileUrl(prefix, mediaId) {
51345
51341
  return `${prefix}/${MEDIA_ID_SEGMENT}/${mediaId}`;
51346
51342
  }
51347
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
+ /**
51348
51361
  * A stored row as the wire sees it: the ref, the address, and — transitionally
51349
51362
  * — the bytes.
51350
51363
  *
@@ -59195,6 +59208,12 @@ function openGzipStream(out) {
59195
59208
  onceDrain: (listener) => {
59196
59209
  gzip.once("drain", listener);
59197
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
+ }),
59198
59217
  finish: async () => {
59199
59218
  gzip.end();
59200
59219
  await closed;
@@ -59290,6 +59309,25 @@ var TapeSegmentWriter = class {
59290
59309
  });
59291
59310
  }
59292
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
+ /**
59293
59331
  * Always ends the segment's records (queue flush, lost-span gap, `end`
59294
59332
  * record) before touching the sink, so a sink that cannot finish still
59295
59333
  * leaves a decodable body behind it. `sink.finish()`'s rejection is caught
@@ -59616,6 +59654,21 @@ var TapeRecorder = class {
59616
59654
  if (cam !== void 0) await this.closeSegment(deviceId, cam, "segment-end");
59617
59655
  }
59618
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
+ /**
59619
59672
  * Close the camera's open segment only if it holds time at or before
59620
59673
  * `atMs` — what a promotion of a window ending at `atMs` needs on disk
59621
59674
  * (Task 12 fix round, H1). A window wholly before the open segment leaves
@@ -60115,6 +60168,29 @@ var TapeShelf = class {
60115
60168
  };
60116
60169
  }
60117
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
+ }
60118
60194
  //#endregion
60119
60195
  //#region src/pipeline-analytics/tracking-bench/recorder/tape-operation-queue.ts
60120
60196
  /** Generous: a promotion reads its window's segments and walks back through
@@ -61168,6 +61244,17 @@ function shelfFigures(listing, now) {
61168
61244
  ...last !== void 0 ? { heldToMs: last.originMs } : {}
61169
61245
  };
61170
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
+ }
61171
61258
  function describeTapeStatus(input) {
61172
61259
  return input.deviceIds.map((deviceId) => {
61173
61260
  const shelf = {
@@ -66078,6 +66165,51 @@ var EventStore = class {
66078
66165
  return found;
66079
66166
  }
66080
66167
  /**
66168
+ * The GENERATION of each live event in `table` among `eventIds` — its camera
66169
+ * and its own timestamp (D694). Same throw-on-failure contract as
66170
+ * {@link existingEventIdsIn}: a quiet under-report here would let the orphan
66171
+ * audit keep treating another camera's leftovers as owned.
66172
+ */
66173
+ async eventGenerationsIn(table, eventIds) {
66174
+ const collection = EVENT_COLLECTION_BY_TABLE[table];
66175
+ const found = /* @__PURE__ */ new Map();
66176
+ for (let i = 0; i < eventIds.length; i += PRUNE_PAGE_SIZE) {
66177
+ const chunk = eventIds.slice(i, i + PRUNE_PAGE_SIZE);
66178
+ if (chunk.length === 0) continue;
66179
+ const rows = await this.store.query.query({
66180
+ collection,
66181
+ columns: [
66182
+ "id",
66183
+ "deviceId",
66184
+ "timestamp"
66185
+ ],
66186
+ filter: {
66187
+ whereIn: { id: [...chunk] },
66188
+ limit: chunk.length
66189
+ }
66190
+ });
66191
+ for (const row of rows) {
66192
+ const deviceId = row.data["deviceId"];
66193
+ const timestamp = row.data["timestamp"];
66194
+ if (typeof deviceId !== "number" || !Number.isFinite(deviceId)) continue;
66195
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) continue;
66196
+ found.set(row.id, {
66197
+ deviceId,
66198
+ timestamp
66199
+ });
66200
+ }
66201
+ }
66202
+ return found;
66203
+ }
66204
+ /**
66205
+ * One event's generation, or `null` when the id is not live in `table`.
66206
+ * Used by the `/o/<eventOwner>/<id>` plane ladder to scope media to the
66207
+ * current holder (D694).
66208
+ */
66209
+ async eventGenerationOf(table, eventId) {
66210
+ return (await this.eventGenerationsIn(table, [eventId])).get(eventId) ?? null;
66211
+ }
66212
+ /**
66081
66213
  * The SUBSET of `eventIds` that are MOTION events specifically.
66082
66214
  *
66083
66215
  * {@link existingEventIds} answers "is this event alive", which is the
@@ -72981,7 +73113,7 @@ var AnalyticsQueryFacade = class {
72981
73113
  async getMotionEvents(input) {
72982
73114
  const events = await (this.deps.eventStore()?.queryMotion(input) ?? Promise.resolve([]));
72983
73115
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
72984
- return this.withMediaUrl(events);
73116
+ return this.withMediaUrl(events, "motion");
72985
73117
  }
72986
73118
  async getEventDensity(input) {
72987
73119
  return this.deps.eventStore()?.densityByKind(input) ?? [];
@@ -73038,12 +73170,12 @@ var AnalyticsQueryFacade = class {
73038
73170
  async getObjectEvents(input) {
73039
73171
  const events = await (this.deps.eventStore()?.queryObject(input) ?? Promise.resolve([]));
73040
73172
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73041
- return this.withMediaUrl(events);
73173
+ return this.withMediaUrl(events, "object");
73042
73174
  }
73043
73175
  async getAudioEvents(input) {
73044
73176
  const events = await (this.deps.eventStore()?.queryAudio(input) ?? Promise.resolve([]));
73045
73177
  if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
73046
- return this.withMediaUrl(events);
73178
+ return this.withMediaUrl(events, "audio");
73047
73179
  }
73048
73180
  async getEventMedia(input) {
73049
73181
  const store = this.deps.mediaStore();
@@ -73310,13 +73442,20 @@ var AnalyticsQueryFacade = class {
73310
73442
  *
73311
73443
  * Only called when `eventMediaBaseUrl()` is non-null (guard sits in
73312
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/…`.
73313
73449
  */
73314
- withMediaUrl(events) {
73450
+ withMediaUrl(events, ownerType) {
73315
73451
  const base = this.deps.eventMediaBaseUrl();
73316
73452
  if (base === null) return events;
73317
73453
  return events.map((e) => ({
73318
73454
  ...e,
73319
- mediaUrl: `${base}/${e.id}`
73455
+ mediaUrl: mediaOwnerUrl(base, ownerType, String(e.id), {
73456
+ deviceId: e.deviceId,
73457
+ recordAt: e.timestamp
73458
+ })
73320
73459
  }));
73321
73460
  }
73322
73461
  };
@@ -76148,18 +76287,26 @@ function eventMediaEtag(media) {
76148
76287
  }
76149
76288
  /** Digits only — `Number('')` is 0 and `Number(' ')` is 0, so neither is used. */
76150
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}))?$`);
76151
76298
  /**
76152
76299
  * Read a plane path. `null` for anything that is not one of the two forms —
76153
76300
  * including the BARE id the plane used to accept, which is gone: a bare number
76154
76301
  * could be a media id or an event id, and after D474 an event id could be one
76155
76302
  * of three rows. Ambiguity at the door is what this record removes.
76156
76303
  *
76157
- * The owner id is taken as the whole remainder rather than one segment: the
76158
- * hub's wildcard param is percent-decoded by find-my-way before the sub-path
76159
- * is proxied, so a `%2F` an owner id carried would arrive here as a real
76160
- * slash, and re-cutting on it would silently address a different owner. It is
76161
- * only ever a DB query VALUE (`where: { ownerId }`) — never a filesystem path,
76162
- * 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).
76163
76310
  */
76164
76311
  function parseMediaAddress(rawPath) {
76165
76312
  const path = rawPath.replace(/^\/+/, "");
@@ -76179,13 +76326,35 @@ function parseMediaAddress(rawPath) {
76179
76326
  const typeSlash = rest.indexOf("/");
76180
76327
  if (typeSlash <= 0) return null;
76181
76328
  const ownerType = rest.slice(0, typeSlash);
76182
- const ownerId = rest.slice(typeSlash + 1);
76183
- if (ownerId.length === 0) return null;
76329
+ const remainder = rest.slice(typeSlash + 1);
76330
+ if (remainder.length === 0) return null;
76184
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
+ }
76185
76354
  return {
76186
76355
  kind: "owner",
76187
76356
  ownerType,
76188
- ownerId
76357
+ ownerId: remainder
76189
76358
  };
76190
76359
  }
76191
76360
  /** Set by the HUB for a `csv_*` share token admitted on this plane; never by a client. */
@@ -78862,6 +79031,218 @@ function createRetrainExportHandler(deps) {
78862
79031
  };
78863
79032
  }
78864
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
78865
79246
  //#region src/pipeline-analytics/retrain/retrain-assist.ts
78866
79247
  /** The whole frame, as a normalised rectangle. */
78867
79248
  var WHOLE_FRAME = {
@@ -82271,7 +82652,8 @@ async function rankKeyEvents(candidates, options, peakLookup) {
82271
82652
  ...t.markForTrain !== void 0 ? { markForTrain: t.markForTrain } : {},
82272
82653
  ...t.retrainStatus !== void 0 ? { retrainStatus: t.retrainStatus } : {},
82273
82654
  ...t.debug !== void 0 ? { debug: t.debug } : {},
82274
- ...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 } : {}
82275
82657
  });
82276
82658
  }
82277
82659
  scored.sort((a, b) => b.importance - a.importance);
@@ -82698,16 +83080,19 @@ var MotionOnsetRegistry = class {
82698
83080
  * - `resolveSearchThumbnailUrl` — the search-hit thumbnail resolver. It points
82699
83081
  * a hit at the CLIP embedding row's OWN crop (the crop captured on the
82700
83082
  * embedding's peak frame → the hit thumbnail IS the embedded crop), and only
82701
- * falls back to the representative event's crop when the embedding row has no
82702
- * 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.
82703
83085
  */
82704
83086
  function isClipObjectEmbedding(t, registry = BUILTIN_CLIP_MODELS) {
82705
83087
  return Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && registry.resolve(t.embeddingModelId) !== null;
82706
83088
  }
82707
83089
  function resolveSearchThumbnailUrl(input) {
82708
83090
  if (input.baseUrl === null) return void 0;
82709
- const id = input.embeddingMediaKey ?? input.eventId;
82710
- 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
+ });
82711
83096
  }
82712
83097
  //#endregion
82713
83098
  //#region src/pipeline-analytics/pipeline/onboard-birth-authority.ts
@@ -93397,6 +93782,7 @@ async function auditScope(scope, deps, limits, cursor) {
93397
93782
  const { trackIds, eventIdsByTable } = questionsFor(refs);
93398
93783
  let liveTracks;
93399
93784
  const liveEvents = /* @__PURE__ */ new Map();
93785
+ const eventGens = /* @__PURE__ */ new Map();
93400
93786
  let keptMedia = /* @__PURE__ */ new Set();
93401
93787
  try {
93402
93788
  liveTracks = trackIds.length > 0 ? await deps.liveTrackIds(trackIds) : /* @__PURE__ */ new Set();
@@ -93404,6 +93790,7 @@ async function auditScope(scope, deps, limits, cursor) {
93404
93790
  for (const [table, ids] of eventIdsByTable) {
93405
93791
  if (ids.length === 0) continue;
93406
93792
  liveEvents.set(table, await deps.liveEventIds(table, ids));
93793
+ if (deps.eventGenerations !== void 0 && scope.collection === MEDIA_COLLECTION) eventGens.set(table, await deps.eventGenerations(table, ids));
93407
93794
  }
93408
93795
  } catch (err) {
93409
93796
  deps.logger.warn("orphan audit: liveness probe failed — scope abandoned for this run", { meta: {
@@ -93432,7 +93819,13 @@ async function auditScope(scope, deps, limits, cursor) {
93432
93819
  tally.unresolvable += 1;
93433
93820
  continue;
93434
93821
  }
93435
- 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;
93822
+ 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;
93823
+ if (live && ref.kind === "event" && scope.collection === MEDIA_COLLECTION) {
93824
+ const gen = eventGens.get(ref.table)?.get(ref.eventId);
93825
+ const mediaDeviceId = num(row.data, scope.deviceIdField);
93826
+ if (gen !== void 0 && mediaDeviceId !== null && mediaDeviceId !== gen.deviceId) live = false;
93827
+ }
93828
+ if (live) continue;
93436
93829
  const ts = num(row.data, scope.timestampField);
93437
93830
  if (ts !== null && ts > graceFloor) {
93438
93831
  tally.withinGrace += 1;
@@ -93447,7 +93840,7 @@ async function auditScope(scope, deps, limits, cursor) {
93447
93840
  break;
93448
93841
  }
93449
93842
  try {
93450
- const outcome = await deps.reclaim(scope.collection, row.key);
93843
+ const outcome = await deps.reclaim(scope.collection, row.key, row);
93451
93844
  if (outcome.rows === 0) continue;
93452
93845
  tally.reclaimed += 1;
93453
93846
  reclaimedInPage += 1;
@@ -93603,7 +93996,10 @@ var ORPHAN_AUDIT_DEFAULTS = {
93603
93996
  intervalHours: OrphanAuditIntervalHoursSchema.parse(void 0),
93604
93997
  pageSize: OrphanAuditPageSizeSchema.parse(void 0)
93605
93998
  };
93606
- var RAW_DELETE_COLLECTIONS = new Set(["pipeline-analytics:object-events", "pipeline-analytics:plates"]);
93999
+ //#endregion
94000
+ //#region src/pipeline-analytics/retention/orphan-reclaimers.ts
94001
+ var OBJECT_EVENTS_COLLECTION = "pipeline-analytics:object-events";
94002
+ var RAW_DELETE_COLLECTIONS = new Set([OBJECT_EVENTS_COLLECTION, "pipeline-analytics:plates"]);
93607
94003
  /**
93608
94004
  * The `deleteRow` port: ONE row by its primary key `id` (what every
93609
94005
  * raw-delete collection above declares), answering whether a row went.
@@ -93612,12 +94008,19 @@ var RAW_DELETE_COLLECTIONS = new Set(["pipeline-analytics:object-events", "pipel
93612
94008
  * is `z.void()`, and reading it as `!== undefined` answered `false` for every
93613
94009
  * row it really deleted — the audit counted every plates/object-events reclaim
93614
94010
  * as "found nothing", and the per-scope reclaim ceiling never tripped.
94011
+ *
94012
+ * When `assert.trackId` is set, the delete also requires that column: an
94013
+ * object-events rowid alone is not an identity across the row's life (D694).
93615
94014
  */
93616
94015
  function settingsRowDeleter(store) {
93617
- return async (collection, key) => {
94016
+ return async (collection, key, assert) => {
94017
+ const where = assert !== void 0 ? {
94018
+ id: key,
94019
+ trackId: assert.trackId
94020
+ } : { id: key };
93618
94021
  const { deleted } = await store.deleteWhere.mutate({
93619
94022
  collection,
93620
- filter: { where: { id: key } }
94023
+ filter: { where }
93621
94024
  });
93622
94025
  return deleted > 0;
93623
94026
  };
@@ -93632,14 +94035,19 @@ function settingsRowDeleter(store) {
93632
94035
  * `scripts/check-orphan-reclaimers.ts` checks `index.ts` uses it.
93633
94036
  */
93634
94037
  function settingsRowDeleterOf(getStore) {
93635
- return async (collection, key) => {
94038
+ return async (collection, key, assert) => {
93636
94039
  const store = getStore();
93637
94040
  if (store === null) throw new Error("orphan audit: settings store not bound");
93638
- return await settingsRowDeleter(store)(collection, key);
94041
+ return await settingsRowDeleter(store)(collection, key, assert);
93639
94042
  };
93640
94043
  }
94044
+ /** Pull the trackId the raw delete must re-assert, or `null` when absent. */
94045
+ function trackIdAssertOf(data) {
94046
+ const trackId = data["trackId"];
94047
+ return typeof trackId === "string" && trackId.length > 0 ? { trackId } : null;
94048
+ }
93641
94049
  function buildOrphanReclaimer(ports) {
93642
- return async (collection, key) => {
94050
+ return async (collection, key, data = {}) => {
93643
94051
  if (collection === MEDIA_COLLECTION) {
93644
94052
  const mediaId = Number(key);
93645
94053
  if (!Number.isInteger(mediaId) || mediaId <= 0) {
@@ -93665,10 +94073,23 @@ function buildOrphanReclaimer(ports) {
93665
94073
  rows: await ports.deleteTrackAlias(key) ? 1 : 0,
93666
94074
  bytes: null
93667
94075
  };
93668
- if (RAW_DELETE_COLLECTIONS.has(collection)) return {
93669
- rows: await ports.deleteRow(collection, key) ? 1 : 0,
93670
- bytes: null
93671
- };
94076
+ if (RAW_DELETE_COLLECTIONS.has(collection)) {
94077
+ const assert = trackIdAssertOf(data);
94078
+ if (collection === OBJECT_EVENTS_COLLECTION && assert === null) {
94079
+ ports.logger.warn("orphan audit: object-events reclaim without trackId — row kept (id-only delete refused)", { meta: {
94080
+ collection,
94081
+ key
94082
+ } });
94083
+ return {
94084
+ rows: 0,
94085
+ bytes: null
94086
+ };
94087
+ }
94088
+ return {
94089
+ rows: await ports.deleteRow(collection, key, assert === null ? void 0 : assert) ? 1 : 0,
94090
+ bytes: null
94091
+ };
94092
+ }
93672
94093
  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`);
93673
94094
  };
93674
94095
  }
@@ -100831,7 +101252,10 @@ function ofGeneration(files, scope) {
100831
101252
  function makeNcOwnerMediaReader(deps) {
100832
101253
  return async (ownerKind, ownerId, eventScope) => {
100833
101254
  if (ownerKind === "track") return deps.media.listByOwner("track", await deps.resolveTrackOwnerId(ownerId));
100834
- 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
+ }
100835
101259
  const onCamera = await deps.media.listByOwner("object", ownerId, eventScope.deviceId);
100836
101260
  const kept = ofGeneration(onCamera, eventScope);
100837
101261
  if (kept.length < onCamera.length) deps.logger.warn("event media of another record on a reused event id — not used", {
@@ -101068,8 +101492,14 @@ async function buildTrainingExportPlan(readers, input) {
101068
101492
  const eventsByTrack = /* @__PURE__ */ new Map();
101069
101493
  for (const link of eventLinks) {
101070
101494
  const list = eventsByTrack.get(link.trackId);
101071
- if (list) list.push(link.eventId);
101072
- else eventsByTrack.set(link.trackId, [link.eventId]);
101495
+ if (list) list.push({
101496
+ eventId: link.eventId,
101497
+ recordAt: link.recordAt
101498
+ });
101499
+ else eventsByTrack.set(link.trackId, [{
101500
+ eventId: link.eventId,
101501
+ recordAt: link.recordAt
101502
+ }]);
101073
101503
  }
101074
101504
  for (const track of take) {
101075
101505
  const files = [];
@@ -101089,15 +101519,17 @@ async function buildTrainingExportPlan(readers, input) {
101089
101519
  ownerId: `${PLATE_OWNER_PREFIX}${track.trackId}`,
101090
101520
  scope: "plate"
101091
101521
  },
101092
- ...(eventsByTrack.get(track.trackId) ?? []).map((eventId) => ({
101522
+ ...(eventsByTrack.get(track.trackId) ?? []).map((ev) => ({
101093
101523
  ownerKind: "object",
101094
- ownerId: eventId,
101095
- scope: `events/${eventId}`
101524
+ ownerId: ev.eventId,
101525
+ scope: `events/${ev.eventId}`,
101526
+ recordAt: ev.recordAt
101096
101527
  }))
101097
101528
  ];
101098
101529
  for (const owner of owners) {
101099
- const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId);
101100
- for (const info of infos) {
101530
+ const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId, owner.ownerKind === "object" ? track.deviceId : void 0);
101531
+ const kept = owner.recordAt === void 0 ? infos : infos.filter((info) => info.timestamp === owner.recordAt);
101532
+ for (const info of kept) {
101101
101533
  const archivePath = archivePathFor({
101102
101534
  deviceId: track.deviceId,
101103
101535
  trackId: track.trackId,
@@ -101916,6 +102348,10 @@ var NC_ARTIFACT_PRUNE_INTERVAL_MS = 36e5;
101916
102348
  * (every camera switched off): the bound is 24 h, not "24 h after the next
101917
102349
  * close" (D681). */
101918
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;
101919
102355
  /** What a publish answers on a node whose artifact plane never came up: no
101920
102356
  * link, no index row — and, exactly as before, bytes-only delivery. */
101921
102357
  var NO_PUBLICATION = {
@@ -102449,6 +102885,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102449
102885
  ncArtifactReadDataPlane = null;
102450
102886
  ncArtifactPruneTimer = null;
102451
102887
  tapeSweepTimer = null;
102888
+ tapeFlushTimer = null;
102452
102889
  /** Why `startTrackingTapes` threw on a node that was allowed to record —
102453
102890
  * reported `failing` per switched-on camera, never as `recording`. */
102454
102891
  tapeStartFailure = null;
@@ -104253,6 +104690,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
104253
104690
  StationaryObjectRegistry.declare(api.settingsStore),
104254
104691
  RetrainFrameStore.declare(api.settingsStore),
104255
104692
  RetrainAnnotationStore.declare(api.settingsStore),
104693
+ RetrainCollectionStore.declare(api.settingsStore),
104256
104694
  OpsLogStore.declare(api.settingsStore),
104257
104695
  DebugNoteArchiveStore.declare(api.settingsStore),
104258
104696
  TrackingTapeIndexStore.declare(api.settingsStore),
@@ -105608,15 +106046,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
105608
106046
  if (!trackStore || !mediaStore) throw new Error("training export: analytics stores unavailable");
105609
106047
  return buildTrainingExportPlan({
105610
106048
  listMarkedTracks: (deviceId, limit) => trackStore.listStaging(deviceId, limit),
105611
- listMediaInfo: async (ownerKind, ownerId) => {
106049
+ listMediaInfo: async (ownerKind, ownerId, deviceId) => {
105612
106050
  if (!isMediaOwnerKind(ownerKind)) return [];
105613
- return mediaStore.listInfoByOwner(ownerKind, ownerId);
106051
+ return mediaStore.listInfoByOwner(ownerKind, ownerId, deviceId);
105614
106052
  },
105615
106053
  listEventIdsForTracks: async (trackIds) => {
105616
106054
  if (!eventStore || trackIds.length === 0) return [];
105617
106055
  return (await eventStore.queryObjectByTrackIds(trackIds)).flatMap((e) => e.trackId === void 0 ? [] : [{
105618
106056
  eventId: String(e.id),
105619
- trackId: e.trackId
106057
+ trackId: e.trackId,
106058
+ recordAt: e.timestamp
105620
106059
  }]);
105621
106060
  },
105622
106061
  now: () => Date.now()
@@ -106700,6 +107139,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
106700
107139
  clearInterval(this.tapeSweepTimer);
106701
107140
  this.tapeSweepTimer = null;
106702
107141
  }
107142
+ if (this.tapeFlushTimer !== null) {
107143
+ clearInterval(this.tapeFlushTimer);
107144
+ this.tapeFlushTimer = null;
107145
+ }
106703
107146
  if (this.ncArtifactPruneTimer !== null) {
106704
107147
  clearInterval(this.ncArtifactPruneTimer);
106705
107148
  this.ncArtifactPruneTimer = null;
@@ -111817,26 +112260,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
111817
112260
  },
111818
112261
  liveTrackIds: (ids) => this.liveTrackIdsThroughAliases(ids, trackStore),
111819
112262
  liveEventIds: (table, ids) => eventStore.existingEventIdsIn(table, ids),
112263
+ eventGenerations: (table, ids) => eventStore.eventGenerationsIn(table, ids),
111820
112264
  keptMediaKeys: async (keys) => this.keptStationaryMediaKeys(keys),
111821
- reclaim: buildOrphanReclaimer({
111822
- deleteFace: async (faceId) => {
111823
- await this.faceStore?.delete(faceId);
111824
- },
111825
- deleteMediaConfirmed: async (mediaId) => {
111826
- const count = await mediaStore.deleteConfirmed(mediaId);
111827
- return {
111828
- rows: count.rows,
111829
- bytes: count.bytes
111830
- };
111831
- },
111832
- deleteTrackAlias: async (aliasId) => {
111833
- const aliasStore = this.trackAliasStore;
111834
- if (aliasStore === null) throw new Error("track alias store not bound");
111835
- return await aliasStore.deleteAlias(aliasId);
111836
- },
111837
- deleteRow: settingsRowDeleterOf(() => this.settingsStore),
111838
- logger: this.ctx.logger
111839
- }),
112265
+ reclaim: (() => {
112266
+ const reclaimOne = buildOrphanReclaimer({
112267
+ deleteFace: async (faceId) => {
112268
+ await this.faceStore?.delete(faceId);
112269
+ },
112270
+ deleteMediaConfirmed: async (mediaId) => {
112271
+ const count = await mediaStore.deleteConfirmed(mediaId);
112272
+ return {
112273
+ rows: count.rows,
112274
+ bytes: count.bytes
112275
+ };
112276
+ },
112277
+ deleteTrackAlias: async (aliasId) => {
112278
+ const aliasStore = this.trackAliasStore;
112279
+ if (aliasStore === null) throw new Error("track alias store not bound");
112280
+ return await aliasStore.deleteAlias(aliasId);
112281
+ },
112282
+ deleteRow: settingsRowDeleterOf(() => this.settingsStore),
112283
+ logger: this.ctx.logger
112284
+ });
112285
+ return (collection, key, row) => reclaimOne(collection, key, row.data);
112286
+ })(),
111840
112287
  now: () => Date.now(),
111841
112288
  logger: this.ctx.logger,
111842
112289
  breathe: () => new Promise((resolve) => setImmediate(resolve))
@@ -113469,10 +113916,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113469
113916
  });
113470
113917
  const scored = [];
113471
113918
  for (const { event, score } of bestEventByTrackId.values()) {
113919
+ const embeddingMediaId = event.trackId !== void 0 ? embeddingMediaIdByTrackId.get(event.trackId) : void 0;
113472
113920
  const mediaUrl = resolveSearchThumbnailUrl({
113473
113921
  baseUrl: this.eventMediaBaseUrl,
113474
113922
  eventId: String(event.id),
113475
- ...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 } : {}
113476
113926
  });
113477
113927
  const keyFrameMediaId = event.trackId !== void 0 ? keyFrameMediaIdByTrackId.get(event.trackId) : void 0;
113478
113928
  scored.push({
@@ -113859,7 +114309,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113859
114309
  logger
113860
114310
  });
113861
114311
  const sweep = (deviceId) => {
113862
- shelf.sweep().catch((err) => {
114312
+ shelf.sweep().then((report) => {
114313
+ logTapeSweepReport(report, logger);
114314
+ }).catch((err) => {
113863
114315
  logger.warn("tracking tape sweep failed", {
113864
114316
  ...deviceId !== null ? { tags: { deviceId } } : {},
113865
114317
  meta: { error: errMsg(err) }
@@ -113891,6 +114343,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113891
114343
  sweep(null);
113892
114344
  this.tapeSweepTimer = setInterval(() => sweep(null), TAPE_SWEEP_INTERVAL_MS);
113893
114345
  this.tapeSweepTimer.unref?.();
114346
+ this.tapeFlushTimer = setInterval(() => {
114347
+ this.tapeRecorder?.flushOpenSegments();
114348
+ }, TAPE_FLUSH_INTERVAL_MS);
114349
+ this.tapeFlushTimer.unref?.();
113894
114350
  this.ctx.logger.info("tracking tapes ready", { meta: { seededProcessors: this.processorInputs.size } });
113895
114351
  } catch (err) {
113896
114352
  this.tapeStartFailure = errMsg(err);
@@ -114083,7 +114539,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114083
114539
  segments.set(deviceId, await this.tapeListingOf(shelf, deviceId));
114084
114540
  }
114085
114541
  const recorder = this.tapeRecorder;
114086
- return { cameras: describeTapeStatus({
114542
+ const cameras = describeTapeStatus({
114087
114543
  deviceIds,
114088
114544
  switchOn,
114089
114545
  switchUnreadable,
@@ -114092,7 +114548,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114092
114548
  startFailure: this.tapeStartFailure,
114093
114549
  segments,
114094
114550
  now: Date.now()
114095
- }) };
114551
+ });
114552
+ return { cameras: input.deviceIds === void 0 ? defaultTapeStatusCameras(cameras) : cameras };
114096
114553
  },
114097
114554
  "debug.tape.promote": async (input) => {
114098
114555
  const deps = this.tapePromotionDeps();
@@ -114541,7 +114998,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114541
114998
  * owner table, so the motion/object coin-flip is gone from here too.
114542
114999
  */
114543
115000
  async readMediaByAddress(address, variant, preferKind) {
114544
- 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);
114545
115002
  if (base === null || variant === void 0) return base;
114546
115003
  return this.applyThumbVariant(base, variant);
114547
115004
  }
@@ -114599,9 +115056,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114599
115056
  * ~144 MB and allocated ~190 MB of base64 to deliver ~1.2 MB, all of it on
114600
115057
  * the single-threaded post-analysis runner.
114601
115058
  */
114602
- async readOwnerThumbnail(ownerType, rawOwnerId, preferKind) {
115059
+ async readOwnerThumbnail(ownerType, rawOwnerId, preferKind, addressGeneration) {
114603
115060
  const ownerId = ownerType === "track" ? await resolveMediaOwnerId(ownerType, rawOwnerId, (id) => this.trackAliasStore?.resolve(id) ?? Promise.resolve(null), (failure) => this.aliasResolveFailureLogger?.(failure), "readOwnerThumbnail") : rawOwnerId;
114604
- const listInfo = (ownerKind, id) => this.mediaStore?.listInfoByOwner(ownerKind, id) ?? Promise.resolve([]);
115061
+ const listInfo = (ownerKind, id, deviceId) => this.mediaStore?.listInfoByOwner(ownerKind, id, deviceId) ?? Promise.resolve([]);
114605
115062
  const getTrackIdForEvent = (eventId) => isEventOwnerType(ownerType) ? this.eventStore?.getTrackIdForEvent(eventId, ownerType) ?? Promise.resolve(null) : Promise.resolve(null);
114606
115063
  const readBlob = async (mediaId) => {
114607
115064
  const file = await (this.mediaStore?.getById(mediaId) ?? Promise.resolve(null));
@@ -114612,7 +115069,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
114612
115069
  deviceId: file.deviceId
114613
115070
  };
114614
115071
  };
114615
- const ownFiles = await listInfo(ownerType, ownerId);
115072
+ let ownFiles = await listInfo(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;
115078
+ if (gen !== null) {
115079
+ const onCamera = await listInfo(ownerType, ownerId, gen.deviceId);
115080
+ const kept = onCamera.filter((f) => f.timestamp === gen.timestamp);
115081
+ if (kept.length < onCamera.length) this.ctx.logger.warn("event media of another record on a reused event id — not used", {
115082
+ tags: { deviceId: gen.deviceId },
115083
+ meta: {
115084
+ ownerId,
115085
+ ownerType,
115086
+ recordAt: gen.timestamp,
115087
+ refused: onCamera.length - kept.length,
115088
+ kept: kept.length,
115089
+ via: "readOwnerThumbnail",
115090
+ scopedBy: addressGeneration !== void 0 ? "url" : "live-row"
115091
+ }
115092
+ });
115093
+ ownFiles = kept;
115094
+ }
115095
+ }
114616
115096
  if (preferKind !== void 0 && preferKind.length > 0) {
114617
115097
  const layers = await collectCropForcedMedia({
114618
115098
  listByOwner: listInfo,