@camstack/addon-pipeline 1.2.43 → 1.2.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/detection-pipeline/index.js +157 -81
  4. package/dist/detection-pipeline/index.mjs +157 -81
  5. package/dist/{dist-UHbKZiSa.js → dist-BuFE5rOK.js} +724 -39
  6. package/dist/{dist-B-VVBzrL.mjs → dist-DOu93i_g.mjs} +659 -40
  7. package/dist/{event-loop-stall-monitor-DfKdC7G4.mjs → event-loop-stall-monitor-CShgQE6l.mjs} +40 -2
  8. package/dist/{event-loop-stall-monitor-C7_L6vHO.js → event-loop-stall-monitor-L08yG6bB.js} +40 -2
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/pipeline-runner/index.js +342 -69
  12. package/dist/pipeline-runner/index.mjs +342 -69
  13. package/dist/recorder/index.js +189 -6
  14. package/dist/recorder/index.mjs +189 -6
  15. package/dist/session-decode/decode-worker-child.js +197 -24
  16. package/dist/session-decode/decode-worker-child.mjs +197 -24
  17. package/dist/stream-broker/_stub.js +2 -2
  18. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D7iQbuKY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DiXoZnou.mjs} +2 -2
  19. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BRc-m6W-.mjs +26 -0
  20. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-54IklEep.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B_hakWAb.mjs} +1 -1
  21. package/dist/stream-broker/{hostInit-CKYS9yZO.mjs → hostInit-khRXMnjl.mjs} +2 -2
  22. package/dist/stream-broker/index.js +3952 -1103
  23. package/dist/stream-broker/index.mjs +3953 -1104
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/dist/worker-protocol-BOXlUhWO.mjs +244 -0
  26. package/dist/worker-protocol-VURr0nUh.js +279 -0
  27. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DxZ75vB8.js → MaskShapeCanvas-DI4BY7W2-DHZuYYjH.js} +1 -1
  28. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BBY2Ztz5.js → MotionZonesSettings-NcxxQN8r-jFDOzPCD.js} +1 -1
  29. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BC-3QmHa.js → PrivacyMaskSettings-APgPLF7p-BPBXgIX8.js} +1 -1
  30. package/embed-dist/assets/{index-on1wyOnd.js → index-4CtQAybX.js} +12 -12
  31. package/embed-dist/assets/index-DC63og2c.css +2 -0
  32. package/embed-dist/index.html +2 -2
  33. package/package.json +1 -1
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCC9fqHH.mjs +0 -26
  35. package/dist/worker-protocol-D7RzZIla.mjs +0 -77
  36. package/dist/worker-protocol-DextwlTX.js +0 -94
  37. package/embed-dist/assets/index-CGdwTcwE.css +0 -2
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../chunk-emK7D4bc.js");
3
- const require_worker_protocol = require("../worker-protocol-DextwlTX.js");
3
+ const require_worker_protocol = require("../worker-protocol-VURr0nUh.js");
4
4
  let node_url = require("node:url");
5
5
  let node_fs = require("node:fs");
6
6
  node_fs = require_chunk.__toESM(node_fs);
@@ -375,6 +375,94 @@ var LeaseActivityGate = class {
375
375
  return this.now() < this.armedUntil;
376
376
  }
377
377
  };
378
+ /**
379
+ * The marked-frame window plus the offered/admitted/unmarked-crop counters for
380
+ * ONE decode worker. Pure and node-av-free so the filter is unit-testable.
381
+ */
382
+ var LeaseAdmissionLedger = class {
383
+ mode;
384
+ capacity;
385
+ /** Insertion-ordered so the oldest mark is `keys().next()` — a FIFO window. */
386
+ marked = /* @__PURE__ */ new Set();
387
+ markedTotal = 0;
388
+ offeredCount = 0;
389
+ admittedCount = 0;
390
+ unmarkedCropCount = 0;
391
+ constructor(options) {
392
+ this.mode = options.mode;
393
+ this.capacity = Math.max(1, options.capacity ?? 1024);
394
+ }
395
+ /**
396
+ * Marks RECEIVED since the worker started.
397
+ *
398
+ * Not a curiosity: in `all` mode every other counter looks identical whether
399
+ * the mark signal is working or has silently stopped arriving, and
400
+ * {@link unmarkedCrops} reads 0 in BOTH cases — an empty marked window
401
+ * answers "was this frame inferred?" with "I cannot say", which is correctly
402
+ * not counted as evidence. So a premise measured while `marks` is 0 measures
403
+ * nothing. This is the counter that tells the two apart.
404
+ */
405
+ get marks() {
406
+ return this.markedTotal;
407
+ }
408
+ /** Frames offered to the retention hook since the worker started. */
409
+ get offered() {
410
+ return this.offeredCount;
411
+ }
412
+ /** Frames the ledger let through to the lease store. */
413
+ get admitted() {
414
+ return this.admittedCount;
415
+ }
416
+ /**
417
+ * Crop requests naming a frame inside the tracked window that was never
418
+ * marked. **This is the premise counter**: any value above 0 means something
419
+ * can ask for a frame the runner never sent to inference, and `inferred` is
420
+ * dropping frames that someone wants.
421
+ */
422
+ get unmarkedCrops() {
423
+ return this.unmarkedCropCount;
424
+ }
425
+ /** Marks currently remembered (tests / bound checks). */
426
+ get markedSize() {
427
+ return this.marked.size;
428
+ }
429
+ /** The runner admitted `frameId` to its detection queue. */
430
+ mark(frameId) {
431
+ this.markedTotal++;
432
+ this.marked.delete(frameId);
433
+ this.marked.add(frameId);
434
+ while (this.marked.size > this.capacity) {
435
+ const oldest = this.marked.values().next().value;
436
+ if (oldest === void 0) break;
437
+ this.marked.delete(oldest);
438
+ }
439
+ }
440
+ /**
441
+ * Should this delivered frame get a native lease? `true` in `all` mode
442
+ * always; in `inferred` mode only when the runner marked it. Counts both
443
+ * either way, so the saving is visible on the metrics line in both modes.
444
+ */
445
+ admit(frameId) {
446
+ this.offeredCount++;
447
+ const keep = this.mode === "all" || this.marked.has(frameId);
448
+ if (keep) this.admittedCount++;
449
+ return keep;
450
+ }
451
+ /**
452
+ * Record a native-crop request for `frameId`. Returns `true` when that frame
453
+ * was never marked AND is recent enough for the window to be sure of it —
454
+ * a frame older than the oldest remembered mark is UNKNOWABLE, not evidence,
455
+ * and is deliberately not counted (over-reporting here would read as a broken
456
+ * premise and send the next session chasing nothing).
457
+ */
458
+ noteCropRequest(frameId) {
459
+ if (this.marked.has(frameId)) return false;
460
+ const oldest = this.marked.values().next().value;
461
+ if (oldest === void 0 || frameId < oldest) return false;
462
+ this.unmarkedCropCount++;
463
+ return true;
464
+ }
465
+ };
378
466
  //#endregion
