@camstack/system 1.2.320 → 1.2.322

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 (61) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/camera-grid/addon.d.ts +13 -1
  12. package/dist/builtins/camera-grid/index.js +46 -12
  13. package/dist/builtins/camera-grid/index.mjs +46 -12
  14. package/dist/builtins/composer/composer.addon.js +2 -2
  15. package/dist/builtins/composer/composer.addon.mjs +2 -2
  16. package/dist/builtins/console-logging/index.js +1 -1
  17. package/dist/builtins/console-logging/index.mjs +1 -1
  18. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  19. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  20. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  21. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  23. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  24. package/dist/builtins/hub-forwarder/index.js +1 -1
  25. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  27. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  28. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  29. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  30. package/dist/builtins/local-network/local-network.addon.js +1 -1
  31. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  32. package/dist/builtins/loki-logging/index.js +1 -1
  33. package/dist/builtins/loki-logging/index.mjs +1 -1
  34. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  35. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  36. package/dist/builtins/platform-probe/index.js +1 -1
  37. package/dist/builtins/platform-probe/index.mjs +1 -1
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  39. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  40. package/dist/builtins/snapshot/index.js +18 -5
  41. package/dist/builtins/snapshot/index.mjs +18 -5
  42. package/dist/builtins/snapshot/snapshot.addon.d.ts +3 -0
  43. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  44. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  45. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  46. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  47. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  48. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  49. package/dist/builtins/system-config/system-config.addon.js +1 -1
  50. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  51. package/dist/builtins/winston-logging/index.js +1 -1
  52. package/dist/builtins/winston-logging/index.mjs +1 -1
  53. package/dist/{composition-sources-D4UkcIhl.js → composition-sources-CB_xQieM.js} +1 -1
  54. package/dist/{composition-sources-Due6vto8.mjs → composition-sources-RzhpNlEu.mjs} +1 -1
  55. package/dist/{dist-B0AvBZxe.js → dist-D25eq33W.js} +94 -2
  56. package/dist/{dist-XfFiGX64.mjs → dist-DENeGKrn.mjs} +89 -3
  57. package/dist/index.js +1 -1
  58. package/dist/index.mjs +1 -1
  59. package/dist/{retired-settings-keys-AmO1MZks.js → retired-settings-keys-BH9LRNsg.js} +1 -1
  60. package/dist/{retired-settings-keys-DXI77cc8.mjs → retired-settings-keys-iFdgpbfM.mjs} +1 -1
  61. package/package.json +1 -1
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, R as addonPagesCapability, Wn as errMsg, hr as EventCategory } from "../../dist-XfFiGX64.mjs";
1
+ import { Gn as errMsg, Kn as BaseAddon, R as addonPagesCapability, gr as EventCategory } from "../../dist-DENeGKrn.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { B as addonWidgetsCapability, Gn as BaseAddon, Wn as errMsg, hr as EventCategory } from "../../dist-XfFiGX64.mjs";
1
+ import { B as addonWidgetsCapability, Gn as errMsg, Kn as BaseAddon, gr as EventCategory } from "../../dist-DENeGKrn.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  //#region src/builtins/alerts/prune-policy.ts
8
8
  /**
9
9
  * Ids older than `cutoffMs`, oldest first.
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, V as alertsCapability, Wn as errMsg, hr as EventCategory, n as AlertSchema, tr as createEvent } from "../../dist-XfFiGX64.mjs";
1
+ import { Gn as errMsg, Kn as BaseAddon, V as alertsCapability, gr as EventCategory, n as AlertSchema, nr as createEvent } from "../../dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/alerts/prune-policy.ts
3
3
  /**
4
4
  * Ids older than `cutoffMs`, oldest first.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  //#region src/builtins/autotrack/autotrack-config.ts
8
8
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
9
9
  deadZoneFraction: .5,
@@ -1,4 +1,4 @@
1
- import { $t as ptzAutotrackCapability, Gn as BaseAddon, W as bareAddonId, Wn as errMsg, _ as FIRST_LEVEL_MACRO_CLASSES, hr as EventCategory, sr as isEvent } from "../../dist-XfFiGX64.mjs";
1
+ import { Gn as errMsg, Kn as BaseAddon, W as bareAddonId, _ as FIRST_LEVEL_MACRO_CLASSES, cr as isEvent, en as ptzAutotrackCapability, gr as EventCategory } from "../../dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/autotrack/autotrack-config.ts
3
3
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
4
4
  deadZoneFraction: .5,
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let buffer = require("buffer");
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Ht as mayWriteToLocation, U as backupCapability, hr as EventCategory, mn as storageOccupancyCapability } from "../../dist-XfFiGX64.mjs";
1
+ import { Ht as mayWriteToLocation, Kn as BaseAddon, U as backupCapability, gr as EventCategory, hn as storageOccupancyCapability } from "../../dist-DENeGKrn.mjs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import { Buffer as Buffer$1 } from "buffer";
4
4
  import * as fsp from "node:fs/promises";
@@ -26,7 +26,13 @@ export declare class CameraGridAddon extends BaseAddon<CameraGridAddonConfig> {
26
26
  private activity;
27
27
  private snapshots;
28
28
  private frameSampler;
29
+ /** The node's pinned ffmpeg (D540). `null` until init resolved it — a
30
+ * composition then refuses by name; there is no PATH default. */
29
31
  private ffmpegBinaryPath;
32
+ /** The node's pinned build, remembered only on success (D540). */
33
+ private readonly pinnedFfmpeg;
34
+ /** A resolution in flight, so a reconcile pass never starts a second. */
35
+ private ffmpegResolving;
30
36
  private integrationId;
31
37
  constructor();
32
38
  protected onInitialize(): Promise<AddonInitResult>;
@@ -147,7 +153,13 @@ export declare class CameraGridAddon extends BaseAddon<CameraGridAddonConfig> {
147
153
  private instanceForDevice;
148
154
  private gridViewFor;
149
155
  private saveGridLayout;
150
- private resolveFfmpegBinaryPath;
156
+ /**
157
+ * Resolve the pinned build, single-flighted, and hand it to the frame sampler.
158
+ * Never throws: a failure is an ERROR and leaves the binary unknown, so the
159
+ * next pass that wants a composition asks again. Never PATH: the host's
160
+ * ffmpeg is not ours (`packages/types/src/deps/ffmpeg-binary-source.ts`).
161
+ */
162
+ private resolveFfmpeg;
151
163
  /**
152
164
  * The integration page: a list of grids, with a NAME.
153
165
  *
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  let zod = require("zod");
8
8
  let _camstack_types_node = require("@camstack/types/node");
9
9
  let node_path = require("node:path");
@@ -2982,7 +2982,13 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
2982
2982
  activity = null;
2983
2983
  snapshots = null;
2984
2984
  frameSampler = null;
2985
- ffmpegBinaryPath = "ffmpeg";
2985
+ /** The node's pinned ffmpeg (D540). `null` until init resolved it — a
2986
+ * composition then refuses by name; there is no PATH default. */
2987
+ ffmpegBinaryPath = null;
2988
+ /** The node's pinned build, remembered only on success (D540). */
2989
+ pinnedFfmpeg = require_dist.memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
2990
+ /** A resolution in flight, so a reconcile pass never starts a second. */
2991
+ ffmpegResolving = null;
2986
2992
  integrationId;
2987
2993
  constructor() {
2988
2994
  super({ ...DEFAULTS });
@@ -2991,17 +2997,13 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
2991
2997
  this.replaceTombstones(this.config.cameraGridTombstones);
2992
2998
  await this.normalizeRows();
2993
2999
  if (require_dist.declarationOwnerNodeId(this.ctx.kernel.localNodeId) === "hub") {
2994
- this.ffmpegBinaryPath = await this.resolveFfmpegBinaryPath();
3000
+ await this.resolveFfmpeg();
2995
3001
  const relay = new GridStreamRelay({
2996
3002
  logger: this.ctx.logger.child("relay"),
2997
3003
  sessionFor: (request) => this.sessionFor(request)
2998
3004
  });
2999
3005
  await relay.start();
3000
3006
  this.relay = relay;
3001
- this.frameSampler = new GridFrameSampler({
3002
- binaryPath: this.ffmpegBinaryPath,
3003
- logger: this.ctx.logger.child("snapshot")
3004
- });
3005
3007
  this.snapshots = new GridSnapshotSource({
3006
3008
  logger: this.ctx.logger.child("snapshot"),
3007
3009
  sessionsFor: (deviceId) => this.sessionsForDevice(deviceId),
@@ -3341,7 +3343,19 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
3341
3343
  continue;
3342
3344
  }
3343
3345
  if (this.sessions.has(key)) continue;
3344
- this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile)));
3346
+ const binaryPath = this.ffmpegBinaryPath;
3347
+ if (binaryPath === null) {
3348
+ this.ctx.logger.error("camera-grid: composition refused — no pinned ffmpeg on this node (D540)", {
3349
+ tags: { deviceId },
3350
+ meta: {
3351
+ instanceId,
3352
+ profile
3353
+ }
3354
+ });
3355
+ this.resolveFfmpeg();
3356
+ continue;
3357
+ }
3358
+ this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile, binaryPath)));
3345
3359
  }
