@camstack/addon-post-analysis 1.2.214 → 1.2.217

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
  *
@@ -36075,6 +36091,17 @@ function mapObjectStateToTrackState(s) {
36075
36091
  default: return "new";
36076
36092
  }
36077
36093
  }
36094
+ /**
36095
+ * How long an identical refusal stays collapsed. A stationary subject is
36096
+ * refused on every analysed frame; at ~2 fps of analysis, 60 s turns a parked
36097
+ * car's 120 lines into one and still reports a subject that comes back later.
36098
+ */
36099
+ var ZONE_REFUSAL_THROTTLE_MS = 6e4;
36100
+ /** Box quantisation for the collapse key: a subject that moves less than this
36101
+ * is the same sighting for diagnostic purposes. */
36102
+ var ZONE_REFUSAL_BOX_CELL_PX = 64;
36103
+ /** Bound on the collapse map; stale keys are swept when it is exceeded. */
36104
+ var ZONE_REFUSAL_KEY_LIMIT = 512;
36078
36105
  var FrameProcessor = class {
36079
36106
  deviceId;
36080
36107
  /** Detection source this processor handles (`pipeline` | `onboard`).
@@ -36134,6 +36161,23 @@ var FrameProcessor = class {
36134
36161
  /** Sink + sampler for the drops this stage owns (the detection-stage zone
36135
36162
  * filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
36136
36163
  spawnRefusalObserver = null;
36164
+ /** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
36165
+ spawnRefusalForward = null;
36166
+ /**
36167
+ * The spawn refusals of the frame being processed.
36168
+ *
36169
+ * Filled by the same sink that feeds the diagnostic log, and drained at the
36170
+ * end of `process`. It exists because a refused detection inside a watched
36171
+ * zone still has to become an EVENT: an immediate zone rule asks "is there an
36172
+ * animal in the flowerbed now?", which does not need "is it the same animal
36173
+ * as before?" answered first. See `zone-detection-emits-without-a-track`.
36174
+ */
36175
+ refusalsThisFrame = [];
36176
+ /** Running count of refusals-in-zone SEEN — never the throttled number. */
36177
+ zoneRefusalEventCount = 0;
36178
+ zoneRefusalObserver = null;
36179
+ /** Last report time per collapsed key, for {@link ZONE_REFUSAL_THROTTLE_MS}. */
36180
+ zoneRefusalLastReport = /* @__PURE__ */ new Map();
36137
36181
  zoneRefusalSampler = new SpawnRefusalSampler();
36138
36182
  /** Optional stationary-object gate (parked-object suppression). Null until
36139
36183
  * the addon wires it via {@link setStationaryGate}. */
@@ -36148,6 +36192,9 @@ var FrameProcessor = class {
36148
36192
  this.deviceId = deviceId;
36149
36193
  this.source = source;
36150
36194
  this.tracker = new SortTracker(trackerConfig);
36195
+ this.tracker.setSpawnRefusalSink((record) => {
36196
+ this.refusalsThisFrame.push(record);
36197
+ });
36151
36198
  this.stateAnalyzer = new StateAnalyzer(stateConfig);
36152
36199
  this.eventEmitter = new DetectionEventEmitter(emitterConfig);
36153
36200
  this.zones = [];
@@ -36231,6 +36278,14 @@ var FrameProcessor = class {
36231
36278
  this.tracker.setSpawnRefusalObserver(observer);
36232
36279
  this.spawnRefusalObserver = observer;
36233
36280
  }
36281
+ /**
36282
+ * Where a refused-inside-a-zone detection goes. Diagnostics only: this is
36283
+ * deliberately NOT the event stream, so nothing downstream can alert on a
36284
+ * box the confirmation gate threw away (D426).
36285
+ */
36286
+ setZoneRefusalObserver(observer) {
36287
+ this.zoneRefusalObserver = observer;
36288
+ }
36234
36289
  setBindObserver(observer) {
36235
36290
  this.tracker.setBindObserver(observer);
36236
36291
  }
@@ -36254,6 +36309,88 @@ var FrameProcessor = class {
36254
36309
  * ate it. Sampled like every other refusal: an excluded box is excluded on
36255
36310
  * every frame of a passage.
36256
36311
  */
36312
+ /**
36313
+ * Turn this frame's refused spawns into events, but only those inside a zone.
36314
+ *
36315
+ * Drains {@link refusalsThisFrame}. Each surviving record becomes an
36316
+ * `object.detected` event with NO track — carrying the zones it fell in, the
36317
+ * percentage of the box inside each, and the refusal reason so a consumer can
36318
+ * tell it apart from a track whose media went missing.
36319
+ *
36320
+ * `zone-filtered` refusals are excluded on purpose: that reason means the
36321
+ * operator's own detection rules already said the box does not count on this
36322
+ * camera, and re-admitting it here would overrule a decision they made.
36323
+ */
36324
+ /**
36325
+ * Report every detection the tracker refused that fell INSIDE a watched
36326
+ * zone. Diagnostics only — see the spec
36327
+ * `zone-refusal-is-observed-never-notified` for the ten hours of production
36328
+ * that decided this: 1473 of 1511 such refusals were one stationary parcel
36329
+ * re-offered every frame, and both notifications this path ever produced
36330
+ * were phantoms the confirmation gate had correctly rejected.
36331
+ *
36332
+ * Drains {@link refusalsThisFrame}. `zone-filtered` is skipped: a box the
36333
+ * zone filter already removed was never in a zone.
36334
+ */
36335
+ reportRefusedDetectionsInZones(timestamp, frameWidth, frameHeight) {
36336
+ const refusals = this.refusalsThisFrame;
36337
+ this.refusalsThisFrame = [];
36338
+ if (refusals.length === 0 || this.zones.length === 0) return;
36339
+ const observer = this.zoneRefusalObserver;
36340
+ if (observer === null) return;
36341
+ for (const r of refusals) {
36342
+ if (r.reason === "zone-filtered") continue;
36343
+ const { memberships } = this.zoneEngine.splitDetectionZones(r.bbox, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
36344
+ if (memberships.length === 0) continue;
36345
+ this.zoneRefusalEventCount += 1;
36346
+ if (!this.shouldReportZoneRefusal(r, memberships, timestamp)) continue;
36347
+ observer({
36348
+ deviceId: this.deviceId,
36349
+ timestamp,
36350
+ className: r.detClass,
36351
+ confidence: r.detScore,
36352
+ bbox: r.bbox,
36353
+ reason: r.reason,
36354
+ zones: memberships.map((m) => ({
36355
+ zoneId: m.zoneId,
36356
+ ...m.zoneName === void 0 ? {} : { zoneName: m.zoneName },
36357
+ overlapPct: Math.round(m.overlap * 1e3) / 10
36358
+ })),
36359
+ seenSoFar: this.zoneRefusalEventCount
36360
+ });
36361
+ }
36362
+ }
36363
+ /**
36364
+ * Collapse identical refusals. A refused box is refused on EVERY analysed
36365
+ * frame, so a subject that does not leave — the parcel at the door, the
36366
+ * parked car — is an unbounded emitter. The key includes a coarse box, so a
36367
+ * subject that actually moves keeps reporting while a stationary one does
36368
+ * not.
36369
+ */
36370
+ shouldReportZoneRefusal(r, memberships, timestamp) {
36371
+ const cell = (v) => Math.round(v / ZONE_REFUSAL_BOX_CELL_PX);
36372
+ const key = [
36373
+ r.detClass,
36374
+ r.reason,
36375
+ memberships.map((m) => m.zoneId).join("+"),
36376
+ cell(r.bbox.x),
36377
+ cell(r.bbox.y),
36378
+ cell(r.bbox.width),
36379
+ cell(r.bbox.height)
36380
+ ].join("|");
36381
+ const last = this.zoneRefusalLastReport.get(key);
36382
+ if (last !== void 0 && timestamp - last < ZONE_REFUSAL_THROTTLE_MS) return false;
36383
+ this.zoneRefusalLastReport.set(key, timestamp);
36384
+ if (this.zoneRefusalLastReport.size > ZONE_REFUSAL_KEY_LIMIT) {
36385
+ for (const [k, t] of this.zoneRefusalLastReport) if (timestamp - t >= ZONE_REFUSAL_THROTTLE_MS) this.zoneRefusalLastReport.delete(k);
36386
+ }
36387
+ return true;
36388
+ }
36389
+ /** How many refusals-in-zone this processor has SEEN — the measure that says
36390
+ * whether this path is a trickle or a storm, before any throttling. */
36391
+ zoneRefusalEventsEmitted() {
36392
+ return this.zoneRefusalEventCount;
36393
+ }
36257
36394
  reportZoneFiltered(excluded) {
36258
36395
  const observer = this.spawnRefusalObserver;
36259
36396
  if (!observer || excluded.length === 0) return;
@@ -36655,6 +36792,7 @@ var FrameProcessor = class {
36655
36792
  frameHeight
36656
36793
  };
36657
36794
  };
36795
+ this.reportRefusedDetectionsInZones(timestamp, frameWidth, frameHeight);
36658
36796
  const withTrackId = rawEvents.filter((e) => e.detection.trackId);
36659
36797
  const objectEvents = withTrackId.filter((e) => e.type !== "object.detected").map((e) => toObjectEvent(e));
36660
36798
  const appearanceEvents = withTrackId.filter((e) => e.type === "object.detected").map((e) => toObjectEvent(e, "entered"));
@@ -47320,6 +47458,7 @@ var TrackDetailDispatcher = class {
47320
47458
  if (!dev) return;
47321
47459
  dev.scheduler.onTrackEnded(trackId);
47322
47460
  dev.tracks.delete(trackId);
47461
+ dev.parcelEnriched.delete(trackId);
47323
47462
  dev.candidateBest.delete(trackId);
47324
47463
  if (dev.tracks.size === 0) this.clearTimer(dev);
47325
47464
  }
@@ -47329,6 +47468,7 @@ var TrackDetailDispatcher = class {
47329
47468
  this.clearTimer(dev);
47330
47469
  dev.queue.length = 0;
47331
47470
  dev.tracks.clear();
47471
+ dev.parcelEnriched.clear();
47332
47472
  dev.candidateBest.clear();
47333
47473
  }
47334
47474
  this.devices.clear();
@@ -47339,6 +47479,7 @@ var TrackDetailDispatcher = class {
47339
47479
  dev = {
47340
47480
  scheduler: new DetailScheduler(),
47341
47481
  tracks: /* @__PURE__ */ new Map(),
47482
+ parcelEnriched: /* @__PURE__ */ new Map(),
47342
47483
  candidateBest: new BestDetectionTracker(CANDIDATE_IMPROVE_POLICY),
47343
47484
  queue: [],
47344
47485
  coastedFramesIgnored: 0,
@@ -47455,19 +47596,21 @@ var TrackDetailDispatcher = class {
47455
47596
  *
47456
47597
  * 1. `frameHandle` — cheapest when it hits: no payload moves and the runner's
47457
47598
  * 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.
47599
+ * 2. `frameJpeg` — the track's PARKED key frame (D422): the full native frame
47600
+ * the owning node copied at the best instant, retrieved by track and handed
47601
+ * to the runner UNCUT, so the rectangle still comes from the single
47602
+ * derivation (D52). This rung exists because rung 1 is structurally
47603
+ * unreachable for most dispatches a tick + inference routinely lands
47604
+ * outside the lease and it cannot miss on age. It runs ONCE per parcel
47605
+ * per step; a dispatch that finds the same parcel already enriched skips
47606
+ * it, so a coasting track does not re-embed the same image every second.
47464
47607
  * 3. `cropJpeg` — the pre-cut tile. Last, because the caller decided its
47465
47608
  * rectangle and because in practice the same native miss that emptied rung 1
47466
47609
  * empties this one too.
47467
47610
  *
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.
47611
+ * On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG retrieved
47612
+ * on demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
47613
+ * after rung 1 missed — never a frame-rate stream, and never raw pixels.
47471
47614
  *
47472
47615
  * ONE outcome stops the ladder cold: the runner's typed `not-attached`
47473
47616
  * refusal (`detail-refusal.ts`). It names the camera's SESSION state, not
@@ -47505,34 +47648,39 @@ var TrackDetailDispatcher = class {
47505
47648
  }
47506
47649
  });
47507
47650
  }
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", {
47651
+ if (this.deps.captureParkedFrame !== void 0) try {
47652
+ const parked = await this.deps.captureParkedFrame(deviceId, req.trackId);
47653
+ 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
47654
  tags: { deviceId },
47512
47655
  meta: {
47513
47656
  trackId: req.trackId,
47514
47657
  stepId: req.stepId,
47515
- frameWidth: full.width,
47516
- frameHeight: full.height,
47517
- bboxFrameWidth: frame.frameWidth,
47518
- bboxFrameHeight: frame.frameHeight
47658
+ parkedTimestamp: parked.timestamp
47519
47659
  }
47520
47660
  });
47521
47661
  else {
47522
47662
  const derived = await this.deps.runDetailSubtree({
47523
47663
  deviceId,
47524
- frameJpeg: full.base64,
47525
- parent,
47664
+ frameJpeg: parked.base64,
47665
+ parent: {
47666
+ bbox: { ...parked.bbox },
47667
+ className: frame.className
47668
+ },
47526
47669
  steps
47527
- }, frame.nodeId);
47528
- if (derived !== null) return derived.details;
47670
+ }, parked.nodeId);
47671
+ if (derived !== null) {
47672
+ const perStep = dev.parcelEnriched.get(req.trackId) ?? /* @__PURE__ */ new Map();
47673
+ perStep.set(req.stepId, parked.timestamp);
47674
+ dev.parcelEnriched.set(req.trackId, perStep);
47675
+ return derived.details;
47676
+ }
47529
47677
  }
47530
47678
  } catch (err) {
47531
47679
  if (isDetailNotAttachedRefusal(err)) {
47532
47680
  this.warnNotAttachedThrottled(deviceId, dev, req);
47533
47681
  return null;
47534
47682
  }
47535
- this.deps.logger.debug("detail full-frame fallback call failed", {
47683
+ this.deps.logger.debug("detail parked-frame rung failed", {
47536
47684
  tags: { deviceId },
47537
47685
  meta: {
47538
47686
  trackId: req.trackId,
@@ -47599,7 +47747,7 @@ var TrackDetailDispatcher = class {
47599
47747
  trackId: req.trackId,
47600
47748
  stepId: req.stepId,
47601
47749
  reason: req.reason,
47602
- triedFullFrame: this.deps.captureFullFrameBase64 !== void 0,
47750
+ triedParkedFrame: this.deps.captureParkedFrame !== void 0,
47603
47751
  triedCropJpeg: this.deps.captureCropBase64 !== void 0
47604
47752
  }
47605
47753
  });
@@ -53964,6 +54112,17 @@ async function encodeKeyFrameVariants(native) {
53964
54112
  })
53965
54113
  };
53966
54114
  }
54115
+ /**
54116
+ * The web companion from the PARKED key frame's own JPEG (D422): the parcel is
54117
+ * stored verbatim as `keyFrame`, and `keyFrameSmall` is this — the same bytes
54118
+ * downscaled to {@link KEYFRAME_SMALL_MAX_WIDTH}. One decode, one encode,
54119
+ * never a second fetch; `withoutEnlargement` never upscales a sub-960 frame.
54120
+ * The by-handle twin ({@link encodeKeyFrameVariants}) remains for the
54121
+ * summary group shot, which still fetches a native surface by handle.
54122
+ */
54123
+ async function deriveKeyFrameSmall(keyFrameJpeg) {
54124
+ return (0, sharp.default)(keyFrameJpeg).resize(960, null, { withoutEnlargement: true }).jpeg({ quality: 88 }).toBuffer();
54125
+ }
53967
54126
  //#endregion
53968
54127
  //#region src/pipeline-analytics/pipeline/key-frame-metrics.ts
53969
54128
  var DEFAULT_FLUSH_INTERVAL_MS = 6e4;
@@ -53989,11 +54148,8 @@ var KeyFrameCaptureLog = class {
53989
54148
  w.landed += 1;
53990
54149
  if (sample.retry) w.retryLanded += 1;
53991
54150
  break;
53992
- case "native-miss":
53993
- w.nativeMiss += 1;
53994
- break;
53995
- case "ram-tier":
53996
- w.ramTier += 1;
54151
+ case "parcel-missing":
54152
+ w.parcelMissing += 1;
53997
54153
  break;
53998
54154
  case "error":
53999
54155
  w.error += 1;
@@ -54022,8 +54178,7 @@ var KeyFrameCaptureLog = class {
54022
54178
  landed: 0,
54023
54179
  retryAttempts: 0,
54024
54180
  retryLanded: 0,
54025
- nativeMiss: 0,
54026
- ramTier: 0,
54181
+ parcelMissing: 0,
54027
54182
  error: 0,
54028
54183
  skippedNodeOffline: 0
54029
54184
  };
@@ -54038,8 +54193,7 @@ var KeyFrameCaptureLog = class {
54038
54193
  landed: w.landed,
54039
54194
  retryAttempts: w.retryAttempts,
54040
54195
  retryLanded: w.retryLanded,
54041
- nativeMiss: w.nativeMiss,
54042
- ramTier: w.ramTier,
54196
+ parcelMissing: w.parcelMissing,
54043
54197
  error: w.error,
54044
54198
  skippedNodeOffline: w.skippedNodeOffline
54045
54199
  };
@@ -54048,63 +54202,44 @@ var KeyFrameCaptureLog = class {
54048
54202
  //#endregion
54049
54203
  //#region src/pipeline-analytics/pipeline/key-frame-persist.ts
54050
54204
  /**
54051
- * Fetch, encode and store one track's key frame + its 960 companion.
54205
+ * Read, derive and store one track's key frame + its web companion.
54052
54206
  *
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
54207
+ * PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
54208
+ * parks a later frame. The `keyFrame` write lands FIRST and its key is what
54209
+ * the outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
54056
54210
  * retry that would re-shoot a key frame the rebuild can already read.
54057
54211
  */
54058
54212
  async function persistTrackKeyFrame(deps, input) {
54059
- const { deviceId, trackId, frameHandle, retry } = input;
54213
+ const { deviceId, trackId, retry } = input;
54060
54214
  const drop = {
54061
54215
  tags: { deviceId },
54062
54216
  meta: {
54063
54217
  trackId,
54064
- nodeId: frameHandle.nodeId,
54065
54218
  retry
54066
54219
  }
54067
54220
  };
54068
54221
  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" };
54222
+ const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
54223
+ if (parcel === null || parcel.jpeg.byteLength === 0) {
54224
+ deps.logger.error("key-frame parcel missing nothing to write, retry parks a later frame", { ...drop });
54225
+ return { kind: "parcel-missing" };
54091
54226
  }
54092
- const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
54093
54227
  const keyFrameKey = await deps.media.putReplacing({
54094
54228
  deviceId,
54095
54229
  ownerKind: "track",
54096
54230
  ownerId: trackId,
54097
54231
  kind: "keyFrame",
54098
- timestamp: input.timestamp,
54099
- data: keyFrame
54232
+ timestamp: parcel.timestamp,
54233
+ data: parcel.jpeg
54100
54234
  });
54101
54235
  try {
54236
+ const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
54102
54237
  await deps.media.putReplacing({
54103
54238
  deviceId,
54104
54239
  ownerKind: "track",
54105
54240
  ownerId: trackId,
54106
54241
  kind: "keyFrameSmall",
54107
- timestamp: input.timestamp,
54242
+ timestamp: parcel.timestamp,
54108
54243
  data: keyFrameSmall
54109
54244
  });
54110
54245
  } catch (err) {
@@ -54112,17 +54247,29 @@ async function persistTrackKeyFrame(deps, input) {
54112
54247
  ...drop,
54113
54248
  meta: {
54114
54249
  ...drop.meta,
54250
+ nodeId: parcel.nodeId,
54115
54251
  error: require_dist.errMsg(err)
54116
54252
  }
54117
54253
  });
54118
54254
  }
54255
+ deps.logger.debug("key frame written from the parked parcel", {
54256
+ ...drop,
54257
+ meta: {
54258
+ ...drop.meta,
54259
+ nodeId: parcel.nodeId,
54260
+ width: parcel.width,
54261
+ height: parcel.height,
54262
+ bytes: parcel.jpeg.byteLength,
54263
+ parkedTimestamp: parcel.timestamp
54264
+ }
54265
+ });
54119
54266
  return {
54120
54267
  kind: "landed",
54121
54268
  keyFrameKey
54122
54269
  };
54123
54270
  } catch (err) {
54124
54271
  const error = require_dist.errMsg(err);
54125
- deps.logger.warn("key-frame capture failed", {
54272
+ deps.logger.warn("key-frame write failed", {
54126
54273
  ...drop,
54127
54274
  meta: {
54128
54275
  ...drop.meta,
@@ -54309,6 +54456,61 @@ function createKeyFrameCrop(deps) {
54309
54456
  };
54310
54457
  }
54311
54458
  //#endregion
54459
+ //#region src/pipeline-analytics/pipeline/track-park.ts
54460
+ /** The whole frame — what a `keyFrame` park cuts. */
54461
+ var FULL_FRAME = {
54462
+ x: 0,
54463
+ y: 0,
54464
+ w: 1,
54465
+ h: 1
54466
+ };
54467
+ /** The two parcels a best-frame decision orders, in a fixed order. */
54468
+ function buildParkOrders(decision) {
54469
+ const subject = {
54470
+ bbox: { ...decision.bbox },
54471
+ frameWidth: decision.frameWidth,
54472
+ frameHeight: decision.frameHeight
54473
+ };
54474
+ const layout = deliveredWideCentralSquareLayout(decision.bbox, {
54475
+ W: decision.frameWidth,
54476
+ H: decision.frameHeight
54477
+ });
54478
+ const base = {
54479
+ deviceId: decision.deviceId,
54480
+ trackId: decision.trackId,
54481
+ handle: decision.frameHandle,
54482
+ timestamp: decision.timestamp,
54483
+ subject
54484
+ };
54485
+ return [{
54486
+ ...base,
54487
+ kind: "keyFrame",
54488
+ bbox: FULL_FRAME,
54489
+ maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
54490
+ }, {
54491
+ ...base,
54492
+ kind: "thumbnail",
54493
+ bbox: layout.fetch,
54494
+ layout
54495
+ }];
54496
+ }
54497
+ /**
54498
+ * The subject's box in the PARCEL's pixel space. The box was measured on the
54499
+ * ≤640 detection raster; the parcel is native-resolution. Both show the same
54500
+ * scene at the same aspect, so a uniform per-axis scale places the box on the
54501
+ * parcel — the runner then derives the crop rectangle from it (D52).
54502
+ */
54503
+ function scaleSubjectIntoParcel(subject, parcelWidth, parcelHeight) {
54504
+ const sx = parcelWidth / subject.frameWidth;
54505
+ const sy = parcelHeight / subject.frameHeight;
54506
+ return {
54507
+ x: subject.bbox.x * sx,
54508
+ y: subject.bbox.y * sy,
54509
+ w: subject.bbox.w * sx,
54510
+ h: subject.bbox.h * sy
54511
+ };
54512
+ }
54513
+ //#endregion
54312
54514
  //#region src/pipeline-analytics/pipeline/native-detection.ts
54313
54515
  /**
54314
54516
  * Nominal frame size used to denormalize native `[0,1]` boxes when a
@@ -58839,7 +59041,28 @@ async function reconcileGallerySamples(deps, options) {
58839
59041
  }
58840
59042
  //#endregion
58841
59043
  //#region src/pipeline-analytics/summary/summary-store.ts
59044
+ /**
59045
+ * @durable class=ledger owner=pipeline-analytics
59046
+ * write="one row per SUMMARY — a window of co-present tracks on one camera
59047
+ * group. Opened by `openOrAbsorb` when the first member arrives, updated
59048
+ * in place while the window absorbs more, and finalised by `seal` when the
59049
+ * window closes. Per WINDOW, never per frame or per track: the sealed
59050
+ * summary is what a notification rule reads."
59051
+ * retention="age-keyed on `sealedAt`, swept with the rest of the analytics
59052
+ * retention. An unsealed row belongs to a window still open and is never
59053
+ * swept by age — it is closed or cascaded away with its members."
59054
+ */
58842
59055
  var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
59056
+ /**
59057
+ * @durable class=ledger owner=pipeline-analytics
59058
+ * write="one row per (summary, track) membership, written as a window
59059
+ * absorbs a track. The join that lets a sealed summary name its subjects
59060
+ * without re-reading the track store."
59061
+ * retention="cascaded — a member dies with its summary (`delete-track-cascade`
59062
+ * counts them as leaves) or with the track it names. It carries no clock of
59063
+ * its own: an age sweep here could outlive or predecease the summary the
59064
+ * row only means anything inside of."
59065
+ */
58843
59066
  var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
58844
59067
  var SUMMARIES_COLUMNS = [
58845
59068
  {
@@ -63667,35 +63890,6 @@ function createNativeFrameTransport(deps) {
63667
63890
  const getRemoteFrame = async (handle) => requestNativeCrop(handle, FULL_FRAME_BBOX, handle.width, replyToFullFrame);
63668
63891
  const fetchNativeFullFrameTiered = async (handle, maxWidth) => requestNativeCrop(handle, FULL_FRAME_BBOX, maxWidth, replyToTieredFullFrame);
63669
63892
  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
63893
  const cropMetricLogger = logger.child("NativeCrop");
63700
63894
  let nativeHits = 0;
63701
63895
  let nativeFallbacks = 0;
@@ -63775,7 +63969,6 @@ function createNativeFrameTransport(deps) {
63775
63969
  getRemoteFrame,
63776
63970
  getNativeFullFrameRgb,
63777
63971
  getNativeKeyFrameRgb: fetchNativeFullFrameTiered,
63778
- getFullFrameJpegBase64,
63779
63972
  getNativeCropJpeg,
63780
63973
  captureCrop: createCaptureCrop({
63781
63974
  tryNativeCrop,
@@ -63886,6 +64079,233 @@ function parseTopologySnapshotNodes(data) {
63886
64079
  return parsed;
63887
64080
  }
63888
64081
  //#endregion
64082
+ //#region src/pipeline-analytics/runtime/parked-frame-client.ts
64083
+ /**
64084
+ * Bound on the mirror. A track's record leaves at release; this is the
64085
+ * backstop for a lost close. At the busiest measured hub (~12 sessions,
64086
+ * a few tracks each) 4 096 is an hour of tracks, past which the OLDEST record
64087
+ * is forgotten — its parcel still ages out on the node on its own.
64088
+ */
64089
+ var MAX_MIRRORED_TRACKS = 4096;
64090
+ /** One "runner has no park method" line per camera per minute. */
64091
+ var SKEW_WARN_THROTTLE_MS = 6e4;
64092
+ function inFlightKey(trackId, kind) {
64093
+ return `${trackId} ${kind}`;
64094
+ }
64095
+ /** Strip the hub-side context off an order: only the wire fields travel. */
64096
+ function toWireInput(order) {
64097
+ return {
64098
+ deviceId: order.deviceId,
64099
+ trackId: order.trackId,
64100
+ kind: order.kind,
64101
+ handle: order.handle,
64102
+ bbox: order.bbox,
64103
+ ...order.maxWidth !== void 0 ? { maxWidth: order.maxWidth } : {},
64104
+ timestamp: order.timestamp
64105
+ };
64106
+ }
64107
+ function createParkedFrameClient(deps) {
64108
+ const { api, logger } = deps;
64109
+ const now = deps.now ?? Date.now;
64110
+ /** trackId → kind → what the hub kept beside the parcel. */
64111
+ const mirror = /* @__PURE__ */ new Map();
64112
+ const inFlight = /* @__PURE__ */ new Map();
64113
+ const skewWarnedAt = /* @__PURE__ */ new Map();
64114
+ const remember = (order, answer) => {
64115
+ let kinds = mirror.get(order.trackId);
64116
+ if (kinds === void 0) {
64117
+ kinds = /* @__PURE__ */ new Map();
64118
+ mirror.set(order.trackId, kinds);
64119
+ while (mirror.size > MAX_MIRRORED_TRACKS) {
64120
+ const oldest = mirror.keys().next().value;
64121
+ if (oldest === void 0) break;
64122
+ mirror.delete(oldest);
64123
+ }
64124
+ }
64125
+ kinds.set(order.kind, {
64126
+ nodeId: answer.nodeId,
64127
+ deviceId: order.deviceId,
64128
+ kind: order.kind,
64129
+ timestamp: order.timestamp,
64130
+ width: answer.width,
64131
+ height: answer.height,
64132
+ subject: order.subject,
64133
+ ...order.layout !== void 0 ? { layout: order.layout } : {}
64134
+ });
64135
+ };
64136
+ const warnSkew = (order) => {
64137
+ const at = skewWarnedAt.get(order.deviceId) ?? 0;
64138
+ if (now() - at < SKEW_WARN_THROTTLE_MS) return;
64139
+ skewWarnedAt.set(order.deviceId, now());
64140
+ logger.warn("track frame park impossible — the runner api has no park method (version skew)", {
64141
+ tags: { deviceId: order.deviceId },
64142
+ meta: {
64143
+ trackId: order.trackId,
64144
+ kind: order.kind,
64145
+ nodeId: order.handle.nodeId
64146
+ }
64147
+ });
64148
+ };
64149
+ const sendPark = async (order) => {
64150
+ if (api === void 0) {
64151
+ warnSkew(order);
64152
+ return false;
64153
+ }
64154
+ if (deps.isNodeOffline?.(order.handle.nodeId) === true) {
64155
+ logger.debug("track frame park skipped — the owning node is offline in the registry", {
64156
+ tags: { deviceId: order.deviceId },
64157
+ meta: {
64158
+ trackId: order.trackId,
64159
+ kind: order.kind,
64160
+ nodeId: order.handle.nodeId
64161
+ }
64162
+ });
64163
+ return false;
64164
+ }
64165
+ const meta = {
64166
+ trackId: order.trackId,
64167
+ kind: order.kind,
64168
+ nodeId: order.handle.nodeId,
64169
+ handle: `${order.handle.shmId}#${order.handle.slot}#${order.handle.seq}`
64170
+ };
64171
+ try {
64172
+ const answer = await api.parkTrackFrame.mutate(toWireInput(order), require_dist.nodePin(order.handle.nodeId));
64173
+ if (answer.parked) {
64174
+ remember(order, answer);
64175
+ logger.debug("track frame parked", {
64176
+ tags: { deviceId: order.deviceId },
64177
+ meta: {
64178
+ ...meta,
64179
+ bytes: answer.bytes,
64180
+ replaced: answer.replaced
64181
+ }
64182
+ });
64183
+ return true;
64184
+ }
64185
+ logger.warn("track frame park refused by the owning node", {
64186
+ tags: { deviceId: order.deviceId },
64187
+ meta: {
64188
+ ...meta,
64189
+ reason: answer.reason,
64190
+ handleAgeMs: answer.handleAgeMs,
64191
+ ...answer.detail !== void 0 ? { detail: answer.detail } : {}
64192
+ }
64193
+ });
64194
+ return false;
64195
+ } catch (err) {
64196
+ logger.warn("track frame park failed — the order did not reach the node", {
64197
+ tags: { deviceId: order.deviceId },
64198
+ meta: {
64199
+ ...meta,
64200
+ error: require_dist.errMsg(err)
64201
+ }
64202
+ });
64203
+ return false;
64204
+ }
64205
+ };
64206
+ const park = (order) => {
64207
+ const key = inFlightKey(order.trackId, order.kind);
64208
+ const pending = inFlight.get(key);
64209
+ if (pending !== void 0) return pending;
64210
+ const run = sendPark(order).finally(() => {
64211
+ if (inFlight.get(key) === run) inFlight.delete(key);
64212
+ });
64213
+ inFlight.set(key, run);
64214
+ return run;
64215
+ };
64216
+ const awaitPark = async (trackId, kind) => {
64217
+ const pending = inFlight.get(inFlightKey(trackId, kind));
64218
+ if (pending !== void 0) await pending;
64219
+ };
64220
+ const info = (trackId, kind) => mirror.get(trackId)?.get(kind);
64221
+ const fetch = async (trackId, kind) => {
64222
+ const known = info(trackId, kind);
64223
+ if (known === void 0 || api === void 0) return null;
64224
+ if (deps.isNodeOffline?.(known.nodeId) === true) {
64225
+ logger.warn("parked frame retrieval skipped — the holding node is offline in the registry", {
64226
+ tags: { deviceId: known.deviceId },
64227
+ meta: {
64228
+ trackId,
64229
+ kind,
64230
+ nodeId: known.nodeId
64231
+ }
64232
+ });
64233
+ return null;
64234
+ }
64235
+ try {
64236
+ const parcel = await api.getParkedTrackFrame.query({
64237
+ deviceId: known.deviceId,
64238
+ trackId,
64239
+ kind
64240
+ }, require_dist.nodePin(known.nodeId));
64241
+ if (parcel === null) {
64242
+ logger.warn("parked frame gone at retrieval — the node no longer holds it", {
64243
+ tags: { deviceId: known.deviceId },
64244
+ meta: {
64245
+ trackId,
64246
+ kind,
64247
+ nodeId: known.nodeId,
64248
+ parkedTimestamp: known.timestamp
64249
+ }
64250
+ });
64251
+ return null;
64252
+ }
64253
+ return {
64254
+ jpeg: Buffer.from(parcel.jpeg, "base64"),
64255
+ width: parcel.width,
64256
+ height: parcel.height,
64257
+ timestamp: parcel.timestamp,
64258
+ info: known
64259
+ };
64260
+ } catch (err) {
64261
+ logger.warn("parked frame retrieval failed", {
64262
+ tags: { deviceId: known.deviceId },
64263
+ meta: {
64264
+ trackId,
64265
+ kind,
64266
+ nodeId: known.nodeId,
64267
+ error: require_dist.errMsg(err)
64268
+ }
64269
+ });
64270
+ return null;
64271
+ }
64272
+ };
64273
+ const release = (trackId) => {
64274
+ const kinds = mirror.get(trackId);
64275
+ if (kinds === void 0) return;
64276
+ mirror.delete(trackId);
64277
+ if (api === void 0) return;
64278
+ const targets = /* @__PURE__ */ new Map();
64279
+ for (const record of kinds.values()) targets.set(record.nodeId, record.deviceId);
64280
+ for (const [nodeId, deviceId] of targets) api.releaseParkedTrackFrames.mutate({
64281
+ deviceId,
64282
+ trackId
64283
+ }, require_dist.nodePin(nodeId)).catch((err) => {
64284
+ logger.debug("parked frame release did not reach the node", {
64285
+ tags: { deviceId },
64286
+ meta: {
64287
+ trackId,
64288
+ nodeId,
64289
+ error: require_dist.errMsg(err)
64290
+ }
64291
+ });
64292
+ });
64293
+ };
64294
+ const dispose = () => {
64295
+ mirror.clear();
64296
+ inFlight.clear();
64297
+ skewWarnedAt.clear();
64298
+ };
64299
+ return {
64300
+ park,
64301
+ awaitPark,
64302
+ info,
64303
+ fetch,
64304
+ release,
64305
+ dispose
64306
+ };
64307
+ }
64308
+ //#endregion
63889
64309
  //#region src/pipeline-analytics/runtime/widget-manifest.ts
63890
64310
  /** The remote-module coordinates shared by every analytics widget. */
63891
64311
  var REMOTE_NAME = "addon_pipeline_analytics_widgets";
@@ -64987,7 +65407,8 @@ function isUniformRgbFrame(data, width, height) {
64987
65407
  * frame downscaled to {@link SNAPSHOT_MAX_WIDTH} (960) with ONLY the track's own
64988
65408
  * box burned in, falling back to the ≤640 detection frame (honest lower res,
64989
65409
  * never an upscale) on a native miss. The best `thumbnail` stays a clean,
64990
- * uncapped native subject crop (native-or-nothing).
65410
+ * uncapped native subject crop — read from the track's PARKED slab, by track
65411
+ * (D422), never by handle.
64991
65412
  *
64992
65413
  * The detection frame is resolved ONCE per call (blank-frame guard); native full
64993
65414
  * frames + subject crops are fetched per target by handle. Frames recycle in
@@ -65255,10 +65676,10 @@ var EventMediaDispatcher = class {
65255
65676
  if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
65256
65677
  let thumbnailWritten = false;
65257
65678
  if (sn.bestThumbnail) {
65258
- const variants = await this.cropSubjectVariants(deviceId, sn.trackId, frameHandle, fw, fh, sn.bbox);
65679
+ const variants = await this.cropSubjectVariants(deviceId, sn.trackId);
65259
65680
  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");
65681
+ thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", variants.timestamp, variants.thumbnail, "native");
65682
+ await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", variants.timestamp, variants.thumbnailSmall, "native");
65262
65683
  }
65263
65684
  }
65264
65685
  return {
@@ -65336,81 +65757,49 @@ var EventMediaDispatcher = class {
65336
65757
  }
65337
65758
  /**
65338
65759
  * 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
- }
65760
+ * fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22), from the
65761
+ * track's PARKED slab (D422). ONE read, by `(deviceId, trackId)`: the
65762
+ * in-frame slab of the 16:9 central-square WINDOW
65763
+ * ({@link wideCentralSquareLayout}) the runner cut at uncapped native
65764
+ * resolution the instant the track's best was decided. It is composed into
65765
+ * the full 16:9 window (lateral letterbox only for the part outside the
65766
+ * frame) the `thumbnail`. The `thumbnailSmall` is DERIVED by downscaling
65767
+ * that SAME window to the 480 long-side cap never a second read, never
65768
+ * upscaled ({@link deriveThumbnailSmall} never upscales a smaller window).
65769
+ *
65770
+ * Both variants share the SAME framing + central-square containment
65771
+ * guarantee: wide consumers use the 16:9 image as-is; square consumers
65772
+ * center-crop the middle square (subject always inside it).
65773
+ *
65774
+ * PARCEL-OR-NOTHING: no parcel (or a throw) returns `null` after a loud
65775
+ * `logger.warn`; the caller SKIPS the write and the per-frame retry parks a
65776
+ * later frame. Never a by-handle crop, never a local upscale of a ≤640 tile.
65777
+ */
65778
+ async cropSubjectVariants(deviceId, trackId) {
65372
65779
  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", {
65780
+ const parked = await this.deps.getParkedSubjectSlab(deviceId, trackId);
65781
+ if (!parked) {
65782
+ this.deps.logger.warn("best-shot parcel missing — no thumbnail this frame, will retry", {
65392
65783
  tags: { deviceId },
65393
65784
  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"
65785
+ trackId,
65786
+ reason: "parcel-missing"
65399
65787
  }
65400
65788
  });
65401
- this.deps.onSubjectCropMiss?.(deviceId, trackId, "native-miss");
65789
+ this.deps.onSubjectCropMiss?.(deviceId, trackId, "parcel-missing");
65402
65790
  return null;
65403
65791
  }
65404
- const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
65792
+ const thumbnail = await composeWideCentralSquareThumbnail(parked.slab, parked.layout);
65405
65793
  return {
65406
65794
  thumbnail,
65407
- thumbnailSmall: await deriveThumbnailSmall(thumbnail)
65795
+ thumbnailSmall: await deriveThumbnailSmall(thumbnail),
65796
+ timestamp: parked.timestamp
65408
65797
  };
65409
65798
  } catch (err) {
65410
- this.deps.logger.warn("native subject crop misswill retry, no upscale", {
65799
+ this.deps.logger.warn("best-shot parcel read failedno thumbnail this frame, will retry", {
65411
65800
  tags: { deviceId },
65412
65801
  meta: {
65413
- shmId: frameHandle.shmId,
65802
+ trackId,
65414
65803
  reason: "threw",
65415
65804
  error: err instanceof Error ? err.message : String(err)
65416
65805
  }
@@ -70701,7 +71090,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
70701
71090
  * the FULL cleanup litany on every teardown path (and cancels the track's
70702
71091
  * queued captures via the onClose hook) and `closeDevice` finally clears
70703
71092
  * per-track state on device removal (the two documented leaks). */
70704
- residents = new TrackResidentState({ onClose: (trackId) => this.captureScheduler.cancelForTrack(trackId) });
71093
+ residents = new TrackResidentState({ onClose: (trackId) => {
71094
+ this.captureScheduler.cancelForTrack(trackId);
71095
+ this.parkedFrames?.release(trackId);
71096
+ } });
71097
+ /**
71098
+ * The hub's side of a PARK (D422): orders the owning node to keep a track's
71099
+ * best frame the instant `BestDetectionTracker` accepts it, and answers the
71100
+ * keyFrame writer, the best-shot thumbnail and the detail plane's full-frame
71101
+ * rung by `(deviceId, trackId, kind)` — never by handle. Null until init.
71102
+ */
71103
+ parkedFrames = null;
70705
71104
  /** Defence in depth for the 2026-08-21 `b50e09f7` incident: a row the track
70706
71105
  * store has already CLOSED never earns another media target, whatever the
70707
71106
  * tracker keeps handing back. Reads the store's in-RAM close record only —
@@ -71096,17 +71495,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71096
71495
  noteCropOutcome: (deviceId, reason) => this.failureReport.noteEnrichmentCrop(deviceId, reason)
71097
71496
  });
71098
71497
  this.getNativeKeyFrameRgb = transport.getNativeKeyFrameRgb;
71498
+ this.parkedFrames = createParkedFrameClient({
71499
+ api: api.pipelineRunner,
71500
+ logger: logger.child("ParkedFrames"),
71501
+ isNodeOffline: (nodeId) => this.nodeLiveness.isKnownOffline(nodeId, Date.now())
71502
+ });
71099
71503
  this.captureCrop = transport.captureCrop;
71100
71504
  this.eventMediaDispatcher = new EventMediaDispatcher({
71101
71505
  getRemoteFrame: transport.getRemoteFrame,
71102
71506
  getNativeCropJpeg: transport.getNativeCropJpeg,
71103
71507
  getNativeFullFrameRgb: transport.getNativeFullFrameRgb,
71508
+ getParkedSubjectSlab: (deviceId, trackId) => this.fetchParkedSubjectSlab(deviceId, trackId),
71104
71509
  mediaStore: stores.mediaStore,
71105
71510
  logger: logger.child("EventMediaDispatcher"),
71106
71511
  onSubjectCropMiss: (deviceId, trackId, reason) => this.residents.notePreviewCaptureMiss(deviceId, trackId, `thumbnail:${reason}`)
71107
71512
  });
71108
71513
  this.buildRecognizers(api, logger, stores, transport);
71109
- this.buildDetailDispatcher(api, logger, transport);
71514
+ this.buildDetailDispatcher(api, logger);
71110
71515
  const capProviders = this.buildAnalyticsProviders(api, logger);
71111
71516
  await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
71112
71517
  await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
@@ -71936,7 +72341,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71936
72341
  * demand via `pipelineRunner.runDetailSubtree`. The dispatcher owns the
71937
72342
  * per-device schedulers, concurrency cap, and 1 s tick; result routing +
71938
72343
  * the pinned call + crop fallback are injected here. */
71939
- buildDetailDispatcher(api, logger, transport) {
72344
+ buildDetailDispatcher(api, logger) {
71940
72345
  const runnerApi = api.pipelineRunner;
71941
72346
  this.detailDispatcher = new TrackDetailDispatcher({
71942
72347
  logger: logger.child("DetailDispatcher"),
@@ -71946,13 +72351,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71946
72351
  return runnerApi.runDetailSubtree.mutate(input);
71947
72352
  },
71948
72353
  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
72354
+ captureParkedFrame: async (_deviceId, trackId) => {
72355
+ const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
72356
+ if (parcel === null) return null;
72357
+ return {
72358
+ base64: parcel.jpeg.toString("base64"),
72359
+ width: parcel.width,
72360
+ height: parcel.height,
72361
+ bbox: scaleSubjectIntoParcel(parcel.info.subject, parcel.width, parcel.height),
72362
+ nodeId: parcel.info.nodeId,
72363
+ timestamp: parcel.timestamp
71956
72364
  };
71957
72365
  },
71958
72366
  captureCropBase64: async (frame) => {
@@ -73594,6 +74002,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
73594
74002
  this.audioMarkers?.flush(Date.now());
73595
74003
  this.detailDispatcher?.dispose();
73596
74004
  this.detailDispatcher = null;
74005
+ this.parkedFrames?.dispose();
73597
74006
  this.overlayState.clear();
73598
74007
  this.overlaySynthesisWarnAt.clear();
73599
74008
  this.processors.clear();
@@ -74252,7 +74661,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
74252
74661
  let plateCrops = 0;
74253
74662
  for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
74254
74663
  else plateCrops += 1;
74255
- const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
74664
+ const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight, frameHandle);
74256
74665
  this.offerGroupShot(deviceId, result, frameHandle);
74257
74666
  for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
74258
74667
  const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
@@ -74371,7 +74780,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
74371
74780
  }).catch(() => {});
74372
74781
  }
74373
74782
  const keyFrameCaptures = selectKeyFrameCaptures(snapshotTargets);
74374
- if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures, frameHandle);
74783
+ if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures);
74375
74784
  }
74376
74785
  if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
74377
74786
  deviceId,
@@ -75301,10 +75710,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75301
75710
  });
75302
75711
  }).catch(() => {});
75303
75712
  }
75304
- async persistKeyFrames(deviceId, timestamp, captures, frameHandle) {
75305
- const getNative = this.getNativeKeyFrameRgb;
75713
+ /**
75714
+ * Order the parcels a best-frame decision wants (D422). Skipped entirely at
75715
+ * a node the registry already called offline (D58) — logged on the
75716
+ * transition by `noteCapturesGated`, counted in the key-frame window.
75717
+ * `onlyIfMissing` is the retry rule: a later frame may fill a kind that has
75718
+ * no parcel, never replace one the ranking preferred.
75719
+ */
75720
+ issueParkOrders(decision, kinds, onlyIfMissing) {
75721
+ const client = this.parkedFrames;
75722
+ if (client === null) return;
75723
+ if (this.nodeLiveness.isKnownOffline(decision.frameHandle.nodeId, Date.now())) {
75724
+ this.noteCapturesGated(decision.deviceId, decision.frameHandle.nodeId);
75725
+ return;
75726
+ }
75727
+ for (const order of buildParkOrders(decision)) {
75728
+ if (!kinds.includes(order.kind)) continue;
75729
+ if (onlyIfMissing && client.info(order.trackId, order.kind) !== void 0) continue;
75730
+ client.park(order);
75731
+ }
75732
+ }
75733
+ /** A parcel by track, after any park in flight for it has landed. */
75734
+ async fetchParkedParcel(trackId, kind) {
75735
+ const client = this.parkedFrames;
75736
+ if (client === null) return null;
75737
+ await client.awaitPark(trackId, kind);
75738
+ return client.fetch(trackId, kind);
75739
+ }
75740
+ /** The best-shot slab for the dispatcher: the parcel plus the layout the hub kept. */
75741
+ async fetchParkedSubjectSlab(_deviceId, trackId) {
75742
+ const parcel = await this.fetchParkedParcel(trackId, "thumbnail");
75743
+ if (parcel === null || parcel.info.layout === void 0) return null;
75744
+ return {
75745
+ slab: parcel.jpeg,
75746
+ layout: parcel.info.layout,
75747
+ timestamp: parcel.timestamp
75748
+ };
75749
+ }
75750
+ async persistKeyFrames(deviceId, timestamp, captures) {
75306
75751
  const mediaStore = this.mediaStore;
75307
- if (!getNative || !mediaStore) return;
75752
+ if (this.parkedFrames === null || !mediaStore) return;
75308
75753
  await Promise.all(captures.map((capture) => this.captureScheduler.request({
75309
75754
  deviceId,
75310
75755
  trackId: capture.trackId,
@@ -75314,16 +75759,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75314
75759
  exec: async () => {
75315
75760
  this.residents.noteKeyFrameAttempt(deviceId, capture.trackId, timestamp, capture.retry);
75316
75761
  const outcome = await persistTrackKeyFrame({
75317
- fetchNativeFullFrame: getNative,
75318
- encodeVariants: encodeKeyFrameVariants,
75762
+ fetchParkedKeyFrame: async (parcelDeviceId, trackId) => {
75763
+ const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
75764
+ if (parcel === null || parcel.info.deviceId !== parcelDeviceId) return null;
75765
+ return {
75766
+ jpeg: parcel.jpeg,
75767
+ width: parcel.width,
75768
+ height: parcel.height,
75769
+ timestamp: parcel.timestamp,
75770
+ nodeId: parcel.info.nodeId
75771
+ };
75772
+ },
75773
+ deriveSmall: deriveKeyFrameSmall,
75319
75774
  media: mediaStore,
75320
75775
  logger: this.ctx.logger
75321
75776
  }, {
75322
75777
  deviceId,
75323
75778
  trackId: capture.trackId,
75324
- timestamp,
75325
- frameHandle,
75326
- maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
75327
75779
  retry: capture.retry
75328
75780
  });
75329
75781
  if (outcome.kind === "landed") {
@@ -75403,10 +75855,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75403
75855
  isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
75404
75856
  captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
75405
75857
  captureKeyFrame: async (req) => {
75858
+ this.issueParkOrders({
75859
+ deviceId: req.deviceId,
75860
+ trackId: req.trackId,
75861
+ timestamp: req.timestamp,
75862
+ frameHandle: req.frameHandle,
75863
+ bbox: { ...req.bodyBbox },
75864
+ frameWidth: req.frameWidth,
75865
+ frameHeight: req.frameHeight
75866
+ }, ["keyFrame"], true);
75406
75867
  await this.persistKeyFrames(req.deviceId, req.timestamp, [{
75407
75868
  trackId: req.trackId,
75408
75869
  retry: false
75409
- }], req.frameHandle);
75870
+ }]);
75410
75871
  }
75411
75872
  }, input);
75412
75873
  }
@@ -75460,10 +75921,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75460
75921
  * (coalescing, per-device budget and land-only bookkeeping identical). */
75461
75922
  captureSemanticBestShot(request) {
75462
75923
  const { deviceId, trackId, timestamp, bbox, frameHandle } = request;
75924
+ this.issueParkOrders({
75925
+ deviceId,
75926
+ trackId,
75927
+ timestamp,
75928
+ frameHandle,
75929
+ bbox: { ...bbox },
75930
+ frameWidth: request.frameWidth,
75931
+ frameHeight: request.frameHeight
75932
+ }, ["keyFrame", "thumbnail"], false);
75463
75933
  this.persistKeyFrames(deviceId, timestamp, [{
75464
75934
  trackId,
75465
75935
  retry: false
75466
- }], frameHandle);
75936
+ }]);
75467
75937
  const dispatcher = this.eventMediaDispatcher;
75468
75938
  if (!dispatcher) return;
75469
75939
  if (this.captureScheduler.isKeyActive(captureCoalesceKey(deviceId, "thumbnail", trackId))) {
@@ -75614,7 +76084,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75614
76084
  }
75615
76085
  return retries;
75616
76086
  }
75617
- buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
76087
+ buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight, frameHandle) {
75618
76088
  const targets = [];
75619
76089
  for (const t of tracked) {
75620
76090
  if (t.matchedThisFrame === false) continue;
@@ -75700,6 +76170,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75700
76170
  state: this.residents.keyFrameCapture(t.trackId),
75701
76171
  now: timestamp
75702
76172
  }).kind;
76173
+ if (frameHandle !== void 0 && plausibleBox) {
76174
+ const decision = {
76175
+ deviceId,
76176
+ trackId: t.trackId,
76177
+ timestamp,
76178
+ frameHandle,
76179
+ bbox: {
76180
+ x: t.bbox.x,
76181
+ y: t.bbox.y,
76182
+ w: t.bbox.w,
76183
+ h: t.bbox.h
76184
+ },
76185
+ frameWidth,
76186
+ frameHeight
76187
+ };
76188
+ if (isNewBest) this.issueParkOrders(decision, ["keyFrame", "thumbnail"], false);
76189
+ else {
76190
+ const retryKinds = [];
76191
+ if (keyFrame === "retry") retryKinds.push("keyFrame");
76192
+ if (bestThumbnail) retryKinds.push("thumbnail");
76193
+ if (retryKinds.length > 0) this.issueParkOrders(decision, retryKinds, true);
76194
+ }
76195
+ }
75703
76196
  const suppressedBy = (() => {
75704
76197
  if (skipParkedThumb) return "parked-thumbnail";
75705
76198
  if (!isNewBest || bestThumbnail) return void 0;
@@ -77115,6 +77608,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
77115
77608
  }
77116
77609
  });
77117
77610
  });
77611
+ p.setZoneRefusalObserver((observation) => {
77612
+ this.ctx.logger.info("detection refused inside a watched zone — not notified", {
77613
+ tags: { deviceId },
77614
+ meta: {
77615
+ source,
77616
+ ...observation
77617
+ }
77618
+ });
77619
+ });
77118
77620
  p.setSpawnBesideCoastingObserver((record) => {
77119
77621
  this.ctx.logger.info("tracker: new id minted beside a live coasting track", {
77120
77622
  tags: { deviceId },