@camstack/addon-pipeline 1.2.127 → 1.2.129

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 (35) hide show
  1. package/dist/{addon-utils-BnprivSs.js → addon-utils-CKB-2oM9.js} +1 -1
  2. package/dist/audio-analyzer/index.js +3 -3
  3. package/dist/audio-analyzer/index.mjs +2 -2
  4. package/dist/detection-pipeline/index.js +241 -95
  5. package/dist/detection-pipeline/index.mjs +239 -93
  6. package/dist/{dist-HLAOw6Pe.mjs → dist-CMio-1qq.mjs} +47 -31
  7. package/dist/{dist-BZUXb5S5.js → dist-D9x2_csL.js} +47 -31
  8. package/dist/{event-loop-stall-monitor-BH-cL_hg.mjs → event-loop-stall-monitor-B9dxYxyf.mjs} +1 -1
  9. package/dist/{event-loop-stall-monitor-BdArVUlx.js → event-loop-stall-monitor-UW8nsiMb.js} +1 -1
  10. package/dist/{lazy-sharp-DTDXrhMG.js → lazy-sharp-DrLwLKpg.js} +1 -1
  11. package/dist/motion-wasm/index.js +2 -2
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +4 -4
  14. package/dist/pipeline-runner/index.mjs +3 -3
  15. package/dist/{process-memory-CDPRLalN.mjs → process-memory-C6es8dMA.mjs} +1 -1
  16. package/dist/{process-memory-Dh1SyzdJ.js → process-memory-CSJ7-Wlp.js} +1 -1
  17. package/dist/recorder/index.js +121 -33
  18. package/dist/recorder/index.mjs +120 -32
  19. package/dist/{segment-demux-js-D-TLcHW5.mjs → segment-demux-js-BRCjXbI0.mjs} +1 -1
  20. package/dist/{segment-demux-js-DRutggd6.js → segment-demux-js-DiLDoV9_.js} +1 -1
  21. package/dist/session-decode/decode-worker-child.js +2 -2
  22. package/dist/session-decode/decode-worker-child.mjs +1 -1
  23. package/dist/stream-broker/_stub.js +1 -1
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BDR5z0Z9.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-1O99xPTr.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-CxppPmXr.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-Dmw5LWmP.mjs → hostInit-8scvCWua.mjs} +2 -2
  29. package/dist/stream-broker/index.js +530 -154
  30. package/dist/stream-broker/index.mjs +530 -154
  31. package/dist/stream-broker/remoteEntry.js +1 -1
  32. package/dist/{worker-protocol-h7Smb_0i.mjs → worker-protocol-BHNWB9Ve.mjs} +1 -1
  33. package/dist/{worker-protocol-BxkCoVjw.js → worker-protocol-BmBYCioC.js} +1 -1
  34. package/package.json +1 -1
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cs8bmrWP.mjs +0 -26
@@ -1,8 +1,8 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { $ as pickClusterStepModels, D as YAMNET_TO_MACRO, Dt as hydrateSchema, Et as createEvent, Ft as sleep, G as evaluateZoneRules, Gt as string, I as defaultDeviceFor$1, Kt as union, Lt as array, Mt as parseJsonUnknown, St as BaseAddon, Ut as object, V as detectionPipelineCapability, W as enumerateInferenceDevices, Z as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, dt as runtimeDevices$1, et as pickClusterStepSettings, gt as errMsg, jt as nodePin, l as DEFAULT_CLUSTER_STEP_SETTINGS, lt as resolvePoolMemoryPolicy, mt as supportedRuntimes$1, p as DEVICE_BACKEND_TO_FORMAT, q as inferModelProvider, qt as EventCategory, rt as pipelineExecutorCapability, st as resolveClusterStepModelId, t as APPLE_SA_TO_MACRO, x as PoolMemoryWatchdog } from "../dist-HLAOw6Pe.mjs";
3
- import { a as ALL_PIPELINE_STEPS, c as getDefaultModelForFormatFromDef, d as resolveModelForFormat, f as landmarkPrecisionVerdict, l as getStep, n as startEventLoopStallMonitor, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as getStepDefinition } from "../event-loop-stall-monitor-BH-cL_hg.mjs";
2
+ import { $ as pickClusterStepModels, D as YAMNET_TO_MACRO, Dt as hydrateSchema, Et as createEvent, Ft as sleep, G as evaluateZoneRules, Gt as string, I as defaultDeviceFor$1, Kt as union, Lt as array, Mt as parseJsonUnknown, St as BaseAddon, Ut as object, V as detectionPipelineCapability, W as enumerateInferenceDevices, Z as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, dt as runtimeDevices$1, et as pickClusterStepSettings, gt as errMsg, jt as nodePin, l as DEFAULT_CLUSTER_STEP_SETTINGS, lt as resolvePoolMemoryPolicy, mt as supportedRuntimes$1, p as DEVICE_BACKEND_TO_FORMAT, q as inferModelProvider, qt as EventCategory, rt as pipelineExecutorCapability, st as resolveClusterStepModelId, t as APPLE_SA_TO_MACRO, x as PoolMemoryWatchdog } from "../dist-CMio-1qq.mjs";
3
+ import { a as ALL_PIPELINE_STEPS, c as getDefaultModelForFormatFromDef, d as resolveModelForFormat, f as landmarkPrecisionVerdict, l as getStep, n as startEventLoopStallMonitor, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as getStepDefinition } from "../event-loop-stall-monitor-B9dxYxyf.mjs";
4
4
  import { t as getSharp } from "../lazy-sharp-BtT5x-qP.mjs";