3346
3360
  }
3347
3361
  /**
@@ -3371,9 +3385,9 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
3371
3385
  }
3372
3386
  return names;
3373
3387
  }
3374
- makeSession(instanceId, deviceId, profile) {
3388
+ makeSession(instanceId, deviceId, profile, binaryPath) {
3375
3389
  const childContext = {
3376
- binaryPath: this.ffmpegBinaryPath,
3390
+ binaryPath,
3377
3391
  logger: this.ctx.logger,
3378
3392
  deviceId,
3379
3393
  resolveSourceNames: (deviceIds) => this.sourceNames(deviceIds)
@@ -3590,8 +3604,28 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
3590
3604
  if (!view) throw new Error("camera-grid: the grid vanished while it was being saved");
3591
3605
  return view;
3592
3606
  }
3593
- async resolveFfmpegBinaryPath() {
3594
- return await this.ctx.deps.ensureFfmpeg();
3607
+ /**
3608
+ * Resolve the pinned build, single-flighted, and hand it to the frame sampler.
3609
+ * Never throws: a failure is an ERROR and leaves the binary unknown, so the
3610
+ * next pass that wants a composition asks again. Never PATH: the host's
3611
+ * ffmpeg is not ours (`packages/types/src/deps/ffmpeg-binary-source.ts`).
3612
+ */
3613
+ resolveFfmpeg() {
3614
+ if (this.ffmpegBinaryPath !== null) return Promise.resolve();
3615
+ if (this.ffmpegResolving !== null) return this.ffmpegResolving;
3616
+ const attempt = this.pinnedFfmpeg().then((binaryPath) => {
3617
+ this.ffmpegBinaryPath = binaryPath;
3618
+ this.frameSampler = new GridFrameSampler({
3619
+ binaryPath,
3620
+ logger: this.ctx.logger.child("snapshot")
3621
+ });
3622
+ }).catch((err) => {
3623
+ this.ctx.logger.error("camera-grid: the pinned ffmpeg could not be resolved — compositions refuse until it is (D540)", { meta: { error: require_dist.errMsg(err) } });
3624
+ }).finally(() => {
3625
+ this.ffmpegResolving = null;
3626
+ });
3627
+ this.ffmpegResolving = attempt;
3628
+ return attempt;
3595
3629
  }
3596
3630
  /**
3597
3631
  * The integration page: a list of grids, with a NAME.
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Gt as motionCapability, J as cameraGridLayoutCapability, T as MOTION_CLOSE_AFTER_MS, Un as DeviceType, Vn as DeviceFeature, Wn as errMsg, ar as hydrateSchema, dn as snapshotCapability, gn as streamCatalogCapability, h as DeclaredDevices, hr as EventCategory, i as BaseDevice, kn as buildFfmpegArgs, m as DERIVED_CAMERA_SILENCED_CAP_NAMES, ot as declarationOwnerNodeId, tr as createEvent } from "../../dist-XfFiGX64.mjs";
1
+ import { An as buildFfmpegArgs, Gn as errMsg, Hn as DeviceFeature, J as cameraGridLayoutCapability, Kn as BaseAddon, Kt as motionCapability, T as MOTION_CLOSE_AFTER_MS, Ut as memoizePinnedFfmpeg, Wn as DeviceType, _n as streamCatalogCapability, fn as snapshotCapability, gr as EventCategory, h as DeclaredDevices, i as BaseDevice, m as DERIVED_CAMERA_SILENCED_CAP_NAMES, nr as createEvent, or as hydrateSchema, ot as declarationOwnerNodeId } from "../../dist-DENeGKrn.mjs";
2
2
  import { z } from "zod";
3
3
  import { FfmpegProcess } from "@camstack/types/node";
4
4
  import { join } from "node:path";
@@ -2977,7 +2977,13 @@ var CameraGridAddon = class extends BaseAddon {
2977
2977
  activity = null;
2978
2978
  snapshots = null;
2979
2979
  frameSampler = null;
2980
- ffmpegBinaryPath = "ffmpeg";
2980
+ /** The node's pinned ffmpeg (D540). `null` until init resolved it — a
2981
+ * composition then refuses by name; there is no PATH default. */
2982
+ ffmpegBinaryPath = null;
2983
+ /** The node's pinned build, remembered only on success (D540). */
2984
+ pinnedFfmpeg = memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
2985
+ /** A resolution in flight, so a reconcile pass never starts a second. */
2986
+ ffmpegResolving = null;
2981
2987
  integrationId;
2982
2988
  constructor() {
2983
2989
  super({ ...DEFAULTS });
@@ -2986,17 +2992,13 @@ var CameraGridAddon = class extends BaseAddon {
2986
2992
  this.replaceTombstones(this.config.cameraGridTombstones);
2987
2993
  await this.normalizeRows();
2988
2994
  if (declarationOwnerNodeId(this.ctx.kernel.localNodeId) === "hub") {
2989
- this.ffmpegBinaryPath = await this.resolveFfmpegBinaryPath();
2995
+ await this.resolveFfmpeg();
2990
2996
  const relay = new GridStreamRelay({
2991
2997
  logger: this.ctx.logger.child("relay"),
2992
2998
  sessionFor: (request) => this.sessionFor(request)
2993
2999
  });
2994
3000
  await relay.start();
2995
3001
  this.relay = relay;
2996
- this.frameSampler = new GridFrameSampler({
2997
- binaryPath: this.ffmpegBinaryPath,
2998
- logger: this.ctx.logger.child("snapshot")
2999
- });
3000
3002
  this.snapshots = new GridSnapshotSource({
3001
3003
  logger: this.ctx.logger.child("snapshot"),
3002
3004
  sessionsFor: (deviceId) => this.sessionsForDevice(deviceId),
@@ -3336,7 +3338,19 @@ var CameraGridAddon = class extends BaseAddon {
3336
3338
  continue;
3337
3339
  }
3338
3340
  if (this.sessions.has(key)) continue;
3339
- this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile)));
3341
+ const binaryPath = this.ffmpegBinaryPath;
3342
+ if (binaryPath === null) {
3343
+ this.ctx.logger.error("camera-grid: composition refused — no pinned ffmpeg on this node (D540)", {
3344
+ tags: { deviceId },
3345
+ meta: {
3346
+ instanceId,
3347
+ profile
3348
+ }
3349
+ });
3350
+ this.resolveFfmpeg();
3351
+ continue;
3352
+ }
3353
+ this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile, binaryPath)));
3340
3354
  }
3341
3355
  }
3342
3356
  /**
@@ -3366,9 +3380,9 @@ var CameraGridAddon = class extends BaseAddon {
3366
3380
  }
3367
3381
  return names;
3368
3382
  }
3369
- makeSession(instanceId, deviceId, profile) {
3383
+ makeSession(instanceId, deviceId, profile, binaryPath) {
3370
3384
  const childContext = {
3371
- binaryPath: this.ffmpegBinaryPath,
3385
+ binaryPath,
3372
3386
  logger: this.ctx.logger,
3373
3387
  deviceId,
3374
3388
  resolveSourceNames: (deviceIds) => this.sourceNames(deviceIds)
@@ -3585,8 +3599,28 @@ var CameraGridAddon = class extends BaseAddon {
3585
3599
  if (!view) throw new Error("camera-grid: the grid vanished while it was being saved");
3586
3600
  return view;
3587
3601
  }
3588
- async resolveFfmpegBinaryPath() {
3589
- return await this.ctx.deps.ensureFfmpeg();
3602
+ /**
3603
+ * Resolve the pinned build, single-flighted, and hand it to the frame sampler.
3604
+ * Never throws: a failure is an ERROR and leaves the binary unknown, so the
3605
+ * next pass that wants a composition asks again. Never PATH: the host's
3606
+ * ffmpeg is not ours (`packages/types/src/deps/ffmpeg-binary-source.ts`).
3607
+ */
3608
+ resolveFfmpeg() {
3609
+ if (this.ffmpegBinaryPath !== null) return Promise.resolve();
3610
+ if (this.ffmpegResolving !== null) return this.ffmpegResolving;
3611
+ const attempt = this.pinnedFfmpeg().then((binaryPath) => {
3612
+ this.ffmpegBinaryPath = binaryPath;
3613
+ this.frameSampler = new GridFrameSampler({
3614
+ binaryPath,
3615
+ logger: this.ctx.logger.child("snapshot")
3616
+ });
3617
+ }).catch((err) => {
3618
+ this.ctx.logger.error("camera-grid: the pinned ffmpeg could not be resolved — compositions refuse until it is (D540)", { meta: { error: errMsg(err) } });
3619
+ }).finally(() => {
3620
+ this.ffmpegResolving = null;
3621
+ });
3622
+ this.ffmpegResolving = attempt;
3623
+ return attempt;
3590
3624
  }
