@camstack/addon-post-analysis 1.2.627 → 1.2.628

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 (15) hide show
  1. package/dist/{clip-model-registry-RSxtlgdN.js → clip-model-registry-CK0QHBLo.js} +77 -198
  2. package/dist/{clip-model-registry-CCMO8jrp.mjs → clip-model-registry-CXp8hOtz.mjs} +77 -198
  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-CB62NeGh.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BSMQI14W.mjs} +2 -2
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CC5fsXdm.mjs +26 -0
  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-DdDfkgWU.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-BUdkjx1s.mjs → hostInit-DZur54eI.mjs} +2 -2
  10. package/dist/pipeline-analytics/index.js +150 -118
  11. package/dist/pipeline-analytics/index.mjs +150 -118
  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_types__loadShare__.js-DY-_tUCM.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-_Ip2ZGVX.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-RSxtlgdN.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-CK0QHBLo.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");
@@ -9913,7 +9913,6 @@ var SCENE_LATCH_SPEC = {
9913
9913
  fromRecord: latchFromRecord,
9914
9914
  deviceIdOf: (row) => row.deviceId
9915
9915
  };
9916
- SCENE_LATCH_SPEC.fromRecord, SCENE_LATCH_SPEC.toValue, SCENE_LATCH_SPEC.deviceIdOf;
9917
9916
  /**
9918
9917
  * The two ledgers behind one façade. Callers never see the split, but they can
9919
9918
  * never accidentally write one when they meant the other either: the config
@@ -22328,6 +22327,8 @@ function proposalOrigin(source, index, facts) {
22328
22327
  ...facts.modelId !== void 0 ? { modelId: facts.modelId } : {}
22329
22328
  };
22330
22329
  }
22330
+ //#endregion
22331
+ //#region src/pipeline-analytics/retrain/retrain-taxonomy.ts
22331
22332
  var MACRO_SET = new Set([
22332
22333
  "person",
22333
22334
  "vehicle",
@@ -25908,7 +25909,6 @@ var NC_ALERTS_SPEC = {
25908
25909
  equalFact: (held, incoming) => held.alerting === incoming.alerting,
25909
25910
  deviceIdOf: (row) => deviceIdOf(row.subject)
25910
25911
  };
25911
- NC_ALERTS_SPEC.fromRecord, NC_ALERTS_SPEC.toValue, NC_ALERTS_SPEC.deviceIdOf;
25912
25912
  /**
25913
25913
  * Which row the boot settle keeps for one subject observed inside the window
25914
25914
  * (D698's rule, applied to the alert ledger).
@@ -30641,7 +30641,6 @@ var NC_DEVICE_PRUNES_SPEC = {
30641
30641
  deviceIdOf: (row) => row.subjectKind === "device" ? Number(row.subject) : void 0,
30642
30642
  loadLimit: LOAD_LIMIT$1
30643
30643
  };
30644
- NC_DEVICE_PRUNES_SPEC.fromRecord, NC_DEVICE_PRUNES_SPEC.toValue, NC_DEVICE_PRUNES_SPEC.deviceIdOf;
30645
30644
  /** The outcomes that mean "this last reference was already dealt with". */
30646
30645
  var KEPT_OUTCOMES = new Set(["kept-disabled", "kept"]);
30647
30646
  var NcPruneLedger = class {
@@ -45648,7 +45647,6 @@ var NC_ALERT_EPISODES_SPEC = {
45648
45647
  };
45649
45648
  }
45650
45649
  };