5
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-CDPRLalN.mjs";
5
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-C6es8dMA.mjs";
6
6
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-CgBCF-mE.mjs";
7
7
  import sharp from "sharp";
8
8
  import { spawn } from "node:child_process";
@@ -228,6 +228,94 @@ function pickBestRuntime(env, bestBackendHint) {
228
228
  };
229
229
  }
230
230
  //#endregion
231
+ //#region src/detection-pipeline/engine/pool-provisioning.ts
232
+ /** The fields a {@link PoolProvisioningOverride} may carry, in table order. */
233
+ var OVERRIDABLE_KEYS = [
234
+ "concurrency",
235
+ "numWorkers",
236
+ "batchMode",
237
+ "windowMs",
238
+ "maxBatchSize",
239
+ "numStreams",
240
+ "intraOpThreads"
241
+ ];
242
+ /** Backends that are not a runtime of their own and run on onnxruntime. */
243
+ var BACKEND_TO_RUNTIME = {
244
+ onnx: "onnx",
245
+ cpu: "onnx",
246
+ cuda: "onnx",
247
+ openvino: "openvino",
248
+ coreml: "coreml",
249
+ edgetpu: "edgetpu"
250
+ };
251
+ /**
252
+ * The registry runtime a device-pool backend provisions from. Unknown backends
253
+ * floor to `onnx` — the same floor `resolveDeviceEngine` applies to the model
254
+ * format, so a pool never comes up unprovisioned.
255
+ */
256
+ function runtimeIdForBackend(backend) {
257
+ return BACKEND_TO_RUNTIME[backend] ?? "onnx";
258
+ }
259
+ /**
260
+ * Resolve the provisioning for a pool running `engine`, applying the precedence
261
+ * documented at the top of this module. `engine.device` is not consulted today
262
+ * (see the module doc) but is part of the signature so a bench-backed
263
+ * per-device row lands here and nowhere else.
264
+ */
265
+ function resolvePoolProvisioning(engine, override = {}) {
266
+ const runtime = runtimeIdForBackend(engine.backend);
267
+ const profile = tuningFor(runtime);
268
+ return {
269
+ runtime,
270
+ overriddenFields: OVERRIDABLE_KEYS.filter((k) => override[k] !== void 0),
271
+ concurrency: Math.max(1, override.concurrency ?? profile.concurrency),
272
+ numWorkers: Math.max(1, override.numWorkers ?? profile.numWorkers),
273
+ batchMode: override.batchMode ?? profile.batchMode,
274
+ windowMs: override.windowMs ?? profile.windowMs,
275
+ maxBatchSize: override.maxBatchSize ?? profile.maxBatchSize,
276
+ numStreams: override.numStreams ?? profile.numStreams,
277
+ intraOpThreads: override.intraOpThreads ?? profile.intraOpThreads
278
+ };
279
+ }
280
+ function positiveNumber(value) {
281
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
282
+ }
283
+ function batchMode(value) {
284
+ return value === "none" || value === "list" || value === "window" ? value : void 0;
285
+ }
286
+ /**
287
+ * Read the operator override out of the detection addon's settings. Only a
288
+ * NON-AUTO value counts: the sliders persist `0` and the select persists `''`
289
+ * for "Auto (backend default)", and those must resolve to the runtime's
290
+ * registry value, not to a literal 0 worker threads.
291
+ *
292
+ * The result is deliberately sparse — an empty object means "nothing
293
+ * overridden", which is what lets a per-device pool provision from its own
294
+ * runtime instead of inheriting the node's.
295
+ */
296
+ function readOperatorOverride(settings) {
297
+ const override = {};
298
+ const concurrency = positiveNumber(settings.concurrency);
299
+ if (concurrency !== void 0) override.concurrency = concurrency;
300
+ const numWorkers = positiveNumber(settings.numWorkers);
301
+ if (numWorkers !== void 0) override.numWorkers = numWorkers;
302
+ const mode = batchMode(settings.batchMode);
303
+ if (mode !== void 0) override.batchMode = mode;
304
+ const windowMs = positiveNumber(settings.windowMs);
305
+ if (windowMs !== void 0) override.windowMs = windowMs;
306
+ const maxBatchSize = positiveNumber(settings.maxBatchSize);
307
+ if (maxBatchSize !== void 0) override.maxBatchSize = maxBatchSize;
308
+ const numStreams = positiveNumber(settings.numStreams);
309
+ if (numStreams !== void 0) override.numStreams = numStreams;
310
+ const intraOpThreads = positiveNumber(settings.intraOpThreads);
311
+ if (intraOpThreads !== void 0) override.intraOpThreads = intraOpThreads;
312
+ return override;
313
+ }
314
+ /** Two overrides carry the same fields with the same values. */
315
+ function overridesEqual(a, b) {
316
+ return OVERRIDABLE_KEYS.every((k) => a[k] === b[k]);
317
+ }
318
+ //#endregion
231
319
  //#region src/detection-pipeline/engine-store-keys.ts
