@camstack/addon-post-analysis 1.2.315 → 1.2.316

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_clip_model_registry = require("../clip-model-registry-8Ss3XHG0.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-D-zzCBKK.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -16996,7 +16996,7 @@ var NcStateSummaryTestSchema = require_clip_model_registry.object({
16996
16996
  /** Nothing open and every section read: nothing was enqueued. */
16997
16997
  nothingToReport: require_clip_model_registry.boolean(),
16998
16998
  sections: require_clip_model_registry.array(require_clip_model_registry.object({
16999
- id: require_clip_model_registry.string(),
16999
+ id: require_clip_model_registry.NcStateSummarySectionIdSchema,
17000
17000
  status: require_clip_model_registry._enum(["read", "unavailable"]),
17001
17001
  items: require_clip_model_registry.number().int().nonnegative(),
17002
17002
  reason: require_clip_model_registry.string().optional()
@@ -17004,7 +17004,7 @@ var NcStateSummaryTestSchema = require_clip_model_registry.object({
17004
17004
  rows: require_clip_model_registry.array(require_clip_model_registry.object({
17005
17005
  recordId: require_clip_model_registry.string(),
17006
17006
  /** The section of a per-section row. */
17007
- section: require_clip_model_registry.string().optional(),
17007
+ section: require_clip_model_registry.NcStateSummarySectionIdSchema.optional(),
17008
17008
  enqueued: require_clip_model_registry.number().int().nonnegative(),
17009
17009
  targetIds: require_clip_model_registry.array(require_clip_model_registry.string())
17010
17010
  }))
@@ -20730,17 +20730,8 @@ var NcDeviceStateCache = class {
20730
20730
  * a job that COMPLETED, and the state it leaves behind is not a condition that
20731
20731
  * is still true. The ADR records why it is excluded rather than approximated.
20732
20732
  */
20733
- var NC_STATE_SUMMARY_SECTION_IDS = [
20734
- "device-state",
20735
- "streams",
20736
- "battery",
20737
- "consumables",
20738
- "tanks",
20739
- "detection-blind",
20740
- "nodes",
20741
- "addons",
20742
- "updates"
20743
- ];
20733
+ /** One source: the cap's Zod enum (D698 LOW-6). */
20734
+ var NC_STATE_SUMMARY_SECTION_IDS = require_clip_model_registry.NcStateSummarySectionIdSchema.options;
20744
20735
  /** The kind groups that have no section, and why — said on the preview. */
20745
20736
  var NC_STATE_SUMMARY_EXCLUDED_GROUPS = { "backups-exports": "a backup or an export is a completed job, not a state that is still true" };
20746
20737
  function isStateSectionId(value) {
@@ -35209,10 +35200,10 @@ var NcStateSummaryService = class {
35209
35200
  const rows = [];
35210
35201
  for (const subject of subjects) {
35211
35202
  const enqueued = await this.deps.enqueue(rule, subject, "synthetic");
35212
- const section = subject.systemEvent?.data["section"];
35203
+ const section = stateSummarySectionOf(subject);
35213
35204
  rows.push({
35214
35205
  recordId: subject.recordId,
35215
- ...typeof section === "string" ? { section } : {},
35206
+ ...section !== void 0 ? { section } : {},
35216
35207
  enqueued: enqueued.inserted,
35217
35208
  targetIds: [...enqueued.targetIds]
35218
35209
  });
@@ -66122,6 +66113,51 @@ var EventStore = class {
66122
66113
  return found;
66123
66114
  }
66124
66115
  /**
66116
+ * The GENERATION of each live event in `table` among `eventIds` — its camera
66117
+ * and its own timestamp (D694). Same throw-on-failure contract as
66118
+ * {@link existingEventIdsIn}: a quiet under-report here would let the orphan
66119
+ * audit keep treating another camera's leftovers as owned.
66120
+ */
66121
+ async eventGenerationsIn(table, eventIds) {
66122
+ const collection = EVENT_COLLECTION_BY_TABLE[table];
66123
+ const found = /* @__PURE__ */ new Map();
66124
+ for (let i = 0; i < eventIds.length; i += PRUNE_PAGE_SIZE) {
66125
+ const chunk = eventIds.slice(i, i + PRUNE_PAGE_SIZE);
66126
+ if (chunk.length === 0) continue;
66127
+ const rows = await this.store.query.query({
66128
+ collection,
66129
+ columns: [
66130
+ "id",
66131
+ "deviceId",
66132
+ "timestamp"
66133
+ ],
66134
+ filter: {
66135
+ whereIn: { id: [...chunk] },
66136
+ limit: chunk.length
66137
+ }
66138
+ });
66139
+ for (const row of rows) {
66140
+ const deviceId = row.data["deviceId"];
66141
+ const timestamp = row.data["timestamp"];
66142
+ if (typeof deviceId !== "number" || !Number.isFinite(deviceId)) continue;
66143
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) continue;
66144
+ found.set(row.id, {
66145
+ deviceId,
66146
+ timestamp
66147
+ });
66148
+ }
66149
+ }
66150
+ return found;
66151
+ }
66152
+ /**
66153
+ * One event's generation, or `null` when the id is not live in `table`.
66154
+ * Used by the `/o/<eventOwner>/<id>` plane ladder to scope media to the
66155
+ * current holder (D694).
66156
+ */
66157
+ async eventGenerationOf(table, eventId) {
66158
+ return (await this.eventGenerationsIn(table, [eventId])).get(eventId) ?? null;
66159
+ }
66160
+ /**
66125
66161
  * The SUBSET of `eventIds` that are MOTION events specifically.
66126
66162
  *
66127
66163
  * {@link existingEventIds} answers "is this event alive", which is the
@@ -93492,6 +93528,7 @@ async function auditScope(scope, deps, limits, cursor) {
93492
93528
  const { trackIds, eventIdsByTable } = questionsFor(refs);
93493
93529
  let liveTracks;
93494
93530
  const liveEvents = /* @__PURE__ */ new Map();
93531
+ const eventGens = /* @__PURE__ */ new Map();
93495
93532
  let keptMedia = /* @__PURE__ */ new Set();
93496
93533
  try {
93497
93534
  liveTracks = trackIds.length > 0 ? await deps.liveTrackIds(trackIds) : /* @__PURE__ */ new Set();
@@ -93499,6 +93536,7 @@ async function auditScope(scope, deps, limits, cursor) {
93499
93536
  for (const [table, ids] of eventIdsByTable) {
93500
93537
  if (ids.length === 0) continue;
93501
93538
  liveEvents.set(table, await deps.liveEventIds(table, ids));
93539
+ if (deps.eventGenerations !== void 0 && scope.collection === MEDIA_COLLECTION) eventGens.set(table, await deps.eventGenerations(table, ids));
93502
93540
  }
93503
93541
  } catch (err) {
93504
93542
  deps.logger.warn("orphan audit: liveness probe failed — scope abandoned for this run", { meta: {
@@ -93527,7 +93565,13 @@ async function auditScope(scope, deps, limits, cursor) {
93527
93565
  tally.unresolvable += 1;
93528
93566
  continue;
93529
93567
  }
93530
- if (ref.kind === "track" ? liveTracks.has(ref.trackId) || keptMedia.has(row.key) : ref.kind === "event" ? liveEvents.get(ref.table)?.has(ref.eventId) ?? false : false) continue;
93568
+ 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;
93569
+ if (live && ref.kind === "event" && scope.collection === MEDIA_COLLECTION) {
93570
+ const gen = eventGens.get(ref.table)?.get(ref.eventId);
93571
+ const mediaDeviceId = num(row.data, scope.deviceIdField);
93572
+ if (gen !== void 0 && mediaDeviceId !== null && mediaDeviceId !== gen.deviceId) live = false;
93573
+ }
93574
+ if (live) continue;
93531
93575
  const ts = num(row.data, scope.timestampField);
93532
93576
  if (ts !== null && ts > graceFloor) {
93533
93577
  tally.withinGrace += 1;
@@ -93542,7 +93586,7 @@ async function auditScope(scope, deps, limits, cursor) {
93542
93586
  break;
93543
93587
  }
93544
93588
  try {
93545
- const outcome = await deps.reclaim(scope.collection, row.key);
93589
+ const outcome = await deps.reclaim(scope.collection, row.key, row);
93546
93590
  if (outcome.rows === 0) continue;
93547
93591
  tally.reclaimed += 1;
93548
93592
  reclaimedInPage += 1;
@@ -93698,7 +93742,10 @@ var ORPHAN_AUDIT_DEFAULTS = {
93698
93742
  intervalHours: OrphanAuditIntervalHoursSchema.parse(void 0),
93699
93743
  pageSize: OrphanAuditPageSizeSchema.parse(void 0)
93700
93744
  };
93701
- var RAW_DELETE_COLLECTIONS = new Set(["pipeline-analytics:object-events", "pipeline-analytics:plates"]);
93745
+ //#endregion
93746
+ //#region src/pipeline-analytics/retention/orphan-reclaimers.ts
93747
+ var OBJECT_EVENTS_COLLECTION = "pipeline-analytics:object-events";
93748
+ var RAW_DELETE_COLLECTIONS = new Set([OBJECT_EVENTS_COLLECTION, "pipeline-analytics:plates"]);
93702
93749
  /**
93703
93750
  * The `deleteRow` port: ONE row by its primary key `id` (what every
93704
93751
  * raw-delete collection above declares), answering whether a row went.
@@ -93707,12 +93754,19 @@ var RAW_DELETE_COLLECTIONS = new Set(["pipeline-analytics:object-events", "pipel
93707
93754
  * is `z.void()`, and reading it as `!== undefined` answered `false` for every
93708
93755
  * row it really deleted — the audit counted every plates/object-events reclaim
93709
93756
  * as "found nothing", and the per-scope reclaim ceiling never tripped.
93757
+ *
93758
+ * When `assert.trackId` is set, the delete also requires that column: an
93759
+ * object-events rowid alone is not an identity across the row's life (D694).
93710
93760
  */
93711
93761
  function settingsRowDeleter(store) {
93712
- return async (collection, key) => {
93762
+ return async (collection, key, assert) => {
93763
+ const where = assert !== void 0 ? {
93764
+ id: key,
93765
+ trackId: assert.trackId
93766
+ } : { id: key };
93713
93767
  const { deleted } = await store.deleteWhere.mutate({
93714
93768
  collection,
93715
- filter: { where: { id: key } }
93769
+ filter: { where }
93716
93770
  });
93717
93771
  return deleted > 0;
93718
93772
  };
@@ -93727,14 +93781,19 @@ function settingsRowDeleter(store) {
93727
93781
  * `scripts/check-orphan-reclaimers.ts` checks `index.ts` uses it.
93728
93782
  */
93729
93783
  function settingsRowDeleterOf(getStore) {
93730
- return async (collection, key) => {
93784
+ return async (collection, key, assert) => {
93731
93785
  const store = getStore();
93732
93786
  if (store === null) throw new Error("orphan audit: settings store not bound");
93733
- return await settingsRowDeleter(store)(collection, key);
93787
+ return await settingsRowDeleter(store)(collection, key, assert);
93734
93788
  };
93735
93789
  }
93790
+ /** Pull the trackId the raw delete must re-assert, or `null` when absent. */
93791
+ function trackIdAssertOf(data) {
93792
+ const trackId = data["trackId"];
93793
+ return typeof trackId === "string" && trackId.length > 0 ? { trackId } : null;
93794
+ }
93736
93795
  function buildOrphanReclaimer(ports) {
93737
- return async (collection, key) => {
93796
+ return async (collection, key, data = {}) => {
93738
93797
  if (collection === MEDIA_COLLECTION) {
93739
93798
  const mediaId = Number(key);
93740
93799
  if (!Number.isInteger(mediaId) || mediaId <= 0) {
@@ -93760,10 +93819,23 @@ function buildOrphanReclaimer(ports) {
93760
93819
  rows: await ports.deleteTrackAlias(key) ? 1 : 0,
93761
93820
  bytes: null
93762
93821
  };
93763
- if (RAW_DELETE_COLLECTIONS.has(collection)) return {
93764
- rows: await ports.deleteRow(collection, key) ? 1 : 0,
93765
- bytes: null
93766
- };
93822
+ if (RAW_DELETE_COLLECTIONS.has(collection)) {
93823
+ const assert = trackIdAssertOf(data);
93824
+ if (collection === OBJECT_EVENTS_COLLECTION && assert === null) {
93825
+ ports.logger.warn("orphan audit: object-events reclaim without trackId — row kept (id-only delete refused)", { meta: {
93826
+ collection,
93827
+ key
93828
+ } });
93829
+ return {
93830
+ rows: 0,
93831
+ bytes: null
93832
+ };
93833
+ }
93834
+ return {
93835
+ rows: await ports.deleteRow(collection, key, assert === null ? void 0 : assert) ? 1 : 0,
93836
+ bytes: null
93837
+ };
93838
+ }
93767
93839
  throw new Error(`orphan audit: '${collection}' is not declared in RECLAIMED_COLLECTIONS — a collection walked by the audit must say how its rows are deleted, because the raw path leaves behind anything its store owns outside the row`);
93768
93840
  };
93769
93841
  }
@@ -101163,8 +101235,14 @@ async function buildTrainingExportPlan(readers, input) {
101163
101235
  const eventsByTrack = /* @__PURE__ */ new Map();
101164
101236
  for (const link of eventLinks) {
101165
101237
  const list = eventsByTrack.get(link.trackId);
101166
- if (list) list.push(link.eventId);
101167
- else eventsByTrack.set(link.trackId, [link.eventId]);
101238
+ if (list) list.push({
101239
+ eventId: link.eventId,
101240
+ recordAt: link.recordAt
101241
+ });
101242
+ else eventsByTrack.set(link.trackId, [{
101243
+ eventId: link.eventId,
101244
+ recordAt: link.recordAt
101245
+ }]);
101168
101246
  }
101169
101247
  for (const track of take) {
101170
101248
  const files = [];
@@ -101184,15 +101262,17 @@ async function buildTrainingExportPlan(readers, input) {
101184
101262
  ownerId: `${PLATE_OWNER_PREFIX}${track.trackId}`,
101185
101263
  scope: "plate"
101186
101264
  },
101187
- ...(eventsByTrack.get(track.trackId) ?? []).map((eventId) => ({
101265
+ ...(eventsByTrack.get(track.trackId) ?? []).map((ev) => ({
101188
101266
  ownerKind: "object",
101189
- ownerId: eventId,
101190
- scope: `events/${eventId}`
101267
+ ownerId: ev.eventId,
101268
+ scope: `events/${ev.eventId}`,
101269
+ recordAt: ev.recordAt
101191
101270
  }))
101192
101271
  ];
101193
101272
  for (const owner of owners) {
101194
- const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId);
101195
- for (const info of infos) {
101273
+ const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId, owner.ownerKind === "object" ? track.deviceId : void 0);
101274
+ const kept = owner.recordAt === void 0 ? infos : infos.filter((info) => info.timestamp === owner.recordAt);
101275
+ for (const info of kept) {
101196
101276
  const archivePath = archivePathFor({
101197
101277
  deviceId: track.deviceId,
101198
101278
  trackId: track.trackId,
@@ -105703,15 +105783,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
105703
105783
  if (!trackStore || !mediaStore) throw new Error("training export: analytics stores unavailable");
105704
105784
  return buildTrainingExportPlan({
105705
105785
  listMarkedTracks: (deviceId, limit) => trackStore.listStaging(deviceId, limit),
105706
- listMediaInfo: async (ownerKind, ownerId) => {
105786
+ listMediaInfo: async (ownerKind, ownerId, deviceId) => {
105707
105787
  if (!isMediaOwnerKind(ownerKind)) return [];
105708
- return mediaStore.listInfoByOwner(ownerKind, ownerId);
105788
+ return mediaStore.listInfoByOwner(ownerKind, ownerId, deviceId);
105709
105789
  },
105710
105790
  listEventIdsForTracks: async (trackIds) => {
105711
105791
  if (!eventStore || trackIds.length === 0) return [];
105712
105792
  return (await eventStore.queryObjectByTrackIds(trackIds)).flatMap((e) => e.trackId === void 0 ? [] : [{
105713
105793
  eventId: String(e.id),
105714
- trackId: e.trackId
105794
+ trackId: e.trackId,
105795
+ recordAt: e.timestamp
105715
105796
  }]);
105716
105797
  },
105717
105798
  now: () => Date.now()
@@ -111912,26 +111993,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
111912
111993
  },
111913
111994
  liveTrackIds: (ids) => this.liveTrackIdsThroughAliases(ids, trackStore),
111914
111995
  liveEventIds: (table, ids) => eventStore.existingEventIdsIn(table, ids),
111996
+ eventGenerations: (table, ids) => eventStore.eventGenerationsIn(table, ids),
111915
111997
  keptMediaKeys: async (keys) => this.keptStationaryMediaKeys(keys),
111916
- reclaim: buildOrphanReclaimer({
111917
- deleteFace: async (faceId) => {
111918
- await this.faceStore?.delete(faceId);
111919
- },
111920
- deleteMediaConfirmed: async (mediaId) => {
111921
- const count = await mediaStore.deleteConfirmed(mediaId);
111922
- return {
111923
- rows: count.rows,
111924
- bytes: count.bytes
111925
- };
111926
- },
111927
- deleteTrackAlias: async (aliasId) => {
111928
- const aliasStore = this.trackAliasStore;
111929
- if (aliasStore === null) throw new Error("track alias store not bound");
111930
- return await aliasStore.deleteAlias(aliasId);
111931
- },
111932
- deleteRow: settingsRowDeleterOf(() => this.settingsStore),
111933
- logger: this.ctx.logger
111934
- }),
111998
+ reclaim: (() => {
111999
+ const reclaimOne = buildOrphanReclaimer({
112000
+ deleteFace: async (faceId) => {
112001
+ await this.faceStore?.delete(faceId);
112002
+ },
112003
+ deleteMediaConfirmed: async (mediaId) => {
112004
+ const count = await mediaStore.deleteConfirmed(mediaId);
112005
+ return {
112006
+ rows: count.rows,
112007
+ bytes: count.bytes
112008
+ };
112009
+ },
112010
+ deleteTrackAlias: async (aliasId) => {
112011
+ const aliasStore = this.trackAliasStore;
112012
+ if (aliasStore === null) throw new Error("track alias store not bound");
112013
+ return await aliasStore.deleteAlias(aliasId);
112014
+ },
112015
+ deleteRow: settingsRowDeleterOf(() => this.settingsStore),
112016
+ logger: this.ctx.logger
112017
+ });
112018
+ return (collection, key, row) => reclaimOne(collection, key, row.data);
112019
+ })(),
111935
112020
  now: () => Date.now(),
111936
112021
  logger: this.ctx.logger,
111937
112022
  breathe: () => new Promise((resolve) => setImmediate(resolve))
@@ -114696,7 +114781,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114696
114781
  */
114697
114782
  async readOwnerThumbnail(ownerType, rawOwnerId, preferKind) {
114698
114783
  const ownerId = ownerType === "track" ? await resolveMediaOwnerId(ownerType, rawOwnerId, (id) => this.trackAliasStore?.resolve(id) ?? Promise.resolve(null), (failure) => this.aliasResolveFailureLogger?.(failure), "readOwnerThumbnail") : rawOwnerId;
114699
- const listInfo = (ownerKind, id) => this.mediaStore?.listInfoByOwner(ownerKind, id) ?? Promise.resolve([]);
114784
+ const listInfo = (ownerKind, id, deviceId) => this.mediaStore?.listInfoByOwner(ownerKind, id, deviceId) ?? Promise.resolve([]);
114700
114785
  const getTrackIdForEvent = (eventId) => require_clip_model_registry.isEventOwnerType(ownerType) ? this.eventStore?.getTrackIdForEvent(eventId, ownerType) ?? Promise.resolve(null) : Promise.resolve(null);
114701
114786
  const readBlob = async (mediaId) => {
114702
114787
  const file = await (this.mediaStore?.getById(mediaId) ?? Promise.resolve(null));
@@ -114707,7 +114792,26 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114707
114792
  deviceId: file.deviceId
114708
114793
  };
114709
114794
  };
114710
- const ownFiles = await listInfo(ownerType, ownerId);
114795
+ let ownFiles = await listInfo(ownerType, ownerId);
114796
+ if (require_clip_model_registry.isEventOwnerType(ownerType) && this.eventStore !== null) {
114797
+ const gen = await this.eventStore.eventGenerationOf(ownerType, ownerId);
114798
+ if (gen !== null) {
114799
+ const onCamera = await listInfo(ownerType, ownerId, gen.deviceId);
114800
+ const kept = onCamera.filter((f) => f.timestamp === gen.timestamp);
114801
+ if (kept.length < onCamera.length) this.ctx.logger.warn("event media of another record on a reused event id — not used", {
114802
+ tags: { deviceId: gen.deviceId },
114803
+ meta: {
114804
+ ownerId,
114805
+ ownerType,
114806
+ recordAt: gen.timestamp,
114807
+ refused: onCamera.length - kept.length,
114808
+ kept: kept.length,
114809
+ via: "readOwnerThumbnail"
114810
+ }
114811
+ });
114812
+ ownFiles = kept;
114813
+ }
114814
+ }
114711
114815
  if (preferKind !== void 0 && preferKind.length > 0) {
114712
114816
  const layers = await collectCropForcedMedia({
114713
114817
  listByOwner: listInfo,