@camstack/addon-pipeline 1.2.317 → 1.2.320

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 (33) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-CILrD3sC.mjs → default-detection-model-B-iMtrXt.mjs} +853 -14
  4. package/dist/{default-detection-model-DdIJSdQB.js → default-detection-model-CO4pZxbd.js} +864 -13
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-3isAnyIX.mjs → dist-BK-pWZzI.mjs} +27 -7
  8. package/dist/{dist-DXU3Fu7j.js → dist-Bce5_ocm.js} +27 -7
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-PY3N7_ym.mjs → node-DQsiK6Vt.mjs} +1 -1
  12. package/dist/{node-DEpHS48Q.js → node-UJwLVGTT.js} +1 -1
  13. package/dist/pipeline-runner/index.js +13 -821
  14. package/dist/pipeline-runner/index.mjs +12 -820
  15. package/dist/{process-memory-DE0M2AiK.mjs → process-memory-Cn3QEo37.mjs} +1 -1
  16. package/dist/{process-memory-DAGs0yZp.js → process-memory-DR7jI54j.js} +1 -1
  17. package/dist/recorder/index.js +2 -2
  18. package/dist/recorder/index.mjs +2 -2
  19. package/dist/{segment-demux-js-k3D5kt3W.mjs → segment-demux-js-CTMbCGRL.mjs} +1 -1
  20. package/dist/{segment-demux-js-BZhn3qOl.js → segment-demux-js-CbazlBOP.js} +1 -1
  21. package/dist/session-decode/decode-worker-main.js +194 -13
  22. package/dist/session-decode/decode-worker-main.mjs +194 -13
  23. package/dist/stream-broker/_stub.js +1 -1
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DqP6Ev3L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DP4h-J_V.mjs} +2 -2
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BAYDyjOG.mjs +26 -0
  26. package/dist/stream-broker/demux-worker-child.js +1 -1
  27. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  28. package/dist/stream-broker/{hostInit-BRiacIOY.mjs → hostInit-CnrH7DvQ.mjs} +2 -2
  29. package/dist/stream-broker/index.js +19 -8
  30. package/dist/stream-broker/index.mjs +19 -8
  31. package/dist/stream-broker/remoteEntry.js +1 -1
  32. package/package.json +1 -1
  33. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CVT3OmxC.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { jt as parseProcStatus } from "./dist-3isAnyIX.mjs";
1
+ import { jt as parseProcStatus } from "./dist-BK-pWZzI.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
4
  //#region src/shared/node-topology-platform.ts
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-DXU3Fu7j.js");
2
+ const require_dist = require("./dist-Bce5_ocm.js");
3
3
  let node_fs = require("node:fs");
4
4
  node_fs = require_chunk.__toESM(node_fs);