232
320
  /**
233
321
  * Node-id normalisation for the detection pipeline.
@@ -438,6 +526,122 @@ function resolveDefaultDetectionModel(backend, device, format, getStepDef = getS
438
526
  return getDefaultModelForFormatFromDef(def, format);
439
527
  }
440
528
  //#endregion
529
+ //#region src/detection-pipeline/device-override-mirror.ts
530
+ /**
531
+ * The per-device detection overrides, kept fresh OFF the frame path.
532
+ *
533
+ * ## The read this replaces
534
+ *
535
+ * `runPipeline` resolved a device's overrides by calling
536
+ * `settings.readDeviceStore(deviceId)` on **every inference frame**. Measured
537
+ * on the live hub 2026-08-25: `get detection-pipeline:addon-devices`, 1 143
538
+ * calls a minute at 0.44 rows per call — the disk asked, nineteen times a
539
+ * second, whether an operator has ever changed a threshold.
540
+ *
541
+ * ## Why this is not "a cache in the consumer" (D224)
542
+ *
543
+ * D224 forbids a SECOND authority over a fact that belongs to somebody else:
544
+ * the failure it records is a consumer caching a value whose write path does
545
+ * not run through that consumer, so an operator's own change is the one read
546
+ * the cache gets wrong.
547
+ *
548
+ * This addon OWNS the row. `<addonId>:addon-devices` is detection-pipeline's
549
+ * own store, and the only writers are this addon's `updateDeviceSettings` and
550
+ * its `clearDeviceOverrides` — both of which call {@link invalidate}. A write
551
+ * is therefore its own invalidation, exactly as D224 requires, and the window
552
+ * below governs only the case nobody writes through this process.
553
+ *
554
+ * ## The window, declared
555
+ *
556
+ * {@link DEVICE_OVERRIDE_STALE_MS} is what an operator's change costs on a node
557
+ * that did NOT serve the write — this addon runs per node, and a settings write
558
+ * lands on one of them. Thirty seconds is the trade: it removes 99.9% of the
559
+ * reads (one per device per window instead of one per frame) and it is short
560
+ * enough that "I moved the threshold and nothing happened" is not a support
561
+ * question. Ten minutes — {@link OPERATOR_WRITTEN_STALE_MS}, the constant for a
562
+ * cap whose value only an operator changes — is right for a POLLED reader that
563
+ * costs a camera round trip; this read costs one local SQLite `get`, and the
564
+ * cheap read buys a short window.
565
+ *
566
+ * A stale entry is SERVED and revalidated behind the caller, never awaited: a
567
+ * frame that waits for the settings store is a frame the pipeline dropped, and
568
+ * the reader is by definition about to ask again.
569
+ */
570
+ /**
571
+ * How long a mirrored entry is served before a refresh is started behind the
572
+ * next reader. See the module doc for why it is 30 s and not ten minutes.
573
+ */
574
+ var DEVICE_OVERRIDE_STALE_MS = 3e4;
575
+ /**
576
+ * How long a FAILED refresh is not retried.
577
+ *
578
+ * A refresh that throws leaves `fetchedAt` where it was, so without a cooldown
579
+ * every subsequent frame would start another one — a broken settings store
580
+ * would be hammered at frame rate by the very mechanism built to stop that.
581
+ */
582
+ var DEVICE_OVERRIDE_MISS_COOLDOWN_MS = 5e3;
583
+ var DeviceOverrideMirror = class {
584
+ deps;
585
+ staleMs;
586
+ missCooldownMs;
587
+ entries = /* @__PURE__ */ new Map();
588
+ /** In-flight reads, so a burst of frames on a cold device costs ONE read and
589
+ * not one per frame — the exact failure this class exists to remove. */
590
+ inFlight = /* @__PURE__ */ new Map();
591
+ now;
592
+ constructor(deps, staleMs = DEVICE_OVERRIDE_STALE_MS, missCooldownMs = DEVICE_OVERRIDE_MISS_COOLDOWN_MS) {
593
+ this.deps = deps;
594
+ this.staleMs = staleMs;
595
+ this.missCooldownMs = missCooldownMs;
596
+ this.now = deps.now ?? (() => Date.now());
597
+ }
598
+ /**
599
+ * The device's overrides.
600
+ *
601
+ * COLD awaits — the first frame on a camera has to get the real answer, and a
602
+ * default is exactly what "the operator lowered this threshold" looks like.
603
+ * WARM returns immediately, stale or not, and starts the refresh behind the
604
+ * caller.
605
+ */
606
+ async resolve(deviceId) {
607
+ const entry = this.entries.get(deviceId);
608
+ if (entry === void 0) return this.load(deviceId);
609
+ if (this.now() - entry.fetchedAt >= this.staleMs) this.load(deviceId);
610
+ return entry.value;
611
+ }
612
+ /** Drop a device's entry. Called by every writer in this process, so an
613
+ * operator's own change is never served from the mirror. */
614
+ invalidate(deviceId) {
615
+ this.entries.delete(deviceId);
616
+ }
617
+ invalidateAll() {
618
+ this.entries.clear();
619
+ }
620
+ async load(deviceId) {
621
+ const pending = this.inFlight.get(deviceId);
622
+ if (pending !== void 0) return pending;
623
+ const run = this.deps.read(deviceId).then((value) => {
624
+ this.entries.set(deviceId, {
625
+ value,
626
+ fetchedAt: this.now()
627
+ });
628
+ return value;
629
+ }).catch((err) => {
630
+ this.deps.onRefreshFailed?.(deviceId, err);
631
+ const existing = this.entries.get(deviceId);
632
+ if (existing !== void 0) {
633
+ existing.fetchedAt = this.now() - this.staleMs + this.missCooldownMs;
634
+ return existing.value;
635
+ }
636
+ return {};
637
+ }).finally(() => {
638
+ this.inFlight.delete(deviceId);
639
+ });
640
+ this.inFlight.set(deviceId, run);
641
+ return run;
642
+ }
643
+ };
644
+ //#endregion
441
645
  //#region src/detection-pipeline/engine/shared-inference-pool.ts