45651
- NC_ALERT_EPISODES_SPEC.fromRecord, NC_ALERT_EPISODES_SPEC.toValue;
45652
45650
  var NcAlertEpisodeLedger = class {
45653
45651
  ledger;
45654
45652
  constructor(deps) {
@@ -45806,6 +45804,8 @@ function occupancyClassWarning(className) {
45806
45804
  if (className === void 0 || require_clip_model_registry.isDetectionMacroClass(className)) return null;
45807
45805
  return `⚠ “${className}” is a sub-type, not a macro class: this counter is always 0 (use ${[...require_clip_model_registry.DETECTION_MACRO_CLASSES].join(" / ")})`;
45808
45806
  }
45807
+ //#endregion
45808
+ //#region src/notification-center/occupancy-store.ts
45809
45809
  /**
45810
45810
  * The mechanics, declared once.
45811
45811
  *
@@ -45957,7 +45957,6 @@ function recordToRow(key, data) {
45957
45957
  updatedAt
45958
45958
  };
45959
45959
  }
45960
- NC_OCCUPANCY_SPEC.fromRecord, NC_OCCUPANCY_SPEC.toValue, NC_OCCUPANCY_SPEC.deviceIdOf;
45961
45960
  //#endregion
45962
45961
  //#region src/notification-center/outbox.ts
45963
45962
  /**
@@ -48428,7 +48427,6 @@ var NC_SNOOZES_SPEC = {
48428
48427
  },
48429
48428
  loadLimit: 1e3
48430
48429
  };
48431
- NC_SNOOZES_SPEC.fromRecord, NC_SNOOZES_SPEC.toValue;
48432
48430
  var NcSnoozeStore = class {
48433
48431
  windows;
48434
48432
  /** The `snooze-suppressed` records kind (events.db): the counters. */
@@ -52526,7 +52524,6 @@ var NC_SYSTEM_STATE_SPEC = {
52526
52524
  },
52527
52525
  deviceIdOf: (row) => row.deviceId
52528
52526
  };
52529
- NC_SYSTEM_STATE_SPEC.fromRecord, NC_SYSTEM_STATE_SPEC.toValue, NC_SYSTEM_STATE_SPEC.deviceIdOf;
52530
52527
  /** The hub's own node id when an update row names none. */
52531
52528
  var HUB_NODE = "hub";
52532
52529
  function isUpdateTarget(value) {
@@ -53612,6 +53609,8 @@ var NcStateSummaryScheduler = class {
53612
53609
  }
53613
53610
  }
53614
53611
  };
53612
+ //#endregion
53613
+ //#region src/notification-center/state-summary/state-summary-stamps.ts
53615
53614
  var SPEC = {
53616
53615
  collection: "notification-center:state-summary-stamps",
53617
53616
  columns: [
@@ -53659,7 +53658,6 @@ var SPEC = {
53659
53658
  },
53660
53659
  loadLimit: 1e4
53661
53660
  };
53662
- SPEC.fromRecord, SPEC.toValue;
53663
53661
  var NcStateSummaryStampStore = class {
53664
53662
  ledger;
53665
53663
  constructor(deps) {
@@ -74888,8 +74886,8 @@ var TrackCloser = class {
74888
74886
  }
74889
74887
  });
74890
74888
  }