5
5
  let node_child_process = require("node:child_process");
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-DXU3Fu7j.js");
7
- const require_node = require("../node-DEpHS48Q.js");
6
+ const require_dist = require("../dist-Bce5_ocm.js");
7
+ const require_node = require("../node-UJwLVGTT.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
9
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
10
10
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
@@ -1,5 +1,5 @@
1
- import { $t as stripRetiredBandPreBufferSec, An as sleep$1, B as VISIT_MERGE_GAP_MS, Bn as record, Bt as recordingExportCapability, C as ExportRecordSchema, Dt as migrateLegacyDeviceSignalConfig, En as nodePin, Et as memoizePinnedFfmpeg, F as RECORDING_SOURCE_CAMSTACK_ADDON, Gt as resolveLocationMode, I as RecordingConfigSchema, K as batteryCapability, L as RecordingSignalStatusSchema, Ln as number, Mn as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nn as boolean, Ot as modeMayWrite, P as RECORDING_SOURCE_CAMSTACK, Rn as object, Rt as recordingArchiveCapability, Sn as hydrateSchema, Tt as mayWriteToLocation, Un as EventCategory, Ut as resolveCreateExportSubject, Vn as string, Vt as recordingSignalCapability, X as clipExportFailureOf, Xt as storageEvictableCapability, Y as clipExportFailure, Zt as storageOccupancyCapability, _ as DEFAULT_EVENTS_BAND_BUFFER_SEC, b as EVENT_OWNER_TYPES, bn as defineCustomActions, bt as loadContributionCapability, dn as DeviceType, dt as exportSubjectOf, fn as errMsg$1, jn as _enum, k as OpsLogEntrySchema, kn as selectAssignedProfileSlots, lt as evictionPolicyForMode, mn as BaseAddon, mt as gbToBytes, o as BatteryStatusSchema, qt as resolveRecordingProfiles, rt as deriveRecordingMode, s as CLIP_BROKER_PACED_RATES, sn as evaluateSensorEdge, tn as videoclipsCapability, tt as deriveBatteryPresence, un as DeviceFeature, ut as evictionPolicyOfLocation, wt as mayReadLocation, x as EVENT_PAD_MS, yn as customAction, yt as isFirstLevelMacroClass, z as VIDEOCLIPS_MAX_OFFER_BYTES, zn as preprocess, zt as recordingCapability } from "../dist-3isAnyIX.mjs";
2
- import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-PY3N7_ym.mjs";
1
+ import { $t as stripRetiredBandPreBufferSec, An as sleep$1, B as VISIT_MERGE_GAP_MS, Bn as record, Bt as recordingExportCapability, C as ExportRecordSchema, Dt as migrateLegacyDeviceSignalConfig, En as nodePin, Et as memoizePinnedFfmpeg, F as RECORDING_SOURCE_CAMSTACK_ADDON, Gt as resolveLocationMode, I as RecordingConfigSchema, K as batteryCapability, L as RecordingSignalStatusSchema, Ln as number, Mn as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nn as boolean, Ot as modeMayWrite, P as RECORDING_SOURCE_CAMSTACK, Rn as object, Rt as recordingArchiveCapability, Sn as hydrateSchema, Tt as mayWriteToLocation, Un as EventCategory, Ut as resolveCreateExportSubject, Vn as string, Vt as recordingSignalCapability, X as clipExportFailureOf, Xt as storageEvictableCapability, Y as clipExportFailure, Zt as storageOccupancyCapability, _ as DEFAULT_EVENTS_BAND_BUFFER_SEC, b as EVENT_OWNER_TYPES, bn as defineCustomActions, bt as loadContributionCapability, dn as DeviceType, dt as exportSubjectOf, fn as errMsg$1, jn as _enum, k as OpsLogEntrySchema, kn as selectAssignedProfileSlots, lt as evictionPolicyForMode, mn as BaseAddon, mt as gbToBytes, o as BatteryStatusSchema, qt as resolveRecordingProfiles, rt as deriveRecordingMode, s as CLIP_BROKER_PACED_RATES, sn as evaluateSensorEdge, tn as videoclipsCapability, tt as deriveBatteryPresence, un as DeviceFeature, ut as evictionPolicyOfLocation, wt as mayReadLocation, x as EVENT_PAD_MS, yn as customAction, yt as isFirstLevelMacroClass, z as VIDEOCLIPS_MAX_OFFER_BYTES, zn as preprocess, zt as recordingCapability } from "../dist-BK-pWZzI.mjs";
2
+ import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-DQsiK6Vt.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
4
  import { d as PREBUFFER_MAX_MS, l as withRecordingIntent, r as RECORDING_PRE_ROLL_REQUEST_MS } from "../restream-intent-DyGmLAU9.mjs";
5
5
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "./chunk-DnnnRqeS.mjs";
2
- import { fn as errMsg } from "./dist-3isAnyIX.mjs";
2
+ import { fn as errMsg } from "./dist-BK-pWZzI.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region src/stream-broker/recorded/avcc-annexb.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-DXU3Fu7j.js");
2
+ const require_dist = require("./dist-Bce5_ocm.js");
3
3
  let node_child_process = require("node:child_process");
4
4
  //#region src/stream-broker/recorded/avcc-annexb.ts
