@camstack/addon-post-analysis 1.2.618 → 1.2.619

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.
Files changed (14) hide show
  1. package/dist/{clip-model-registry-CYWuH4Rx.js → clip-model-registry-B4VtQ3mk.js} +232 -24
  2. package/dist/{clip-model-registry-D6yfX_Xq.mjs → clip-model-registry-Clb51mcj.mjs} +203 -19
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B0Kz5Uye.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BGo4IMTT.mjs} +3 -3
  7. package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CSQBGkvs.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BSMSev-b.mjs} +1 -1
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CssYIiFD.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-DjzBKo20.mjs → hostInit-p9APfKsm.mjs} +3 -3
  10. package/dist/pipeline-analytics/index.js +146 -209
  11. package/dist/pipeline-analytics/index.mjs +146 -208
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +1 -1
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BB2kgLY1.mjs +0 -26
@@ -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-CYWuH4Rx.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-B4VtQ3mk.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");
@@ -9393,7 +9393,7 @@ var IdentityConfigBodySchema = require_clip_model_registry.object({
9393
9393
  *
9394
9394
  * `addedAt` is ALSO in the body: the `identity_added` index names it, and an
9395
9395
  * index reads the body, never the header. Optional only because rows written
9396
- * before the fix lack it until `records.backfillIndexFields` runs.
9396
+ * before the fix lacked it (repaired on the operator hubs on 2026-10-22).
9397
9397
  */
9398
9398
  var IdentitySampleLedgerBodySchema = require_clip_model_registry.object({
9399
9399
  identityId: require_clip_model_registry.string(),
@@ -9476,7 +9476,7 @@ var FaceRescoreJobStatusSchema = require_clip_model_registry.object({
9476
9476
  *
9477
9477
  * Each kind replaced the legacy `pipeline-analytics:*` collection of the same
9478
9478
  * name; the records migration that copied them is closed (D900) and the tables
9479
- * are gone (`retired-legacy-tables.ts`). The addon declares no
9479
+ * are gone (dropped offline on the operator hubs). The addon declares no
9480
9480
  * `execution.group`, so the hub owner stamp is the addon id
9481
9481
  * (`pipeline-analytics`).
9482
9482
  */
@@ -11797,14 +11797,8 @@ var DebugNoteArchiveStore = class {
11797
11797
  };
11798
11798
  //#endregion
11799
11799
  //#region src/pipeline-analytics/store/event-ledger-collection.ts
11800
- /**
11801
- * One trackless event ledger (motion, audio, or sensor) on settings-store
11802
- * (agents) or `ctx.records` (hub after phase 5).
11803
- */
11804
11800
  /** Matches {@link EventStore.LEDGER_PAGE_SIZE} — kept local to avoid a circular import. */
11805
11801
  var LEDGER_PAGE_SIZE = 500;
11806
- /** A backfill patch refused for one of these lost a race with the row's writer, nothing more. */
11807
- var RACED_CODES = new Set(["rev-conflict", "not-found"]);
11808
11802
  var EventLedgerCollection = class EventLedgerCollection {
11809
11803
  collection;
11810
11804
  store;
@@ -11813,8 +11807,6 @@ var EventLedgerCollection = class EventLedgerCollection {
11813
11807
  legacyAtField;
11814
11808
  deviceIdMode;
11815
11809
  logger;
11816
- /** Body fields a declared index names that the header also carries (see {@link backfillIndexFields}). */
11817
- headerIndexFields;
11818
11810
  constructor(deps) {
11819
11811
  this.collection = deps.collection;
11820
11812
  this.store = deps.store;
@@ -11822,7 +11814,6 @@ var EventLedgerCollection = class EventLedgerCollection {
11822
11814
  this.legacyAtField = deps.legacyAtField ?? "timestamp";
11823
11815
  this.deviceIdMode = deps.deviceIdMode ?? "required";
11824
11816
  this.logger = deps.logger;
11825
- this.headerIndexFields = headerIndexFieldsOf(deps.definition, this.legacyAtField);
11826
11817
  if (deps.records !== void 0) {
11827
11818
  const handle = deps.records.kind(deps.definition);
11828
11819
  this.kind = handle;
@@ -12069,73 +12060,6 @@ var EventLedgerCollection = class EventLedgerCollection {
12069
12060
  nextCursor: page.nextCursor
12070
12061
  };
12071
12062
  }
12072
- /**
12073
- * Copy the header values a declared index needs into the body of rows that
12074
- * lack them — a bounded keyset walk of the kind in `at` order per call.
12075
- *
12076
- * An index reads the BODY (`json_extract(data, …)`), never the header. Until
12077
- * this fix the writer moved the clock (`timestamp`, `addedAt` …) into `at`
12078
- * only, and the records adoption did the same with `deviceId`, so a `where`
12079
- * page of such a kind found no ordering value on the last row and threw
12080
- * "cannot be paged". The value is derivable — the header holds it — so the
12081
- * repair is a merge patch guarded by `ifRev`: a row rewritten since it was
12082
- * read is left to its writer (which now writes the field) and counted.
12083
- *
12084
- * `dryRun` patches nothing and reports the same counts.
12085
- */
12086
- async backfillIndexFields(input) {
12087
- if (this.kind === null) throw new Error(`event ledger ${this.collection}: backfillIndexFields needs the records kind`);
12088
- const missingByField = {};
12089
- let scanned = 0;
12090
- let missing = 0;
12091
- let patched = 0;
12092
- let raced = 0;
12093
- let cursor = input.cursor;
12094
- while (scanned < input.maxRows) {
12095
- const page = await this.kind.query({
12096
- order: "at",
12097
- limit: Math.min(LEDGER_PAGE_SIZE, input.maxRows - scanned),
12098
- ...cursor === null ? {} : { cursor }
12099
- });
12100
- for (const row of page.records) {
12101
- scanned += 1;
12102
- const patch = this.headerPatchFor(row);
12103
- const fields = Object.keys(patch);
12104
- if (fields.length === 0) continue;
12105
- missing += 1;
12106
- for (const f of fields) missingByField[f] = (missingByField[f] ?? 0) + 1;
12107
- if (input.dryRun) continue;
12108
- try {
12109
- await this.kind.patch(row.id, patch, { ifRev: row.rev });
12110
- patched += 1;
12111
- } catch (err) {
12112
- if (!(err instanceof require_clip_model_registry.RecordsError) || !RACED_CODES.has(err.code)) throw err;
12113
- raced += 1;
12114
- }
12115
- }
12116
- cursor = page.nextCursor;
12117
- if (cursor === null) break;
12118
- }
12119
- return {
12120
- kind: this.kind.name,
12121
- fields: this.headerIndexFields.map((f) => f.field),
12122
- scanned,
12123
- missing,
12124
- missingByField,
12125
- patched,
12126
- raced,
12127
- nextCursor: cursor
12128
- };
12129
- }
12130
- headerPatchFor(row) {
12131
- const patch = {};
12132
- for (const { field, from } of this.headerIndexFields) {
12133
- if (row.data[field] !== void 0 && row.data[field] !== null) continue;
12134
- const value = from === "at" ? row.at : row.deviceId;
12135
- if (typeof value === "number") patch[field] = value;
12136
- }
12137
- return patch;
12138
- }
12139
12063
  async getManyByIds(ids) {
12140
12064
  if (ids.length === 0) return [];
12141
12065
  if (this.kind === null) return (await this.store.query.query({
@@ -12375,20 +12299,6 @@ function eventBodyFromLegacyRecord(record) {
12375
12299
  }
12376
12300
  return out;
12377
12301
  }
12378
- /** The body fields of `definition`'s indexes that mirror a header column. */
12379
- function headerIndexFieldsOf(definition, legacyAtField) {
12380
- const named = new Set(definition.declaration.indexes.flatMap((i) => i.fields));
12381
- const out = [];
12382
- if (named.has(legacyAtField)) out.push({
12383
- field: legacyAtField,
12384
- from: "at"
12385
- });
12386
- if (named.has("deviceId")) out.push({
12387
- field: "deviceId",
12388
- from: "deviceId"
12389
- });
12390
- return out;
12391
- }
12392
12302
  function recordsRowToLegacy(row, legacyAtField) {
12393
12303
  const deviceId = row.deviceId ?? row.data["deviceId"];
12394
12304
  const clock = row.at ?? readLegacyClock(row.data, legacyAtField);
@@ -20136,7 +20046,7 @@ function isWriterBoundedOwnerKind(value) {
20136
20046
  * `'group'` is the co-moving group entity, removed on 2026-09-01 together with
20137
20047
  * `pipeline-analytics:track-groups` / `:track-group-members`
20138
20048
  * ([D183](../../../../../docs/decisions/adr-0183-code-that-is-deleted-takes-its-tables-with-it.md)).
20139
- * The tables are dropped at boot from `RETIRED_COLLECTIONS`, so a group-owned
20049
+ * The tables are gone (dropped on every hub), so a group-owned
20140
20050
  * media row can never be claimed again: `resolveMediaOwner` maps it to
20141
20051
  * `{ kind: 'retired-owner' }` and the orphan audit reclaims it under the same
20142
20052
  * grace window and ceilings as any other orphan.
@@ -41843,6 +41753,53 @@ function selectTriggerZoneCandidates(input) {
41843
41753
  }
41844
41754
  //#endregion
41845
41755
  //#region src/notification-center/notification-image.ts
41756
+ /**
41757
+ * notificationImage — ONE stage renders every notification picture (D897).
41758
+ *
41759
+ * ## The defect this replaces
41760
+ *
41761
+ * A rule's picture used to be a STORED kind picked by a ladder: `best` walked
41762
+ * subject crops and ended in the boxed `firstFrame`, so one rule shipped a
41763
+ * crop on one event and a boxed scene on the next depending on what had
41764
+ * landed; boxes were burnt only at WRITE time and only around the track's own
41765
+ * box; `zoneCrop` was a third mechanism, and the sensor, sound, linked-camera
41766
+ * and mosaic paths each had their own rules for what "the picture" was.
41767
+ *
41768
+ * ## The stage
41769
+ *
41770
+ * The dispatcher resolves a CLEAN source frame of the trigger moment
41771
+ * (`cleanSourcePreference`: keyFrame → sceneFrame → lastSceneFrame, after the
41772
+ * MOMENT and PLACE filters), and this stage renders it by the rule's mode:
41773
+ *
41774
+ * - `scene-boxes` — the whole scene; the subject outlined THICK in the accent
41775
+ * colour, every OTHER track visible at that instant outlined THIN, each
41776
+ * captioned with class and confidence ("dog 87%") on a dark plate that
41777
+ * reads on any background. Boxes are the tracks' normalised trail boxes AT
41778
+ * the source frame's timestamp — never the track envelope — scaled to the
41779
+ * JPEG.
41780
+ * - `crop` — the subject cut out of the clean frame around its box at that
41781
+ * instant.
41782
+ * - `clean` — the scene, nothing drawn.
41783
+ * - `showTriggerZone` adds the zone that fired the rule to a scene mode.
41784
+ * - `showSubjectPath` (D903) adds the subject's path — a line in the
41785
+ * subject's colour through its trail centres up to the frame shown — to
41786
+ * every mode; on a `crop` it is mapped into the crop and clipped. The
41787
+ * geometry is `buildSubjectPath` / `projectSubjectPath` from
41788
+ * `@camstack/types`, the same the debug views draw.
41789
+ *
41790
+ * A mode that cannot be honoured ships `clean` and says why, once per record
41791
+ * (D400: "a branch that fails to produce media names the reason"): no subject
41792
+ * box at that instant, no track visible at that instant, a source that is
41793
+ * already annotated (a track that holds no clean frame), a composite.
41794
+ *
41795
+ * ## Once per record
41796
+ *
41797
+ * N targets and every outbox retry resolve the same row; the render is
41798
+ * memoised per record + subject + mode + zone flag + camera for
41799
+ * {@link NC_IMAGE_MEMO_TTL_MS}, so a 4K decode happens once, not once per
41800
+ * phone. It runs in the process that hosts the Notification Center (the
41801
+ * pipeline-analytics runner), which already carries `sharp`.
41802
+ */
41846
41803
  /** How long a rendered picture is reused for the same record (N targets, retries). */
41847
41804
  var NC_IMAGE_MEMO_TTL_MS = 15 * 6e4;
41848
41805
  /** At most this many rendered pictures are held. */
@@ -41910,6 +41867,10 @@ function notificationOverlaySvg(input) {
41910
41867
  const points = zone.map((p) => `${String(clampPx(p.x * width, width))},${String(clampPx(p.y * height, height))}`).join(" ");
41911
41868
  parts.push(`<polygon data-role="zone" points="${points}" fill="${NC_IMAGE_ZONE_COLOR}" fill-opacity="0.18" stroke="${NC_IMAGE_ZONE_COLOR}" stroke-width="${String(strokes.other)}" stroke-dasharray="${String(strokes.subject * 2)},${String(strokes.subject)}"/>`);
41912
41869
  }
41870
+ for (const run of input.paths ?? []) {
41871
+ if (run.length < 2) continue;
41872
+ parts.push(`<polyline data-role="subject-path" points="${require_clip_model_registry.subjectPathSvgPoints(run, width, height)}" fill="none" stroke="${NC_IMAGE_SUBJECT_COLOR}" stroke-width="${String(strokes.subject)}" stroke-linejoin="round" stroke-linecap="round" stroke-opacity="0.9"/>`);
41873
+ }
41913
41874
  const ordered = [...input.boxes.filter((b) => b.role === "other"), ...input.boxes.filter((b) => b.role === "subject")];
41914
41875
  for (const box of ordered) {
41915
41876
  const subject = box.role === "subject";
@@ -41980,6 +41941,10 @@ function payloadZoneOverlayIds(payload) {
41980
41941
  if (payload.mediaImage === void 0) return payload.mediaZoneIds ?? [];
41981
41942
  return [];
41982
41943
  }
41944
+ /** True when a row's picture draws the subject's path (D903). */
41945
+ function payloadShowsSubjectPath(payload) {
41946
+ return payload.mediaShowSubjectPath === true;
41947
+ }
41983
41948
  /**
41984
41949
  * Which camera the primary still SHOWS, or `composite` when it is a composed
41985
41950
  * mosaic of several. A sensor row of a linked set names its cameras primary
@@ -42013,16 +41978,47 @@ async function renderNotificationImage(req) {
42013
41978
  if (srcW === 0 || srcH === 0) throw new Error("source frame has no dimensions");
42014
41979
  if (req.mode === "crop" && req.subject !== null) {
42015
41980
  const rect = subjectCropRect(req.subject, srcW, srcH);
42016
- if (rect !== null) return {
42017
- bytes: toArrayBufferBytes(await sharp$18(Buffer.from(req.bytes)).extract(rect).resize({
41981
+ if (rect !== null) {
41982
+ const view = {
41983
+ x: rect.left / srcW,
41984
+ y: rect.top / srcH,
41985
+ w: rect.width / srcW,
41986
+ h: rect.height / srcH
41987
+ };
41988
+ const paths = require_clip_model_registry.projectSubjectPath(req.path ?? [], view);
41989
+ const cut = sharp$18(Buffer.from(req.bytes)).extract(rect).resize({
42018
41990
  width: NC_IMAGE_CROP_MAX_EDGE,
42019
41991
  height: NC_IMAGE_CROP_MAX_EDGE,
42020
41992
  fit: "inside",
42021
41993
  withoutEnlargement: true
42022
- }).jpeg({ quality: JPEG_QUALITY }).toBuffer()),
42023
- name: "crop.jpg",
42024
- rendered: "crop"
42025
- };
41994
+ });
41995
+ if (paths.length === 0) return {
41996
+ bytes: toArrayBufferBytes(await cut.jpeg({ quality: JPEG_QUALITY }).toBuffer()),
41997
+ name: "crop.jpg",
41998
+ rendered: "crop"
41999
+ };
42000
+ const { data, info } = await cut.raw().toBuffer({ resolveWithObject: true });
42001
+ const svg = notificationOverlaySvg({
42002
+ width: info.width,
42003
+ height: info.height,
42004
+ boxes: [],
42005
+ zones: [],
42006
+ paths
42007
+ });
42008
+ return {
42009
+ bytes: toArrayBufferBytes(await sharp$18(data, { raw: {
42010
+ width: info.width,
42011
+ height: info.height,
42012
+ channels: info.channels
42013
+ } }).composite([{
42014
+ input: Buffer.from(svg),
42015
+ top: 0,
42016
+ left: 0
42017
+ }]).jpeg({ quality: JPEG_QUALITY }).toBuffer()),
42018
+ name: "crop.jpg",
42019
+ rendered: "crop"
42020
+ };
42021
+ }
42026
42022
  }
42027
42023
  const scale = Math.min(1, NC_IMAGE_SCENE_MAX_WIDTH / srcW);
42028
42024
  const outW = Math.max(1, Math.round(srcW * scale));
@@ -42037,17 +42033,24 @@ async function renderNotificationImage(req) {
42037
42033
  label: req.subject.label
42038
42034
  }] : []] : [];
42039
42035
  const zones = req.mode === "crop" ? [] : req.zones;
42036
+ const paths = require_clip_model_registry.projectSubjectPath(req.path ?? [], {
42037
+ x: 0,
42038
+ y: 0,
42039
+ w: 1,
42040
+ h: 1
42041
+ });
42040
42042
  let pipeline = sharp$18(Buffer.from(req.bytes)).resize({
42041
42043
  width: outW,
42042
42044
  height: outH,
42043
42045
  fit: "fill"
42044
42046
  });
42045
- if (boxes.length > 0 || zones.length > 0) {
42047
+ if (boxes.length > 0 || zones.length > 0 || paths.length > 0) {
42046
42048
  const svg = notificationOverlaySvg({
42047
42049
  width: outW,
42048
42050
  height: outH,
42049
42051
  boxes,
42050
- zones
42052
+ zones,
42053
+ paths
42051
42054
  });
42052
42055
  pipeline = pipeline.composite([{
42053
42056
  input: Buffer.from(svg),
@@ -42085,11 +42088,13 @@ var NcNotificationImageStage = class {
42085
42088
  const mode = payloadImageMode(payload);
42086
42089
  if (mode === "none") return null;
42087
42090
  const zoneIds = input.withSubject ? payloadZoneOverlayIds(payload) : [];
42091
+ const withPath = input.withSubject && payloadShowsSubjectPath(payload);
42088
42092
  const key = [
42089
42093
  input.entry.recordId,
42090
42094
  payload.subject.trackId ?? "",
42091
42095
  mode,
42092
42096
  zoneIds.length > 0 ? "z" : "-",
42097
+ withPath ? "p" : "-",
42093
42098
  String(input.camera)
42094
42099
  ].join("|");
42095
42100
  const now = this.deps.now();
@@ -42099,7 +42104,7 @@ var NcNotificationImageStage = class {
42099
42104
  this.memo.set(key, hit);
42100
42105
  return hit.value;
42101
42106
  }
42102
- const value = this.compute(input, mode, zoneIds);
42107
+ const value = this.compute(input, mode, zoneIds, withPath);
42103
42108
  this.remember(key, {
42104
42109
  at: now,
42105
42110
  value
@@ -42119,13 +42124,15 @@ var NcNotificationImageStage = class {
42119
42124
  }
42120
42125
  this.memo.set(key, entry);
42121
42126
  }
42122
- async compute(input, mode, zoneIds) {
42127
+ async compute(input, mode, zoneIds, withPath) {
42123
42128
  const { entry, source, camera } = input;
42124
42129
  const payload = entry.payload;
42125
42130
  if (source.annotated) return this.asStored(input, mode, "annotated-source");
42126
42131
  if (camera === "composite") return mode === "clean" ? this.asStored(input, mode) : this.asStored(input, mode, "composite-source");
42127
42132
  const subjectTrackId = input.withSubject ? payload.subject.trackId : void 0;
42128
- const subjectBox = hasTrackedSubject(subjectTrackId) ? await this.subjectBoxAt(camera, subjectTrackId, source.timestamp) : null;
42133
+ const trail = hasTrackedSubject(subjectTrackId) ? await this.subjectTrail(camera, subjectTrackId) : [];
42134
+ const subjectBox = trailAt(trail, source.timestamp, NC_IMAGE_BOX_MATCH_MAX_MS)?.bbox ?? null;
42135
+ const path = withPath ? this.subjectPath(input, trail) : [];
42129
42136
  const subject = subjectBox !== null ? {
42130
42137
  ...subjectBox,
42131
42138
  label: imageLabelText(payload.subject.label ?? payload.subject.className, payload.subject.confidence)
@@ -42141,7 +42148,8 @@ var NcNotificationImageStage = class {
42141
42148
  bytes: source.bytes,
42142
42149
  subject,
42143
42150
  others,
42144
- zones
42151
+ zones,
42152
+ ...path.length > 0 ? { path } : {}
42145
42153
  });
42146
42154
  const reason = image.rendered === mode ? void 0 : mode === "crop" ? "no-subject-box" : "no-tracks-at-instant";
42147
42155
  if (reason !== void 0) this.reportFallback(input, mode, reason);
@@ -42193,22 +42201,48 @@ var NcNotificationImageStage = class {
42193
42201
  }
42194
42202
  });
42195
42203
  }
42196
- async subjectBoxAt(camera, trackId, timestamp) {
42204
+ /** The subject's trail, read once per render for its box AND its path. */
42205
+ async subjectTrail(camera, trackId) {
42197
42206
  const read = this.deps.getSubjectTrail;
42198
- if (read === void 0) return null;
42207
+ if (read === void 0) return [];
42199
42208
  try {
42200
- return trailAt(await read(camera, trackId), timestamp, NC_IMAGE_BOX_MATCH_MAX_MS)?.bbox ?? null;
42209
+ return await read(camera, trackId);
42201
42210
  } catch (err) {
42202
- this.deps.logger.debug("subject trail read failed — no subject box on the picture", {
42211
+ this.deps.logger.debug("subject trail read failed — no subject box or path on the picture", {
42203
42212
  tags: { deviceId: camera },
42204
42213
  meta: {
42205
42214
  trackId,
42206
42215
  error: String(err)
42207
42216
  }
42208
42217
  });
42209
- return null;
42218
+ return [];
42210
42219
  }
42211
42220
  }
42221
+ /**
42222
+ * The subject's path up to the frame shown, normalised in that frame, or
42223
+ * empty when the trail has fewer than two drawable points — then the
42224
+ * picture ships without it, and the line below says so (D400: a branch
42225
+ * that produces nothing names why).
42226
+ */
42227
+ subjectPath(input, trail) {
42228
+ const path = require_clip_model_registry.buildSubjectPath(trail.map((p) => require_clip_model_registry.subjectPathSampleFromBox(p.bbox, p.timestamp)), { untilTimestamp: input.source.timestamp });
42229
+ if (require_clip_model_registry.isDrawableSubjectPath(path)) return path.nodes.map((n) => ({
42230
+ x: n.x,
42231
+ y: n.y
42232
+ }));
42233
+ const camera = input.camera === "composite" ? input.entry.payload.subject.deviceId : input.camera;
42234
+ this.deps.logger.debug("subject path not drawn — fewer than two trail points", {
42235
+ tags: { deviceId: camera },
42236
+ meta: {
42237
+ ruleId: input.entry.ruleId,
42238
+ recordId: input.entry.recordId,
42239
+ trailPoints: trail.length,
42240
+ nodes: path.nodes.length,
42241
+ unlocalized: path.unlocalized
42242
+ }
42243
+ });
42244
+ return [];
42245
+ }
42212
42246
  async tracksAt(camera, timestamp) {
42213
42247
  const read = this.deps.getTracksAt;
42214
42248
  if (read === void 0) return [];
@@ -56098,6 +56132,7 @@ function mediaPolicyFields(mediaRule, cropZoneIds) {
56098
56132
  media: image === "none" ? "none" : "best",
56099
56133
  mediaImage: image,
56100
56134
  ...media.showTriggerZone === true && cropZoneIds !== void 0 && cropZoneIds.length > 0 ? { mediaShowTriggerZone: true } : {},
56135
+ ...media.showSubjectPath === true && image !== "none" ? { mediaShowSubjectPath: true } : {},
56101
56136
  ...cropZoneIds !== void 0 ? { triggerZoneIds: [...cropZoneIds] } : {},
56102
56137
  ...media.profile !== void 0 ? { mediaProfile: media.profile } : {},
56103
56138
  ...media.gif === true ? { mediaGif: true } : {},
@@ -79498,97 +79533,6 @@ async function forgetMissingMediaRows(deps, rawInput) {
79498
79533
  };
79499
79534
  }
79500
79535
  //#endregion
79501
- //#region src/pipeline-analytics/store/ledger-index-backfill.ts
79502
- /**
79503
- * The operator door for repairing ledger rows whose BODY lacks a field a
79504
- * declared index names (`records.backfillIndexFields`).
79505
- *
79506
- * Until the fix the event-ledger writer moved the clock (`timestamp`,
79507
- * `addedAt`, `updatedAt` …) into the record header `at` only, and the records
79508
- * adoption also left `deviceId` header-only. Indexes read the body, so any
79509
- * `where` page of such a kind (faces `assigned_ts`, plates `vehicle_ts`,
79510
- * identity samples `identity_added` …) refused to page: "a row has no value for
79511
- * an ordering field". The writer now keeps the body field; this copies the
79512
- * header value into rows written before it. It is a repair of derivable data,
79513
- * not a migration: nothing is read from anywhere but the row itself.
79514
- *
79515
- * One kind per call, bounded, resumable: `dryRun` defaults to `true` (counts
79516
- * only); pass the returned `nextCursor` back until it is `null`.
79517
- */
79518
- /** Hard ceiling on rows one call examines — the call is synchronous. */
79519
- var LEDGER_BACKFILL_MAX_ROWS = 5e4;
79520
- var LEDGER_BACKFILL_DEFAULT_ROWS = 2e4;
79521
- /** Every event ledger, by its records kind name. A kind with nothing to backfill reports `fields: []`. */
79522
- var LEDGER_WIRES = {
79523
- faces: wireFaceLedger,
79524
- plates: wirePlateLedger,
79525
- identities: wireIdentityLedger,
79526
- "identity-samples": wireIdentitySampleLedger,
79527
- vehicles: wireVehicleLedger,
79528
- "vehicle-samples": wireVehicleSampleLedger,
79529
- "track-aliases": wireTrackAliasLedger,
79530
- "track-purity": wireTrackPurityLedger,
79531
- "track-relations": wireTrackRelationsLedger,
79532
- "tracking-tapes": wireTrackingTapeLedger,
79533
- "motion-events": wireMotionEventLedger,
79534
- "audio-events": wireAudioEventLedger,
79535
- "sensor-events": wireSensorEventLedger,
79536
- "birth-decision-ledger": wireBirthDecisionLedger,
79537
- "debug-note-archive": wireDebugNoteArchiveLedger,
79538
- "ops-log": wireOpsLogLedger,
79539
- "retrain-frames": wireRetrainFrameLedger,
79540
- "retrain-annotations": wireRetrainAnnotationLedger,
79541
- "retrain-collections": wireRetrainCollectionLedger,
79542
- "retrain-collection-frames": wireRetrainCollectionFrameLedger
79543
- };
79544
- function isLedgerKindName(name) {
79545
- return Object.hasOwn(LEDGER_WIRES, name);
79546
- }
79547
- var [FIRST_LEDGER_KIND, ...OTHER_LEDGER_KINDS] = Object.keys(LEDGER_WIRES).filter(isLedgerKindName);
79548
- if (FIRST_LEDGER_KIND === void 0) throw new Error("ledger-index-backfill: no ledger kinds");
79549
- var LedgerBackfillInputSchema = require_clip_model_registry.object({
79550
- kind: require_clip_model_registry._enum([FIRST_LEDGER_KIND, ...OTHER_LEDGER_KINDS]),
79551
- dryRun: require_clip_model_registry.boolean().default(true),
79552
- /** The previous call's `nextCursor`. Omitted / null starts from the top. */
79553
- cursor: require_clip_model_registry.string().min(1).nullable().optional(),
79554
- /** Rows examined in this call. Default 20 000, max {@link LEDGER_BACKFILL_MAX_ROWS}. */
79555
- maxRows: require_clip_model_registry.number().int().min(1).max(LEDGER_BACKFILL_MAX_ROWS).optional()
79556
- });
79557
- var LedgerBackfillResultSchema = require_clip_model_registry.object({
79558
- kind: require_clip_model_registry.string(),
79559
- dryRun: require_clip_model_registry.boolean(),
79560
- /** The body fields this kind's indexes need from the header (`[]` = nothing to repair). */
79561
- fields: require_clip_model_registry.array(require_clip_model_registry.string()),
79562
- scanned: require_clip_model_registry.number().int(),
79563
- /** Rows lacking at least one of `fields`. */
79564
- missing: require_clip_model_registry.number().int(),
79565
- missingByField: require_clip_model_registry.record(require_clip_model_registry.string(), require_clip_model_registry.number().int()),
79566
- /** Rows patched (0 on a dry run). */
79567
- patched: require_clip_model_registry.number().int(),
79568
- /** Rows rewritten or deleted between the read and the patch: left to their writer. */
79569
- raced: require_clip_model_registry.number().int(),
79570
- /** Pass back to continue; `null` = the kind has been walked to the end. */
79571
- nextCursor: require_clip_model_registry.string().nullable()
79572
- });
79573
- var ledgerIndexBackfillActions = require_clip_model_registry.defineCustomActions({ "records.backfillIndexFields": require_clip_model_registry.customAction(LedgerBackfillInputSchema, LedgerBackfillResultSchema, {
79574
- kind: "mutation",
79575
- auth: "admin"
79576
- }) });
79577
- /** Run one bounded backfill page of `input.kind` through its ledger. */
79578
- async function backfillLedgerIndexFields(wiring, rawInput) {
79579
- const input = LedgerBackfillInputSchema.parse(rawInput);
79580
- const page = await LEDGER_WIRES[input.kind](wiring).backfillIndexFields({
79581
- dryRun: input.dryRun,
79582
- cursor: input.cursor ?? null,
79583
- maxRows: input.maxRows ?? LEDGER_BACKFILL_DEFAULT_ROWS
79584
- });
79585
- return {
79586
- ...page,
79587
- fields: [...page.fields],
79588
- dryRun: input.dryRun
79589
- };
79590
- }
79591
- //#endregion
79592
79536
  //#region src/pipeline-analytics/retention/media-reclaim.ts
79593
79537
  var MEDIA_RECLAIM_SCOPES = ["motion-still", "track-filmstrip"];
79594
79538
  var DEFAULT_MEDIA_RECLAIM_LIMITS = {
@@ -115640,7 +115584,6 @@ var customActions = {
115640
115584
  ...mediaReclaimActions,
115641
115585
  ...locationDiscardActions,
115642
115586
  ...mediaForgetActions,
115643
- ...ledgerIndexBackfillActions,
115644
115587
  ...photoEnrollActions,
115645
115588
  ...faceRescoreActions,
115646
115589
  ...stillnessCalibrationActions
@@ -117673,7 +117616,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
117673
117616
  ...mediaReclaimActions,
117674
117617
  ...locationDiscardActions,
117675
117618
  ...mediaForgetActions,
117676
- ...ledgerIndexBackfillActions,
117677
117619
  ...photoEnrollActions,
117678
117620
  ...faceRescoreActions,
117679
117621
  ...stillnessCalibrationActions
@@ -127484,11 +127426,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
127484
127426
  const deps = this.mediaForgetDeps;
127485
127427
  if (deps === null) throw new Error("storage.forgetMissingMedia: not initialised");
127486
127428
  return forgetMissingMediaRows(deps, input);
127487
- },
127488
- "records.backfillIndexFields": async (input) => {
127489
- const settingsStore = this.settingsStore;
127490
- if (!settingsStore) throw new Error("records.backfillIndexFields: not initialised");
127491
- return backfillLedgerIndexFields(this.eventLedgerWiring(settingsStore), input);
127492
127429
  }
127493
127430
  };
127494
127431
  }