74891
- /** Emit the deprecated thin `TrackEnded` alias + the unified lifecycle
74892
- * `end` event (the FULL final summary — fires ONCE per real track). */
74889
+ /** Emit the thin `TrackEnded` event + the unified lifecycle `end` event
74890
+ * (the FULL final summary — fires ONCE per real track). */
74893
74891
  emitEndEvents(t, duration, trackerPeakConfidence, keyFrameMediaId, summary) {
74894
74892
  this.deps.emitTrackEnded({
74895
74893
  deviceId: t.deviceId,
@@ -79215,22 +79213,22 @@ function buildTapeDeleteDeps(src) {
79215
79213
  removeFile: src.removeFile
79216
79214
  };
79217
79215
  }
79218
- function unavailable(reason) {
79216
+ function unavailable$1(reason) {
79219
79217
  return {
79220
79218
  status: "unavailable",
79221
79219
  reason
79222
79220
  };
79223
79221
  }
79224
79222
  async function releaseOrUnavailable(deps, tapeId, queue) {
79225
- if (typeof deps === "string") return unavailable(deps);
79223
+ if (typeof deps === "string") return unavailable$1(deps);
79226
79224
  return queue.run((signal) => releaseTape(deps, tapeId, signal), { name: "release" });
79227
79225
  }
79228
79226
  async function deleteOrUnavailable(deps, tapeId, queue) {
79229
- if (typeof deps === "string") return unavailable(deps);
79227
+ if (typeof deps === "string") return unavailable$1(deps);
79230
79228
  return queue.run((signal) => deleteTape(deps, tapeId, signal), { name: "delete" });
79231
79229
  }
79232
79230
  async function reconcileOrUnavailable(deps, queue) {
79233
- if (typeof deps === "string") return unavailable(deps);
79231
+ if (typeof deps === "string") return unavailable$1(deps);
79234
79232
  return queue.run((signal) => reconcileTapePins(deps, signal), { name: "reconcile" });
79235
79233
  }
79236
79234
  /**
@@ -83794,6 +83792,9 @@ function isConfirmationCompatible(trackClassName, detectionMacroClass) {
83794
83792
  if (track === "other" || det === "other") return true;
83795
83793
  return track === det;
83796
83794
  }
83795
+ function isSessionDetectorUnavailable(answer) {
83796
+ return answer !== null && "unavailable" in answer;
83797
+ }
83797
83798
  var undecided = (candidate, reason) => ({
83798
83799
  trackId: candidate.trackId,
83799
83800
  verdict: "undecided",
@@ -83816,7 +83817,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
83816
83817
  const carried = candidate.evidenceCrop ?? null;
83817
83818
  if (carried) {
83818
83819
  const verdict = await measure(candidate, carried, config, deps, "carried");
83819
- if (verdict.verdict === "confirmed" || verdict.reason === "redetect-error") return verdict;
83820
+ if (verdict.verdict === "confirmed" || verdict.reason === "redetect-error" || verdict.reason === "session-device-unavailable") return verdict;
83820
83821
  }
83821
83822
  let crop = await deps.fetchCrop(candidate);
83822
83823
  let cropSource = "native";
@@ -83838,32 +83839,26 @@ async function measure(candidate, crop, config, deps, cropSource) {
83838
83839
  deps.keepCrop?.(candidate, crop, cropSource);
83839
83840
  const detections = await deps.redetect(crop);
83840
83841
  if (detections === null) return undecided(candidate, "redetect-error");
83842
+ if (isSessionDetectorUnavailable(detections)) return undecided(candidate, "session-device-unavailable");
83841
83843
  let best;
83842
83844
  let bestIncompatible;
83843
- let bestRefused;
83844
- for (const d of detections) {
83845
- const compatible = isConfirmationCompatible(candidate.className, d.macroClass);
83846
- if (d.refusedByDetectorFloor === true) {
83847
- if (compatible && (!bestRefused || d.score > bestRefused.score)) bestRefused = d;
83848
- continue;
83849
- }
83850
- if (compatible) {
83851
- if (!best || d.score > best.score) best = d;
83852
- } else if (!bestIncompatible || d.score > bestIncompatible.score) bestIncompatible = d;
83845
+ for (const d of detections) if (isConfirmationCompatible(candidate.className, d.macroClass)) {
83846
+ if (!best || d.score > best.score) best = d;
83847
+ } else if (d.refusedByDetectorFloor !== true) {
83848
+ if (!bestIncompatible || d.score > bestIncompatible.score) bestIncompatible = d;
83853
83849
  }
83854
83850
  const bar = barFor(candidate, config, deps);
83855
83851
  const confirmed = best !== void 0 && best.score >= bar;
83856
83852
  if (!confirmed && cropSource !== "native") return undecided(candidate, cropSource === "fallback" ? "fallback-inconclusive" : "carried-inconclusive");
83857
83853
  const secondary = bestIncompatible ? promoteSecondary(candidate, bestIncompatible, bar, deps) : null;
83858
- const scored = best ?? bestRefused;
83859
83854
  return {
83860
83855
  trackId: candidate.trackId,
83861
83856
  verdict: confirmed ? "confirmed" : "suppressed",
83862
- reason: confirmed ? "confirmed" : suppressionReason(best, bestRefused, bestIncompatible),
83857
+ reason: confirmed ? "confirmed" : suppressionReason(best, bestIncompatible),
83863
83858
  className: candidate.className,
83864
83859
  appliedMinConfidence: bar,
83865
83860
  cropSource,
83866
- ...scored ? { bestScore: scored.score } : {},
83861
+ ...best ? { bestScore: best.score } : {},
83867
83862
  ...bestIncompatible ? {
83868
83863
  bestIncompatibleClass: bestIncompatible.macroClass,
83869
83864
  bestIncompatibleScore: bestIncompatible.score
@@ -83871,10 +83866,13 @@ async function measure(candidate, crop, config, deps, cropSource) {
83871
83866
  ...secondary ? { secondary } : {}
83872
83867
  };
83873
83868
  }
83874
- /** Why a measured native crop said no — first matching rule wins (D922). */
83875
- function suppressionReason(best, bestRefused, bestIncompatible) {
83876
- if (best !== void 0) return "scored-below-threshold";
83877
- if (bestRefused !== void 0) return "below-detector-floor";
83869
+ /**
83870
+ * Why a measured native crop said no — first matching rule wins (D922). The
83871
+ * best compatible box is under the gate's bar either way; `below-detector-floor`
83872
+ * says the detector's own floor had refused it too (D925).
83873
+ */
83874
+ function suppressionReason(best, bestIncompatible) {
83875
+ if (best !== void 0) return best.refusedByDetectorFloor === true ? "below-detector-floor" : "scored-below-threshold";
83878
83876
  if (bestIncompatible !== void 0) return "incompatible-class";
83879
83877
  return "no-detection";
83880
83878
  }
@@ -86624,11 +86622,9 @@ function faceSizeScore(detectionFrameShortSidePx) {
86624
86622
  return detectionFrameShortSidePx / FACE_SIZE_SCORE_REFERENCE_PX;
86625
86623
  }
86626
86624
  //#endregion
86627
- //#region src/pipeline-analytics/face-identity-retraction.ts
86625
+ //#region src/pipeline-analytics/face-recognizer.ts
86628
86626
  /** The recognizer's step id — the attribution a face identity label carries. */
86629
86627
  var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
86630
- //#endregion
86631
- //#region src/pipeline-analytics/face-recognizer.ts
86632
86628
  /** At most one "dropping imageless track" log per this interval, per recognizer. */
86633
86629
  var FACE_IMAGELESS_LOG_THROTTLE_MS = 6e4;
86634
86630
  /** At most one 'embedding magnitude' sample line per device per this interval. */
@@ -93068,6 +93064,9 @@ function buildBirthDecisionRecord(input) {
93068
93064
  bestScore: input.bestScore ?? null,
93069
93065
  appliedMinConfidence: input.appliedMinConfidence ?? null,
93070
93066
  cropSource: input.cropSource ?? null,
93067
+ detectorModelId: input.detector?.modelId ?? null,
93068
+ detectorDeviceKey: input.detector?.deviceKey ?? null,
93069
+ detectorNodeId: input.detector?.nodeId ?? null,
93071
93070
  firstSeen,
93072
93071
  ...latency
93073
93072
  };
@@ -93098,6 +93097,85 @@ function toConfirmationDetections(result) {
93098
93097
  return [...kept, ...refused];
93099
93098
  }
93100
93099
  //#endregion
93100
+ //#region src/pipeline-analytics/confirmation-session-detector.ts
93101
+ /**
93102
+ * The detector the birth confirmation gate re-judges a birth WITH: the
93103
+ * session's own (D925).
93104
+ *
93105
+ * A birth is a claim made by one detector — the root model of the session that
93106
+ * produced the frame, on the inference-device pool it ran on. The gate asks
93107
+ * whether that claim holds on better pixels, so it must ask the SAME detector:
93108
+ * a different model has a different score scale and a different floor, and its
93109
+ * "no" says nothing about the claim. It used to ask the node's global default
93110
+ * (`getGlobalSteps`), which on a Coral node meant a CPU `yolov9t` re-judging an
93111
+ * `edgetpu` model's births.
93112
+ *
93113
+ * The identity rides the frame result (`rootStepId` / `rootModelId` /
93114
+ * `rootDeviceKey`, stamped by the executor with what it RAN), and the node is
93115
+ * the frame handle's — the same node the crop is cut on, so no new bytes cross
93116
+ * anywhere.
93117
+ *
93118
+ * When that detector cannot answer — no identity on the frame (an onboard
93119
+ * detector), the device in backoff or failed, or the executor running anything
93120
+ * other than exactly that model on exactly that pool — the answer is
93121
+ * `session-device-unavailable`: NO measurement. The gate defers the birth and
93122
+ * admits it once the deferral is exhausted. Another model is never asked.
93123
+ *
93124
+ * PURE apart from the injected `run`, so the matrix is testable without an
93125
+ * executor.
93126
+ */
93127
+ /** The session detector named by a frame, or `null` when the frame names none in full. */
93128
+ function sessionDetectorOf(frame, nodeId) {
93129
+ const { rootStepId, rootModelId, rootDeviceKey } = frame;
93130
+ if (!rootStepId || !rootModelId || !rootDeviceKey || !nodeId) return null;
93131
+ return {
93132
+ stepId: rootStepId,
93133
+ modelId: rootModelId,
93134
+ deviceKey: rootDeviceKey,
93135
+ nodeId
93136
+ };
93137
+ }
93138
+ /**
93139
+ * The executor's refusal of a device it will not dispatch to: the liveness
93140
+ * refusal (`InferenceDeviceUnusableError`), a device key whose backend this
93141
+ * node does not know, or a pinned pool that cannot serve. Read from the
93142
+ * message because the error crosses a process boundary without its class.
93143
+ */
93144
+ var DEVICE_REFUSAL = /inference device ".+" is (failed|backoff)|unknown backend|device-not-servable|model-not-servable/;
93145
+ /** Re-detect a crop with the session detector, or say why it could not. */
93146
+ async function redetectWithSessionDetector(detector, cropJpeg, deviceId, run) {
93147
+ if (detector === null) return unavailable("the frame names no session detector");
93148
+ let result;
93149
+ try {
93150
+ result = await run({
93151
+ steps: [{
93152
+ addonId: detector.stepId,
93153
+ modelId: detector.modelId,
93154
+ enabled: true
93155
+ }],
93156
+ image: new Uint8Array(cropJpeg),
93157
+ deviceId,
93158
+ plane: "frame",
93159
+ deviceKey: detector.deviceKey
93160
+ }, detector.nodeId);
93161
+ } catch (err) {
93162
+ const message = require_clip_model_registry.errMsg(err);
93163
+ if (DEVICE_REFUSAL.test(message)) return unavailable(message);
93164
+ return {
93165
+ answer: null,
93166
+ error: message
93167
+ };
93168
+ }
93169
+ if (result !== null && (result.rootModelId !== detector.modelId || result.rootDeviceKey !== detector.deviceKey)) return unavailable(`ran ${result.rootModelId ?? "an unnamed model"} on ${result.rootDeviceKey ?? "an unnamed pool"}, not ${detector.modelId} on ${detector.deviceKey}`);
93170
+ return { answer: toConfirmationDetections(result) };
93171
+ }
93172
+ function unavailable(detail) {
93173
+ return { answer: {
93174
+ unavailable: "session-device-unavailable",
93175
+ detail
93176
+ } };
93177
+ }
93178
+ //#endregion
93101
93179
  //#region src/pipeline-analytics/pipeline/birth-evidence-store.ts
93102
93180
  /**
93103
93181
  * The pixels a birth arrived with, held for as long as its deferral can last —
@@ -94007,7 +94085,6 @@ var PHANTOM_CELLS_SPEC = {
94007
94085
  },
94008
94086
  deviceIdOf: (row) => row.deviceId
94009
94087
  };
94010
- PHANTOM_CELLS_SPEC.fromRecord, PHANTOM_CELLS_SPEC.toValue, PHANTOM_CELLS_SPEC.deviceIdOf;
94011
94088
  /**
94012
94089
  * The durable cell memory. Reads answer from RAM only (D49) — a gate that
94013
94090
  * consulted SQLite would be a gate that fails when SQLite is slow.
@@ -94207,7 +94284,6 @@ var PACKAGE_BACKGROUND_SPEC = {
94207
94284
  },
94208
94285
  deviceIdOf: (row) => row.deviceId
94209
94286
  };
94210
- PACKAGE_BACKGROUND_SPEC.fromRecord, PACKAGE_BACKGROUND_SPEC.toValue, PACKAGE_BACKGROUND_SPEC.deviceIdOf;
94211
94287
  /**
94212
94288
  * The durable ghost-cycle memory. Reads answer from RAM only (D49); writes go
94213
94289
  * write-behind — a lost write costs one under-counted cycle, which is the
@@ -96772,7 +96848,6 @@ var STATIONARY_EVIDENCE_SPEC = {
96772
96848
  },
96773
96849
  deviceIdOf: (row) => row.deviceId
96774
96850
  };
96775
- STATIONARY_EVIDENCE_SPEC.fromRecord, STATIONARY_EVIDENCE_SPEC.toValue, STATIONARY_EVIDENCE_SPEC.deviceIdOf;
96776
96851
  var StationaryEvidenceLedger = class {
96777
96852
  ledger;
96778
96853
  spec = STATIONARY_EVIDENCE_SPEC;
@@ -97307,7 +97382,6 @@ var STATIONARY_SPEC = {
97307
97382
  fromRecord: rowToEntry,
97308
97383
  deviceIdOf: (entry) => entry.deviceId
97309
97384
  };
97310
- STATIONARY_SPEC.fromRecord, STATIONARY_SPEC.toValue, STATIONARY_SPEC.deviceIdOf;
97311
97385
  var StationaryObjectRegistry = class {
97312
97386
  /** Entry ids per device — an INDEX over the ledger's mirror, not a second
97313
97387
  * copy of it. The entries themselves live in exactly one place. */
@@ -113388,9 +113462,6 @@ var KEYFRAME_CROP_MIN_SUBJECT_IOU = .5;
113388
113462
  * evidence" and the temporal bound takes over. */
113389
113463
  var KEYFRAME_CROP_MAX_SAMPLE_GAP_MS = 1e3;
113390
113464
  var SETTINGS_CACHE_TTL_MS = 5e3;
113391
- /** TTL for the per-node confirmation-gate detector-step cache (the node's
113392
- * default object-detector step rarely changes). */
113393
- var CONFIRMATION_DETECTOR_CACHE_TTL_MS = 6e4;
113394
113465
  /** Padding applied to a birth box before re-detecting it — a little context
113395
113466
  * around the subject improves the confirmation detector's recall. */
113396
113467
  var CONFIRMATION_CROP_PADDING = .15;
@@ -114178,11 +114249,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
114178
114249
  trackingSettingsCache = this.settingsCaches.registerDeviceCache("tracking", SETTINGS_CACHE_TTL_MS);
114179
114250
  /** Per-device confirmation-gate settings cache (default off), TTL-mirrored. */
114180
114251
  confirmationGateSettingsCache = this.settingsCaches.registerDeviceCache("confirmation-gate", SETTINGS_CACHE_TTL_MS);
114181
- /** Per-node detector-step cache for the confirmation gate's re-detection —
114182
- * the node's default root object-detector reduced to a single childless
114183
- * step, resolved lazily from `pipelineExecutor.getGlobalSteps` and reused.
114184
- * NOT part of the settings-cache hub: node-keyed, not device-keyed. */
114185
- confirmationDetectorStepByNode = /* @__PURE__ */ new Map();
114186
114252
  faceSettingsCache = this.settingsCaches.registerDeviceCache("face", SETTINGS_CACHE_TTL_MS);
114187
114253
  /** GLOBAL face-recognition master switch (addon store), TTL-cached. */
114188
114254
  faceGlobalEnabledCache = this.settingsCaches.registerGlobalCache("face-global-enabled", SETTINGS_CACHE_TTL_MS);
@@ -118452,7 +118518,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
118452
118518
  this.settingsCaches.clearAll();
118453
118519
  this.rawDeviceBlobMirror.invalidateAll();
118454
118520
  this.rawGlobalStoreMirror.invalidate();
118455
- this.confirmationDetectorStepByNode.clear();
118456
118521
  this.trackStore?.clearAll();
118457
118522
  this.stationaryRegistry = null;
118458
118523
  this.bindingCache?.clearAll();
@@ -119110,6 +119175,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
119110
119175
  this.birthEvidence.remember(key, id, Buffer.from(t.birthEvidence.jpegBase64, "base64"), exhaustionNowMs);
119111
119176
  }
119112
119177
  const withBirthEvidence = /* @__PURE__ */ new Set();
119178
+ const sessionDetector = frameHandle ? sessionDetectorOf(frame, frameHandle.nodeId) : null;
119113
119179
  const run = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => {
119114
119180
  const evidenceCrop = this.birthEvidence.get(key, id, exhaustionNowMs);
119115
119181
  if (evidenceCrop !== null) withBirthEvidence.add(id);
@@ -119125,7 +119191,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
119125
119191
  source,
119126
119192
  frameHandle,
119127
119193
  frameWidth: result.frameWidth,
119128
- frameHeight: result.frameHeight
119194
+ frameHeight: result.frameHeight,
119195
+ sessionDetector,
119196
+ attemptsFor: (trackId) => this.deferredBirths.attemptsFor(key, trackId)
119129
119197
  }, exhaustedIds);
119130
119198
  const outcome = run.outcome;
119131
119199
  const gateNowMs = Date.now();
@@ -119180,6 +119248,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
119180
119248
  matchedThisFrame: t.matchedThisFrame,
119181
119249
  birthEvidenceAvailable: withBirthEvidence.has(id),
119182
119250
  decision: run.decisions.get(id),
119251
+ sessionDetector,
119183
119252
  decidedAtMs: gateNowMs,
119184
119253
  firstSeen: this.trackStore?.peekActive(id)?.firstSeen ?? null
119185
119254
  });