5
5
  /**
@@ -512,9 +512,15 @@ var FrameSlot = class {
512
512
  * native-quality media before any crop request has been seen;
513
513
  * - `arm()` on every native-crop request — post-analysis snapshot crops and
514
514
  * the detail plane re-arm it continuously while tracks are active;
515
+ * - `arm()` on every `markInferred` — the runner admitted the frame to
516
+ * detection and will lazy-`toBuffer` it after the next pull supersedes the
517
+ * reserved slot; without this, a quiet always-on camera closed the gate
518
+ * after `activityMs` and every marked supersede was freed
519
+ * (`toBuffer miss — released`);
515
520
  * - after `windowMs` without demand it closes and per-frame downloads stop
516
- * (a later crop request simply misses once — the caller's designed
517
- * detection-frame-crop fallback — and re-arms the gate for the retry).
521
+ * for UNMARKED frames (a later crop request simply misses once — the
522
+ * caller's designed detection-frame-crop fallback — and re-arms the gate
523
+ * for the retry). A marked frame still captures when the gate is cold.
518
524
  *
519
525
  * `windowMs: 0` disables gating entirely (always active — the legacy eager
520
526
  * behaviour).
@@ -642,6 +648,14 @@ var LeaseAdmissionLedger = class {
642
648
  isMarked(frameId) {
643
649
  return this.marked.has(frameId);
644
650
  }
651
+ /**
652
+ * Same keep rule as {@link admit}, without counting. Used to park a
653
+ * just-superseded frame for a late mark without burning an offered/declined
654
+ * sample that a later promote would double-count.
655
+ */
656
+ wouldAdmit(frameId) {
657
+ return this.mode === "all" || this.marked.has(frameId);
658
+ }
645
659
  /** The runner admitted `frameId` to its detection queue. */
646
660
  mark(frameId) {
647
661
  this.markedTotal++;
@@ -680,6 +694,103 @@ var LeaseAdmissionLedger = class {
680
694
  }
681
695
  };
682
696
  //#endregion