3591
3625
  /**
3592
3626
  * The integration page: a list of grids, with a NAME.
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_device_state_changed = require("../../device-state-changed-Bk7cvTnz.js");
8
- const require_composition_sources = require("../../composition-sources-D4UkcIhl.js");
8
+ const require_composition_sources = require("../../composition-sources-CB_xQieM.js");
9
9
  //#region src/builtins/composer/composed-device.ts
10
10
  /**
11
11
  * A composed device: an ordinary `BaseDevice` whose caps are served by the
@@ -1,6 +1,6 @@
1
- import { An as cloneStatefulExpressionMemory, Bn as toExpressionValue, Fn as compositionSliceKey, Gn as BaseAddon, In as compositionSourceKey, K as buildCapabilityLookup, Ln as createStatefulExpressionMemory, Mn as composedDeviceStableId, Nn as compositionFeatureEnabled, Pn as compositionItemFieldPath, Un as DeviceType, Wn as errMsg, X as composerCapability, Xt as planComposition, Z as compositionFieldKey, c as COMPOSED_ITEM_FIXED_FIELDS, h as DeclaredDevices, hr as EventCategory, i as BaseDevice, jn as composedDeviceRef, t as ALL_CAPABILITY_DEFINITIONS, tr as createEvent, vt as evaluateCompositionField, wn as COMPOSER_ADDON_ID } from "../../dist-XfFiGX64.mjs";
1
+ import { Fn as compositionItemFieldPath, Gn as errMsg, In as compositionSliceKey, K as buildCapabilityLookup, Kn as BaseAddon, Ln as compositionSourceKey, Mn as composedDeviceRef, Nn as composedDeviceStableId, Pn as compositionFeatureEnabled, Rn as createStatefulExpressionMemory, Tn as COMPOSER_ADDON_ID, Vn as toExpressionValue, Wn as DeviceType, X as composerCapability, Z as compositionFieldKey, Zt as planComposition, c as COMPOSED_ITEM_FIXED_FIELDS, gr as EventCategory, h as DeclaredDevices, i as BaseDevice, jn as cloneStatefulExpressionMemory, nr as createEvent, t as ALL_CAPABILITY_DEFINITIONS, vt as evaluateCompositionField } from "../../dist-DENeGKrn.mjs";
2
2
  import { n as parseDeviceStateChanged, t as isRecord$2 } from "../../device-state-changed-DoBbkyEI.mjs";
3
- import { n as normalizedNativeOwner } from "../../composition-sources-Due6vto8.mjs";
3
+ import { n as normalizedNativeOwner } from "../../composition-sources-RzhpNlEu.mjs";
4
4
  import { z } from "zod";
5
5
  //#region src/builtins/composer/composed-device.ts
6
6
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Rt as logDestinationCapability } from "../../dist-XfFiGX64.mjs";
1
+ import { Kn as BaseAddon, Rt as logDestinationCapability } from "../../dist-DENeGKrn.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-B0AvBZxe.js");
4
- const require_composition_sources = require("../../composition-sources-D4UkcIhl.js");
3
+ const require_dist = require("../../dist-D25eq33W.js");
4
+ const require_composition_sources = require("../../composition-sources-CB_xQieM.js");
5
5
  let node_crypto = require("node:crypto");
6
6
  let _camstack_types_node = require("@camstack/types/node");
7
7
  let node_fs_promises = require("node:fs/promises");
@@ -1,5 +1,5 @@
1
- import { $ as coreBlockAddonId, Gn as BaseAddon, In as compositionSourceKey, K as buildCapabilityLookup, Q as compositionSourceRefs, Wn as errMsg, Xt as planComposition, d as CORE_BLOCK_ADDON_PREFIX, et as coreBlockComposition, f as CoreBlockSchema, h as DeclaredDevices, jn as composedDeviceRef, l as COMPOSER_RUNNER_ID, nt as coreBlockSourceKind, rt as coreBlocksCapability, t as ALL_CAPABILITY_DEFINITIONS, tn as replacedFieldKeys, tt as coreBlockIdFromAddonId, u as CORE_BLOCKS_ADDON_ID, yn as validateComposition } from "../../dist-XfFiGX64.mjs";
2
- import { t as describeCompositionSources } from "../../composition-sources-Due6vto8.mjs";
1
+ import { $ as coreBlockAddonId, Gn as errMsg, K as buildCapabilityLookup, Kn as BaseAddon, Ln as compositionSourceKey, Mn as composedDeviceRef, Q as compositionSourceRefs, Zt as planComposition, bn as validateComposition, d as CORE_BLOCK_ADDON_PREFIX, et as coreBlockComposition, f as CoreBlockSchema, h as DeclaredDevices, l as COMPOSER_RUNNER_ID, nn as replacedFieldKeys, nt as coreBlockSourceKind, rt as coreBlocksCapability, t as ALL_CAPABILITY_DEFINITIONS, tt as coreBlockIdFromAddonId, u as CORE_BLOCKS_ADDON_ID } from "../../dist-DENeGKrn.mjs";
2
+ import { t as describeCompositionSources } from "../../composition-sources-RzhpNlEu.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { customizationBlockName } from "@camstack/types/node";
5
5
  import * as fs from "node:fs/promises";
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
7
- const require_retired_settings_keys = require("../../retired-settings-keys-AmO1MZks.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
+ const require_retired_settings_keys = require("../../retired-settings-keys-BH9LRNsg.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
10
10
  let _camstack_types_node = require("@camstack/types/node");
@@ -1,5 +1,5 @@
1
- import { Gn as BaseAddon, Hn as DeviceRole, Kt as normalizeUnit, Lt as locationIconKey, Nt as legacyAutoLinkIds, S as LocationIconIdSchema, Un as DeviceType, Vn as DeviceFeature, Wn as errMsg, Yt as parseStreamParamsFormPatch, Zn as WELL_KNOWN_TAB_MAP, _t as enumerateSchemaFields, an as runtimeStatePolicyFor, bt as expandContainer, ct as defineExtensionSlotProvider, dt as deviceManagerCapability, ft as deviceStateCapability, g as DeviceStatusSchema, gt as enumerateItemArrayFields, hr as EventCategory, it as createDeviceExtensionProvider, j as STREAM_PROFILE_META, mr as sleep$1, o as CAMERA_SWITCH_ORDER, or as isDeviceConfigCap, p as DAY_NAMES, pt as deviceStatusCapability, q as buildStreamParamsConfigSchema, s as CAP_NAMES_WITH_STATUS, t as ALL_CAPABILITY_DEFINITIONS, un as siteZoneKey, ut as deviceExtensionCapability, v as FieldClaimModeSchema, w as MINUTES_PER_DAY, wt as formatMinutes, y as FieldClaimSchema } from "../../dist-XfFiGX64.mjs";
2
- import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-DXI77cc8.mjs";
1
+ import { Gn as errMsg, Hn as DeviceFeature, Kn as BaseAddon, Lt as locationIconKey, Nt as legacyAutoLinkIds, Qn as WELL_KNOWN_TAB_MAP, S as LocationIconIdSchema, Un as DeviceRole, Wn as DeviceType, Xt as parseStreamParamsFormPatch, _t as enumerateSchemaFields, bt as expandContainer, ct as defineExtensionSlotProvider, dn as siteZoneKey, dt as deviceManagerCapability, ft as deviceStateCapability, g as DeviceStatusSchema, gr as EventCategory, gt as enumerateItemArrayFields, hr as sleep$1, it as createDeviceExtensionProvider, j as STREAM_PROFILE_META, o as CAMERA_SWITCH_ORDER, on as runtimeStatePolicyFor, p as DAY_NAMES, pt as deviceStatusCapability, q as buildStreamParamsConfigSchema, qt as normalizeUnit, s as CAP_NAMES_WITH_STATUS, sr as isDeviceConfigCap, t as ALL_CAPABILITY_DEFINITIONS, ut as deviceExtensionCapability, v as FieldClaimModeSchema, w as MINUTES_PER_DAY, wt as formatMinutes, y as FieldClaimSchema } from "../../dist-DENeGKrn.mjs";
2
+ import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-iFdgpbfM.mjs";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_device_state_changed = require("../../device-state-changed-Bk7cvTnz.js");
8
8
  let zod = require("zod");
9
9
  //#region src/builtins/doorbell/trigger-engine.ts
@@ -1,4 +1,4 @@
1
- import { At as isSameAddonId, Dn as SOURCE_CAP_ACTIVE_FIELD, En as SOURCE_CAPS, Gn as BaseAddon, On as SOURCE_CAP_CHANGED_AT_FIELD, Rn as evaluateSensorEdge, Un as DeviceType, W as bareAddonId, Wn as errMsg, ct as defineExtensionSlotProvider, hr as EventCategory, it as createDeviceExtensionProvider, mt as doorbellCapability, tr as createEvent, ut as deviceExtensionCapability, zn as isSourceCap } from "../../dist-XfFiGX64.mjs";
1
+ import { At as isSameAddonId, Bn as isSourceCap, Dn as SOURCE_CAPS, Gn as errMsg, Kn as BaseAddon, On as SOURCE_CAP_ACTIVE_FIELD, W as bareAddonId, Wn as DeviceType, ct as defineExtensionSlotProvider, gr as EventCategory, it as createDeviceExtensionProvider, kn as SOURCE_CAP_CHANGED_AT_FIELD, mt as doorbellCapability, nr as createEvent, ut as deviceExtensionCapability, zn as evaluateSensorEdge } from "../../dist-DENeGKrn.mjs";
2
2
  import { n as parseDeviceStateChanged } from "../../device-state-changed-DoBbkyEI.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/builtins/doorbell/trigger-engine.ts
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Rt as logDestinationCapability } from "../../dist-XfFiGX64.mjs";
1
+ import { Kn as BaseAddon, Rt as logDestinationCapability } from "../../dist-DENeGKrn.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-B0AvBZxe.js");
3
+ const require_dist = require("../../dist-D25eq33W.js");
4
4
  //#region src/builtins/liveness-monitor/liveness-checks.ts
5
5
  var NO_DEVICES = "liveness:no-devices";
6
6
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -1,4 +1,4 @@
1
- import { E as RECORDING_SOURCE_CAMSTACK_ADDON, Gn as BaseAddon, Wn as errMsg, hr as EventCategory, k as RecordingStatusSchema, tr as createEvent } from "../../dist-XfFiGX64.mjs";
1
+ import { E as RECORDING_SOURCE_CAMSTACK_ADDON, Gn as errMsg, Kn as BaseAddon, gr as EventCategory, k as RecordingStatusSchema, nr as createEvent } from "../../dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/liveness-monitor/liveness-checks.ts
3
3
  var NO_DEVICES = "liveness:no-devices";
4
4
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,5 +1,5 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { Gn as BaseAddon, H as authProviderCapability, L as UserRecordSchema, M as ScopedTokenSchema, r as ApiKeyRecordSchema, vn as userManagementCapability } from "../../dist-XfFiGX64.mjs";
2
+ import { H as authProviderCapability, Kn as BaseAddon, L as UserRecordSchema, M as ScopedTokenSchema, r as ApiKeyRecordSchema, yn as userManagementCapability } from "../../dist-DENeGKrn.mjs";
3
3
  import * as crypto$2 from "node:crypto";
4
4
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
5
5
  import nodeCrypto from "crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-B0AvBZxe.js");
3
+ const require_dist = require("../../dist-D25eq33W.js");
4
4
  const require_tls = require("../../tls-D_wUeOTD.js");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, It as localNetworkCapability, hr as EventCategory } from "../../dist-XfFiGX64.mjs";
1
+ import { It as localNetworkCapability, Kn as BaseAddon, gr as EventCategory } from "../../dist-DENeGKrn.mjs";
2
2
  import { S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, i as writeExtraSans, l as reissueTlsLeaf, n as readTlsAccessStatus, o as validateUploadedTls, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, y as applyLanHttp } from "../../tls-IM-v7JV4.mjs";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Rt as logDestinationCapability } from "../../dist-XfFiGX64.mjs";
1
+ import { Kn as BaseAddon, Rt as logDestinationCapability } from "../../dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_proc_stat = require("../../proc-stat-DL-WUOhl.js");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_fs = require("node:fs");
@@ -1,4 +1,4 @@
1
- import { Ct as foldSnapshotByFunction, Gn as BaseAddon, Ut as metricsProviderCapability, Wn as errMsg, en as reducePoints, hr as EventCategory, nn as resolveBucketMs, tr as createEvent } from "../../dist-XfFiGX64.mjs";
1
+ import { Ct as foldSnapshotByFunction, Gn as errMsg, Kn as BaseAddon, Wt as metricsProviderCapability, gr as EventCategory, nr as createEvent, rn as resolveBucketMs, tn as reducePoints } from "../../dist-DENeGKrn.mjs";
2
2
  import { a as parseProcStat, i as nodeProcReader, n as elapsedSecSince, o as parseProcStatusRss, r as lifetimeCpuPercent, t as commandFromCmdline } from "../../proc-stat-CbC55Kqi.mjs";
3
3
  import { access, readFile, readdir } from "node:fs/promises";
4
4
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { Gn as BaseAddon, Wn as errMsg, Zt as platformProbeCapability, cn as scoreRuntimes, hr as EventCategory, ht as enumerateInferenceDevices, rr as emitReadiness } from "../../dist-XfFiGX64.mjs";
2
+ import { Gn as errMsg, Kn as BaseAddon, Qt as platformProbeCapability, gr as EventCategory, ht as enumerateInferenceDevices, ir as emitReadiness, ln as scoreRuntimes } from "../../dist-DENeGKrn.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { execFile } from "node:child_process";
5
5
  import * as os from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, hr as EventCategory } from "../../dist-XfFiGX64.mjs";
1
+ import { Kn as BaseAddon, gr as EventCategory } from "../../dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_snapshot_courtesy = require("../../snapshot-courtesy-Cqs7GkOV.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1135,6 +1135,9 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
1135
1135
  captureFlight = new SingleFlight(COALESCE_MS, (outcome) => outcome.ok && outcome.image !== null);
1136
1136
  /** Bounds simultaneous ffmpeg keyframe grabs (the wrapper path — common case). */
