@camstack/addon-post-analysis 1.2.214 → 1.2.216

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_dist = require("../dist-DA1sJKXM.js");
5
+ const require_dist = require("../dist-B_6UZCut.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -3224,8 +3224,8 @@ async function raceTimeout(call, timeoutMs) {
3224
3224
  * detail the encoder can see.
3225
3225
  */
3226
3226
  async function downscaleJpeg(bytes, maxPx) {
3227
- const { default: sharp$15 } = await import("sharp");
3228
- const out = await sharp$15(Buffer.from(bytes)).resize({
3227
+ const { default: sharp$16 } = await import("sharp");
3228
+ const out = await sharp$16(Buffer.from(bytes)).resize({
3229
3229
  width: maxPx,
3230
3230
  height: maxPx,
3231
3231
  fit: "inside",
@@ -11741,8 +11741,8 @@ var NcDispatcher = class {
11741
11741
  try {
11742
11742
  const points = (await this.deps.getZonePolygons?.(deviceId, zoneIds) ?? []).flat();
11743
11743
  if (points.length === 0) return null;
11744
- const { default: sharp$14 } = await import("sharp");
11745
- const img = sharp$14(Buffer.from(jpeg));
11744
+ const { default: sharp$15 } = await import("sharp");
11745
+ const img = sharp$15(Buffer.from(jpeg));
11746
11746
  const meta = await img.metadata();
11747
11747
  const W = meta.width ?? 0;
11748
11748
  const H = meta.height ?? 0;
@@ -14921,7 +14921,7 @@ function buildOccupancyStatus(input) {
14921
14921
  * `this.byId.clear()` happened to sit AFTER the `await` that threw. Nothing
14922
14922
  * said so, and hoisting one line would have silently turned every transient
14923
14923
  * store error into a hub that stops notifying — the reversal behind the D130
14924
- * flood and the `3f9345fc3` occupancy cold-seed loss. The ledger's contract
14924
+ * flood and the occupancy cold-seed loss. The ledger's contract
14925
14925
  * rule 2 (a failed load NEVER clears the mirror, deliberately no knob) makes
14926
14926
  * that reversal unexpressible here. `rule-store-load-failure-direction.spec.ts`
14927
14927
  * holds the direction and records what was removed to make it red.
@@ -18138,7 +18138,7 @@ function clockOf$2(atMs) {
18138
18138
  *
18139
18139
  * ## The `.partial()` trap this file exists to avoid
18140
18140
  *
18141
- * Commit `e0921ac6e`, found in production 2026-08-12: `NcRulePatchSchema` was
18141
+ * Found in production 2026-08-12: `NcRulePatchSchema` was
18142
18142
  * `NcRuleInputSchema.partial()`, and Zod 4 `.partial()` KEEPS the inner
18143
18143
  * `.default()`. Parsing `{ name: 'x' }` therefore materialised every defaulted
18144
18144
  * key, and `{ ...existing, ...patch }` wrote them over the rule — a camera- and
@@ -19033,8 +19033,8 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
19033
19033
  ];
19034
19034
  if (bbox === void 0) return files;
19035
19035
  try {
19036
- const { default: sharp$13 } = await import("sharp");
19037
- const meta = await sharp$13(Buffer.from(jpeg)).metadata();
19036
+ const { default: sharp$14 } = await import("sharp");
19037
+ const meta = await sharp$14(Buffer.from(jpeg)).metadata();
19038
19038
  const W = meta.width ?? 0;
19039
19039
  const H = meta.height ?? 0;
19040
19040
  if (W === 0 || H === 0) return files;
@@ -19042,7 +19042,7 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
19042
19042
  const top = Math.max(0, Math.min(H - 1, Math.floor(bbox.y * H)));
19043
19043
  const width = Math.max(1, Math.min(W - left, Math.ceil(bbox.w * W)));
19044
19044
  const height = Math.max(1, Math.min(H - top, Math.ceil(bbox.h * H)));
19045
- const cropBuf = await sharp$13(Buffer.from(jpeg)).extract({
19045
+ const cropBuf = await sharp$14(Buffer.from(jpeg)).extract({
19046
19046
  left,
19047
19047
  top,
19048
19048
  width,
@@ -19064,7 +19064,7 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
19064
19064
  });
19065
19065
  const stroke = Math.max(2, Math.round(Math.min(W, H) / 240));
19066
19066
  const overlay = Buffer.from(`<svg width="${W}" height="${H}"><rect x="${left}" y="${top}" width="${width}" height="${height}" fill="none" stroke="#22d3ee" stroke-width="${stroke}"/></svg>`);
19067
- const boxedBuf = await sharp$13(Buffer.from(jpeg)).composite([{
19067
+ const boxedBuf = await sharp$14(Buffer.from(jpeg)).composite([{
19068
19068
  input: overlay,
19069
19069
  top: 0,
19070
19070
  left: 0
@@ -34906,6 +34906,8 @@ var SortTracker = class SortTracker {
34906
34906
  refusalSampler = new SpawnRefusalSampler();
34907
34907
  /** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
34908
34908
  spawnRefusalObserver = null;
34909
+ /** Unsampled copy for the frame path — see `setSpawnRefusalSink`. */
34910
+ refusalSink = null;
34909
34911
  /** Per-reason counters behind the sampled spawn-beside-coasting diagnostics. */
34910
34912
  spawnBesideSampler = new SpawnBesideCoastingSampler();
34911
34913
  /** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
@@ -35018,11 +35020,7 @@ var SortTracker = class SortTracker {
35018
35020
  }
35019
35021
  /** Emit one dropped-detection record, subject to the refusal sampler. */
35020
35022
  reportRefusal(reason, det, extra = {}) {
35021
- const observer = this.spawnRefusalObserver;
35022
- if (!observer) return;
35023
- const refusalCount = this.refusalSampler.next(reason);
35024
- if (refusalCount === void 0) return;
35025
- observer({
35023
+ const base = {
35026
35024
  reason,
35027
35025
  detClass: det.class,
35028
35026
  detScore: det.score,
@@ -35034,13 +35032,31 @@ var SortTracker = class SortTracker {
35034
35032
  againstTrackId: extra.against.id,
35035
35033
  againstTrackClass: extra.against.class
35036
35034
  } : {},
35037
- refusalCount,
35038
- sampledEvery: 10,
35039
35035
  ...extra.droppedTrackId !== void 0 ? { droppedTrackId: extra.droppedTrackId } : {},
35040
35036
  ...extra.trackLifeMs !== void 0 ? { trackLifeMs: extra.trackLifeMs } : {}
35037
+ };
35038
+ this.refusalSink?.(base);
35039
+ const observer = this.spawnRefusalObserver;
35040
+ if (!observer) return;
35041
+ const refusalCount = this.refusalSampler.next(reason);
35042
+ if (refusalCount === void 0) return;
35043
+ observer({
35044
+ ...base,
35045
+ refusalCount,
35046
+ sampledEvery: 10
35041
35047
  });
35042
35048
  }
35043
35049
  /**
35050
+ * Wire the UNSAMPLED refusal sink — the frame path's copy.
35051
+ *
35052
+ * Separate from {@link setSpawnRefusalObserver} because the two want
35053
+ * different things from the same record: the observer wants a readable log
35054
+ * and accepts a sample; this decides whether an EVENT exists.
35055
+ */
35056
+ setSpawnRefusalSink(sink) {
35057
+ this.refusalSink = sink;
35058
+ }
35059
+ /**
35044
35060
  * Report a TRACK that ends having emitted nothing — the last silent branch
35045
35061
  * on the detection→position path (D391).
35046
35062
  *
@@ -36134,6 +36150,20 @@ var FrameProcessor = class {
36134
36150
  /** Sink + sampler for the drops this stage owns (the detection-stage zone
36135
36151
  * filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
36136
36152
  spawnRefusalObserver = null;
36153
+ /** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
36154
+ spawnRefusalForward = null;
36155
+ /**
36156
+ * The spawn refusals of the frame being processed.
36157
+ *
36158
+ * Filled by the same sink that feeds the diagnostic log, and drained at the
36159
+ * end of `process`. It exists because a refused detection inside a watched
36160
+ * zone still has to become an EVENT: an immediate zone rule asks "is there an
36161
+ * animal in the flowerbed now?", which does not need "is it the same animal
36162
+ * as before?" answered first. See `zone-detection-emits-without-a-track`.
36163
+ */
36164
+ refusalsThisFrame = [];
36165
+ /** Running count of events this path has produced — the volume measure. */
36166
+ zoneRefusalEventCount = 0;
36137
36167
  zoneRefusalSampler = new SpawnRefusalSampler();
36138
36168
  /** Optional stationary-object gate (parked-object suppression). Null until
36139
36169
  * the addon wires it via {@link setStationaryGate}. */
@@ -36148,6 +36178,9 @@ var FrameProcessor = class {
36148
36178
  this.deviceId = deviceId;
36149
36179
  this.source = source;
36150
36180
  this.tracker = new SortTracker(trackerConfig);
36181
+ this.tracker.setSpawnRefusalSink((record) => {
36182
+ this.refusalsThisFrame.push(record);
36183
+ });
36151
36184
  this.stateAnalyzer = new StateAnalyzer(stateConfig);
36152
36185
  this.eventEmitter = new DetectionEventEmitter(emitterConfig);
36153
36186
  this.zones = [];
@@ -36254,6 +36287,55 @@ var FrameProcessor = class {
36254
36287
  * ate it. Sampled like every other refusal: an excluded box is excluded on
36255
36288
  * every frame of a passage.
36256
36289
  */
36290
+ /**
36291
+ * Turn this frame's refused spawns into events, but only those inside a zone.
36292
+ *
36293
+ * Drains {@link refusalsThisFrame}. Each surviving record becomes an
36294
+ * `object.detected` event with NO track — carrying the zones it fell in, the
36295
+ * percentage of the box inside each, and the refusal reason so a consumer can
36296
+ * tell it apart from a track whose media went missing.
36297
+ *
36298
+ * `zone-filtered` refusals are excluded on purpose: that reason means the
36299
+ * operator's own detection rules already said the box does not count on this
36300
+ * camera, and re-admitting it here would overrule a decision they made.
36301
+ */
36302
+ eventsForRefusedDetectionsInZones(timestamp, frame, frameWidth, frameHeight) {
36303
+ const refusals = this.refusalsThisFrame;
36304
+ this.refusalsThisFrame = [];
36305
+ if (refusals.length === 0 || this.zones.length === 0) return [];
36306
+ const out = [];
36307
+ for (const r of refusals) {
36308
+ if (r.reason === "zone-filtered") continue;
36309
+ const { memberships } = this.zoneEngine.splitDetectionZones(r.bbox, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
36310
+ if (memberships.length === 0) continue;
36311
+ this.zoneRefusalEventCount += 1;
36312
+ out.push({
36313
+ id: (0, node_crypto.randomUUID)(),
36314
+ kind: "object",
36315
+ deviceId: this.deviceId,
36316
+ timestamp,
36317
+ frameId: frame.frameId,
36318
+ className: r.detClass,
36319
+ confidence: r.detScore,
36320
+ bbox: r.bbox,
36321
+ frameWidth,
36322
+ frameHeight,
36323
+ zones: memberships.map((m) => m.zoneId),
36324
+ zoneOverlaps: memberships.map((m) => ({
36325
+ zoneId: m.zoneId,
36326
+ ...m.zoneName === void 0 ? {} : { zoneName: m.zoneName },
36327
+ overlapPct: Math.round(m.overlap * 1e3) / 10
36328
+ })),
36329
+ spawnRefused: r.reason
36330
+ });
36331
+ }
36332
+ return out;
36333
+ }
36334
+ /** How many zone-refusal events this processor has emitted — the measure that
36335
+ * says whether this path is a trickle or a storm. Read by the addon. */
36336
+ zoneRefusalEventsEmitted() {
36337
+ return this.zoneRefusalEventCount;
36338
+ }
36257
36339
  reportZoneFiltered(excluded) {
36258
36340
  const observer = this.spawnRefusalObserver;
36259
36341
  if (!observer || excluded.length === 0) return;
@@ -36655,9 +36737,11 @@ var FrameProcessor = class {
36655
36737
  frameHeight
36656
36738
  };
36657
36739
  };
36740
+ const zoneRefusalEvents = this.eventsForRefusedDetectionsInZones(timestamp, frame, frameWidth, frameHeight);
36658
36741
  const withTrackId = rawEvents.filter((e) => e.detection.trackId);
36659
36742
  const objectEvents = withTrackId.filter((e) => e.type !== "object.detected").map((e) => toObjectEvent(e));
36660
36743
  const appearanceEvents = withTrackId.filter((e) => e.type === "object.detected").map((e) => toObjectEvent(e, "entered"));
36744
+ objectEvents.push(...zoneRefusalEvents);
36661
36745
  return {
36662
36746
  deviceId: this.deviceId,
36663
36747
  timestamp,
@@ -47320,6 +47404,7 @@ var TrackDetailDispatcher = class {
47320
47404
  if (!dev) return;
47321
47405
  dev.scheduler.onTrackEnded(trackId);
47322
47406
  dev.tracks.delete(trackId);
47407
+ dev.parcelEnriched.delete(trackId);
47323
47408
  dev.candidateBest.delete(trackId);
47324
47409
  if (dev.tracks.size === 0) this.clearTimer(dev);
47325
47410
  }
@@ -47329,6 +47414,7 @@ var TrackDetailDispatcher = class {
47329
47414
  this.clearTimer(dev);
47330
47415
  dev.queue.length = 0;
47331
47416
  dev.tracks.clear();
47417
+ dev.parcelEnriched.clear();
47332
47418
  dev.candidateBest.clear();
47333
47419
  }
47334
47420
  this.devices.clear();
@@ -47339,6 +47425,7 @@ var TrackDetailDispatcher = class {
47339
47425
  dev = {
47340
47426
  scheduler: new DetailScheduler(),
47341
47427
  tracks: /* @__PURE__ */ new Map(),
47428
+ parcelEnriched: /* @__PURE__ */ new Map(),
47342
47429
  candidateBest: new BestDetectionTracker(CANDIDATE_IMPROVE_POLICY),
47343
47430
  queue: [],
47344
47431
  coastedFramesIgnored: 0,
@@ -47455,19 +47542,21 @@ var TrackDetailDispatcher = class {
47455
47542
  *
47456
47543
  * 1. `frameHandle` — cheapest when it hits: no payload moves and the runner's
47457
47544
  * leaf children re-cut their ROI at native resolution. Tried first, always.
47458
- * 2. `frameJpeg` — the freshest frame's FULL raster, fetched from the node that
47459
- * owns the handle and handed to the runner UNCUT, so the rectangle still
47460
- * comes from the single derivation (D52). This rung exists because rung 1 is
47461
- * structurally unreachable for most dispatches: the decode worker's native
47462
- * lease is 1.2 s and a tick + inference routinely lands outside it, while
47463
- * the retained store holds the frame for 3 s.
47545
+ * 2. `frameJpeg` — the track's PARKED key frame (D422): the full native frame
47546
+ * the owning node copied at the best instant, retrieved by track and handed
47547
+ * to the runner UNCUT, so the rectangle still comes from the single
47548
+ * derivation (D52). This rung exists because rung 1 is structurally
47549
+ * unreachable for most dispatches a tick + inference routinely lands
47550
+ * outside the lease and it cannot miss on age. It runs ONCE per parcel
47551
+ * per step; a dispatch that finds the same parcel already enriched skips
47552
+ * it, so a coasting track does not re-embed the same image every second.
47464
47553
  * 3. `cropJpeg` — the pre-cut tile. Last, because the caller decided its
47465
47554
  * rectangle and because in practice the same native miss that emptied rung 1
47466
47555
  * empties this one too.
47467
47556
  *
47468
- * On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG fetched on
47469
- * demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only after
47470
- * rung 1 missed — never a frame-rate stream, and never raw pixels.
47557
+ * On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG retrieved
47558
+ * on demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
47559
+ * after rung 1 missed — never a frame-rate stream, and never raw pixels.
47471
47560
  *
47472
47561
  * ONE outcome stops the ladder cold: the runner's typed `not-attached`
47473
47562
  * refusal (`detail-refusal.ts`). It names the camera's SESSION state, not
@@ -47505,34 +47594,39 @@ var TrackDetailDispatcher = class {
47505
47594
  }
47506
47595
  });
47507
47596
  }
47508
- if (this.deps.captureFullFrameBase64 !== void 0 && frame.frameHandle !== void 0) try {
47509
- const full = await this.deps.captureFullFrameBase64(frame);
47510
- if (full !== null) if (full.width !== frame.frameWidth || full.height !== frame.frameHeight) this.deps.logger.warn("detail full-frame fallback refused frame is not the bbox scale", {
47597
+ if (this.deps.captureParkedFrame !== void 0) try {
47598
+ const parked = await this.deps.captureParkedFrame(deviceId, req.trackId);
47599
+ if (parked !== null) if (dev.parcelEnriched.get(req.trackId)?.get(req.stepId) === parked.timestamp) this.deps.logger.debug("detail parcel already enriched for this step skipping", {
47511
47600
  tags: { deviceId },
47512
47601
  meta: {
47513
47602
  trackId: req.trackId,
47514
47603
  stepId: req.stepId,
47515
- frameWidth: full.width,
47516
- frameHeight: full.height,
47517
- bboxFrameWidth: frame.frameWidth,
47518
- bboxFrameHeight: frame.frameHeight
47604
+ parkedTimestamp: parked.timestamp
47519
47605
  }
47520
47606
  });
47521
47607
  else {
47522
47608
  const derived = await this.deps.runDetailSubtree({
47523
47609
  deviceId,
47524
- frameJpeg: full.base64,
47525
- parent,
47610
+ frameJpeg: parked.base64,
47611
+ parent: {
47612
+ bbox: { ...parked.bbox },
47613
+ className: frame.className
47614
+ },
47526
47615
  steps
47527
- }, frame.nodeId);
47528
- if (derived !== null) return derived.details;
47616
+ }, parked.nodeId);
47617
+ if (derived !== null) {
47618
+ const perStep = dev.parcelEnriched.get(req.trackId) ?? /* @__PURE__ */ new Map();
47619
+ perStep.set(req.stepId, parked.timestamp);
47620
+ dev.parcelEnriched.set(req.trackId, perStep);
47621
+ return derived.details;
47622
+ }
47529
47623
  }
47530
47624
  } catch (err) {
47531
47625
  if (isDetailNotAttachedRefusal(err)) {
47532
47626
  this.warnNotAttachedThrottled(deviceId, dev, req);
47533
47627
  return null;
47534
47628
  }
47535
- this.deps.logger.debug("detail full-frame fallback call failed", {
47629
+ this.deps.logger.debug("detail parked-frame rung failed", {
47536
47630
  tags: { deviceId },
47537
47631
  meta: {
47538
47632
  trackId: req.trackId,
@@ -47599,7 +47693,7 @@ var TrackDetailDispatcher = class {
47599
47693
  trackId: req.trackId,
47600
47694
  stepId: req.stepId,
47601
47695
  reason: req.reason,
47602
- triedFullFrame: this.deps.captureFullFrameBase64 !== void 0,
47696
+ triedParkedFrame: this.deps.captureParkedFrame !== void 0,
47603
47697
  triedCropJpeg: this.deps.captureCropBase64 !== void 0
47604
47698
  }
47605
47699
  });
@@ -53964,6 +54058,17 @@ async function encodeKeyFrameVariants(native) {
53964
54058
  })
53965
54059
  };
53966
54060
  }
54061
+ /**
54062
+ * The web companion from the PARKED key frame's own JPEG (D422): the parcel is
54063
+ * stored verbatim as `keyFrame`, and `keyFrameSmall` is this — the same bytes
54064
+ * downscaled to {@link KEYFRAME_SMALL_MAX_WIDTH}. One decode, one encode,
54065
+ * never a second fetch; `withoutEnlargement` never upscales a sub-960 frame.
54066
+ * The by-handle twin ({@link encodeKeyFrameVariants}) remains for the
54067
+ * summary group shot, which still fetches a native surface by handle.
54068
+ */
54069
+ async function deriveKeyFrameSmall(keyFrameJpeg) {
54070
+ return (0, sharp.default)(keyFrameJpeg).resize(960, null, { withoutEnlargement: true }).jpeg({ quality: 88 }).toBuffer();
54071
+ }
53967
54072
  //#endregion
53968
54073
  //#region src/pipeline-analytics/pipeline/key-frame-metrics.ts
53969
54074
  var DEFAULT_FLUSH_INTERVAL_MS = 6e4;
@@ -53989,11 +54094,8 @@ var KeyFrameCaptureLog = class {
53989
54094
  w.landed += 1;
53990
54095
  if (sample.retry) w.retryLanded += 1;
53991
54096
  break;
53992
- case "native-miss":
53993
- w.nativeMiss += 1;
53994
- break;
53995
- case "ram-tier":
53996
- w.ramTier += 1;
54097
+ case "parcel-missing":
54098
+ w.parcelMissing += 1;
53997
54099
  break;
53998
54100
  case "error":
53999
54101
  w.error += 1;
@@ -54022,8 +54124,7 @@ var KeyFrameCaptureLog = class {
54022
54124
  landed: 0,
54023
54125
  retryAttempts: 0,
54024
54126
  retryLanded: 0,
54025
- nativeMiss: 0,
54026
- ramTier: 0,
54127
+ parcelMissing: 0,
54027
54128
  error: 0,
54028
54129
  skippedNodeOffline: 0
54029
54130
  };
@@ -54038,8 +54139,7 @@ var KeyFrameCaptureLog = class {
54038
54139
  landed: w.landed,
54039
54140
  retryAttempts: w.retryAttempts,
54040
54141
  retryLanded: w.retryLanded,
54041
- nativeMiss: w.nativeMiss,
54042
- ramTier: w.ramTier,
54142
+ parcelMissing: w.parcelMissing,
54043
54143
  error: w.error,
54044
54144
  skippedNodeOffline: w.skippedNodeOffline
54045
54145
  };
@@ -54048,63 +54148,44 @@ var KeyFrameCaptureLog = class {
54048
54148
  //#endregion
54049
54149
  //#region src/pipeline-analytics/pipeline/key-frame-persist.ts
54050
54150
  /**
54051
- * Fetch, encode and store one track's key frame + its 960 companion.
54151
+ * Read, derive and store one track's key frame + its web companion.
54052
54152
  *
54053
- * NATIVE-OR-NOTHING: a miss writes nothing and the caller's bounded retry
54054
- * re-fires later. The `keyFrame` write lands FIRST and its key is what the
54055
- * outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
54153
+ * PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
54154
+ * parks a later frame. The `keyFrame` write lands FIRST and its key is what
54155
+ * the outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
54056
54156
  * retry that would re-shoot a key frame the rebuild can already read.
54057
54157
  */
54058
54158
  async function persistTrackKeyFrame(deps, input) {
54059
- const { deviceId, trackId, frameHandle, retry } = input;
54159
+ const { deviceId, trackId, retry } = input;
54060
54160
  const drop = {
54061
54161
  tags: { deviceId },
54062
54162
  meta: {
54063
54163
  trackId,
54064
- nodeId: frameHandle.nodeId,
54065
54164
  retry
54066
54165
  }
54067
54166
  };
54068
54167
  try {
54069
- const result = await deps.fetchNativeFullFrame(frameHandle, input.maxWidth);
54070
- const native = result?.frame;
54071
- if (!native || native.format !== "rgb" || native.width <= 0 || native.height <= 0) {
54072
- deps.logger.error("key-frame native miss — no upscale, will retry while budget remains", {
54073
- ...drop,
54074
- meta: {
54075
- ...drop.meta,
54076
- shmId: frameHandle.shmId
54077
- }
54078
- });
54079
- return { kind: "native-miss" };
54080
- }
54081
- if (!keyFrameAcceptsTier(result.tier)) {
54082
- deps.logger.warn("keyFrame native miss — RAM tier rejected, will retry while budget remains", {
54083
- ...drop,
54084
- meta: {
54085
- ...drop.meta,
54086
- shmId: frameHandle.shmId,
54087
- tier: result.tier
54088
- }
54089
- });
54090
- return { kind: "ram-tier" };
54168
+ const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
54169
+ if (parcel === null || parcel.jpeg.byteLength === 0) {
54170
+ deps.logger.error("key-frame parcel missing nothing to write, retry parks a later frame", { ...drop });
54171
+ return { kind: "parcel-missing" };
54091
54172
  }
54092
- const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
54093
54173
  const keyFrameKey = await deps.media.putReplacing({
54094
54174
  deviceId,
54095
54175
  ownerKind: "track",
54096
54176
  ownerId: trackId,
54097
54177
  kind: "keyFrame",
54098
- timestamp: input.timestamp,
54099
- data: keyFrame
54178
+ timestamp: parcel.timestamp,
54179
+ data: parcel.jpeg
54100
54180
  });
54101
54181
  try {
54182
+ const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
54102
54183
  await deps.media.putReplacing({
54103
54184
  deviceId,
54104
54185
  ownerKind: "track",
54105
54186
  ownerId: trackId,
54106
54187
  kind: "keyFrameSmall",
54107
- timestamp: input.timestamp,
54188
+ timestamp: parcel.timestamp,
54108
54189
  data: keyFrameSmall
54109
54190
  });
54110
54191
  } catch (err) {
@@ -54112,17 +54193,29 @@ async function persistTrackKeyFrame(deps, input) {
54112
54193
  ...drop,
54113
54194
  meta: {
54114
54195
  ...drop.meta,
54196
+ nodeId: parcel.nodeId,
54115
54197
  error: require_dist.errMsg(err)
54116
54198
  }
54117
54199
  });
54118
54200
  }
54201
+ deps.logger.debug("key frame written from the parked parcel", {
54202
+ ...drop,
54203
+ meta: {
54204
+ ...drop.meta,
54205
+ nodeId: parcel.nodeId,
54206
+ width: parcel.width,
54207
+ height: parcel.height,
54208
+ bytes: parcel.jpeg.byteLength,
54209
+ parkedTimestamp: parcel.timestamp
54210
+ }
54211
+ });
54119
54212
  return {
54120
54213
  kind: "landed",
54121
54214
  keyFrameKey
54122
54215
  };
54123
54216
  } catch (err) {
54124
54217
  const error = require_dist.errMsg(err);
54125
- deps.logger.warn("key-frame capture failed", {
54218
+ deps.logger.warn("key-frame write failed", {
54126
54219
  ...drop,
54127
54220
  meta: {
54128
54221
  ...drop.meta,
@@ -54309,6 +54402,61 @@ function createKeyFrameCrop(deps) {
54309
54402
  };
54310
54403
  }
54311
54404
  //#endregion
54405
+ //#region src/pipeline-analytics/pipeline/track-park.ts
54406
+ /** The whole frame — what a `keyFrame` park cuts. */
54407
+ var FULL_FRAME = {
54408
+ x: 0,
54409
+ y: 0,
54410
+ w: 1,
54411
+ h: 1
54412
+ };
54413
+ /** The two parcels a best-frame decision orders, in a fixed order. */
54414
+ function buildParkOrders(decision) {
54415
+ const subject = {
54416
+ bbox: { ...decision.bbox },
54417
+ frameWidth: decision.frameWidth,
54418
+ frameHeight: decision.frameHeight
54419
+ };
54420
+ const layout = deliveredWideCentralSquareLayout(decision.bbox, {
54421
+ W: decision.frameWidth,
54422
+ H: decision.frameHeight
54423
+ });
54424
+ const base = {
54425
+ deviceId: decision.deviceId,
54426
+ trackId: decision.trackId,
54427
+ handle: decision.frameHandle,
54428
+ timestamp: decision.timestamp,
54429
+ subject
54430
+ };
54431
+ return [{
54432
+ ...base,
54433
+ kind: "keyFrame",
54434
+ bbox: FULL_FRAME,
54435
+ maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
54436
+ }, {
54437
+ ...base,
54438
+ kind: "thumbnail",
54439
+ bbox: layout.fetch,
54440
+ layout
54441
+ }];
54442
+ }
54443
+ /**
54444
+ * The subject's box in the PARCEL's pixel space. The box was measured on the
54445
+ * ≤640 detection raster; the parcel is native-resolution. Both show the same
54446
+ * scene at the same aspect, so a uniform per-axis scale places the box on the
54447
+ * parcel — the runner then derives the crop rectangle from it (D52).
54448
+ */
54449
+ function scaleSubjectIntoParcel(subject, parcelWidth, parcelHeight) {
54450
+ const sx = parcelWidth / subject.frameWidth;
54451
+ const sy = parcelHeight / subject.frameHeight;
54452
+ return {
54453
+ x: subject.bbox.x * sx,
54454
+ y: subject.bbox.y * sy,
54455
+ w: subject.bbox.w * sx,
54456
+ h: subject.bbox.h * sy
54457
+ };
54458
+ }
54459
+ //#endregion
54312
54460
  //#region src/pipeline-analytics/pipeline/native-detection.ts
54313
54461
  /**
54314
54462
  * Nominal frame size used to denormalize native `[0,1]` boxes when a
@@ -58839,7 +58987,28 @@ async function reconcileGallerySamples(deps, options) {
58839
58987
  }
58840
58988
  //#endregion
58841
58989
  //#region src/pipeline-analytics/summary/summary-store.ts
58990
+ /**
58991
+ * @durable class=ledger owner=pipeline-analytics
58992
+ * write="one row per SUMMARY — a window of co-present tracks on one camera
58993
+ * group. Opened by `openOrAbsorb` when the first member arrives, updated
58994
+ * in place while the window absorbs more, and finalised by `seal` when the
58995
+ * window closes. Per WINDOW, never per frame or per track: the sealed
58996
+ * summary is what a notification rule reads."
58997
+ * retention="age-keyed on `sealedAt`, swept with the rest of the analytics
58998
+ * retention. An unsealed row belongs to a window still open and is never
58999
+ * swept by age — it is closed or cascaded away with its members."
59000
+ */
58842
59001
  var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
59002
+ /**
59003
+ * @durable class=ledger owner=pipeline-analytics
59004
+ * write="one row per (summary, track) membership, written as a window
59005
+ * absorbs a track. The join that lets a sealed summary name its subjects
59006
+ * without re-reading the track store."
59007
+ * retention="cascaded — a member dies with its summary (`delete-track-cascade`
59008
+ * counts them as leaves) or with the track it names. It carries no clock of
59009
+ * its own: an age sweep here could outlive or predecease the summary the
59010
+ * row only means anything inside of."
59011
+ */
58843
59012
  var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
58844
59013
  var SUMMARIES_COLUMNS = [
58845
59014
  {
@@ -63667,35 +63836,6 @@ function createNativeFrameTransport(deps) {
63667
63836
  const getRemoteFrame = async (handle) => requestNativeCrop(handle, FULL_FRAME_BBOX, handle.width, replyToFullFrame);
63668
63837
  const fetchNativeFullFrameTiered = async (handle, maxWidth) => requestNativeCrop(handle, FULL_FRAME_BBOX, maxWidth, replyToTieredFullFrame);
63669
63838
  const getNativeFullFrameRgb = async (handle, maxWidth) => (await fetchNativeFullFrameTiered(handle, maxWidth))?.frame ?? null;
63670
- const getFullFrameJpegBase64 = async (handle, maxWidth) => {
63671
- if (!pipelineRunnerApi?.getNativeCrop) return null;
63672
- try {
63673
- const full = await pipelineRunnerApi.getNativeCrop.query({
63674
- handle,
63675
- bbox: {
63676
- x: 0,
63677
- y: 0,
63678
- w: 1,
63679
- h: 1
63680
- },
63681
- maxWidth,
63682
- encodeJpeg: true
63683
- }, require_dist.nodePin(handle.nodeId));
63684
- if (!full?.jpeg || full.width <= 0 || full.height <= 0) return null;
63685
- return {
63686
- base64: full.jpeg,
63687
- width: full.width,
63688
- height: full.height,
63689
- tier: full.tier === "ram-fullframe" ? "ram-fullframe" : "native"
63690
- };
63691
- } catch (err) {
63692
- logger.debug("full-frame jpeg fetch failed", { meta: {
63693
- nodeId: handle.nodeId,
63694
- error: require_dist.errMsg(err)
63695
- } });
63696
- return null;
63697
- }
63698
- };
63699
63839
  const cropMetricLogger = logger.child("NativeCrop");
63700
63840
  let nativeHits = 0;
63701
63841
  let nativeFallbacks = 0;
@@ -63775,7 +63915,6 @@ function createNativeFrameTransport(deps) {
63775
63915
  getRemoteFrame,
63776
63916
  getNativeFullFrameRgb,
63777
63917
  getNativeKeyFrameRgb: fetchNativeFullFrameTiered,
63778
- getFullFrameJpegBase64,
63779
63918
  getNativeCropJpeg,
63780
63919
  captureCrop: createCaptureCrop({
63781
63920
  tryNativeCrop,
@@ -63886,6 +64025,233 @@ function parseTopologySnapshotNodes(data) {
63886
64025
  return parsed;
63887
64026
  }
63888
64027
  //#endregion
64028
+ //#region src/pipeline-analytics/runtime/parked-frame-client.ts
64029
+ /**
64030
+ * Bound on the mirror. A track's record leaves at release; this is the
64031
+ * backstop for a lost close. At the busiest measured hub (~12 sessions,
64032
+ * a few tracks each) 4 096 is an hour of tracks, past which the OLDEST record
64033
+ * is forgotten — its parcel still ages out on the node on its own.
64034
+ */
64035
+ var MAX_MIRRORED_TRACKS = 4096;
64036
+ /** One "runner has no park method" line per camera per minute. */
64037
+ var SKEW_WARN_THROTTLE_MS = 6e4;
64038
+ function inFlightKey(trackId, kind) {
64039
+ return `${trackId} ${kind}`;
64040
+ }
64041
+ /** Strip the hub-side context off an order: only the wire fields travel. */
64042
+ function toWireInput(order) {
64043
+ return {
64044
+ deviceId: order.deviceId,
64045
+ trackId: order.trackId,
64046
+ kind: order.kind,
64047
+ handle: order.handle,
64048
+ bbox: order.bbox,
64049
+ ...order.maxWidth !== void 0 ? { maxWidth: order.maxWidth } : {},
64050
+ timestamp: order.timestamp
64051
+ };
64052
+ }
64053
+ function createParkedFrameClient(deps) {
64054
+ const { api, logger } = deps;
64055
+ const now = deps.now ?? Date.now;
64056
+ /** trackId → kind → what the hub kept beside the parcel. */
64057
+ const mirror = /* @__PURE__ */ new Map();
64058
+ const inFlight = /* @__PURE__ */ new Map();
64059
+ const skewWarnedAt = /* @__PURE__ */ new Map();
64060
+ const remember = (order, answer) => {
64061
+ let kinds = mirror.get(order.trackId);
64062
+ if (kinds === void 0) {
64063
+ kinds = /* @__PURE__ */ new Map();
64064
+ mirror.set(order.trackId, kinds);
64065
+ while (mirror.size > MAX_MIRRORED_TRACKS) {
64066
+ const oldest = mirror.keys().next().value;
64067
+ if (oldest === void 0) break;
64068
+ mirror.delete(oldest);
64069
+ }
64070
+ }
64071
+ kinds.set(order.kind, {
64072
+ nodeId: answer.nodeId,
64073
+ deviceId: order.deviceId,
64074
+ kind: order.kind,
64075
+ timestamp: order.timestamp,
64076
+ width: answer.width,
64077
+ height: answer.height,
64078
+ subject: order.subject,
64079
+ ...order.layout !== void 0 ? { layout: order.layout } : {}
64080
+ });
64081
+ };
64082
+ const warnSkew = (order) => {
64083
+ const at = skewWarnedAt.get(order.deviceId) ?? 0;
64084
+ if (now() - at < SKEW_WARN_THROTTLE_MS) return;
64085
+ skewWarnedAt.set(order.deviceId, now());
64086
+ logger.warn("track frame park impossible — the runner api has no park method (version skew)", {
64087
+ tags: { deviceId: order.deviceId },
64088
+ meta: {
64089
+ trackId: order.trackId,
64090
+ kind: order.kind,
64091
+ nodeId: order.handle.nodeId
64092
+ }
64093
+ });
64094
+ };
64095
+ const sendPark = async (order) => {
64096
+ if (api === void 0) {
64097
+ warnSkew(order);
64098
+ return false;
64099
+ }
64100
+ if (deps.isNodeOffline?.(order.handle.nodeId) === true) {
64101
+ logger.debug("track frame park skipped — the owning node is offline in the registry", {
64102
+ tags: { deviceId: order.deviceId },
64103
+ meta: {
64104
+ trackId: order.trackId,
64105
+ kind: order.kind,
64106
+ nodeId: order.handle.nodeId
64107
+ }
64108
+ });
64109
+ return false;
64110
+ }
64111
+ const meta = {
64112
+ trackId: order.trackId,
64113
+ kind: order.kind,
64114
+ nodeId: order.handle.nodeId,
64115
+ handle: `${order.handle.shmId}#${order.handle.slot}#${order.handle.seq}`
64116
+ };
64117
+ try {
64118
+ const answer = await api.parkTrackFrame.mutate(toWireInput(order), require_dist.nodePin(order.handle.nodeId));
64119
+ if (answer.parked) {
64120
+ remember(order, answer);
64121
+ logger.debug("track frame parked", {
64122
+ tags: { deviceId: order.deviceId },
64123
+ meta: {
64124
+ ...meta,
64125
+ bytes: answer.bytes,
64126
+ replaced: answer.replaced
64127
+ }
64128
+ });
64129
+ return true;
64130
+ }
64131
+ logger.warn("track frame park refused by the owning node", {
64132
+ tags: { deviceId: order.deviceId },
64133
+ meta: {
64134
+ ...meta,
64135
+ reason: answer.reason,
64136
+ handleAgeMs: answer.handleAgeMs,
64137
+ ...answer.detail !== void 0 ? { detail: answer.detail } : {}
64138
+ }
64139
+ });
64140
+ return false;
64141
+ } catch (err) {
64142
+ logger.warn("track frame park failed — the order did not reach the node", {
64143
+ tags: { deviceId: order.deviceId },
64144
+ meta: {
64145
+ ...meta,
64146
+ error: require_dist.errMsg(err)
64147
+ }
64148
+ });
64149
+ return false;
64150
+ }
64151
+ };
64152
+ const park = (order) => {
64153
+ const key = inFlightKey(order.trackId, order.kind);
64154
+ const pending = inFlight.get(key);
64155
+ if (pending !== void 0) return pending;
64156
+ const run = sendPark(order).finally(() => {
64157
+ if (inFlight.get(key) === run) inFlight.delete(key);
64158
+ });
64159
+ inFlight.set(key, run);
64160
+ return run;
64161
+ };
64162
+ const awaitPark = async (trackId, kind) => {
64163
+ const pending = inFlight.get(inFlightKey(trackId, kind));
64164
+ if (pending !== void 0) await pending;
64165
+ };
64166
+ const info = (trackId, kind) => mirror.get(trackId)?.get(kind);
64167
+ const fetch = async (trackId, kind) => {
64168
+ const known = info(trackId, kind);
64169
+ if (known === void 0 || api === void 0) return null;
64170
+ if (deps.isNodeOffline?.(known.nodeId) === true) {
64171
+ logger.warn("parked frame retrieval skipped — the holding node is offline in the registry", {
64172
+ tags: { deviceId: known.deviceId },
64173
+ meta: {
64174
+ trackId,
64175
+ kind,
64176
+ nodeId: known.nodeId
64177
+ }
64178
+ });
64179
+ return null;
64180
+ }
64181
+ try {
64182
+ const parcel = await api.getParkedTrackFrame.query({
64183
+ deviceId: known.deviceId,
64184
+ trackId,
64185
+ kind
64186
+ }, require_dist.nodePin(known.nodeId));
64187
+ if (parcel === null) {
64188
+ logger.warn("parked frame gone at retrieval — the node no longer holds it", {
64189
+ tags: { deviceId: known.deviceId },
64190
+ meta: {
64191
+ trackId,
64192
+ kind,
64193
+ nodeId: known.nodeId,
64194
+ parkedTimestamp: known.timestamp
64195
+ }
64196
+ });
64197
+ return null;
64198
+ }
64199
+ return {
64200
+ jpeg: Buffer.from(parcel.jpeg, "base64"),
64201
+ width: parcel.width,
64202
+ height: parcel.height,
64203
+ timestamp: parcel.timestamp,
64204
+ info: known
64205
+ };
64206
+ } catch (err) {
64207
+ logger.warn("parked frame retrieval failed", {
64208
+ tags: { deviceId: known.deviceId },
64209
+ meta: {
64210
+ trackId,
64211
+ kind,
64212
+ nodeId: known.nodeId,
64213
+ error: require_dist.errMsg(err)
64214
+ }
64215
+ });
64216
+ return null;
64217
+ }
64218
+ };
64219
+ const release = (trackId) => {
64220
+ const kinds = mirror.get(trackId);
64221
+ if (kinds === void 0) return;
64222
+ mirror.delete(trackId);
64223
+ if (api === void 0) return;
64224
+ const targets = /* @__PURE__ */ new Map();
64225
+ for (const record of kinds.values()) targets.set(record.nodeId, record.deviceId);
64226
+ for (const [nodeId, deviceId] of targets) api.releaseParkedTrackFrames.mutate({
64227
+ deviceId,
64228
+ trackId
64229
+ }, require_dist.nodePin(nodeId)).catch((err) => {
64230
+ logger.debug("parked frame release did not reach the node", {
64231
+ tags: { deviceId },
64232
+ meta: {
64233
+ trackId,
64234
+ nodeId,
64235
+ error: require_dist.errMsg(err)
64236
+ }
64237
+ });
64238
+ });
64239
+ };
64240
+ const dispose = () => {
64241
+ mirror.clear();
64242
+ inFlight.clear();
64243
+ skewWarnedAt.clear();
64244
+ };
64245
+ return {
64246
+ park,
64247
+ awaitPark,
64248
+ info,
64249
+ fetch,
64250
+ release,
64251
+ dispose
64252
+ };
64253
+ }
64254
+ //#endregion
63889
64255
  //#region src/pipeline-analytics/runtime/widget-manifest.ts
63890
64256
  /** The remote-module coordinates shared by every analytics widget. */
63891
64257
  var REMOTE_NAME = "addon_pipeline_analytics_widgets";
@@ -64987,7 +65353,8 @@ function isUniformRgbFrame(data, width, height) {
64987
65353
  * frame downscaled to {@link SNAPSHOT_MAX_WIDTH} (960) with ONLY the track's own
64988
65354
  * box burned in, falling back to the ≤640 detection frame (honest lower res,
64989
65355
  * never an upscale) on a native miss. The best `thumbnail` stays a clean,
64990
- * uncapped native subject crop (native-or-nothing).
65356
+ * uncapped native subject crop — read from the track's PARKED slab, by track
65357
+ * (D422), never by handle.
64991
65358
  *
64992
65359
  * The detection frame is resolved ONCE per call (blank-frame guard); native full
64993
65360
  * frames + subject crops are fetched per target by handle. Frames recycle in
@@ -65255,10 +65622,10 @@ var EventMediaDispatcher = class {
65255
65622
  if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
65256
65623
  let thumbnailWritten = false;
65257
65624
  if (sn.bestThumbnail) {
65258
- const variants = await this.cropSubjectVariants(deviceId, sn.trackId, frameHandle, fw, fh, sn.bbox);
65625
+ const variants = await this.cropSubjectVariants(deviceId, sn.trackId);
65259
65626
  if (variants) {
65260
- thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", sn.timestamp, variants.thumbnail, "native");
65261
- await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", sn.timestamp, variants.thumbnailSmall, "native");
65627
+ thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", variants.timestamp, variants.thumbnail, "native");
65628
+ await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", variants.timestamp, variants.thumbnailSmall, "native");
65262
65629
  }
65263
65630
  }
65264
65631
  return {
@@ -65336,81 +65703,49 @@ var EventMediaDispatcher = class {
65336
65703
  }
65337
65704
  /**
65338
65705
  * The best-shot subject crop as its TWO persisted variants (best-crop
65339
- * fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22). ONE native
65340
- * fetch: the in-frame slab of the 16:9 central-square WINDOW
65341
- * ({@link wideCentralSquareLayout} a `side × 16/9` window whose middle square
65342
- * of side `max(w,h)×1.2` fully contains the subject, clamped/anchored so the
65343
- * subject stays in that central square at every frame edge) is requested from
65344
- * the runner's retained native surface with NO `maxWidth` (uncapped TRUE
65345
- * native). It is composed into the full 16:9 window (lateral letterbox only for
65346
- * the part outside the frame) the `thumbnail`. The `thumbnailSmall` is
65347
- * DERIVED by downscaling that SAME window to the 480 long-side cap (480×270)
65348
- * never a second `getNativeCropJpeg`, never upscaled ({@link deriveThumbnailSmall}
65349
- * never upscales a smaller window).
65350
- *
65351
- * Both variants share the SAME framing + central-square containment guarantee:
65352
- * wide consumers use the 16:9 image as-is (subject centered, no cut); square
65353
- * consumers center-crop the middle square (subject always inside it).
65354
- *
65355
- * NATIVE-OR-NOTHING: any miss/error (or a runner without the method) returns
65356
- * `null` after a loud `logger.warn`; the caller SKIPS the write and the
65357
- * per-frame retry lands a real native crop later. Never a local resize
65358
- * upscale of a ≤640 tile (a blurred lie).
65359
- */
65360
- async cropSubjectVariants(deviceId, trackId, frameHandle, fw, fh, bbox) {
65361
- if (!this.deps.getNativeCropJpeg) {
65362
- this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
65363
- tags: { deviceId },
65364
- meta: {
65365
- shmId: frameHandle.shmId,
65366
- reason: "no-native-cap"
65367
- }
65368
- });
65369
- this.deps.onSubjectCropMiss?.(deviceId, trackId, "no-native-cap");
65370
- return null;
65371
- }
65706
+ * fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22), from the
65707
+ * track's PARKED slab (D422). ONE read, by `(deviceId, trackId)`: the
65708
+ * in-frame slab of the 16:9 central-square WINDOW
65709
+ * ({@link wideCentralSquareLayout}) the runner cut at uncapped native
65710
+ * resolution the instant the track's best was decided. It is composed into
65711
+ * the full 16:9 window (lateral letterbox only for the part outside the
65712
+ * frame) the `thumbnail`. The `thumbnailSmall` is DERIVED by downscaling
65713
+ * that SAME window to the 480 long-side cap never a second read, never
65714
+ * upscaled ({@link deriveThumbnailSmall} never upscales a smaller window).
65715
+ *
65716
+ * Both variants share the SAME framing + central-square containment
65717
+ * guarantee: wide consumers use the 16:9 image as-is; square consumers
65718
+ * center-crop the middle square (subject always inside it).
65719
+ *
65720
+ * PARCEL-OR-NOTHING: no parcel (or a throw) returns `null` after a loud
65721
+ * `logger.warn`; the caller SKIPS the write and the per-frame retry parks a
65722
+ * later frame. Never a by-handle crop, never a local upscale of a ≤640 tile.
65723
+ */
65724
+ async cropSubjectVariants(deviceId, trackId) {
65372
65725
  try {
65373
- const layout = deliveredWideCentralSquareLayout(bbox, {
65374
- W: fw,
65375
- H: fh
65376
- });
65377
- if (centralSquareContainmentFraction(bbox, {
65378
- W: fw,
65379
- H: fh
65380
- }) < .5) this.deps.logger.info("best-shot delivered via contained square layout (edge subject)", {
65381
- tags: { deviceId },
65382
- meta: {
65383
- bbox,
65384
- frameW: fw,
65385
- frameH: fh
65386
- }
65387
- });
65388
- const askedAt = Date.now();
65389
- const slab = await this.deps.getNativeCropJpeg(frameHandle, layout.fetch, void 0, deviceId);
65390
- if (!slab) {
65391
- this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
65726
+ const parked = await this.deps.getParkedSubjectSlab(deviceId, trackId);
65727
+ if (!parked) {
65728
+ this.deps.logger.warn("best-shot parcel missing — no thumbnail this frame, will retry", {
65392
65729
  tags: { deviceId },
65393
65730
  meta: {
65394
- shmId: frameHandle.shmId,
65395
- handle: `${frameHandle.shmId}#${frameHandle.slot}#${frameHandle.seq}`,
65396
- handleNodeId: frameHandle.nodeId,
65397
- roundTripMs: Date.now() - askedAt,
65398
- reason: "native-miss"
65731
+ trackId,
65732
+ reason: "parcel-missing"
65399
65733
  }
65400
65734
  });
65401
- this.deps.onSubjectCropMiss?.(deviceId, trackId, "native-miss");
65735
+ this.deps.onSubjectCropMiss?.(deviceId, trackId, "parcel-missing");
65402
65736
  return null;
65403
65737
  }
65404
- const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
65738
+ const thumbnail = await composeWideCentralSquareThumbnail(parked.slab, parked.layout);
65405
65739
  return {
65406
65740
  thumbnail,
65407
- thumbnailSmall: await deriveThumbnailSmall(thumbnail)
65741
+ thumbnailSmall: await deriveThumbnailSmall(thumbnail),
65742
+ timestamp: parked.timestamp
65408
65743
  };
65409
65744
  } catch (err) {
65410
- this.deps.logger.warn("native subject crop misswill retry, no upscale", {
65745
+ this.deps.logger.warn("best-shot parcel read failedno thumbnail this frame, will retry", {
65411
65746
  tags: { deviceId },
65412
65747
  meta: {
65413
- shmId: frameHandle.shmId,
65748
+ trackId,
65414
65749
  reason: "threw",
65415
65750
  error: err instanceof Error ? err.message : String(err)
65416
65751
  }
@@ -70701,7 +71036,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
70701
71036
  * the FULL cleanup litany on every teardown path (and cancels the track's
70702
71037
  * queued captures via the onClose hook) and `closeDevice` finally clears
70703
71038
  * per-track state on device removal (the two documented leaks). */
70704
- residents = new TrackResidentState({ onClose: (trackId) => this.captureScheduler.cancelForTrack(trackId) });
71039
+ residents = new TrackResidentState({ onClose: (trackId) => {
71040
+ this.captureScheduler.cancelForTrack(trackId);
71041
+ this.parkedFrames?.release(trackId);
71042
+ } });
71043
+ /**
71044
+ * The hub's side of a PARK (D422): orders the owning node to keep a track's
71045
+ * best frame the instant `BestDetectionTracker` accepts it, and answers the
71046
+ * keyFrame writer, the best-shot thumbnail and the detail plane's full-frame
71047
+ * rung by `(deviceId, trackId, kind)` — never by handle. Null until init.
71048
+ */
71049
+ parkedFrames = null;
70705
71050
  /** Defence in depth for the 2026-08-21 `b50e09f7` incident: a row the track
70706
71051
  * store has already CLOSED never earns another media target, whatever the
70707
71052
  * tracker keeps handing back. Reads the store's in-RAM close record only —
@@ -71096,17 +71441,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71096
71441
  noteCropOutcome: (deviceId, reason) => this.failureReport.noteEnrichmentCrop(deviceId, reason)
71097
71442
  });
71098
71443
  this.getNativeKeyFrameRgb = transport.getNativeKeyFrameRgb;
71444
+ this.parkedFrames = createParkedFrameClient({
71445
+ api: api.pipelineRunner,
71446
+ logger: logger.child("ParkedFrames"),
71447
+ isNodeOffline: (nodeId) => this.nodeLiveness.isKnownOffline(nodeId, Date.now())
71448
+ });
71099
71449
  this.captureCrop = transport.captureCrop;
71100
71450
  this.eventMediaDispatcher = new EventMediaDispatcher({
71101
71451
  getRemoteFrame: transport.getRemoteFrame,
71102
71452
  getNativeCropJpeg: transport.getNativeCropJpeg,
71103
71453
  getNativeFullFrameRgb: transport.getNativeFullFrameRgb,
71454
+ getParkedSubjectSlab: (deviceId, trackId) => this.fetchParkedSubjectSlab(deviceId, trackId),
71104
71455
  mediaStore: stores.mediaStore,
71105
71456
  logger: logger.child("EventMediaDispatcher"),
71106
71457
  onSubjectCropMiss: (deviceId, trackId, reason) => this.residents.notePreviewCaptureMiss(deviceId, trackId, `thumbnail:${reason}`)
71107
71458
  });
71108
71459
  this.buildRecognizers(api, logger, stores, transport);
71109
- this.buildDetailDispatcher(api, logger, transport);
71460
+ this.buildDetailDispatcher(api, logger);
71110
71461
  const capProviders = this.buildAnalyticsProviders(api, logger);
71111
71462
  await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
71112
71463
  await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
@@ -71936,7 +72287,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71936
72287
  * demand via `pipelineRunner.runDetailSubtree`. The dispatcher owns the
71937
72288
  * per-device schedulers, concurrency cap, and 1 s tick; result routing +
71938
72289
  * the pinned call + crop fallback are injected here. */
71939
- buildDetailDispatcher(api, logger, transport) {
72290
+ buildDetailDispatcher(api, logger) {
71940
72291
  const runnerApi = api.pipelineRunner;
71941
72292
  this.detailDispatcher = new TrackDetailDispatcher({
71942
72293
  logger: logger.child("DetailDispatcher"),
@@ -71946,13 +72297,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71946
72297
  return runnerApi.runDetailSubtree.mutate(input);
71947
72298
  },
71948
72299
  routeResults: (deviceId, trackId, details, frame) => this.routeDetailResults(deviceId, trackId, details, frame),
71949
- captureFullFrameBase64: async (frame) => {
71950
- if (frame.frameHandle === void 0) return null;
71951
- const full = await transport.getFullFrameJpegBase64(frame.frameHandle, frame.frameWidth);
71952
- return full === null ? null : {
71953
- base64: full.base64,
71954
- width: full.width,
71955
- height: full.height
72300
+ captureParkedFrame: async (_deviceId, trackId) => {
72301
+ const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
72302
+ if (parcel === null) return null;
72303
+ return {
72304
+ base64: parcel.jpeg.toString("base64"),
72305
+ width: parcel.width,
72306
+ height: parcel.height,
72307
+ bbox: scaleSubjectIntoParcel(parcel.info.subject, parcel.width, parcel.height),
72308
+ nodeId: parcel.info.nodeId,
72309
+ timestamp: parcel.timestamp
71956
72310
  };
71957
72311
  },
71958
72312
  captureCropBase64: async (frame) => {
@@ -73594,6 +73948,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
73594
73948
  this.audioMarkers?.flush(Date.now());
73595
73949
  this.detailDispatcher?.dispose();
73596
73950
  this.detailDispatcher = null;
73951
+ this.parkedFrames?.dispose();
73597
73952
  this.overlayState.clear();
73598
73953
  this.overlaySynthesisWarnAt.clear();
73599
73954
  this.processors.clear();
@@ -74252,7 +74607,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
74252
74607
  let plateCrops = 0;
74253
74608
  for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
74254
74609
  else plateCrops += 1;
74255
- const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
74610
+ const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight, frameHandle);
74256
74611
  this.offerGroupShot(deviceId, result, frameHandle);
74257
74612
  for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
74258
74613
  const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
@@ -74371,7 +74726,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
74371
74726
  }).catch(() => {});
74372
74727
  }
74373
74728
  const keyFrameCaptures = selectKeyFrameCaptures(snapshotTargets);
74374
- if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures, frameHandle);
74729
+ if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures);
74375
74730
  }
74376
74731
  if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
74377
74732
  deviceId,
@@ -75301,10 +75656,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75301
75656
  });
75302
75657
  }).catch(() => {});
75303
75658
  }
75304
- async persistKeyFrames(deviceId, timestamp, captures, frameHandle) {
75305
- const getNative = this.getNativeKeyFrameRgb;
75659
+ /**
75660
+ * Order the parcels a best-frame decision wants (D422). Skipped entirely at
75661
+ * a node the registry already called offline (D58) — logged on the
75662
+ * transition by `noteCapturesGated`, counted in the key-frame window.
75663
+ * `onlyIfMissing` is the retry rule: a later frame may fill a kind that has
75664
+ * no parcel, never replace one the ranking preferred.
75665
+ */
75666
+ issueParkOrders(decision, kinds, onlyIfMissing) {
75667
+ const client = this.parkedFrames;
75668
+ if (client === null) return;
75669
+ if (this.nodeLiveness.isKnownOffline(decision.frameHandle.nodeId, Date.now())) {
75670
+ this.noteCapturesGated(decision.deviceId, decision.frameHandle.nodeId);
75671
+ return;
75672
+ }
75673
+ for (const order of buildParkOrders(decision)) {
75674
+ if (!kinds.includes(order.kind)) continue;
75675
+ if (onlyIfMissing && client.info(order.trackId, order.kind) !== void 0) continue;
75676
+ client.park(order);
75677
+ }
75678
+ }
75679
+ /** A parcel by track, after any park in flight for it has landed. */
75680
+ async fetchParkedParcel(trackId, kind) {
75681
+ const client = this.parkedFrames;
75682
+ if (client === null) return null;
75683
+ await client.awaitPark(trackId, kind);
75684
+ return client.fetch(trackId, kind);
75685
+ }
75686
+ /** The best-shot slab for the dispatcher: the parcel plus the layout the hub kept. */
75687
+ async fetchParkedSubjectSlab(_deviceId, trackId) {
75688
+ const parcel = await this.fetchParkedParcel(trackId, "thumbnail");
75689
+ if (parcel === null || parcel.info.layout === void 0) return null;
75690
+ return {
75691
+ slab: parcel.jpeg,
75692
+ layout: parcel.info.layout,
75693
+ timestamp: parcel.timestamp
75694
+ };
75695
+ }
75696
+ async persistKeyFrames(deviceId, timestamp, captures) {
75306
75697
  const mediaStore = this.mediaStore;
75307
- if (!getNative || !mediaStore) return;
75698
+ if (this.parkedFrames === null || !mediaStore) return;
75308
75699
  await Promise.all(captures.map((capture) => this.captureScheduler.request({
75309
75700
  deviceId,
75310
75701
  trackId: capture.trackId,
@@ -75314,16 +75705,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75314
75705
  exec: async () => {
75315
75706
  this.residents.noteKeyFrameAttempt(deviceId, capture.trackId, timestamp, capture.retry);
75316
75707
  const outcome = await persistTrackKeyFrame({
75317
- fetchNativeFullFrame: getNative,
75318
- encodeVariants: encodeKeyFrameVariants,
75708
+ fetchParkedKeyFrame: async (parcelDeviceId, trackId) => {
75709
+ const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
75710
+ if (parcel === null || parcel.info.deviceId !== parcelDeviceId) return null;
75711
+ return {
75712
+ jpeg: parcel.jpeg,
75713
+ width: parcel.width,
75714
+ height: parcel.height,
75715
+ timestamp: parcel.timestamp,
75716
+ nodeId: parcel.info.nodeId
75717
+ };
75718
+ },
75719
+ deriveSmall: deriveKeyFrameSmall,
75319
75720
  media: mediaStore,
75320
75721
  logger: this.ctx.logger
75321
75722
  }, {
75322
75723
  deviceId,
75323
75724
  trackId: capture.trackId,
75324
- timestamp,
75325
- frameHandle,
75326
- maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
75327
75725
  retry: capture.retry
75328
75726
  });
75329
75727
  if (outcome.kind === "landed") {
@@ -75403,10 +75801,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75403
75801
  isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
75404
75802
  captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
75405
75803
  captureKeyFrame: async (req) => {
75804
+ this.issueParkOrders({
75805
+ deviceId: req.deviceId,
75806
+ trackId: req.trackId,
75807
+ timestamp: req.timestamp,
75808
+ frameHandle: req.frameHandle,
75809
+ bbox: { ...req.bodyBbox },
75810
+ frameWidth: req.frameWidth,
75811
+ frameHeight: req.frameHeight
75812
+ }, ["keyFrame"], true);
75406
75813
  await this.persistKeyFrames(req.deviceId, req.timestamp, [{
75407
75814
  trackId: req.trackId,
75408
75815
  retry: false
75409
- }], req.frameHandle);
75816
+ }]);
75410
75817
  }
75411
75818
  }, input);
75412
75819
  }
@@ -75460,10 +75867,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75460
75867
  * (coalescing, per-device budget and land-only bookkeeping identical). */
75461
75868
  captureSemanticBestShot(request) {
75462
75869
  const { deviceId, trackId, timestamp, bbox, frameHandle } = request;
75870
+ this.issueParkOrders({
75871
+ deviceId,
75872
+ trackId,
75873
+ timestamp,
75874
+ frameHandle,
75875
+ bbox: { ...bbox },
75876
+ frameWidth: request.frameWidth,
75877
+ frameHeight: request.frameHeight
75878
+ }, ["keyFrame", "thumbnail"], false);
75463
75879
  this.persistKeyFrames(deviceId, timestamp, [{
75464
75880
  trackId,
75465
75881
  retry: false
75466
- }], frameHandle);
75882
+ }]);
75467
75883
  const dispatcher = this.eventMediaDispatcher;
75468
75884
  if (!dispatcher) return;
75469
75885
  if (this.captureScheduler.isKeyActive(captureCoalesceKey(deviceId, "thumbnail", trackId))) {
@@ -75614,7 +76030,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75614
76030
  }
75615
76031
  return retries;
75616
76032
  }
75617
- buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
76033
+ buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight, frameHandle) {
75618
76034
  const targets = [];
75619
76035
  for (const t of tracked) {
75620
76036
  if (t.matchedThisFrame === false) continue;
@@ -75700,6 +76116,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75700
76116
  state: this.residents.keyFrameCapture(t.trackId),
75701
76117
  now: timestamp
75702
76118
  }).kind;
76119
+ if (frameHandle !== void 0 && plausibleBox) {
76120
+ const decision = {
76121
+ deviceId,
76122
+ trackId: t.trackId,
76123
+ timestamp,
76124
+ frameHandle,
76125
+ bbox: {
76126
+ x: t.bbox.x,
76127
+ y: t.bbox.y,
76128
+ w: t.bbox.w,
76129
+ h: t.bbox.h
76130
+ },
76131
+ frameWidth,
76132
+ frameHeight
76133
+ };
76134
+ if (isNewBest) this.issueParkOrders(decision, ["keyFrame", "thumbnail"], false);
76135
+ else {
76136
+ const retryKinds = [];
76137
+ if (keyFrame === "retry") retryKinds.push("keyFrame");
76138
+ if (bestThumbnail) retryKinds.push("thumbnail");
76139
+ if (retryKinds.length > 0) this.issueParkOrders(decision, retryKinds, true);
76140
+ }
76141
+ }
75703
76142
  const suppressedBy = (() => {
75704
76143
  if (skipParkedThumb) return "parked-thumbnail";
75705
76144
  if (!isNewBest || bestThumbnail) return void 0;