442
646
  /**
443
647
  * SharedInferencePool — TypeScript wrapper for inference_pool.py.
@@ -1980,94 +2184,6 @@ function deviceKeyOf(engine) {
1980
2184
  return engine.device ? `${engine.backend}:${engine.device}` : engine.backend;
1981
2185
  }
1982
2186
  //#endregion
1983
- //#region src/detection-pipeline/engine/pool-provisioning.ts
1984
- /** The fields a {@link PoolProvisioningOverride} may carry, in table order. */
1985
- var OVERRIDABLE_KEYS = [
1986
- "concurrency",
1987
- "numWorkers",
1988
- "batchMode",
1989
- "windowMs",
1990
- "maxBatchSize",
1991
- "numStreams",
1992
- "intraOpThreads"
1993
- ];
1994
- /** Backends that are not a runtime of their own and run on onnxruntime. */
1995
- var BACKEND_TO_RUNTIME = {
1996
- onnx: "onnx",
1997
- cpu: "onnx",
1998
- cuda: "onnx",
1999
- openvino: "openvino",
2000
- coreml: "coreml",
2001
- edgetpu: "edgetpu"
2002
- };
2003
- /**
2004
- * The registry runtime a device-pool backend provisions from. Unknown backends
2005
- * floor to `onnx` — the same floor `resolveDeviceEngine` applies to the model
2006
- * format, so a pool never comes up unprovisioned.
2007
- */
2008
- function runtimeIdForBackend(backend) {
2009
- return BACKEND_TO_RUNTIME[backend] ?? "onnx";
2010
- }
2011
- /**
2012
- * Resolve the provisioning for a pool running `engine`, applying the precedence
2013
- * documented at the top of this module. `engine.device` is not consulted today
2014
- * (see the module doc) but is part of the signature so a bench-backed
2015
- * per-device row lands here and nowhere else.
2016
- */
2017
- function resolvePoolProvisioning(engine, override = {}) {
2018
- const runtime = runtimeIdForBackend(engine.backend);
2019
- const profile = tuningFor(runtime);
2020
- return {
2021
- runtime,
2022
- overriddenFields: OVERRIDABLE_KEYS.filter((k) => override[k] !== void 0),
2023
- concurrency: Math.max(1, override.concurrency ?? profile.concurrency),
2024
- numWorkers: Math.max(1, override.numWorkers ?? profile.numWorkers),
2025
- batchMode: override.batchMode ?? profile.batchMode,
2026
- windowMs: override.windowMs ?? profile.windowMs,
2027
- maxBatchSize: override.maxBatchSize ?? profile.maxBatchSize,
2028
- numStreams: override.numStreams ?? profile.numStreams,
2029
- intraOpThreads: override.intraOpThreads ?? profile.intraOpThreads
2030
- };
2031
- }
2032
- function positiveNumber(value) {
2033
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
2034
- }
2035
- function batchMode(value) {
2036
- return value === "none" || value === "list" || value === "window" ? value : void 0;
2037
- }
2038
- /**
2039
- * Read the operator override out of the detection addon's settings. Only a
2040
- * NON-AUTO value counts: the sliders persist `0` and the select persists `''`
2041
- * for "Auto (backend default)", and those must resolve to the runtime's
2042
- * registry value, not to a literal 0 worker threads.
2043
- *
2044
- * The result is deliberately sparse — an empty object means "nothing
2045
- * overridden", which is what lets a per-device pool provision from its own
2046
- * runtime instead of inheriting the node's.
2047
- */
2048
- function readOperatorOverride(settings) {
2049
- const override = {};
2050
- const concurrency = positiveNumber(settings.concurrency);
2051
- if (concurrency !== void 0) override.concurrency = concurrency;
2052
- const numWorkers = positiveNumber(settings.numWorkers);
2053
- if (numWorkers !== void 0) override.numWorkers = numWorkers;
2054
- const mode = batchMode(settings.batchMode);
2055
- if (mode !== void 0) override.batchMode = mode;
2056
- const windowMs = positiveNumber(settings.windowMs);
2057
- if (windowMs !== void 0) override.windowMs = windowMs;
2058
- const maxBatchSize = positiveNumber(settings.maxBatchSize);
2059
- if (maxBatchSize !== void 0) override.maxBatchSize = maxBatchSize;
2060
- const numStreams = positiveNumber(settings.numStreams);
2061
- if (numStreams !== void 0) override.numStreams = numStreams;
2062
- const intraOpThreads = positiveNumber(settings.intraOpThreads);
2063
- if (intraOpThreads !== void 0) override.intraOpThreads = intraOpThreads;
2064
- return override;
2065
- }
2066
- /** Two overrides carry the same fields with the same values. */
2067
- function overridesEqual(a, b) {
2068
- return OVERRIDABLE_KEYS.every((k) => a[k] === b[k]);
2069
- }
2070
- //#endregion
2071
2187
  //#region src/detection-pipeline/engine/engine-factory.ts