1137
1137
  grabPool = new Semaphore(6);
1138
+ /** This node's PINNED ffmpeg (D540), asked once. The keyframe grab used to
1139
+ * spawn a bare `'ffmpeg'` — the HOST's binary — off PATH. */
1140
+ pinnedFfmpeg = require_dist.memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
1138
1141
  /** Bounds simultaneous native (vendor HTTP/ONVIF) snapshot fetches. */
1139
1142
  nativePool = new Semaphore(6);
1140
1143
  /**
@@ -2088,14 +2091,24 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
2088
2091
  * "first keyframe" when a suspended broker resumes.
2089
2092
  */
2090
2093
  async runGrabWithResumeRetry(url, deviceId) {
2094
+ let ffmpegPath;
2095
+ try {
2096
+ ffmpegPath = await this.pinnedFfmpeg();
2097
+ } catch (err) {
2098
+ this.ctx.logger.error("grabFrame refused — no pinned ffmpeg on this node (D540)", {
2099
+ tags: { deviceId },
2100
+ meta: { error: require_dist.errMsg(err) }
2101
+ });
2102
+ throw new Error(`grabFrame: no pinned ffmpeg for device ${deviceId} — ${require_dist.errMsg(err)}`, { cause: err });
2103
+ }
2091
2104
  let buf;
2092
2105
  try {
2093
- buf = await this.grabPool.run(() => runFfmpegFrameGrab(url, 15e3));
2106
+ buf = await this.grabPool.run(() => runFfmpegFrameGrab(ffmpegPath, url, 15e3));
2094
2107
  } catch (err) {
2095
2108
  if (isBrokerColdError(require_dist.errMsg(err))) {
2096
2109
  this.ctx.logger.debug("grabFrame: broker-resume race — retrying in 1500ms", { tags: { deviceId } });
2097
2110
  await new Promise((r) => setTimeout(r, 1500));
2098
- buf = await this.grabPool.run(() => runFfmpegFrameGrab(url, 15e3));
2111
+ buf = await this.grabPool.run(() => runFfmpegFrameGrab(ffmpegPath, url, 15e3));
2099
2112
  } else throw err;
2100
2113
  }
2101
2114
  if (buf.length === 0) return null;
@@ -2642,9 +2655,9 @@ function qualityRank(brokerId, prefix) {
2642
2655
  function isBrokerColdError(msg) {
2643
2656
  return msg.includes("Invalid data found when processing input") || msg.includes("Error opening input") || msg.includes("Connection refused") || msg.includes("No route to host");
2644
2657
  }
2645
- function runFfmpegFrameGrab(url, timeoutMs) {
2658
+ function runFfmpegFrameGrab(ffmpegPath, url, timeoutMs) {
2646
2659
  return new Promise((resolve, reject) => {
2647
- (0, node_child_process.execFile)("ffmpeg", [
2660
+ (0, node_child_process.execFile)(ffmpegPath, [
2648
2661
  "-loglevel",
2649
2662
  "error",
2650
2663
  "-threads",
@@ -1,4 +1,4 @@
1
- import { G as batteryCapability, Gn as BaseAddon, Un as DeviceType, Vn as DeviceFeature, W as bareAddonId, Wn as errMsg, _n as streamQualityLabel, a as BatteryStatusSchema, cr as nodePin, dn as snapshotCapability, hr as EventCategory, lt as deriveBatteryPresence } from "../../dist-XfFiGX64.mjs";
1
+ import { G as batteryCapability, Gn as errMsg, Hn as DeviceFeature, Kn as BaseAddon, Ut as memoizePinnedFfmpeg, W as bareAddonId, Wn as DeviceType, a as BatteryStatusSchema, fn as snapshotCapability, gr as EventCategory, lr as nodePin, lt as deriveBatteryPresence, vn as streamQualityLabel } from "../../dist-DENeGKrn.mjs";
2
2
  import { i as renderCourtesyOverlayJpeg, n as courtesyCacheKey, r as renderCourtesyJpeg } from "../../snapshot-courtesy-CZPvteq2.mjs";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -1129,6 +1129,9 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
1129
1129
  captureFlight = new SingleFlight(COALESCE_MS, (outcome) => outcome.ok && outcome.image !== null);
1130
1130
  /** Bounds simultaneous ffmpeg keyframe grabs (the wrapper path — common case). */
1131
1131
  grabPool = new Semaphore(6);
1132
+ /** This node's PINNED ffmpeg (D540), asked once. The keyframe grab used to
1133
+ * spawn a bare `'ffmpeg'` — the HOST's binary — off PATH. */
1134
+ pinnedFfmpeg = memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
1132
1135
  /** Bounds simultaneous native (vendor HTTP/ONVIF) snapshot fetches. */
1133
1136
  nativePool = new Semaphore(6);
1134
1137
  /**
@@ -2082,14 +2085,24 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
2082
2085
  * "first keyframe" when a suspended broker resumes.
2083
2086
  */
2084
2087
  async runGrabWithResumeRetry(url, deviceId) {
2088
+ let ffmpegPath;
2089
+ try {
2090
+ ffmpegPath = await this.pinnedFfmpeg();
2091
+ } catch (err) {
2092
+ this.ctx.logger.error("grabFrame refused — no pinned ffmpeg on this node (D540)", {
2093
+ tags: { deviceId },
2094
+ meta: { error: errMsg(err) }
2095
+ });
2096
+ throw new Error(`grabFrame: no pinned ffmpeg for device ${deviceId} — ${errMsg(err)}`, { cause: err });
2097
+ }
2085
2098
  let buf;
2086
2099
  try {
2087
- buf = await this.grabPool.run(() => runFfmpegFrameGrab(url, 15e3));
2100
+ buf = await this.grabPool.run(() => runFfmpegFrameGrab(ffmpegPath, url, 15e3));
2088
2101
  } catch (err) {
2089
2102
  if (isBrokerColdError(errMsg(err))) {
2090
2103
  this.ctx.logger.debug("grabFrame: broker-resume race — retrying in 1500ms", { tags: { deviceId } });
2091
2104
  await new Promise((r) => setTimeout(r, 1500));
2092
- buf = await this.grabPool.run(() => runFfmpegFrameGrab(url, 15e3));
2105
+ buf = await this.grabPool.run(() => runFfmpegFrameGrab(ffmpegPath, url, 15e3));
2093
2106
  } else throw err;
2094
2107
  }
2095
2108
  if (buf.length === 0) return null;
@@ -2636,9 +2649,9 @@ function qualityRank(brokerId, prefix) {
2636
2649
  function isBrokerColdError(msg) {
2637
2650
  return msg.includes("Invalid data found when processing input") || msg.includes("Error opening input") || msg.includes("Connection refused") || msg.includes("No route to host");
2638
2651
  }
2639
- function runFfmpegFrameGrab(url, timeoutMs) {
2652
+ function runFfmpegFrameGrab(ffmpegPath, url, timeoutMs) {
2640
2653
  return new Promise((resolve, reject) => {
2641
- execFile("ffmpeg", [
2654
+ execFile(ffmpegPath, [
2642
2655
  "-loglevel",
2643
2656
  "error",
2644
2657
  "-threads",
@@ -93,6 +93,9 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
93
93
  private readonly captureFlight;
94
94
  /** Bounds simultaneous ffmpeg keyframe grabs (the wrapper path — common case). */
95
95
  private readonly grabPool;
96
+ /** This node's PINNED ffmpeg (D540), asked once. The keyframe grab used to
97
+ * spawn a bare `'ffmpeg'` — the HOST's binary — off PATH. */
98
+ private readonly pinnedFfmpeg;
96
99
  /** Bounds simultaneous native (vendor HTTP/ONVIF) snapshot fetches. */
97
100
  private readonly nativePool;
98
101
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, St as filesystemBrowseCapability, hn as storageProviderCapability } from "../../dist-XfFiGX64.mjs";
1
+ import { Kn as BaseAddon, St as filesystemBrowseCapability, gn as storageProviderCapability } from "../../dist-DENeGKrn.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_hub_cap_forward = require("../../hub-cap-forward-DmHNjbB0.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { Cn as withLocationMode, D as REDACTED_SECRET, F as StorageLocationTypeSchema, Gn as BaseAddon, Ht as mayWriteToLocation, I as StorageMigrationJobSchema, N as StorageCleanupJobSchema, Ot as isLocationEnabled, P as StorageLocationModeSchema, Pt as legacyModeOf, Tt as gbToBytes, Vt as mayReadLocation, Wt as modeMayWrite, Y as collectSecretConfigKeys, fn as storageCapability, hr as EventCategory, ln as settingsStoreCapability, lr as parseJsonObject, pn as storageMigrationCapability, rn as resolveLocationMode, x as LegacyStorageLocationDefaultSchema, yt as evictionPolicyOfLocation } from "../../dist-XfFiGX64.mjs";
1
+ import { D as REDACTED_SECRET, F as StorageLocationTypeSchema, Gt as modeMayWrite, Ht as mayWriteToLocation, I as StorageMigrationJobSchema, Kn as BaseAddon, N as StorageCleanupJobSchema, Ot as isLocationEnabled, P as StorageLocationModeSchema, Pt as legacyModeOf, Tt as gbToBytes, Vt as mayReadLocation, Y as collectSecretConfigKeys, gr as EventCategory, in as resolveLocationMode, mn as storageMigrationCapability, pn as storageCapability, un as settingsStoreCapability, ur as parseJsonObject, wn as withLocationMode, x as LegacyStorageLocationDefaultSchema, yt as evictionPolicyOfLocation } from "../../dist-DENeGKrn.mjs";
2
2
  import { t as HUB_CAP_FWD_ACTION } from "../../hub-cap-forward-BdT320dq.mjs";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Wn as errMsg, ar as hydrateSchema } from "../../dist-XfFiGX64.mjs";
1
+ import { Gn as errMsg, Kn as BaseAddon, or as hydrateSchema } from "../../dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B0AvBZxe.js");
6
+ const require_dist = require("../../dist-D25eq33W.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Rt as logDestinationCapability } from "../../dist-XfFiGX64.mjs";
1
+ import { Kn as BaseAddon, Rt as logDestinationCapability } from "../../dist-DENeGKrn.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-B0AvBZxe.js");
1
+ const require_dist = require("./dist-D25eq33W.js");
2
2
  //#region src/builtins/core-blocks/composition-sources.ts
3
3
  /**
4
4
  * Describe what a composition reads and writes for SAVE-time validation
@@ -1,4 +1,4 @@
1
- import { In as compositionSourceKey, Wn as errMsg } from "./dist-XfFiGX64.mjs";
1
+ import { Gn as errMsg, Ln as compositionSourceKey } from "./dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/core-blocks/composition-sources.ts
3
3
  /**
4
4
  * Describe what a composition reads and writes for SAVE-time validation
@@ -3512,7 +3512,7 @@ function errMsg(err) {
3512
3512
  return String(err);
3513
3513
  }
3514
3514
  //#endregion
3515
- //#region ../types/dist/composition-BK-GTFYb.mjs
3515
+ //#region ../types/dist/composition-Dq42XWp0.mjs
3516
3516
  var AUDIO_ENCODER_BY_CODEC = {
3517
3517
  opus: "libopus",
3518
3518
  aac: "aac",
@@ -3906,6 +3906,58 @@ function buildFfmpegArgs(inv) {
3906
3906
  ];
3907
3907
  }
3908
3908
  /**
3909
+ * The ffmpeg build every CamStack node downloads, per platform and arch.
3910
+ *
3911
+ * ## Why a download, on every node, including the container
3912
+ *
3913
+ * The version of ffmpeg used to be CODE: baked into the image, changeable only
3914
+ * by rebuilding and rolling it. Now it is DATA — this file states the release,
3915
+ * and a node that boots with a different one downloads it. Changing ffmpeg is a
3916
+ * framework bump, not an image rebuild.
3917
+ *
3918
+ * It also settles the licence question by removing it. FFmpeg's licence is
3919
+ * chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
3920
+ * (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
3921
+ * makes the result undistributable. These are GPL v3 builds, and we do not
3922
+ * distribute them: the user's own node fetches the archive from the project
3923
+ * that publishes it, so we convey no GPL work and inherit none of the
3924
+ * obligations that come with conveying one. That is the whole reason the image
3925
+ * no longer installs ffmpeg at all — not a technical preference.
3926
+ *
3927
+ * ## Why jellyfin-ffmpeg
3928
+ *
3929
+ * One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
3930
+ * from one release, with the widest hardware surface of anything maintained:
3931
+ * VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
3932
+ * MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
3933
+ * so the binary is not linked against a driver stack it may not find.
3934
+ *
3935
+ * **What it still needs from the host, measured 2026-09-19.** On the Unraid
3936
+ * HOST, which has no libva, `-vaapi_device` aborts the process outright —
3937
+ * `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
3938
+ * "Assertion in generated code"' failed`. Inside our container, which installs
3939
+ * libva plus the iHD driver, the same command encodes: verified end to end with
3940
+ * `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
3941
+ * KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
3942
+ *
3943
+ * ## Why each artifact carries a checksum and a claim
3944
+ *
3945
+ * The download is now the only source, so `sha256` is verified before anything
3946
+ * is extracted — jellyfin publishes no checksums on its release assets, so
3947
+ * these are ours, taken at the version bump. And `expectedHwaccels` is what the
3948
+ * build is supposed to be able to do: a binary that probes short of its own
3949
+ * claim is a source that changed under us, which is precisely what went
3950
+ * unnoticed for months when a static build turned out to carry no VAAPI at all
3951
+ * (D536).
3952
+ */
3953
+ /** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
3954
+ var FFMPEG_RELEASE = "8.1.2-5";
3955
+ var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
3956
+ function portable(target) {
3957
+ return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
3958
+ }
3959
+ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
3960
+ /**
3909
3961
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
3910
3962
  * valutatore puro del suo FRONTE.
3911
3963
  *
@@ -5359,6 +5411,26 @@ var BASE_LIVE_EGRESS_PROFILE = {
5359
5411
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
5360
5412
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
5361
5413
  /**
5414
+ * Memoize `ensure` on SUCCESS only.
5415
+ *
5416
+ * A failure is not remembered: the next caller asks again, so a node whose
5417
+ * first download failed (no network at boot) recovers on the next spawn that
5418
+ * needs it — with no timer and no retry loop, because the ask is driven by
5419
+ * work that actually wants the binary.
5420
+ */
5421
+ function memoizePinnedFfmpeg(ensure) {
5422
+ let pending = null;
5423
+ return () => {
5424
+ if (pending !== null) return pending;
5425
+ const attempt = ensure();
5426
+ pending = attempt;
5427
+ attempt.catch(() => {
5428
+ if (pending === attempt) pending = null;
5429
+ });
5430
+ return attempt;
5431
+ };
5432
+ }
5433
+ /**
5362
5434
  * Deep wiring healthcheck — snapshot of active reachability probes across
5363
5435
  * every declared capability + widget of every installed plugin, on every
5364
5436
  * node. Produced by the backend `WiringHealthService` and surfaced via
@@ -21918,6 +21990,21 @@ var ObjectEventSchema = zod.z.object({
21918
21990
  keyFrameMediaId: zod.z.number().int().optional(),
21919
21991
  /** Populated by B5 (recording playback URL for this event). */
21920
21992
  mediaUrl: zod.z.string().optional(),
21993
+ /**
21994
+ * When the change this row records HAPPENED, epoch ms, when that is earlier
21995
+ * than {@link timestamp} and the producer could date it (D684).
21996
+ *
21997
+ * Set on a package PICK-UP row only: its `timestamp` is when the departure
21998
+ * was CONCLUDED — five minutes of unseen observed time after the parcel went
21999
+ * — and a notification's footage has to be cut around the pick-up, not the
22000
+ * empty doormat after it (D682). Stored so every path that rebuilds a
22001
+ * notification subject from the row (the boot reconcile, a dry-run) anchors
22002
+ * on the same instant the live hook did.
22003
+ *
22004
+ * `timestamp` keeps its meaning. Absent on every other row and on every
22005
+ * pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
22006
+ */
22007
+ observedAt: zod.z.number().optional(),
21921
22008
  /** The parent track's key-event importance [0,1], propagated to every object
21922
22009
  * event of the track (so an event row can be sorted by importance without a
21923
22010
  * track join). Absent on legacy rows / before the track was scored. */
@@ -43959,7 +44046,6 @@ var RUNTIME_DEFAULTS = {
43959
44046
  models: "camstack-data/models"
43960
44047
  },
43961
44048
  providers: [],
43962
- "ffmpeg.binaryPath": "ffmpeg",
43963
44049
  "ffmpeg.hwAccel": "auto",
43964
44050
  "ffmpeg.threadCount": 0,
43965
44051
  "auth.tokenExpiry": "30d"
@@ -58246,6 +58332,12 @@ Object.defineProperty(exports, "mayWriteToLocation", {
58246
58332
  return mayWriteToLocation;
58247
58333
  }
58248
58334
  });
58335
+ Object.defineProperty(exports, "memoizePinnedFfmpeg", {
58336
+ enumerable: true,
58337
+ get: function() {
58338
+ return memoizePinnedFfmpeg;
58339
+ }
58340
+ });
58249
58341
  Object.defineProperty(exports, "metricsProviderCapability", {
58250
58342
  enumerable: true,
58251
58343
  get: function() {
@@ -3512,7 +3512,7 @@ function errMsg(err) {
3512
3512
  return String(err);
3513
3513
  }
3514
3514
  //#endregion
3515
- //#region ../types/dist/composition-BK-GTFYb.mjs
3515
+ //#region ../types/dist/composition-Dq42XWp0.mjs
3516
3516
  var AUDIO_ENCODER_BY_CODEC = {
3517
3517
  opus: "libopus",
3518
3518
  aac: "aac",
@@ -3906,6 +3906,58 @@ function buildFfmpegArgs(inv) {
3906
3906
  ];
3907
3907
  }
3908
3908
  /**
3909
+ * The ffmpeg build every CamStack node downloads, per platform and arch.
3910
+ *
3911
+ * ## Why a download, on every node, including the container
3912
+ *
3913
+ * The version of ffmpeg used to be CODE: baked into the image, changeable only
3914
+ * by rebuilding and rolling it. Now it is DATA — this file states the release,
3915
+ * and a node that boots with a different one downloads it. Changing ffmpeg is a
3916
+ * framework bump, not an image rebuild.
3917
+ *
3918
+ * It also settles the licence question by removing it. FFmpeg's licence is
3919
+ * chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
3920
+ * (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
3921
+ * makes the result undistributable. These are GPL v3 builds, and we do not
3922
+ * distribute them: the user's own node fetches the archive from the project
3923
+ * that publishes it, so we convey no GPL work and inherit none of the
3924
+ * obligations that come with conveying one. That is the whole reason the image
3925
+ * no longer installs ffmpeg at all — not a technical preference.
3926
+ *
3927
+ * ## Why jellyfin-ffmpeg
3928
+ *
3929
+ * One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
3930
+ * from one release, with the widest hardware surface of anything maintained:
3931
+ * VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
3932
+ * MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
3933
+ * so the binary is not linked against a driver stack it may not find.
3934
+ *
3935
+ * **What it still needs from the host, measured 2026-09-19.** On the Unraid
3936
+ * HOST, which has no libva, `-vaapi_device` aborts the process outright —
3937
+ * `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
3938
+ * "Assertion in generated code"' failed`. Inside our container, which installs
3939
+ * libva plus the iHD driver, the same command encodes: verified end to end with
3940
+ * `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
3941
+ * KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
3942
+ *
3943
+ * ## Why each artifact carries a checksum and a claim
3944
+ *
3945
+ * The download is now the only source, so `sha256` is verified before anything
3946
+ * is extracted — jellyfin publishes no checksums on its release assets, so
3947
+ * these are ours, taken at the version bump. And `expectedHwaccels` is what the
3948
+ * build is supposed to be able to do: a binary that probes short of its own
3949
+ * claim is a source that changed under us, which is precisely what went
3950
+ * unnoticed for months when a static build turned out to carry no VAAPI at all
3951
+ * (D536).
3952
+ */
3953
+ /** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
3954
+ var FFMPEG_RELEASE = "8.1.2-5";
3955
+ var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
3956
+ function portable(target) {
3957
+ return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
3958
+ }
3959
+ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
3960
+ /**
3909
3961
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
3910
3962
  * valutatore puro del suo FRONTE.
3911
3963
  *
@@ -5359,6 +5411,26 @@ var BASE_LIVE_EGRESS_PROFILE = {
5359
5411
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
5360
5412
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
5361
5413
  /**
5414
+ * Memoize `ensure` on SUCCESS only.
5415
+ *
5416
+ * A failure is not remembered: the next caller asks again, so a node whose
5417
+ * first download failed (no network at boot) recovers on the next spawn that
5418
+ * needs it — with no timer and no retry loop, because the ask is driven by
5419
+ * work that actually wants the binary.
5420
+ */
5421
+ function memoizePinnedFfmpeg(ensure) {
5422
+ let pending = null;
5423
+ return () => {
5424
+ if (pending !== null) return pending;
5425
+ const attempt = ensure();
5426
+ pending = attempt;
5427
+ attempt.catch(() => {
5428
+ if (pending === attempt) pending = null;
5429
+ });
5430
+ return attempt;
5431
+ };
5432
+ }
5433
+ /**
5362
5434
  * Deep wiring healthcheck — snapshot of active reachability probes across
5363
5435
  * every declared capability + widget of every installed plugin, on every
5364
5436
  * node. Produced by the backend `WiringHealthService` and surfaced via
@@ -21918,6 +21990,21 @@ var ObjectEventSchema = z.object({
21918
21990
  keyFrameMediaId: z.number().int().optional(),
21919
21991
  /** Populated by B5 (recording playback URL for this event). */
21920
21992
  mediaUrl: z.string().optional(),
21993
+ /**
21994
+ * When the change this row records HAPPENED, epoch ms, when that is earlier
21995
+ * than {@link timestamp} and the producer could date it (D684).
21996
+ *
21997
+ * Set on a package PICK-UP row only: its `timestamp` is when the departure
21998
+ * was CONCLUDED — five minutes of unseen observed time after the parcel went
21999
+ * — and a notification's footage has to be cut around the pick-up, not the
22000
+ * empty doormat after it (D682). Stored so every path that rebuilds a
22001
+ * notification subject from the row (the boot reconcile, a dry-run) anchors
22002
+ * on the same instant the live hook did.
22003
+ *
22004
+ * `timestamp` keeps its meaning. Absent on every other row and on every
22005
+ * pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
22006
+ */
22007
+ observedAt: z.number().optional(),
21921
22008
  /** The parent track's key-event importance [0,1], propagated to every object
21922
22009
  * event of the track (so an event row can be sorted by importance without a
21923
22010
  * track join). Absent on legacy rows / before the track was scored. */
@@ -43959,7 +44046,6 @@ var RUNTIME_DEFAULTS = {
43959
44046
  models: "camstack-data/models"
43960
44047
  },
43961
44048
  providers: [],
43962
- "ffmpeg.binaryPath": "ffmpeg",
43963
44049
  "ffmpeg.hwAccel": "auto",
43964
44050
  "ffmpeg.threadCount": 0,
43965
44051
  "auth.tokenExpiry": "30d"
@@ -57442,4 +57528,4 @@ function enumerateInferenceDevices(hw) {
57442
57528
  return out;
57443
57529
  }
57444
57530
  //#endregion
57445
- export { coreBlockAddonId as $, asNumber as $n, ptzAutotrackCapability as $t, STORAGE_ACCESS_FALLBACK as A, cloneStatefulExpressionMemory as An, isSameAddonId as At, addonWidgetsCapability as B, toExpressionValue as Bn, looseSchema as Bt, METHOD_ACCESS_MAP as C, withLocationMode as Cn, foldSnapshotByFunction as Ct, REDACTED_SECRET as D, SOURCE_CAP_ACTIVE_FIELD as Dn, isCollectionArrayMethod as Dt, RECORDING_SOURCE_CAMSTACK_ADDON as E, SOURCE_CAPS as En, isArrayOutputSchema as Et, StorageLocationTypeSchema as F, compositionSliceKey as Fn, lifecycleJobSchema as Ft, batteryCapability as G, BaseAddon as Gn, motionCapability as Gt, authProviderCapability as H, DeviceRole as Hn, mayWriteToLocation as Ht, StorageMigrationJobSchema as I, compositionSourceKey as In, localNetworkCapability as It, cameraGridLayoutCapability as J, DEVICE_STATUS_METHOD as Jn, objectInputDeclaresRequiredDeviceId as Jt, buildCapabilityLookup as K, DATAPLANE_SECRET_HEADER as Kn, normalizeUnit as Kt, UserRecordSchema as L, createStatefulExpressionMemory as Ln, locationIconKey as Lt, ScopedTokenSchema as M, composedDeviceStableId as Mn, kebabToCamel as Mt, StorageCleanupJobSchema as N, compositionFeatureEnabled as Nn, legacyAutoLinkIds as Nt, RUNTIME_DEFAULTS as O, SOURCE_CAP_CHANGED_AT_FIELD as On, isLocationEnabled as Ot, StorageLocationModeSchema as P, compositionItemFieldPath as Pn, legacyModeOf as Pt, compositionSourceRefs as Q, asJsonObject as Qn, procedureAuthKey as Qt, addonPagesCapability as R, evaluateSensorEdge as Rn, logDestinationCapability as Rt, LocationIconIdSchema as S, vectorStoreCapability as Sn, filesystemBrowseCapability as St, MOTION_CLOSE_AFTER_MS as T, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as Tn, gbToBytes as Tt, backupCapability as U, DeviceType as Un, metricsProviderCapability as Ut, alertsCapability as V, DeviceFeature as Vn, mayReadLocation as Vt, bareAddonId as W, errMsg as Wn, modeMayWrite as Wt, composerCapability as X, ReadinessTimeoutError as Xn, planComposition as Xt, collectSecretConfigKeys as Y, ReadinessRegistry as Yn, parseStreamParamsFormPatch as Yt, compositionFieldKey as Z, WELL_KNOWN_TAB_MAP as Zn, platformProbeCapability as Zt, FIRST_LEVEL_MACRO_CLASSES as _, streamQualityLabel as _n, enumerateSchemaFields as _t, BatteryStatusSchema as a, runtimeStatePolicyFor as an, hydrateSchema as ar, dataStoreProviderCapability as at, LOG_LEVEL_RANK as b, vectorDimFromBase64 as bn, expandContainer as bt, COMPOSED_ITEM_FIXED_FIELDS as c, scoreRuntimes as cn, nodePin as cr, defineExtensionSlotProvider as ct, CORE_BLOCK_ADDON_PREFIX as d, snapshotCapability as dn, readinessKey as dr, deviceManagerCapability as dt, reducePoints as en, asString$1 as er, coreBlockComposition as et, CoreBlockSchema as f, storageCapability as fn, resolveCapMount as fr, deviceStateCapability as ft, DeviceStatusSchema as g, streamCatalogCapability as gn, enumerateItemArrayFields as gt, DeclaredDevices as h, storageProviderCapability as hn, EventCategory as hr, enumerateInferenceDevices as ht, BaseDevice as i, resolveMethodAuth as in, expandCapMethods as ir, createDeviceExtensionProvider as it, STREAM_PROFILE_META as j, composedDeviceRef as jn, isVoidInput as jt, RecordingStatusSchema as k, buildFfmpegArgs as kn, isObjectInput as kt, COMPOSER_RUNNER_ID as l, settingsStoreCapability as ln, parseJsonObject as lr, deriveBatteryPresence as lt, DERIVED_CAMERA_SILENCED_CAP_NAMES as m, storageOccupancyCapability as mn, sleep as mr, doorbellCapability as mt, AlertSchema as n, resolveBucketMs as nn, emitDownForOwnedCaps as nr, coreBlockSourceKind as nt, CAMERA_SWITCH_ORDER as o, scopesAllowAddon as on, isDeviceConfigCap as or, declarationOwnerNodeId as ot, DAY_NAMES as p, storageMigrationCapability as pn, scopeKey as pr, deviceStatusCapability as pt, buildStreamParamsConfigSchema as q, DEVICE_SETTINGS_CONTRIBUTION_METHODS as qn, objectInputDeclaresAddonId as qt, ApiKeyRecordSchema as r, resolveLocationMode as rn, emitReadiness as rr, coreBlocksCapability as rt, CAP_NAMES_WITH_STATUS as s, scopesAllowDeviceCap as sn, isEvent as sr, decodeVectorBase64 as st, ALL_CAPABILITY_DEFINITIONS as t, replacedFieldKeys as tn, createEvent as tr, coreBlockIdFromAddonId as tt, CORE_BLOCKS_ADDON_ID as u, siteZoneKey as un, parseJsonUnknown as ur, deviceExtensionCapability as ut, FieldClaimModeSchema as v, userManagementCapability as vn, evaluateCompositionField as vt, MINUTES_PER_DAY as w, COMPOSER_ADDON_ID as wn, formatMinutes as wt, LegacyStorageLocationDefaultSchema as x, vectorIndexDimConflictError as xn, extractNestedAddonId as xt, FieldClaimSchema as y, validateComposition as yn, evictionPolicyOfLocation as yt, addonSettingsCapability as z, isSourceCap as zn, logLevelAtMost as zt };
57531
+ export { coreBlockAddonId as $, asJsonObject as $n, procedureAuthKey as $t, STORAGE_ACCESS_FALLBACK as A, buildFfmpegArgs as An, isSameAddonId as At, addonWidgetsCapability as B, isSourceCap as Bn, looseSchema as Bt, METHOD_ACCESS_MAP as C, vectorStoreCapability as Cn, foldSnapshotByFunction as Ct, REDACTED_SECRET as D, SOURCE_CAPS as Dn, isCollectionArrayMethod as Dt, RECORDING_SOURCE_CAMSTACK_ADDON as E, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as En, isArrayOutputSchema as Et, StorageLocationTypeSchema as F, compositionItemFieldPath as Fn, lifecycleJobSchema as Ft, batteryCapability as G, errMsg as Gn, modeMayWrite as Gt, authProviderCapability as H, DeviceFeature as Hn, mayWriteToLocation as Ht, StorageMigrationJobSchema as I, compositionSliceKey as In, localNetworkCapability as It, cameraGridLayoutCapability as J, DEVICE_SETTINGS_CONTRIBUTION_METHODS as Jn, objectInputDeclaresAddonId as Jt, buildCapabilityLookup as K, BaseAddon as Kn, motionCapability as Kt, UserRecordSchema as L, compositionSourceKey as Ln, locationIconKey as Lt, ScopedTokenSchema as M, composedDeviceRef as Mn, kebabToCamel as Mt, StorageCleanupJobSchema as N, composedDeviceStableId as Nn, legacyAutoLinkIds as Nt, RUNTIME_DEFAULTS as O, SOURCE_CAP_ACTIVE_FIELD as On, isLocationEnabled as Ot, StorageLocationModeSchema as P, compositionFeatureEnabled as Pn, legacyModeOf as Pt, compositionSourceRefs as Q, WELL_KNOWN_TAB_MAP as Qn, platformProbeCapability as Qt, addonPagesCapability as R, createStatefulExpressionMemory as Rn, logDestinationCapability as Rt, LocationIconIdSchema as S, vectorIndexDimConflictError as Sn, filesystemBrowseCapability as St, MOTION_CLOSE_AFTER_MS as T, COMPOSER_ADDON_ID as Tn, gbToBytes as Tt, backupCapability as U, DeviceRole as Un, memoizePinnedFfmpeg as Ut, alertsCapability as V, toExpressionValue as Vn, mayReadLocation as Vt, bareAddonId as W, DeviceType as Wn, metricsProviderCapability as Wt, composerCapability as X, ReadinessRegistry as Xn, parseStreamParamsFormPatch as Xt, collectSecretConfigKeys as Y, DEVICE_STATUS_METHOD as Yn, objectInputDeclaresRequiredDeviceId as Yt, compositionFieldKey as Z, ReadinessTimeoutError as Zn, planComposition as Zt, FIRST_LEVEL_MACRO_CLASSES as _, streamCatalogCapability as _n, enumerateSchemaFields as _t, BatteryStatusSchema as a, resolveMethodAuth as an, expandCapMethods as ar, dataStoreProviderCapability as at, LOG_LEVEL_RANK as b, validateComposition as bn, expandContainer as bt, COMPOSED_ITEM_FIXED_FIELDS as c, scopesAllowDeviceCap as cn, isEvent as cr, defineExtensionSlotProvider as ct, CORE_BLOCK_ADDON_PREFIX as d, siteZoneKey as dn, parseJsonUnknown as dr, deviceManagerCapability as dt, ptzAutotrackCapability as en, asNumber as er, coreBlockComposition as et, CoreBlockSchema as f, snapshotCapability as fn, readinessKey as fr, deviceStateCapability as ft, DeviceStatusSchema as g, storageProviderCapability as gn, EventCategory as gr, enumerateItemArrayFields as gt, DeclaredDevices as h, storageOccupancyCapability as hn, sleep as hr, enumerateInferenceDevices as ht, BaseDevice as i, resolveLocationMode as in, emitReadiness as ir, createDeviceExtensionProvider as it, STREAM_PROFILE_META as j, cloneStatefulExpressionMemory as jn, isVoidInput as jt, RecordingStatusSchema as k, SOURCE_CAP_CHANGED_AT_FIELD as kn, isObjectInput as kt, COMPOSER_RUNNER_ID as l, scoreRuntimes as ln, nodePin as lr, deriveBatteryPresence as lt, DERIVED_CAMERA_SILENCED_CAP_NAMES as m, storageMigrationCapability as mn, scopeKey as mr, doorbellCapability as mt, AlertSchema as n, replacedFieldKeys as nn, createEvent as nr, coreBlockSourceKind as nt, CAMERA_SWITCH_ORDER as o, runtimeStatePolicyFor as on, hydrateSchema as or, declarationOwnerNodeId as ot, DAY_NAMES as p, storageCapability as pn, resolveCapMount as pr, deviceStatusCapability as pt, buildStreamParamsConfigSchema as q, DATAPLANE_SECRET_HEADER as qn, normalizeUnit as qt, ApiKeyRecordSchema as r, resolveBucketMs as rn, emitDownForOwnedCaps as rr, coreBlocksCapability as rt, CAP_NAMES_WITH_STATUS as s, scopesAllowAddon as sn, isDeviceConfigCap as sr, decodeVectorBase64 as st, ALL_CAPABILITY_DEFINITIONS as t, reducePoints as tn, asString$1 as tr, coreBlockIdFromAddonId as tt, CORE_BLOCKS_ADDON_ID as u, settingsStoreCapability as un, parseJsonObject as ur, deviceExtensionCapability as ut, FieldClaimModeSchema as v, streamQualityLabel as vn, evaluateCompositionField as vt, MINUTES_PER_DAY as w, withLocationMode as wn, formatMinutes as wt, LegacyStorageLocationDefaultSchema as x, vectorDimFromBase64 as xn, extractNestedAddonId as xt, FieldClaimSchema as y, userManagementCapability as yn, evictionPolicyOfLocation as yt, addonSettingsCapability as z, evaluateSensorEdge as zn, logLevelAtMost as zt };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-B0AvBZxe.js");
3
+ const require_dist = require("./dist-D25eq33W.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_builtins_composer_composer_addon = require("./builtins/composer/composer.addon.js");
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { $n as asNumber, Bt as looseSchema, C as METHOD_ACCESS_MAP, Dt as isCollectionArrayMethod, Et as isArrayOutputSchema, Ft as lifecycleJobSchema, Jn as DEVICE_STATUS_METHOD, Jt as objectInputDeclaresRequiredDeviceId, Kn as DATAPLANE_SECRET_HEADER$1, Mt as kebabToCamel, O as RUNTIME_DEFAULTS, Qn as asJsonObject$1, Qt as procedureAuthKey, Wn as errMsg$1, Xn as ReadinessTimeoutError, Yn as ReadinessRegistry, b as LOG_LEVEL_RANK, dr as readinessKey, er as asString$1, fr as resolveCapMount, hr as EventCategory$1, in as resolveMethodAuth, ir as expandCapMethods, jt as isVoidInput, kt as isObjectInput, lr as parseJsonObject, nr as emitDownForOwnedCaps, on as scopesAllowAddon, pr as scopeKey, qn as DEVICE_SETTINGS_CONTRIBUTION_METHODS, qt as objectInputDeclaresAddonId, sn as scopesAllowDeviceCap, t as ALL_CAPABILITY_DEFINITIONS, tr as createEvent, ur as parseJsonUnknown, xt as extractNestedAddonId, z as addonSettingsCapability, zt as logLevelAtMost } from "./dist-XfFiGX64.mjs";
2
+ import { $n as asJsonObject$1, $t as procedureAuthKey, Bt as looseSchema, C as METHOD_ACCESS_MAP, Dt as isCollectionArrayMethod, Et as isArrayOutputSchema, Ft as lifecycleJobSchema, Gn as errMsg$1, Jn as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Jt as objectInputDeclaresAddonId, Mt as kebabToCamel, O as RUNTIME_DEFAULTS, Xn as ReadinessRegistry, Yn as DEVICE_STATUS_METHOD, Yt as objectInputDeclaresRequiredDeviceId, Zn as ReadinessTimeoutError, an as resolveMethodAuth, ar as expandCapMethods, b as LOG_LEVEL_RANK, cn as scopesAllowDeviceCap, dr as parseJsonUnknown, er as asNumber, fr as readinessKey, gr as EventCategory$1, jt as isVoidInput, kt as isObjectInput, mr as scopeKey, nr as createEvent, pr as resolveCapMount, qn as DATAPLANE_SECRET_HEADER$1, rr as emitDownForOwnedCaps, sn as scopesAllowAddon, t as ALL_CAPABILITY_DEFINITIONS, tr as asString$1, ur as parseJsonObject, xt as extractNestedAddonId, z as addonSettingsCapability, zt as logLevelAtMost } from "./dist-DENeGKrn.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { ComposerAddon } from "./builtins/composer/composer.addon.mjs";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-B0AvBZxe.js");
1
+ const require_dist = require("./dist-D25eq33W.js");
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -1,4 +1,4 @@
1
- import { Qn as asJsonObject } from "./dist-XfFiGX64.mjs";
1
+ import { $n as asJsonObject } from "./dist-DENeGKrn.mjs";
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.320",
3
+ "version": "1.2.322",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",