@@ -119949,74 +120018,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
119949
120018
  return this.confirmationGateSettingsCache.get(deviceId, (id) => this.readDeviceSettings(id, resolveConfirmationGateSettings));
119950
120019
  }
119951
120020
  /**
119952
- * Narrowed, version-skew-safe handle onto the `pipelineExecutor` cap — same
119953
- * structural-cast pattern as the `pipelineRunner` handle above. A node whose
119954
- * executor predates these methods simply yields `undefined`, and the
119955
- * confirmation gate then fails open (allows the birth).
119956
- */
119957
- pipelineExecutorApi() {
119958
- const api = this.ctx?.api;
119959
- if (!api) return void 0;
119960
- return api.pipelineExecutor;
119961
- }
119962
- /**
119963
- * Resolve (and cache) the confirmation gate's re-detection step for a node:
119964
- * the node's DEFAULT root object-detector (from `getGlobalSteps`) reduced to a
119965
- * single CHILDLESS step, so the confirmation run does detection ONLY — no
119966
- * crop/classifier subtree. Cached per node (TTL) since the node default rarely
119967
- * changes. `null` ⇒ no detector available ⇒ the gate fails open.
120021
+ * Re-run object detection on a confirmation crop JPEG with the SESSION'S
120022
+ * detector (D925): the root model the birth's frame ran, on the device pool
120023
+ * it ran on, pinned to that frame's node — the node the crop is cut on, so
120024
+ * no new bytes cross anywhere. A session detector that cannot answer is
120025
+ * `session-device-unavailable` (no measurement, the birth defers); another
120026
+ * model is never asked. An inference failure is `null` (`redetect-error`).
119968
120027
  */