697
+ //#region src/session-decode/pending-mark-hold.ts
698
+ /**
699
+ * One-frame grace for a late `markInferred` IPC.
700
+ *
701
+ * Admission for delivered frame N is decided when pull N+1 supersedes it
702
+ * (`FrameSlot.reserveSlot` → `retainSuperseded`). The contract says the runner
703
+ * sends `markInferred(N)` before that pull, and IPC order usually keeps it.
704
+ * When the mark lands AFTER the supersede, today's path has already freed N:
705
+ * a later lazy detection `toBuffer(N)` misses with `marked=true leaseFrames=0`
706
+ * — the reserved-slot one-frame margin only covers the NEWEST id, not an id
707
+ * already handed to retention and declined.
708
+ *
709
+ * This hold parks the just-superseded frame WITHOUT downloading it (no GPU→RAM
710
+ * burn for a frame that may never be marked). A matching mark takes it for
711
+ * lease capture; the next supersede (or `clear`) discards it. At most one
712
+ * frame per worker.
713
+ */
714
+ var PendingMarkHold = class {
715
+ held = null;
716
+ /** Id currently parked, or `null`. */
717
+ get frameId() {
718
+ return this.held?.frameId ?? null;
719
+ }
720
+ /** 0 or 1 — exposed for metrics / tests. */
721
+ get size() {
722
+ return this.held ? 1 : 0;
723
+ }
724
+ /**
725
+ * Park `frame` awaiting a possible late mark. Frees any previous pending
726
+ * (that id lost the mark race). Takes ownership of `frame`.
727
+ */
728
+ park(frameId, frame) {
729
+ this.discard();
730
+ this.held = {
731
+ frameId,
732
+ frame
733
+ };
734
+ }
735
+ /** Peek without taking — lazy `toBuffer` while still pending. */
736
+ get(frameId) {
737
+ if (!this.held || this.held.frameId !== frameId) return null;
738
+ return this.held.frame;
739
+ }
740
+ /** Take ownership if `frameId` matches; otherwise leave pending alone. */
741
+ take(frameId) {
742
+ if (!this.held || this.held.frameId !== frameId) return null;
743
+ const frame = this.held.frame;
744
+ this.held = null;
745
+ return frame;
746
+ }
747
+ /** Free the pending frame if any. Returns whether something was freed. */
748
+ discard() {
749
+ if (!this.held) return false;
750
+ this.held.frame.free();
751
+ this.held = null;
752
+ return true;
753
+ }
754
+ /** Teardown alias. */
755
+ clear() {
756
+ this.discard();
757
+ }
758
+ };
759
+ /** Named refusal when the per-dial create cap is hit. */
760
+ var LEASE_DOWNLOAD_REBUILD_CAP_REASON = "lease-download-rebuild-cap";
761
+ /**
762
+ * Counts lease-download filter creates for one dial and refuses past the bound.
763
+ * Reset on every fresh dial (alongside {@link LeaseActivityGate.reset}).
764
+ */
765
+ var LeaseDownloadRebuildCap = class {
766
+ maxCreatesPerDial;
767
+ creates = 0;
768
+ constructor(maxCreatesPerDial = 2) {
769
+ this.maxCreatesPerDial = maxCreatesPerDial;
770
+ }
771
+ /** Metric: how many `FilterAPI.create` calls this dial has already paid. */
772
+ get createsThisDial() {
773
+ return this.creates;
774
+ }
775
+ /** Whether another create is still inside the dial bound. */
776
+ canCreate() {
777
+ return this.creates < this.maxCreatesPerDial;
778
+ }
779
+ /**
780
+ * Record a create that is about to happen. Throws
781
+ * {@link LEASE_DOWNLOAD_REBUILD_CAP_REASON} if the dial is already at the cap
782
+ * — the caller must not call `FilterAPI.create` after that.
783
+ */
784
+ recordCreate() {
785
+ if (this.creates >= this.maxCreatesPerDial) throw new Error(LEASE_DOWNLOAD_REBUILD_CAP_REASON);
786
+ this.creates++;
787
+ }
788
+ /** Fresh dial: the create budget starts over with a new `hw_frames_ctx`. */
789
+ resetForDial() {
790
+ this.creates = 0;
791
+ }
792
+ };
793
+ //#endregion
683
794
  //#region src/session-decode/media-clock.ts
684
795
  /**
685
796
  * Convert a container pts to milliseconds.
@@ -2618,6 +2729,13 @@ var DecodeWorkerChild = class {
2618
2729
  */
2619
2730
  admission = new LeaseAdmissionLedger({ mode: UNSTARTED_LEASE_KNOBS.values.admission });
2620
2731
  /**
2732
+ * One-frame park for a late `markInferred` after supersede. Holds the native
2733
+ * surface WITHOUT downloading until the mark arrives or the next supersede
2734
+ * discards it — closes `toBuffer miss — released` with `marked=true` when
2735
+ * the mark IPC loses the race with pull N+1. See `pending-mark-hold.ts`.
2736
+ */
2737
+ pendingMark = new PendingMarkHold();
2738
+ /**
2621
2739
  * The knobs this session runs under — `null` until `start` hands them over.
2622
2740
  * The stores above are REBUILT from them at that moment (they are empty then:
2623
2741
  * nothing is captured before a dial), and never again: a knob that changes
@@ -2638,6 +2756,12 @@ var DecodeWorkerChild = class {
2638
2756
  /** Root-cause instrumentation for the sporadic EIO/EINVAL add-frame failures (task: lease-filter EIO). */
2639
2757
  leaseDownloadFilterUses = 0;
2640
2758
  leaseDownloadFilterBuiltAt = 0;
2759
+ /**
2760
+ * Per-dial create budget for the lease-download filtergraph (S1 / N100 OOM
2761
+ * unit). Metric {@link LeaseDownloadRebuildCap.createsThisDial} is emitted as
2762
+ * `leaseDownloadFilterCreates` on the forensics + metrics lines.
2763
+ */
2764
+ leaseDownloadRebuildCap = new LeaseDownloadRebuildCap();
2641
2765
  dialStartedAt = 0;