379
467
  //#region src/session-decode/native-frame-ring.ts
380
468
  /**
@@ -702,6 +790,17 @@ var METRICS_INTERVAL_MS = 1e4;
702
790
  * leak site), so the cap MUST stay small; a miss is free (caller falls back to
703
791
  * today's detection-frame crop). Override with `CAMSTACK_SESSION_NATIVE_CROP_RING`
704
792
  * (0 disables retention entirely); clamped to [0,4], default 2.
793
+ *
794
+ * **Deliberately NOT an operator setting** (unlike the three lease knobs, which
795
+ * became cluster-wide `pipeline-orchestrator` settings on 2026-08-07). Every
796
+ * slot here pins a GPU/VAAPI surface out of the decoder's own pool, and this is
797
+ * the historic leak site — a value large enough to look useful in a settings UI
798
+ * is a value large enough to starve the decoder, and the failure shows up as a
799
+ * camera at 0 fps rather than as memory pressure anyone would trace back to a
800
+ * slider. The ring is also only reachable at all when the lease is DISABLED
801
+ * (budget 0), so exposing it would offer a knob that does nothing in every
802
+ * default configuration. It stays an env-var escape hatch for the person
803
+ * debugging the lease itself.
705
804
  */
706
805
  var NATIVE_RING_CAP = (() => {
707
806
  const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_CROP_RING"]);
@@ -709,21 +808,33 @@ var NATIVE_RING_CAP = (() => {
709
808
  return Math.min(4, Math.max(0, Math.floor(raw)));
710
809
  })();
711
810
  /**
811
+ * The three native-lease knobs in force for THIS worker, resolved once at
812
+ * module load (the lease store and the demand gate are constructed with the
813
+ * worker instance, before the first `start` message).
814
+ *
815
+ * These are **operator settings**, not env vars: they live on
816
+ * `pipeline-orchestrator`'s cluster-wide globals, the pipeline-runner reads them
817
+ * over the hub-routed `addon-settings` cap, and it injects the ones the operator
818
+ * SET into this child's environment at fork time. Precedence is
819
+ * `setting > env > default` — the `CAMSTACK_SESSION_NATIVE_LEASE_*` vars survive
820
+ * as an emergency/dev override for knobs the operator has not touched. The
821
+ * resolved values AND their source are logged once per worker in
822
+ * {@link DecodeWorkerChild.handleStart}. Full contract:
823
+ * `native-lease-config.ts` and `packages/types/src/pipeline/native-lease.ts`.
824
+ */
825
+ var NATIVE_LEASE_KNOBS = require_worker_protocol.resolveNativeLeaseKnobs(process.env);
826
+ /**
712
827
  * Hard RAM budget (bytes) for the native-frame LEASE store — the primary
713
828
  * native-crop survival window. Unlike {@link NATIVE_RING_CAP} (GPU surfaces,
714
829
  * leak-prone → tiny), a lease is a downloaded RAM copy, so the window is sized
715
830
  * by memory, not a 2-frame count, and the late cross-process crop reliably
716
831
  * hits. The primary eviction is the {@link NATIVE_LEASE_TTL_MS} TTL — a 64MB
717
832
  * budget held only ~5 native 4K frames (~0.2s), which the late crop still
718
- * outran; the budget is now a HIGH safety ceiling (default 1024MB) so the TTL
719
- * is the effective cap and the ~40-200ms crop reliably lands within it.
720
- * `CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB` (default 1024); `0` DISABLES the
721
- * lease and falls back to the {@link NATIVE_RING_CAP} GPU ring (today's path).
833
+ * outran; the budget is a HIGH safety ceiling (default 1024MB) so the TTL is
834
+ * the effective cap and the ~40-200ms crop reliably lands within it. `0`
835
+ * DISABLES the lease and falls back to the {@link NATIVE_RING_CAP} GPU ring.
722
836
  */
723
- var NATIVE_LEASE_BUDGET_BYTES = (() => {
724
- const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB"]);
725
- return (Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 1024) * 1024 * 1024;
726
- })();
837
+ var NATIVE_LEASE_BUDGET_BYTES = NATIVE_LEASE_KNOBS.values.budgetMb * 1024 * 1024;
727
838
  /**
728
839
  * TTL (ms) after which a native-frame lease is treated as a miss and reclaimed.
729
840
  * The backstop that bounds in-flight RAM even if the explicit `releaseNativeLease`
@@ -731,25 +842,19 @@ var NATIVE_LEASE_BUDGET_BYTES = (() => {
731
842
  * inference + the cross-process inference-result hop to hub post-analysis +
732
843
  * tracking + the tRPC crop round-trip back — on a busy camera with many
733
844
  * concurrent tracks that is well past the old 500 ms (which the busiest cameras'
734
- * subject crops outran, missing to the ≤640 fallback). Raised to 1200 ms; still
845
+ * subject crops outran, missing to the ≤640 fallback). Default 1200 ms; still
735
846
  * bounded by the per-session {@link NATIVE_LEASE_BUDGET_BYTES} ceiling and the
736
847
  * {@link NATIVE_LEASE_ACTIVITY_WINDOW_MS} demand gate, so idle cameras retain
737
- * nothing. `CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS` (default 1200).
848
+ * nothing.
738
849
  */
739
- var NATIVE_LEASE_TTL_MS = (() => {
740
- const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS"]);
741
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 1200;
742
- })();
850
+ var NATIVE_LEASE_TTL_MS = NATIVE_LEASE_KNOBS.values.ttlMs;
743
851
  /**
744
852
  * Demand window (ms) for the lease-capture {@link LeaseActivityGate}: eager
745
853
  * per-frame native downloads run only within this window of the last
746
- * native-crop request (or dial start). `0` disables gating (legacy always-on
747
- * eager capture). `CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS` (default 15000).
854
+ * native-crop request (or dial start). `0` disables the GATE (legacy always-on
855
+ * eager capture) — it does not disable retention. Default 15000.
748
856
  */
749
- var NATIVE_LEASE_ACTIVITY_WINDOW_MS = (() => {
750
- const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS"]);
751
- return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 15e3;
752
- })();
857
+ var NATIVE_LEASE_ACTIVITY_WINDOW_MS = NATIVE_LEASE_KNOBS.values.activityMs;
753
858
  /**
754
859
  * `pinnedRgbCrop` — pinned-color RGB24 native-crop retention. DEFAULT **ON**
755
860
  * (2026-07-20 rollout); set `CAMSTACK_SESSION_PINNED_RGB_CROP=0` to opt out.
@@ -1040,6 +1145,15 @@ var DecodeWorkerChild = class {
1040
1145
  */
1041
1146
  leaseGate = new LeaseActivityGate(NATIVE_LEASE_ACTIVITY_WINDOW_MS);
1042
1147
  /**
1148
+ * WHICH delivered frames are worth a native lease — the runner marks the ones
1149
+ * it admitted to inference (`markInferred`), and in `inferred` mode only
1150
+ * those are retained. Also the premise counter: it tallies every crop request
1151
+ * naming a frame that was never marked, in BOTH modes, so the assumption the
1152
+ * filter rests on is measured on live traffic rather than argued. See
1153
+ * `lease-admission-ledger.ts`.
1154
+ */
1155
+ admission = new LeaseAdmissionLedger({ mode: NATIVE_LEASE_KNOBS.values.admission });
1156
+ /**
1043
1157
  * DEDICATED full-frame GPU→system download filtergraph
1044
1158
  * (`scale_<be>=iw:ih,hwdownload,format=nv12,format=yuv420p`) used ONLY to
1045
1159
  * materialize a HW surface into a leasable software frame at NATIVE
@@ -1151,6 +1265,9 @@ var DecodeWorkerChild = class {
1151
1265
  case "releaseNativeLease":
1152
1266
  this.leaseStore.release(message.frameId);
1153
1267
  return;
1268
+ case "markInferred":
1269
+ this.admission.mark(message.frameId);
1270
+ return;
1154
1271
  case "stop":
1155
1272
  this.handleStop();
1156
1273
  return;
@@ -1210,7 +1327,7 @@ var DecodeWorkerChild = class {
1210
1327
  const skipped = this.framesSkipped + this.frames.droppedCount;
1211
1328
  const rssMb = Math.round(process.memoryUsage().rss / 1048576);
1212
1329
  const ageS = Math.round((now - this.startedAt) / 1e3);
1213
- this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}, nativeCropHits:${this.nativeCropHits}, nativeCropMisses:${this.nativeCropMisses}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
1330
+ this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}, nativeCropHits:${this.nativeCropHits}, nativeCropMisses:${this.nativeCropMisses}, leaseOffered:${this.admission.offered}, leaseAdmitted:${this.admission.admitted}, leaseMarks:${this.admission.marks}, leaseUnmarkedCrops:${this.admission.unmarkedCrops}, leaseAdmission:${NATIVE_LEASE_KNOBS.values.admission}, leaseMb:${Math.round(this.leaseStore.totalBytes / 1048576)}, leaseFrames:${this.leaseStore.size}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
1214
1331
  this.lastMetricsAt = now;
1215
1332
  this.lastDeliveredSnapshot = this.framesDelivered;
1216
1333
  if (!this.warnedLarge && rssMb >= WORKER_RSS_WARN_MB) {
@@ -1225,6 +1342,7 @@ var DecodeWorkerChild = class {
1225
1342
  }
1226
1343
  this.started = true;
1227
1344
  this.deviceId = source.deviceId;
1345
+ this.emitStderr(`decode-worker-child: ${require_worker_protocol.formatNativeLeaseKnobs(NATIVE_LEASE_KNOBS)}\n`, "info");
1228
1346
  this.sessionFormat = opts.format ?? "rgb";
1229
1347
  this.minIntervalMs = opts.fps && opts.fps > 0 ? 1e3 / opts.fps : 0;
1230
1348
  this.lastMetricsAt = Date.now();
@@ -1275,6 +1393,7 @@ var DecodeWorkerChild = class {
1275
1393
  */
1276
1394
  handleNativeCrop(requestId, frameId, bbox, maxWidth) {
1277
1395
  this.leaseGate.arm();
1396
+ if (this.admission.noteCropRequest(frameId)) this.emitStderr(`decode-worker-child: native crop named an UNINFERRED frame (frameId ${frameId}) — the 'inferred' lease-admission premise does not hold on this path; total=${this.admission.unmarkedCrops}\n`);
1278
1397
  const frame = this.leaseStore.get(frameId)?.frame ?? this.resolveRetainedFrame(frameId);
1279
1398
  if (!frame) {
1280
1399
  this.nativeCropMisses++;
@@ -1344,10 +1463,59 @@ var DecodeWorkerChild = class {
1344
1463
  * is leased directly. A download failure still consumes ownership (freed
1345
1464
  * here) so the slot never double-frees — the crop simply misses → fallback.
1346
1465
  * - Lease DISABLED (`budget 0`): fall back to today's tiny GPU {@link nativeRing}.
1466
+ *
1467
+ * ## LEASE ADMISSION — what actually gets stored
1468
+ *
1469
+ * Three conditions decide it, and only the third is per-frame:
1470
+ * 1. the lease is enabled (`budgetMb > 0`);
1471
+ * 2. {@link leaseGate} is inside its demand window (`activityMs` since the
1472
+ * last native-crop request or the dial);
1473
+ * 3. {@link admission} accepts the frame — everything in `all` mode, only
1474
+ * frames the runner MARKED as having reached inference in `inferred`
1475
+ * mode (`nativeLeaseAdmission`).
1476
+ *
1477
+ * What reaches this function at all is the {@link FrameSlot} lifecycle, not a
1478
+ * quality filter. `retainSuperseded` is called only from
1479
+ * `FrameSlot.reserveSlot()` (`frame-slot.ts`), on the frame the PREVIOUS
1480
+ * `pull` delivered to the parent, at the moment the next `pull` supersedes it.
1481
+ * So:
1482
+ * - frames dropped by the `minIntervalMs` fps throttle never enter the slot
1483
+ * and are never offered;
1484
+ * - frames superseded in the slot before any `pull` took them (latest-wins)
1485
+ * are freed by `publish` and never offered;
1486
+ * - every DELIVERED frame is offered exactly once, and then rule 3 decides.
1487
+ *
1488
+ * ## Why `inferred` is safe, and how that is checked rather than assumed
1489
+ *
1490
+ * A native crop names a `frameId`, and the only way a `frameId` leaves the
1491
+ * runner is on a `FrameHandle` riding an INFERENCE RESULT — post-analysis
1492
+ * reaches every one of its handles through `processFrame`, whose only
1493
+ * handle-bearing caller is `handleInferenceResult`. A frame the runner never
1494
+ * enqueued is therefore a frame nobody can ask for. The counter that keeps
1495
+ * this honest is `leaseUnmarkedCrops` on the metrics line: it counts crop
1496
+ * requests naming an unmarked frame in BOTH modes, so the premise is a
1497
+ * production number, not a reading of the call graph.
1498
+ *
1499
+ * The NEWEST delivered frame is never at risk either way: it is the slot's
1500
+ * reserved frame and {@link handleNativeCrop} resolves it through
1501
+ * `FrameSlot.toBuffer`, never through the lease.
1502
+ *
1503
+ * ## What it costs / saves
1504
+ *
1505
+ * One 4K frame is ~24.9 MB on the default pinned-RGB24 lease path
1506
+ * ({@link PINNED_RGB_CROP}), ~12.4 MB as YUV420P. At the ~25 delivered fps of
1507
+ * `all` mode that is ~620 MB/s of GPU→RAM download while the gate is open and
1508
+ * a steady-state resident set of roughly `frameBytes × deliveredFps ×
1509
+ * ttlSeconds`. `inferred` cuts both by the governor's admit ratio — ~5 of 25
1510
+ * frames on a two-plane camera, so ~80%. Prompt `releaseNativeLease` reclaims
1511
+ * earlier still, so the observed figure is lower than the ceiling; the
1512
+ * ceiling is what the budget knob has to survive. See
1513
+ * `docs/design/decode-path.md` → "Lease admission".
1347
1514
  */
1348
1515
  captureNativeFrame(frameId, frame) {
1349
1516
  if (!this.leaseStore.enabled) return this.nativeRing.retain(frameId, frame);
1350
1517
  if (!this.leaseGate.active) return false;
1518
+ if (!this.admission.admit(frameId)) return false;
1351
1519
  if (frame.isHwFrame()) {
1352
1520
  const software = this.downloadToSoftware(frame);
1353
1521
  frame.free();
@@ -1935,14 +2103,19 @@ var DecodeWorkerChild = class {
1935
2103
  * output. Public so the module-level process handlers — which only hold the
1936
2104
  * `worker` instance — emit device-tagged lines too. Callers pass the line
1937
2105
  * INCLUDING its trailing newline (unchanged from the prior direct writes).
2106
+ *
2107
+ * `level` overrides the derived severity for the few lines whose importance
2108
+ * is not inferable from their words — the lease-knob line states an
2109
+ * operator's configuration taking effect and would otherwise sit at `debug`,
2110
+ * where a setting that failed to arrive is indistinguishable from silence.
1938
2111
  */
1939
- emitStderr(line) {
2112
+ emitStderr(line, level) {
1940
2113
  const tag = this.deviceId === null ? "[dev:?]" : `[dev:${this.deviceId}]`;
1941
2114
  process.stderr.write(`${tag} ${line}`);
1942
2115
  try {
1943
2116
  this.send({
1944
2117
  kind: "log",
1945
- level: require_worker_protocol.logLevelForLine(line),
2118
+ level: level ?? require_worker_protocol.logLevelForLine(line),
1946
2119
  message: `${tag} ${line.trimEnd()}`
1947
2120
  });
1948
2121
  } catch {}
@@ -1,4 +1,4 @@
1
- import { n as isWorkerRequest, r as logLevelForLine } from "../worker-protocol-D7RzZIla.mjs";
1
+ import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-BOXlUhWO.mjs";
2
2
  import { pathToFileURL } from "node:url";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -371,6 +371,94 @@ var LeaseActivityGate = class {
371
371
  return this.now() < this.armedUntil;
372
372
  }
373
373
  };
374
+ /**
375
+ * The marked-frame window plus the offered/admitted/unmarked-crop counters for
376
+ * ONE decode worker. Pure and node-av-free so the filter is unit-testable.
377
+ */
378
+ var LeaseAdmissionLedger = class {
379
+ mode;
380
+ capacity;
381
+ /** Insertion-ordered so the oldest mark is `keys().next()` — a FIFO window. */
382
+ marked = /* @__PURE__ */ new Set();
383
+ markedTotal = 0;
384
+ offeredCount = 0;
385
+ admittedCount = 0;
386
+ unmarkedCropCount = 0;
387
+ constructor(options) {
388
+ this.mode = options.mode;
389
+ this.capacity = Math.max(1, options.capacity ?? 1024);
390
+ }
391
+ /**
392
+ * Marks RECEIVED since the worker started.
393
+ *
394
+ * Not a curiosity: in `all` mode every other counter looks identical whether
395
+ * the mark signal is working or has silently stopped arriving, and
396
+ * {@link unmarkedCrops} reads 0 in BOTH cases — an empty marked window
397
+ * answers "was this frame inferred?" with "I cannot say", which is correctly
398
+ * not counted as evidence. So a premise measured while `marks` is 0 measures
399
+ * nothing. This is the counter that tells the two apart.
400
+ */
401
+ get marks() {
402
+ return this.markedTotal;
403
+ }
404
+ /** Frames offered to the retention hook since the worker started. */
405
+ get offered() {
406
+ return this.offeredCount;
407
+ }
408
+ /** Frames the ledger let through to the lease store. */
409
+ get admitted() {
410
+ return this.admittedCount;
411
+ }
412
+ /**
413
+ * Crop requests naming a frame inside the tracked window that was never
414
+ * marked. **This is the premise counter**: any value above 0 means something
415
+ * can ask for a frame the runner never sent to inference, and `inferred` is
416
+ * dropping frames that someone wants.
417
+ */
418
+ get unmarkedCrops() {
419
+ return this.unmarkedCropCount;
420
+ }
421
+ /** Marks currently remembered (tests / bound checks). */
422
+ get markedSize() {
423
+ return this.marked.size;
424
+ }
425
+ /** The runner admitted `frameId` to its detection queue. */
426
+ mark(frameId) {
427
+ this.markedTotal++;
428
+ this.marked.delete(frameId);
429
+ this.marked.add(frameId);
430
+ while (this.marked.size > this.capacity) {
431
+ const oldest = this.marked.values().next().value;
432
+ if (oldest === void 0) break;
433
+ this.marked.delete(oldest);
434
+ }
435
+ }
436
+ /**
437
+ * Should this delivered frame get a native lease? `true` in `all` mode
438
+ * always; in `inferred` mode only when the runner marked it. Counts both
439
+ * either way, so the saving is visible on the metrics line in both modes.
440
+ */
441
+ admit(frameId) {
442
+ this.offeredCount++;
443
+ const keep = this.mode === "all" || this.marked.has(frameId);
444
+ if (keep) this.admittedCount++;
445
+ return keep;
446
+ }
447
+ /**
448
+ * Record a native-crop request for `frameId`. Returns `true` when that frame
449
+ * was never marked AND is recent enough for the window to be sure of it —
450
+ * a frame older than the oldest remembered mark is UNKNOWABLE, not evidence,
451
+ * and is deliberately not counted (over-reporting here would read as a broken
452
+ * premise and send the next session chasing nothing).
453
+ */
454
+ noteCropRequest(frameId) {
455
+ if (this.marked.has(frameId)) return false;
456
+ const oldest = this.marked.values().next().value;
457
+ if (oldest === void 0 || frameId < oldest) return false;
458
+ this.unmarkedCropCount++;
459
+ return true;
460
+ }
461
+ };
374
462
  //#endregion
375
463
  //#region src/session-decode/native-frame-ring.ts
376
464
  /**
@@ -698,6 +786,17 @@ var METRICS_INTERVAL_MS = 1e4;
698
786
  * leak site), so the cap MUST stay small; a miss is free (caller falls back to
699
787
  * today's detection-frame crop). Override with `CAMSTACK_SESSION_NATIVE_CROP_RING`
700
788
  * (0 disables retention entirely); clamped to [0,4], default 2.
789
+ *
790
+ * **Deliberately NOT an operator setting** (unlike the three lease knobs, which
791
+ * became cluster-wide `pipeline-orchestrator` settings on 2026-08-07). Every
792
+ * slot here pins a GPU/VAAPI surface out of the decoder's own pool, and this is
793
+ * the historic leak site — a value large enough to look useful in a settings UI
794
+ * is a value large enough to starve the decoder, and the failure shows up as a
795
+ * camera at 0 fps rather than as memory pressure anyone would trace back to a
796
+ * slider. The ring is also only reachable at all when the lease is DISABLED
797
+ * (budget 0), so exposing it would offer a knob that does nothing in every
798
+ * default configuration. It stays an env-var escape hatch for the person
799
+ * debugging the lease itself.
701
800
  */
702
801
  var NATIVE_RING_CAP = (() => {
703
802
  const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_CROP_RING"]);
@@ -705,21 +804,33 @@ var NATIVE_RING_CAP = (() => {
705
804
  return Math.min(4, Math.max(0, Math.floor(raw)));
706
805
  })();
707
806
  /**
807
+ * The three native-lease knobs in force for THIS worker, resolved once at
808
+ * module load (the lease store and the demand gate are constructed with the
809
+ * worker instance, before the first `start` message).
810
+ *
811
+ * These are **operator settings**, not env vars: they live on
812
+ * `pipeline-orchestrator`'s cluster-wide globals, the pipeline-runner reads them
813
+ * over the hub-routed `addon-settings` cap, and it injects the ones the operator
814
+ * SET into this child's environment at fork time. Precedence is
815
+ * `setting > env > default` — the `CAMSTACK_SESSION_NATIVE_LEASE_*` vars survive
816
+ * as an emergency/dev override for knobs the operator has not touched. The
817
+ * resolved values AND their source are logged once per worker in
818
+ * {@link DecodeWorkerChild.handleStart}. Full contract:
819
+ * `native-lease-config.ts` and `packages/types/src/pipeline/native-lease.ts`.
820
+ */
821
+ var NATIVE_LEASE_KNOBS = resolveNativeLeaseKnobs(process.env);
822
+ /**
708
823
  * Hard RAM budget (bytes) for the native-frame LEASE store — the primary
709
824
  * native-crop survival window. Unlike {@link NATIVE_RING_CAP} (GPU surfaces,
710
825
  * leak-prone → tiny), a lease is a downloaded RAM copy, so the window is sized
711
826
  * by memory, not a 2-frame count, and the late cross-process crop reliably
712
827
  * hits. The primary eviction is the {@link NATIVE_LEASE_TTL_MS} TTL — a 64MB
713
828
  * budget held only ~5 native 4K frames (~0.2s), which the late crop still
714
- * outran; the budget is now a HIGH safety ceiling (default 1024MB) so the TTL
715
- * is the effective cap and the ~40-200ms crop reliably lands within it.
716
- * `CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB` (default 1024); `0` DISABLES the
717
- * lease and falls back to the {@link NATIVE_RING_CAP} GPU ring (today's path).
829
+ * outran; the budget is a HIGH safety ceiling (default 1024MB) so the TTL is
830
+ * the effective cap and the ~40-200ms crop reliably lands within it. `0`
831
+ * DISABLES the lease and falls back to the {@link NATIVE_RING_CAP} GPU ring.
718
832
  */
719
- var NATIVE_LEASE_BUDGET_BYTES = (() => {
720
- const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB"]);
721
- return (Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 1024) * 1024 * 1024;
722
- })();
833
+ var NATIVE_LEASE_BUDGET_BYTES = NATIVE_LEASE_KNOBS.values.budgetMb * 1024 * 1024;
723
834
  /**
724
835
  * TTL (ms) after which a native-frame lease is treated as a miss and reclaimed.
725
836
  * The backstop that bounds in-flight RAM even if the explicit `releaseNativeLease`
@@ -727,25 +838,19 @@ var NATIVE_LEASE_BUDGET_BYTES = (() => {
727
838
  * inference + the cross-process inference-result hop to hub post-analysis +
728
839
  * tracking + the tRPC crop round-trip back — on a busy camera with many
729
840
  * concurrent tracks that is well past the old 500 ms (which the busiest cameras'
730
- * subject crops outran, missing to the ≤640 fallback). Raised to 1200 ms; still
841
+ * subject crops outran, missing to the ≤640 fallback). Default 1200 ms; still
731
842
  * bounded by the per-session {@link NATIVE_LEASE_BUDGET_BYTES} ceiling and the
732
843
  * {@link NATIVE_LEASE_ACTIVITY_WINDOW_MS} demand gate, so idle cameras retain
733
- * nothing. `CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS` (default 1200).
844
+ * nothing.
734
845
  */
735
- var NATIVE_LEASE_TTL_MS = (() => {
736
- const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS"]);
737
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 1200;
738
- })();
846
+ var NATIVE_LEASE_TTL_MS = NATIVE_LEASE_KNOBS.values.ttlMs;
739
847
  /**
740
848
  * Demand window (ms) for the lease-capture {@link LeaseActivityGate}: eager
741
849
  * per-frame native downloads run only within this window of the last
742
- * native-crop request (or dial start). `0` disables gating (legacy always-on
743
- * eager capture). `CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS` (default 15000).
850
+ * native-crop request (or dial start). `0` disables the GATE (legacy always-on
851
+ * eager capture) — it does not disable retention. Default 15000.
744
852
  */
745
- var NATIVE_LEASE_ACTIVITY_WINDOW_MS = (() => {
746
- const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS"]);
747
- return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 15e3;
748
- })();
853
+ var NATIVE_LEASE_ACTIVITY_WINDOW_MS = NATIVE_LEASE_KNOBS.values.activityMs;
749
854
  /**
750
855
  * `pinnedRgbCrop` — pinned-color RGB24 native-crop retention. DEFAULT **ON**
751
856
  * (2026-07-20 rollout); set `CAMSTACK_SESSION_PINNED_RGB_CROP=0` to opt out.
@@ -1036,6 +1141,15 @@ var DecodeWorkerChild = class {
1036
1141
  */
1037
1142
  leaseGate = new LeaseActivityGate(NATIVE_LEASE_ACTIVITY_WINDOW_MS);
1038
1143
  /**
1144
+ * WHICH delivered frames are worth a native lease — the runner marks the ones
1145
+ * it admitted to inference (`markInferred`), and in `inferred` mode only
1146
+ * those are retained. Also the premise counter: it tallies every crop request
1147
+ * naming a frame that was never marked, in BOTH modes, so the assumption the
1148
+ * filter rests on is measured on live traffic rather than argued. See
1149
+ * `lease-admission-ledger.ts`.
1150
+ */
1151
+ admission = new LeaseAdmissionLedger({ mode: NATIVE_LEASE_KNOBS.values.admission });
1152
+ /**
1039
1153
  * DEDICATED full-frame GPU→system download filtergraph
1040
1154
  * (`scale_<be>=iw:ih,hwdownload,format=nv12,format=yuv420p`) used ONLY to
1041
1155
  * materialize a HW surface into a leasable software frame at NATIVE
@@ -1147,6 +1261,9 @@ var DecodeWorkerChild = class {
1147
1261
  case "releaseNativeLease":
1148
1262
  this.leaseStore.release(message.frameId);
1149
1263
  return;
1264
+ case "markInferred":
1265
+ this.admission.mark(message.frameId);
1266
+ return;
1150
1267
  case "stop":
1151
1268
  this.handleStop();
1152
1269
  return;
@@ -1206,7 +1323,7 @@ var DecodeWorkerChild = class {
1206
1323
  const skipped = this.framesSkipped + this.frames.droppedCount;
1207
1324
  const rssMb = Math.round(process.memoryUsage().rss / 1048576);
1208
1325
  const ageS = Math.round((now - this.startedAt) / 1e3);
1209
- this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}, nativeCropHits:${this.nativeCropHits}, nativeCropMisses:${this.nativeCropMisses}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
1326
+ this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}, nativeCropHits:${this.nativeCropHits}, nativeCropMisses:${this.nativeCropMisses}, leaseOffered:${this.admission.offered}, leaseAdmitted:${this.admission.admitted}, leaseMarks:${this.admission.marks}, leaseUnmarkedCrops:${this.admission.unmarkedCrops}, leaseAdmission:${NATIVE_LEASE_KNOBS.values.admission}, leaseMb:${Math.round(this.leaseStore.totalBytes / 1048576)}, leaseFrames:${this.leaseStore.size}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
1210
1327
  this.lastMetricsAt = now;
1211
1328
  this.lastDeliveredSnapshot = this.framesDelivered;
1212
1329
  if (!this.warnedLarge && rssMb >= WORKER_RSS_WARN_MB) {
@@ -1221,6 +1338,7 @@ var DecodeWorkerChild = class {
1221
1338
  }
1222
1339
  this.started = true;
1223
1340
  this.deviceId = source.deviceId;
1341
+ this.emitStderr(`decode-worker-child: ${formatNativeLeaseKnobs(NATIVE_LEASE_KNOBS)}\n`, "info");
1224
1342
  this.sessionFormat = opts.format ?? "rgb";
1225
1343
  this.minIntervalMs = opts.fps && opts.fps > 0 ? 1e3 / opts.fps : 0;
1226
1344
  this.lastMetricsAt = Date.now();
@@ -1271,6 +1389,7 @@ var DecodeWorkerChild = class {
1271
1389
  */
1272
1390
  handleNativeCrop(requestId, frameId, bbox, maxWidth) {
1273
1391
  this.leaseGate.arm();
1392
+ if (this.admission.noteCropRequest(frameId)) this.emitStderr(`decode-worker-child: native crop named an UNINFERRED frame (frameId ${frameId}) — the 'inferred' lease-admission premise does not hold on this path; total=${this.admission.unmarkedCrops}\n`);
1274
1393
  const frame = this.leaseStore.get(frameId)?.frame ?? this.resolveRetainedFrame(frameId);
1275
1394
  if (!frame) {
1276
1395
  this.nativeCropMisses++;
@@ -1340,10 +1459,59 @@ var DecodeWorkerChild = class {
1340
1459
  * is leased directly. A download failure still consumes ownership (freed
1341
1460
  * here) so the slot never double-frees — the crop simply misses → fallback.
1342
1461
  * - Lease DISABLED (`budget 0`): fall back to today's tiny GPU {@link nativeRing}.
1462
+ *
1463
+ * ## LEASE ADMISSION — what actually gets stored
1464
+ *
1465
+ * Three conditions decide it, and only the third is per-frame:
1466
+ * 1. the lease is enabled (`budgetMb > 0`);
1467
+ * 2. {@link leaseGate} is inside its demand window (`activityMs` since the
1468
+ * last native-crop request or the dial);
1469
+ * 3. {@link admission} accepts the frame — everything in `all` mode, only
1470
+ * frames the runner MARKED as having reached inference in `inferred`
1471
+ * mode (`nativeLeaseAdmission`).
1472
+ *
1473
+ * What reaches this function at all is the {@link FrameSlot} lifecycle, not a
1474
+ * quality filter. `retainSuperseded` is called only from
1475
+ * `FrameSlot.reserveSlot()` (`frame-slot.ts`), on the frame the PREVIOUS
1476
+ * `pull` delivered to the parent, at the moment the next `pull` supersedes it.
1477
+ * So:
1478
+ * - frames dropped by the `minIntervalMs` fps throttle never enter the slot
1479
+ * and are never offered;
1480
+ * - frames superseded in the slot before any `pull` took them (latest-wins)
1481
+ * are freed by `publish` and never offered;
1482
+ * - every DELIVERED frame is offered exactly once, and then rule 3 decides.
1483
+ *
1484
+ * ## Why `inferred` is safe, and how that is checked rather than assumed
1485
+ *
1486
+ * A native crop names a `frameId`, and the only way a `frameId` leaves the
1487
+ * runner is on a `FrameHandle` riding an INFERENCE RESULT — post-analysis
1488
+ * reaches every one of its handles through `processFrame`, whose only
1489
+ * handle-bearing caller is `handleInferenceResult`. A frame the runner never
1490
+ * enqueued is therefore a frame nobody can ask for. The counter that keeps
1491
+ * this honest is `leaseUnmarkedCrops` on the metrics line: it counts crop
1492
+ * requests naming an unmarked frame in BOTH modes, so the premise is a
1493
+ * production number, not a reading of the call graph.
1494
+ *
1495
+ * The NEWEST delivered frame is never at risk either way: it is the slot's
1496
+ * reserved frame and {@link handleNativeCrop} resolves it through
1497
+ * `FrameSlot.toBuffer`, never through the lease.
1498
+ *
1499
+ * ## What it costs / saves
1500
+ *
1501
+ * One 4K frame is ~24.9 MB on the default pinned-RGB24 lease path
1502
+ * ({@link PINNED_RGB_CROP}), ~12.4 MB as YUV420P. At the ~25 delivered fps of
1503
+ * `all` mode that is ~620 MB/s of GPU→RAM download while the gate is open and
1504
+ * a steady-state resident set of roughly `frameBytes × deliveredFps ×
1505
+ * ttlSeconds`. `inferred` cuts both by the governor's admit ratio — ~5 of 25
1506
+ * frames on a two-plane camera, so ~80%. Prompt `releaseNativeLease` reclaims
1507
+ * earlier still, so the observed figure is lower than the ceiling; the
1508
+ * ceiling is what the budget knob has to survive. See
1509
+ * `docs/design/decode-path.md` → "Lease admission".
1343
1510
  */
1344
1511
  captureNativeFrame(frameId, frame) {
1345
1512
  if (!this.leaseStore.enabled) return this.nativeRing.retain(frameId, frame);
1346
1513
  if (!this.leaseGate.active) return false;
1514
+ if (!this.admission.admit(frameId)) return false;
1347
1515
  if (frame.isHwFrame()) {
1348
1516
  const software = this.downloadToSoftware(frame);
1349
1517
  frame.free();
@@ -1931,14 +2099,19 @@ var DecodeWorkerChild = class {
1931
2099
  * output. Public so the module-level process handlers — which only hold the
1932
2100
  * `worker` instance — emit device-tagged lines too. Callers pass the line
1933
2101
  * INCLUDING its trailing newline (unchanged from the prior direct writes).
2102
+ *
2103
+ * `level` overrides the derived severity for the few lines whose importance
2104
+ * is not inferable from their words — the lease-knob line states an
2105
+ * operator's configuration taking effect and would otherwise sit at `debug`,
2106
+ * where a setting that failed to arrive is indistinguishable from silence.
1934
2107
  */
1935
- emitStderr(line) {
2108
+ emitStderr(line, level) {
1936
2109
  const tag = this.deviceId === null ? "[dev:?]" : `[dev:${this.deviceId}]`;
1937
2110
  process.stderr.write(`${tag} ${line}`);
1938
2111
  try {
1939
2112
  this.send({
1940
2113
  kind: "log",
1941
- level: logLevelForLine(line),
2114
+ level: level ?? logLevelForLine(line),
1942
2115
  message: `${tag} ${line.trimEnd()}`
1943
2116
  });
1944
2117
  } catch {}
@@ -1,8 +1,8 @@
1
- import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-54IklEep.mjs";
1
+ import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B_hakWAb.mjs";
2
2
  import { a as d, i as f, n as p, o as m, r as h, t as g } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
3
3
  import { n as _, r as v, t as y } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
4
4
  import { n as b, t as x } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
5
- import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCC9fqHH.mjs";
5
+ import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BRc-m6W-.mjs";
6
6
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
7
7
  var C = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), w = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), T = (e) => {
8
8
  let t = w(e);