119969
- async resolveConfirmationDetectorStep(nodeId) {
119970
- const now = Date.now();
119971
- const cached = this.confirmationDetectorStepByNode.get(nodeId);
119972
- if (cached && now < cached.expiresAt) return cached.step;
119973
- let step = null;
119974
- const executor = this.pipelineExecutorApi();
119975
- if (executor?.getGlobalSteps) try {
119976
- const steps = await executor.getGlobalSteps.query(void 0, require_clip_model_registry.nodePin(nodeId));
119977
- const detector = steps?.find((s) => s.slot === "detector" && s.enabled) ?? steps?.find((s) => s.slot === "detector");
119978
- if (detector) step = {
119979
- addonId: detector.addonId,
119980
- ...detector.modelId ? { modelId: detector.modelId } : {},
119981
- enabled: true
119982
- };
119983
- } catch (err) {
119984
- this.ctx.logger.debug("confirmation gate: getGlobalSteps failed", { meta: {
119985
- nodeId,
119986
- error: require_clip_model_registry.errMsg(err)
119987
- } });
119988
- }
119989
- this.confirmationDetectorStepByNode.set(nodeId, {
119990
- step,
119991
- expiresAt: now + CONFIRMATION_DETECTOR_CACHE_TTL_MS
120028
+ async redetectCropForConfirmation(detector, deviceId, cropJpeg) {
120029
+ const outcome = await redetectWithSessionDetector(detector, cropJpeg, deviceId, (request, nodeId) => this.ctx.api.pipelineExecutor.runPipeline.mutate({
120030
+ ...request,
120031
+ steps: [...request.steps]
120032
+ }, require_clip_model_registry.nodePin(nodeId)));
120033
+ if (outcome.error !== void 0) this.ctx.logger.warn("confirmation gate: re-detection failed — birth deferred", {
120034
+ tags: { deviceId },
120035
+ meta: {
120036
+ error: outcome.error,
120037
+ ...detector !== null ? {
120038
+ modelId: detector.modelId,
120039
+ deviceKey: detector.deviceKey,
120040
+ node: detector.nodeId
120041
+ } : {}
120042
+ }
119992
120043
  });
119993
- return step;
119994
- }
119995
- /**
119996
- * Re-run object detection on a confirmation crop JPEG via the frame-owning
119997
- * node's `pipelineExecutor` (pinned by `nodeId`, matching the native-crop
119998
- * fetch). Returns the crop's detections, or `null` on any unavailability /
119999
- * error so the gate fails open.
120000
- */
120001
- async redetectCropForConfirmation(nodeId, deviceId, cropJpeg) {
120002
- const step = await this.resolveConfirmationDetectorStep(nodeId);
120003
- if (!step) return null;
120004
- const executor = this.pipelineExecutorApi();
120005
- if (!executor?.runPipeline) return null;
120006
- try {
120007
- return toConfirmationDetections(await executor.runPipeline.mutate({
120008
- steps: [step],
120009
- image: new Uint8Array(cropJpeg),
120010
- deviceId,
120011
- plane: "frame"
120012
- }, require_clip_model_registry.nodePin(nodeId)));
120013
- } catch (err) {
120014
- this.ctx.logger.debug("confirmation gate: re-detection failed — fail-open", {
120015
- tags: { deviceId },
120016
- meta: { error: require_clip_model_registry.errMsg(err) }
120017
- });
120018
- return null;
120019
- }
120044
+ return outcome.answer;
120020
120045
  }
120021
120046
  /**
120022
120047
  * Confirm a batch of track-birth candidates before they are created. Returns
@@ -120071,6 +120096,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
120071
120096
  bestScore: decision?.bestScore ?? null,
120072
120097
  appliedMinConfidence: decision?.appliedMinConfidence ?? null,
120073
120098
  cropSource: decision?.cropSource ?? null,
120099
+ detector: decision !== void 0 && (decision.cropSource !== void 0 || decision.reason === "session-device-unavailable") ? input.sessionDetector ?? null : null,
120074
120100
  decidedAtMs: input.decidedAtMs,
120075
120101
  firstSeen: input.firstSeen,
120076
120102
  motionOnsetAtMs,
@@ -120108,7 +120134,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
120108
120134
  });
120109
120135
  return allConfirmed();
120110
120136
  }
120111
- const nodeId = frameHandle.nodeId;
120137
+ const { sessionDetector } = params;
120112
120138
  const candidateNativeRecovery = /* @__PURE__ */ new Map();
120113
120139
  for (const c of candidates) if (c.nativeRecovery !== void 0) candidateNativeRecovery.set(c.trackId, c.nativeRecovery);
120114
120140
  return {
@@ -120135,7 +120161,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
120135
120161
  h: candidate.bbox.h
120136
120162
  }, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING, candidate.trackId, CONFIRMATION_CROP_MAX_WIDTH);
120137
120163
  },
120138
- redetect: (cropJpeg) => this.redetectCropForConfirmation(nodeId, deviceId, cropJpeg),
120164
+ redetect: (cropJpeg) => this.redetectCropForConfirmation(sessionDetector, deviceId, cropJpeg),
120139
120165
  keepCrop: (candidate, cropJpeg) => {
120140
120166
  offeredCrops.set(candidate.trackId, cropJpeg);
120141
120167
  },
@@ -120160,6 +120186,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
120160
120186
  reason: decision.reason,
120161
120187
  className: decision.className,
120162
120188
  ...decision.bestScore !== void 0 ? { bestScore: decision.bestScore } : {},
120189
+ ...sessionDetector !== null ? {
120190
+ modelId: sessionDetector.modelId,
120191
+ deviceKey: sessionDetector.deviceKey,
120192
+ node: sessionDetector.nodeId
120193
+ } : {},
120194
+ attempts: params.attemptsFor(decision.trackId),
120163
120195
  ...decision.bestIncompatibleClass !== void 0 ? {
120164
120196
  bestIncompatibleClass: decision.bestIncompatibleClass,
120165
120197
  bestIncompatibleScore: decision.bestIncompatibleScore