2072
2188
  var BACKEND_TO_POOL_RUNTIME = {
2073
2189
  coreml: "coreml",
@@ -6220,6 +6336,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6220
6336
  readDeviceStore;
6221
6337
  /** Delete all per-device settings for a device (used by `clearDeviceOverrides`). */
6222
6338
  clearDeviceStore;
6339
+ /**
6340
+ * The per-device overrides the FRAME path reads, kept fresh off that path.
6341
+ *
6342
+ * Public so the addon class can drop a device on its own
6343
+ * `updateDeviceSettings` — the write and the mirror have to be the same
6344
+ * process's, or the mirror becomes the second authority D224 forbids.
6345
+ */
6346
+ deviceOverrides;
6223
6347
  constructor(settings, modelsDir, logger, eventBus = null, detectionConfigSchemaSource = null, executorOptions = {}) {
6224
6348
  this.modelsDir = modelsDir;
6225
6349
  this.eventBus = eventBus;
@@ -6231,6 +6355,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6231
6355
  this.readDeviceStore = settings.readDeviceStore ?? (async () => ({}));
6232
6356
  const clearDeviceStore = settings.clearDeviceStore;
6233
6357
  this.clearDeviceStore = clearDeviceStore ? (deviceId) => clearDeviceStore.call(settings, deviceId) : async () => void 0;
6358
+ this.deviceOverrides = new DeviceOverrideMirror({
6359
+ read: (deviceId) => this.readDeviceStore(deviceId),
6360
+ onRefreshFailed: (deviceId, err) => this.log.warn("per-device detection overrides refresh failed — serving the last value", {
6361
+ tags: { deviceId },
6362
+ meta: { error: errMsg(err) }
6363
+ })
6364
+ });
6234
6365
  this.currentEngine = ONNX_FLOOR;
6235
6366
  this.log.info("Engine pick pending (placeholder until probe / persisted selection)", { meta: {
6236
6367
  runtime: this.currentEngine.runtime,
@@ -7415,7 +7546,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7415
7546
  setupMs = performance.now() - wallT0 - decodeMs;
7416
7547
  const isRuntime = Boolean(runtimeFrame || runtimeFrameRef);
7417
7548
  const effectiveDeviceId = input.deviceId ?? 0;
7418
- const deviceOverrides = effectiveDeviceId > 0 ? await this.readDeviceStore(effectiveDeviceId) : {};
7549
+ const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
7419
7550
  const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
7420
7551
  const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
7421
7552
  let cropZoneBbox;
@@ -8484,7 +8615,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8484
8615
  if (!api) return 0;
8485
8616
  let devices = [];
8486
8617
  try {
8487
- devices = await api.deviceManager.listAll.query({});
8618
+ devices = await api.deviceManager.listAll.query({ projection: "slim" });
8488
8619
  } catch (err) {
8489
8620
  this.log.warn("clearDeviceOverrides: device enumeration failed — skipping per-device clear", { meta: { error: errMsg(err) } });
8490
8621
  return 0;
@@ -8492,6 +8623,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8492
8623
  let cleared = 0;
8493
8624
  for (const device of devices) try {
8494
8625
  await this.clearDeviceStore(device.id);
8626
+ this.deviceOverrides.invalidate(device.id);
8495
8627
  cleared++;
8496
8628
  } catch (err) {
8497
8629
  this.log.warn("clearDeviceOverrides: failed to clear per-device overrides for device", {
@@ -9049,6 +9181,20 @@ function isProfileSliderKey(key) {
9049
9181
  }
9050
9182
  var DetectionPipelineAddon = class extends BaseAddon {
9051
9183
  provider = null;
9184
+ /**
9185
+ * An operator's own write drops the mirror the frame path reads.
9186
+ *
9187
+ * This override is the half of `device-override-mirror.ts` that keeps it from
9188
+ * being the second authority D224 forbids: `BaseAddon.updateDeviceSettings`
9189
+ * writes THIS addon's `<addonId>:addon-devices` row, so the write and the
9190
+ * mirror are in one process and the write invalidates. Without it the
9191
+ * operator's own change would be the one read the mirror got wrong, which is
9192
+ * the exact failure D224 records.
9193
+ */
9194
+ async updateDeviceSettings(deviceId, patch) {
9195
+ await super.updateDeviceSettings(deviceId, patch);
9196
+ this.provider?.deviceOverrides.invalidate(deviceId);
9197
+ }
9052
9198
  /** Last non-null probed hardware PER NODE — reused when the probe transiently
9053
9199
  * returns null so offered backends / device lists don't collapse. Keyed by
9054
9200
  * node because the hub addon probes other nodes when serving their config.
@@ -8599,24 +8599,6 @@ var RecordingRetentionSchema = object({
8599
8599
  maxSizeGb: number().min(0).optional()
8600
8600
  });
8601
8601
  /**
8602
- * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
8603
- * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
8604
- * previews at. Five graduated steps; absent on a config = `standard` (the
8605
- * shipped default, matching `sheet-geometry`/`sheet-composer`).
8606
- *
8607
- * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
8608
- * Each window's index sidecar carries its own tile dims, so a camera whose
8609
- * preset changed over time renders every historical window at the dims it was
8610
- * written with.
8611
- */
8612
- var ScrubThumbnailPresetSchema = _enum([
8613
- "minimal",
8614
- "low",
8615
- "standard",
8616
- "high",
8617
- "max"
8618
- ]);
8619
- /**
8620
8602
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
8621
8603
  *
8622
8604
  * `bands` is the ONLY authored recording intent: what to record, when, and on
@@ -8624,7 +8606,11 @@ var ScrubThumbnailPresetSchema = _enum([
8624
8606
  * other field is a storage knob (profiles, segment length, retention, scrub).
8625
8607
  *
8626
8608
  * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
8627
- * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
8609
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30,
8610
+ * and `scrubThumbnails` — a five-step fidelity knob for a sprite tier that was
8611
+ * deleted on 2026-07-24 and had ZERO consumers in the recorder — on 2026-08-25
8612
+ * (D62: a switch that writes a store nobody reads is worse than no switch).
8613
+ * Stored rows keep loading: the READ schema is `.strip()` (config-store.ts).
8628
8614
  * A stale caller must fail loudly — silently stripping its legacy intent would
8629
8615
  * persist a band-less config, i.e. silently stop recording the camera.
8630
8616
  */
@@ -8647,14 +8633,7 @@ var RecordingConfigSchema = object({
8647
8633
  * "off" is the absence of a covering band, never a band value.
8648
8634
  */
8649
8635
  bands: array(RecordingBandSchema).default([]),
8650
- retention: RecordingRetentionSchema.optional(),
8651
- /**
8652
- * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
8653
- * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
8654
- * windows only — existing sheets are immutable, and each window's index
8655
- * carries its own tile dims so mixed-preset history renders correctly.
8656
- */
8657
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
8636
+ retention: RecordingRetentionSchema.optional()
8658
8637
  }).strict();
8659
8638
  /**
8660
8639
  * Derive the {@link RecordingStorageModeSchema} summary from the authoritative
@@ -12442,7 +12421,8 @@ method(object({
12442
12421
  }), _void(), { kind: "mutation" }), method(object({
12443
12422
  namespace: string().optional(),
12444
12423
  collection: string(),
12445
- filter: QueryFilterSchema.optional()
12424
+ filter: QueryFilterSchema.optional(),
12425
+ columns: array(string()).readonly().optional()
12446
12426
  }), array(SettingsRecordSchema).readonly()), method(object({
12447
12427
  namespace: string().optional(),
12448
12428
  collection: string(),
@@ -12517,7 +12497,17 @@ method(_void(), EngineInfoSchema), method(object({
12517
12497
  }), _void(), { kind: "mutation" }), method(object({
12518
12498
  namespace: string().optional(),
12519
12499
  collection: string(),
12520
- filter: QueryFilterSchema.optional()
12500
+ filter: QueryFilterSchema.optional(),
12501
+ /**
12502
+ * SQL-level column projection — MUST mirror `settings-store.query`.
12503
+ *
12504
+ * This is the second schema the same call passes through, and Zod
12505
+ * objects STRIP what they do not declare: a field present on the door
12506
+ * and absent here does not error, it silently disappears one hop before
12507
+ * the engine, and the caller concludes the projection does nothing.
12508
+ * `data-door-schema-parity.spec.ts` is what keeps the two in step.
12509
+ */
12510
+ columns: array(string()).readonly().optional()
12521
12511
  }), array(SettingsRecordSchema).readonly()), method(object({
12522
12512
  namespace: string().optional(),
12523
12513
  collection: string(),
@@ -24161,7 +24151,27 @@ var MediaFileLiteSchema$1 = object({
24161
24151
  sizeBytes: number(),
24162
24152
  timestamp: number()
24163
24153
  });
24164
- method(_void(), array(IdentitySchema).readonly()), method(object({ name: string().min(1) }), IdentitySchema, {
24154
+ method(object({
24155
+ /**
24156
+ * Inline {@link IdentitySchema.coverBase64} on every row.
24157
+ *
24158
+ * Default `false`, the same inversion `listRecentFaces` and
24159
+ * `listPlates` took on 2026-08-25 (see `include-crops-default.ts` for
24160
+ * why the burden belongs on the caller that WANTS the bytes). Measured
24161
+ * on the live hub the same day: four identities cost 40,979 B with the
24162
+ * covers inline, ~10 KB of base64 per row, on a query this UI mounts
24163
+ * four times and the viewer holds at `staleTime: 30_000`.
24164
+ *
24165
+ * Nothing loses its avatar: `coverMediaKey` is already on every row and
24166
+ * the `event-media` plane serves that key `immutable` with an ETag.
24167
+ *
24168
+ * **This is an INPUT field, so it does not reach the addon until the
24169
+ * next train** — the hub router validates cap inputs against its own
24170
+ * compiled Zod and strips a key it does not know. Until then the
24171
+ * provider sees `undefined`, which resolves to `false`: the cheap shape
24172
+ * is what ships, and the opt-in becomes reachable when the train lands.
24173
+ */
24174
+ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly()), method(object({ name: string().min(1) }), IdentitySchema, {
24165
24175
  kind: "mutation",
24166
24176
  auth: "admin"
24167
24177
  }), method(object({
@@ -26372,7 +26382,13 @@ method(object({
26372
26382
  }), method(object({ plateId: string() }), _void(), {
26373
26383
  kind: "mutation",
26374
26384
  auth: "admin"
26375
- }), method(_void(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
26385
+ }), method(object({
26386
+ /** Inline {@link VehicleSchema.coverBase64} on every row. Default
26387
+ * `false` — the vehicle twin of `faceGallery.listIdentities`'s
26388
+ * option; `coverMediaKey` + the `event-media` plane carry the picture.
26389
+ * INPUT field: stripped by the hub router until the train ships, which
26390
+ * resolves to `false` and is exactly the intended default. */
26391
+ includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
26376
26392
  kind: "mutation",
26377
26393
  auth: "admin"
26378
26394
  }), method(object({
@@ -8622,24 +8622,6 @@ var RecordingRetentionSchema = object({
8622
8622
  maxSizeGb: number().min(0).optional()
8623
8623
  });
8624
8624
  /**
8625
- * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
8626
- * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
8627
- * previews at. Five graduated steps; absent on a config = `standard` (the
8628
- * shipped default, matching `sheet-geometry`/`sheet-composer`).
8629
- *
8630
- * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
8631
- * Each window's index sidecar carries its own tile dims, so a camera whose
8632
- * preset changed over time renders every historical window at the dims it was
8633
- * written with.
8634
- */
8635
- var ScrubThumbnailPresetSchema = _enum([
8636
- "minimal",
8637
- "low",
8638
- "standard",
8639
- "high",
8640
- "max"
8641
- ]);
8642
- /**
8643
8625
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
8644
8626
  *
8645
8627
  * `bands` is the ONLY authored recording intent: what to record, when, and on
@@ -8647,7 +8629,11 @@ var ScrubThumbnailPresetSchema = _enum([
8647
8629
  * other field is a storage knob (profiles, segment length, retention, scrub).
8648
8630
  *
8649
8631
  * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
8650
- * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
8632
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30,
8633
+ * and `scrubThumbnails` — a five-step fidelity knob for a sprite tier that was
8634
+ * deleted on 2026-07-24 and had ZERO consumers in the recorder — on 2026-08-25
8635
+ * (D62: a switch that writes a store nobody reads is worse than no switch).
8636
+ * Stored rows keep loading: the READ schema is `.strip()` (config-store.ts).
8651
8637
  * A stale caller must fail loudly — silently stripping its legacy intent would
8652
8638
  * persist a band-less config, i.e. silently stop recording the camera.
8653
8639
  */
@@ -8670,14 +8656,7 @@ var RecordingConfigSchema = object({
8670
8656
  * "off" is the absence of a covering band, never a band value.
8671
8657
  */
8672
8658
  bands: array(RecordingBandSchema).default([]),
8673
- retention: RecordingRetentionSchema.optional(),
8674
- /**
8675
- * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
8676
- * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
8677
- * windows only — existing sheets are immutable, and each window's index
8678
- * carries its own tile dims so mixed-preset history renders correctly.
8679
- */
8680
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
8659
+ retention: RecordingRetentionSchema.optional()
8681
8660
  }).strict();
8682
8661
  /**
8683
8662
  * Derive the {@link RecordingStorageModeSchema} summary from the authoritative
@@ -12465,7 +12444,8 @@ method(object({
12465
12444
  }), _void(), { kind: "mutation" }), method(object({
12466
12445
  namespace: string().optional(),
12467
12446
  collection: string(),
12468
- filter: QueryFilterSchema.optional()
12447
+ filter: QueryFilterSchema.optional(),
12448
+ columns: array(string()).readonly().optional()
12469
12449
  }), array(SettingsRecordSchema).readonly()), method(object({
12470
12450
  namespace: string().optional(),
12471
12451
  collection: string(),
@@ -12540,7 +12520,17 @@ method(_void(), EngineInfoSchema), method(object({
12540
12520
  }), _void(), { kind: "mutation" }), method(object({
12541
12521
  namespace: string().optional(),
12542
12522
  collection: string(),
12543
- filter: QueryFilterSchema.optional()
12523
+ filter: QueryFilterSchema.optional(),
12524
+ /**
12525
+ * SQL-level column projection — MUST mirror `settings-store.query`.
12526
+ *
12527
+ * This is the second schema the same call passes through, and Zod
12528
+ * objects STRIP what they do not declare: a field present on the door
12529
+ * and absent here does not error, it silently disappears one hop before
12530
+ * the engine, and the caller concludes the projection does nothing.
12531
+ * `data-door-schema-parity.spec.ts` is what keeps the two in step.
12532
+ */
12533
+ columns: array(string()).readonly().optional()
12544
12534
  }), array(SettingsRecordSchema).readonly()), method(object({
12545
12535
  namespace: string().optional(),
12546
12536
  collection: string(),
@@ -24184,7 +24174,27 @@ var MediaFileLiteSchema$1 = object({
24184
24174
  sizeBytes: number(),
24185
24175
  timestamp: number()
24186
24176
  });
24187
- method(_void(), array(IdentitySchema).readonly()), method(object({ name: string().min(1) }), IdentitySchema, {
24177
+ method(object({
24178
+ /**
24179
+ * Inline {@link IdentitySchema.coverBase64} on every row.
24180
+ *
24181
+ * Default `false`, the same inversion `listRecentFaces` and
24182
+ * `listPlates` took on 2026-08-25 (see `include-crops-default.ts` for
24183
+ * why the burden belongs on the caller that WANTS the bytes). Measured
24184
+ * on the live hub the same day: four identities cost 40,979 B with the
24185
+ * covers inline, ~10 KB of base64 per row, on a query this UI mounts
24186
+ * four times and the viewer holds at `staleTime: 30_000`.
24187
+ *
24188
+ * Nothing loses its avatar: `coverMediaKey` is already on every row and
24189
+ * the `event-media` plane serves that key `immutable` with an ETag.
24190
+ *
24191
+ * **This is an INPUT field, so it does not reach the addon until the
24192
+ * next train** — the hub router validates cap inputs against its own
24193
+ * compiled Zod and strips a key it does not know. Until then the
24194
+ * provider sees `undefined`, which resolves to `false`: the cheap shape
24195
+ * is what ships, and the opt-in becomes reachable when the train lands.
24196
+ */
24197
+ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly()), method(object({ name: string().min(1) }), IdentitySchema, {
24188
24198
  kind: "mutation",
24189
24199
  auth: "admin"
24190
24200
  }), method(object({
@@ -26395,7 +26405,13 @@ method(object({
26395
26405
  }), method(object({ plateId: string() }), _void(), {
26396
26406
  kind: "mutation",
26397
26407
  auth: "admin"
26398
- }), method(_void(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
26408
+ }), method(object({
26409
+ /** Inline {@link VehicleSchema.coverBase64} on every row. Default
26410
+ * `false` — the vehicle twin of `faceGallery.listIdentities`'s
26411
+ * option; `coverMediaKey` + the `event-media` plane carry the picture.
26412
+ * INPUT field: stripped by the hub router until the train ships, which
26413
+ * resolves to `false` and is exactly the intended default. */
26414
+ includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
26399
26415
  kind: "mutation",
26400
26416
  auth: "admin"
26401
26417
  }), method(object({
@@ -1,4 +1,4 @@
1
- import { K as hfModelUrl, a as COCO_80_LABELS, j as audioKindId, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-HLAOw6Pe.mjs";
1
+ import { K as hfModelUrl, a as COCO_80_LABELS, j as audioKindId, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-CMio-1qq.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { PerformanceObserver } from "node:perf_hooks";
4
4
  //#region src/detection-pipeline/registry/model-catalogs.ts
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-BZUXb5S5.js");
1
+ const require_dist = require("./dist-D9x2_csL.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  let node_perf_hooks = require("node:perf_hooks");
4
4
  //#region src/detection-pipeline/registry/model-catalogs.ts
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-BZUXb5S5.js");
1
+ const require_dist = require("./dist-D9x2_csL.js");
2
2
  let node_url = require("node:url");
3
3
  let node_module = require("node:module");
4
4
  let node_path = require("node:path");