2642
2766
  /**
2643
2767
  * `HardwareContext.create` cost, measured once per worker.
@@ -2660,9 +2784,9 @@ var DecodeWorkerChild = class {
2660
2784
  * successful uses — the decoder re-creates its hw_frames_ctx shortly after
2661
2785
  * dial start (post-probe pool re-init), stranding the just-built filter on
2662
2786
  * the old pool. One rebuild per dial recovers that case (the ctx has
2663
- * settled by the next capture); a SECOND failure disables the lease for
2664
- * the dial (bounded: max 2 filtergraphs/dial — never the per-frame rebuild
2665
- * that OOM'd the N100).
2787
+ * settled by the next capture); a SECOND failure starts the cooldown, and
2788
+ * {@link leaseDownloadRebuildCap} refuses a third `FilterAPI.create` for
2789
+ * the dial (the OOM unit — never the per-frame rebuild that OOM'd the N100).
2666
2790
  */
2667
2791
  leaseDownloadFailuresThisDial = 0;
2668
2792
  /** Throttled native-crop hit/miss counters, emitted with the throughput line. */
@@ -2830,6 +2954,8 @@ var DecodeWorkerChild = class {
2830
2954
  return;
2831
2955
  case "markInferred":
2832
2956
  this.admission.mark(message.frameId);
2957
+ this.leaseGate.arm();
2958
+ this.promotePendingMark(message.frameId);
2833
2959
  return;
2834
2960
  case "drain":
2835
2961
  this.handleDrain();
@@ -2949,7 +3075,7 @@ var DecodeWorkerChild = class {
2949
3075
  const skipped = this.framesSkipped + this.frames.droppedCount;
2950
3076
  const rssMb = Math.round(process.memoryUsage().rss / 1048576);
2951
3077
  const ageS = Math.round((now - this.startedAt) / 1e3);
2952
- 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:${this.lease?.values.admission ?? "unstarted"}, leaseMb:${Math.round(this.leaseStore.totalBytes / 1048576)}, leaseFrames:${this.leaseStore.size}, holdOverflow:${this.leaseStore.overflowCount}, leaseReleased:${this.leaseReleased}, leaseReleaseMisses:${this.leaseReleaseMisses}, leaseReleasedEarly:${this.leaseReleasedEarly}, tilesCut:${this.tilesCut}, tileHits:${this.tileHits}, tileMisses:${this.tileMisses}, tileCutFailures:${this.tileCutFailures}, tileMissesResolvedByWait:${this.tileMissesResolvedByWait}, tileWaitTimeouts:${this.tileWaitTimeouts}, cutLatencyMsP50:${formatLatency(this.pendingCuts.latencyMs(50))}, cutLatencyMsP95:${formatLatency(this.pendingCuts.latencyMs(95))}, cutsInFlight:${this.pendingCuts.inFlight}, tileMb:${Math.round(this.tileStore.totalBytes / 1048576)}, tileFrames:${this.tileStore.frameCount}, scenesCut:${this.scenesCut}, sceneHits:${this.sceneHits}, sceneRefused:${this.sceneRefused}, sceneUnclassified:${this.sceneUnclassified}, sceneEvicted:${this.sceneStore.evictedCount}, sceneMb:${Math.round(this.sceneStore.totalBytes / 1048576)}, sceneFrames:${this.sceneStore.frameCount}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
3078
+ 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}, leaseDownloadFilterCreates:${this.leaseDownloadRebuildCap.createsThisDial}, leaseMarks:${this.admission.marks}, leaseUnmarkedCrops:${this.admission.unmarkedCrops}, leaseAdmission:${this.lease?.values.admission ?? "unstarted"}, leaseMb:${Math.round(this.leaseStore.totalBytes / 1048576)}, leaseFrames:${this.leaseStore.size}, holdOverflow:${this.leaseStore.overflowCount}, leaseReleased:${this.leaseReleased}, leaseReleaseMisses:${this.leaseReleaseMisses}, leaseReleasedEarly:${this.leaseReleasedEarly}, tilesCut:${this.tilesCut}, tileHits:${this.tileHits}, tileMisses:${this.tileMisses}, tileCutFailures:${this.tileCutFailures}, tileMissesResolvedByWait:${this.tileMissesResolvedByWait}, tileWaitTimeouts:${this.tileWaitTimeouts}, cutLatencyMsP50:${formatLatency(this.pendingCuts.latencyMs(50))}, cutLatencyMsP95:${formatLatency(this.pendingCuts.latencyMs(95))}, cutsInFlight:${this.pendingCuts.inFlight}, tileMb:${Math.round(this.tileStore.totalBytes / 1048576)}, tileFrames:${this.tileStore.frameCount}, scenesCut:${this.scenesCut}, sceneHits:${this.sceneHits}, sceneRefused:${this.sceneRefused}, sceneUnclassified:${this.sceneUnclassified}, sceneEvicted:${this.sceneStore.evictedCount}, sceneMb:${Math.round(this.sceneStore.totalBytes / 1048576)}, sceneFrames:${this.sceneStore.frameCount}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
2953
3079
  this.lastMetricsAt = now;
2954
3080
  this.lastDeliveredSnapshot = this.framesDelivered;
2955
3081
  if (!this.warnedLarge && rssMb >= WORKER_RSS_WARN_MB) {
@@ -2970,6 +3096,7 @@ var DecodeWorkerChild = class {
2970
3096
  this.sceneStore = buildTileStore(lease.values.sceneBudgetMb);
2971
3097
  this.leaseGate = new LeaseActivityGate(lease.values.activityMs);
2972
3098
  this.admission = new LeaseAdmissionLedger({ mode: lease.values.admission });
3099
+ this.pendingMark.clear();
2973
3100
  this.emitStderr(`decode-worker-child: ${require_worker_protocol.formatNativeLeaseKnobs(lease)}\n`, "info");
2974
3101
  this.sessionFormat = opts.format ?? "rgb";
2975
3102
  this.minIntervalMs = opts.fps && opts.fps > 0 ? 1e3 / opts.fps : 0;
@@ -3018,7 +3145,7 @@ var DecodeWorkerChild = class {
3018
3145
  }
3019
3146
  }
3020
3147
  async handleToBuffer(frameId, opts) {
3021
- const frame = this.leaseStore.get(frameId)?.frame ?? this.frames.toBuffer(frameId);
3148
+ const frame = this.leaseStore.get(frameId)?.frame ?? this.pendingMark.get(frameId) ?? this.frames.toBuffer(frameId);
3022
3149
  this.settlePreRoll(frameId, frame ? "read" : "miss");
3023
3150
  if (!frame) {
3024
3151
  this.sendError(describeToBufferMiss({
@@ -3164,9 +3291,9 @@ var DecodeWorkerChild = class {
3164
3291
  if (this.tileStore.get(frameId).length === 0 && this.sceneStore.get(frameId).length === 0) return `frame ${frameId} released and no retained tile for this frame`;
3165
3292
  return `frame ${frameId} released and no retained subject or scene tile contains the requested rectangle`;
3166
3293
  }
3167
- /** The GPU-ring or reserved-slot frame for `frameId` (hold-disabled / newest paths). */
3294
+ /** The GPU-ring, pending-mark, or reserved-slot frame for `frameId`. */
3168
3295
  resolveRetainedFrame(frameId) {
3169
- return this.nativeRing.get(frameId) ?? this.frames.toBuffer(frameId);
3296
+ return this.nativeRing.get(frameId) ?? this.pendingMark.get(frameId) ?? this.frames.toBuffer(frameId);
3170
3297
  }
3171
3298
  /**
3172
3299
  * The runner has this frame's detections. Cut one native TILE per subject and
@@ -3420,6 +3547,11 @@ var DecodeWorkerChild = class {
3420
3547
  * frames the runner MARKED as having reached inference in `inferred`
3421
3548
  * mode (`nativeLeaseAdmission`).
3422
3549
  *
3550
+ * When (2) or (3) would decline, the frame is PARKED in {@link pendingMark}
3551
+ * for one supersede — a late `markInferred` can still promote it. The next
3552
+ * supersede discards an unmarked pending. No GPU download until promote or
3553
+ * lease, so quiet cameras still avoid the per-frame bandwidth burn.
3554
+ *
3423
3555
  * What reaches this function at all is the {@link FrameSlot} lifecycle, not a
3424
3556
  * quality filter. `retainSuperseded` is called only from
3425
3557
  * `FrameSlot.reserveSlot()` (`frame-slot.ts`), on the frame the PREVIOUS
@@ -3444,7 +3576,8 @@ var DecodeWorkerChild = class {
3444
3576
  *
3445
3577
  * The NEWEST delivered frame is never at risk either way: it is the slot's
3446
3578
  * reserved frame and {@link handleNativeCrop} resolves it through
3447
- * `FrameSlot.toBuffer`, never through the lease.
3579
+ * `FrameSlot.toBuffer`, never through the lease. A late mark for the PREVIOUS
3580
+ * frame is the pending-mark hold's job — the reserved margin does not cover it.
3448
3581
  *
3449
3582
  * ## What it costs / saves
3450
3583
  *
@@ -3460,17 +3593,57 @@ var DecodeWorkerChild = class {
3460
3593
  */
3461
3594
  captureNativeFrame(frameId, frame) {
3462
3595
  if (!this.leaseStore.enabled) {
3596
+ this.flushPendingMark();
3463
3597
  this.settlePreRoll(frameId, "released");
3464
3598
  return this.nativeRing.retain(frameId, frame);
3465
3599
  }
3466
- if (!this.leaseGate.active) {
3600
+ if (!this.leaseGate.active && !this.admission.isMarked(frameId)) return this.parkPendingMark(frameId, frame);
3601
+ if (!this.admission.wouldAdmit(frameId)) return this.parkPendingMark(frameId, frame);
3602
+ this.flushPendingMark();
3603
+ if (!this.admission.admit(frameId)) {
3467
3604
  this.settlePreRoll(frameId, "released");
3468
3605
  return false;
3469
3606
  }
3607
+ return this.leaseNativeFrame(frameId, frame);
3608
+ }
3609
+ /**
3610
+ * Discard a pending mark that lost the race (never marked before the next
3611
+ * supersede). Counts as an offered decline so metrics still see the delivery.
3612
+ */
3613
+ flushPendingMark() {
3614
+ const id = this.pendingMark.frameId;
3615
+ if (id === null) return;
3616
+ this.pendingMark.discard();
3617
+ this.admission.admit(id);
3618
+ this.settlePreRoll(id, "released");
3619
+ }
3620
+ /** Park a just-superseded frame awaiting a possible late `markInferred`. */
3621
+ parkPendingMark(frameId, frame) {
3622
+ this.flushPendingMark();
3623
+ this.pendingMark.park(frameId, frame);
3624
+ return true;
3625
+ }
3626
+ /**
3627
+ * Promote a parked frame into the lease after a late mark. No-op when the
3628
+ * pending id does not match (mark arrived in time and the frame was already
3629
+ * leased, or a newer supersede already discarded it).
3630
+ */
3631
+ promotePendingMark(frameId) {
3632
+ const frame = this.pendingMark.take(frameId);
3633
+ if (!frame) return;
3470
3634
  if (!this.admission.admit(frameId)) {
3635
+ frame.free();
3471
3636
  this.settlePreRoll(frameId, "released");
3472
- return false;
3637
+ return;
3473
3638
  }
3639
+ this.leaseNativeFrame(frameId, frame);
3640
+ }
3641
+ /**
3642
+ * Download (if HW) and put into {@link leaseStore}. Consumes ownership of
3643
+ * `frame` whether or not the download succeeds (returns `true` either way
3644
+ * so the slot / pending hold never double-frees).
3645
+ */
3646
+ leaseNativeFrame(frameId, frame) {
3474
3647
  if (frame.isHwFrame()) {
3475
3648
  const software = this.downloadToSoftware(frame);
3476
3649
  frame.free();
@@ -3502,7 +3675,12 @@ var DecodeWorkerChild = class {
3502
3675
  for (let i = 1; i < outputs.length; i++) outputs[i]?.free();
3503
3676
  return first;
3504
3677
  } catch (err) {
3505
- this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)} [forensics: frame=${frame.width}x${frame.height} fmt=${frame.format} hw=${frame.isHwFrame()} filterUses=${this.leaseDownloadFilterUses} filterAgeMs=${Date.now() - this.leaseDownloadFilterBuiltAt} dialAgeMs=${Date.now() - this.dialStartedAt} decoded=${this.framesDecoded}]\n`);
3678
+ const reason = errMessage(err);
3679
+ if (reason === "lease-download-rebuild-cap") {
3680
+ this.emitStderr(`decode-worker-child: native lease download refused — ${LEASE_DOWNLOAD_REBUILD_CAP_REASON} [forensics: frame=${frame.width}x${frame.height} fmt=${frame.format} hw=${frame.isHwFrame()} filterUses=${this.leaseDownloadFilterUses} leaseDownloadFilterCreates=${this.leaseDownloadRebuildCap.createsThisDial} dialAgeMs=${Date.now() - this.dialStartedAt} decoded=${this.framesDecoded}]\n`);
3681
+ return null;
3682
+ }
3683
+ this.emitStderr(`decode-worker-child: native lease download failed — ${reason} [forensics: frame=${frame.width}x${frame.height} fmt=${frame.format} hw=${frame.isHwFrame()} filterUses=${this.leaseDownloadFilterUses} filterAgeMs=${Date.now() - this.leaseDownloadFilterBuiltAt} leaseDownloadFilterCreates=${this.leaseDownloadRebuildCap.createsThisDial} dialAgeMs=${Date.now() - this.dialStartedAt} decoded=${this.framesDecoded}]\n`);
3506
3684
  this.native.run("nativeLeaseDownloadFilter", () => this.nativeLeaseDownloadFilter?.close());
3507
3685
  this.nativeLeaseDownloadFilter = null;
3508
3686
  this.nativeLeaseDownloadFilterKey = "";
@@ -3531,6 +3709,7 @@ var DecodeWorkerChild = class {
3531
3709
  this.leaseDownloadFilterUses++;
3532
3710
  return cached;
3533
3711
  }
3712
+ this.leaseDownloadRebuildCap.recordCreate();
3534
3713
  this.native.run("nativeLeaseDownloadFilter", () => this.nativeLeaseDownloadFilter?.close());
3535
3714
  this.nativeLeaseDownloadFilter = null;
3536
3715
  this.nativeLeaseDownloadFilterKey = "";
@@ -3735,6 +3914,7 @@ var DecodeWorkerChild = class {
3735
3914
  this.dialStartedAt = Date.now();
3736
3915
  this.dialSeq++;
3737
3916
  this.leaseDownloadFailuresThisDial = 0;
3917
+ this.leaseDownloadRebuildCap.resetForDial();
3738
3918
  this.abortController = new AbortController();
3739
3919
  const opened = await openPullDemuxer({
3740
3920
  url,
@@ -4237,6 +4417,7 @@ var DecodeWorkerChild = class {
4237
4417
  */
4238
4418
  closeInput() {
4239
4419
  this.native.run("nativeRing", () => this.nativeRing.clear());
4420
+ this.native.run("pendingMark", () => this.pendingMark.clear());
4240
4421
  this.native.run("leaseStore", () => this.leaseStore.clear());
4241
4422
  const cleared = this.preRollSettle.settleAll("cleared");
4242
4423
  if (cleared) this.send({