@camstack/addon-pipeline 1.2.602 → 1.2.603

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/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-CVvdKnH2.js → custom-model-catalog-BAnOZBOf.js} +2 -2
  4. package/dist/{custom-model-catalog--ms2wSDI.mjs → custom-model-catalog-Dv09Pvq_.mjs} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-BmSyTuy4.mjs → dist-CsCflIM1.mjs} +287 -45
  8. package/dist/{dist-B4HyxJe1.js → dist-DvXRw_dM.js} +292 -44
  9. package/dist/{model-catalogs-Bj0f2Obs.js → model-catalogs-B95QZeYt.js} +1 -1
  10. package/dist/{model-catalogs-OnXVUgQY.mjs → model-catalogs-aTwF92Re.mjs} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node--4HtlkFo.mjs → node-DAp5MmRJ.mjs} +1 -1
  14. package/dist/{node-DfuKhWUZ.js → node-DxAtLKEn.js} +1 -1
  15. package/dist/pipeline-runner/index.js +3 -3
  16. package/dist/pipeline-runner/index.mjs +3 -3
  17. package/dist/{process-memory-uf7ZfkSz.mjs → process-memory-1_2L6U3Q.mjs} +1 -1
  18. package/dist/{process-memory-BZ1_q3g3.js → process-memory-CmWDOMmE.js} +1 -1
  19. package/dist/recorder/index.js +240 -40
  20. package/dist/recorder/index.mjs +240 -40
  21. package/dist/{segment-demux-js-CURD3CEv.mjs → segment-demux-js-DQo9qSHm.mjs} +1 -1
  22. package/dist/{segment-demux-js-C7fMqBGZ.js → segment-demux-js-HagdockZ.js} +1 -1
  23. package/dist/stream-broker/_stub.js +2 -2
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Cd6rjANN.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DoBB8xYv.mjs} +3 -3
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DkA9R3eo.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-RrTYeVaZ.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-CV-FMXnr.mjs → hostInit-DaZYQhX4.mjs} +3 -3
  30. package/dist/stream-broker/index.js +3 -3
  31. package/dist/stream-broker/index.mjs +3 -3
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/package.json +1 -1
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DUPQbxcH.mjs +0 -26
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CGcD3ujP.mjs +0 -26
@@ -1,5 +1,5 @@
1
- import { $n as sleep$1, $t as recordingArchiveCapability, An as errMsg$1, B as RECORDING_SOURCE_CAMSTACK_ADDON, Bn as defineCustomActions, Bt as memoizePinnedFfmpeg, C as EVENT_PAD_MS, Ct as evictionPolicyForMode, Ft as loadContributionCapability, G as VIDEOCLIPS_MAX_OFFER_BYTES, H as RecordingConfigSchema, Ht as modeMayWrite, K as VISIT_MERGE_GAP_MS, Mn as BaseAddon, Mt as isFirstLevelMacroClass, N as OpsLogEntrySchema, On as DeviceFeature, Ot as gbToBytes, Qn as selectAssignedProfileSlots, R as RECORDING_EXPORT_MAX_READ_BYTES, Rt as mayReadLocation, S as EVENT_OWNER_TYPES, T as ExportRecordSchema, Tn as evaluateSensorEdge, Tt as exportSubjectOf, U as RecordingSignalStatusSchema, Vn as defineRecordKinds, Vt as migrateLegacyDeviceSignalConfig, Wn as hydrateSchema, Xn as nodePin, Z as armRemovalReconcile, _n as stripRetiredBandPreBufferSec, at as clipExportFailure, cr as object, dr as string, en as recordingCapability, er as userError, hn as storageOccupancyCapability, ht as deriveRecordingMode, in as resolveCreateExportSubject, kn as DeviceType, ln as resolveRecordingProfiles, lr as preprocess, mn as storageEvictableCapability, nn as recordingSignalCapability, nr as array, o as BatteryStatusSchema, on as resolveLocationMode, ot as clipExportFailureOf, pr as EventCategory, pt as deriveBatteryPresence, rr as boolean, s as CLIP_BROKER_PACED_RATES, sr as number, tn as recordingExportCapability, tr as _enum, tt as batteryCapability, ur as record, v as DEFAULT_EVENTS_BAND_BUFFER_SEC, vt as deviceRemovalHookCapability, wt as evictionPolicyOfLocation, yn as videoclipsCapability, z as RECORDING_SOURCE_CAMSTACK, zn as customAction, zt as mayWriteToLocation } from "../dist-BmSyTuy4.mjs";
2
- import { i as NO_COST_CLAIM, o as physicalRootOf, t as ChildCostRegistry } from "../node--4HtlkFo.mjs";
1
+ import { $n as selectAssignedProfileSlots, $t as recordingArchiveCapability, An as DeviceType, B as RECORDING_SOURCE_CAMSTACK_ADDON, Bn as customAction, Bt as memoizePinnedFfmpeg, C as EVENT_PAD_MS, Ct as evictionPolicyForMode, En as evaluateSensorEdge, Ft as loadContributionCapability, G as VIDEOCLIPS_MAX_OFFER_BYTES, Gn as hydrateSchema, H as RecordingConfigSchema, Hn as defineRecordKinds, Ht as modeMayWrite, K as VISIT_MERGE_GAP_MS, Mt as isFirstLevelMacroClass, N as OpsLogEntrySchema, Nn as BaseAddon, Ot as gbToBytes, R as RECORDING_EXPORT_MAX_READ_BYTES, Rt as mayReadLocation, S as EVENT_OWNER_TYPES, T as ExportRecordSchema, Tt as exportSubjectOf, U as RecordingSignalStatusSchema, Vn as defineCustomActions, Vt as migrateLegacyDeviceSignalConfig, Z as armRemovalReconcile, Zn as nodePin, at as clipExportFailure, bn as videoclipsCapability, cr as number, dr as record, en as recordingCapability, er as sleep$1, fr as string, gn as storageOccupancyCapability, hn as storageEvictableCapability, ht as deriveRecordingMode, in as resolveCreateExportSubject, ir as boolean, jn as errMsg$1, kn as DeviceFeature, ln as resolveRecordingProfiles, lr as object, mr as EventCategory, nn as recordingSignalCapability, nr as _enum, o as BatteryStatusSchema, on as resolveLocationMode, ot as clipExportFailureOf, pn as startMoverUnderClearance, pt as deriveBatteryPresence, rr as array, s as CLIP_BROKER_PACED_RATES, tn as recordingExportCapability, tr as userError, tt as batteryCapability, ur as preprocess, v as DEFAULT_EVENTS_BAND_BUFFER_SEC, vn as stripRetiredBandPreBufferSec, vt as deviceRemovalHookCapability, wt as evictionPolicyOfLocation, z as RECORDING_SOURCE_CAMSTACK, zt as mayWriteToLocation } from "../dist-CsCflIM1.mjs";
2
+ import { i as NO_COST_CLAIM, o as physicalRootOf, t as ChildCostRegistry } from "../node-DAp5MmRJ.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
4
  import { d as PREBUFFER_MAX_MS, l as withRecordingIntent, r as RECORDING_PRE_ROLL_REQUEST_MS } from "../restream-intent-DyGmLAU9.mjs";
5
5
  import { randomUUID } from "node:crypto";
@@ -6729,6 +6729,15 @@ Object.freeze({
6729
6729
  "valueType": "boolean"
6730
6730
  }],
6731
6731
  "dishwasher": [
6732
+ {
6733
+ "path": "availability",
6734
+ "valueType": "enum",
6735
+ "enumValues": [
6736
+ "available",
6737
+ "offline",
6738
+ "description-missing"
6739
+ ]
6740
+ },
6732
6741
  {
6733
6742
  "path": "state",
6734
6743
  "valueType": "enum",
@@ -6741,12 +6750,35 @@ Object.freeze({
6741
6750
  "unavailable"
6742
6751
  ]
6743
6752
  },
6753
+ {
6754
+ "path": "operationState",
6755
+ "valueType": "enum",
6756
+ "enumValues": [
6757
+ "Inactive",
6758
+ "Ready",
6759
+ "DelayedStart",
6760
+ "Run",
6761
+ "Pause",
6762
+ "ActionRequired",
6763
+ "Finished",
6764
+ "Error",
6765
+ "Aborting"
6766
+ ]
6767
+ },
6744
6768
  {
6745
6769
  "path": "program",
6746
6770
  "valueType": "string"
6747
6771
  },
6748
6772
  {
6749
- "path": "remainingMinutes",
6773
+ "path": "selectedProgram",
6774
+ "valueType": "string"
6775
+ },
6776
+ {
6777
+ "path": "remainingSeconds",
6778
+ "valueType": "number"
6779
+ },
6780
+ {
6781
+ "path": "delayedStartSeconds",
6750
6782
  "valueType": "number"
6751
6783
  },
6752
6784
  {
@@ -6757,14 +6789,30 @@ Object.freeze({
6757
6789
  "path": "doorOpen",
6758
6790
  "valueType": "boolean"
6759
6791
  },
6792
+ {
6793
+ "path": "doorLocked",
6794
+ "valueType": "boolean"
6795
+ },
6760
6796
  {
6761
6797
  "path": "remoteStartAllowed",
6762
6798
  "valueType": "boolean"
6763
6799
  },
6800
+ {
6801
+ "path": "remoteControlActive",
6802
+ "valueType": "boolean"
6803
+ },
6804
+ {
6805
+ "path": "localControlActive",
6806
+ "valueType": "boolean"
6807
+ },
6764
6808
  {
6765
6809
  "path": "powerOn",
6766
6810
  "valueType": "boolean"
6767
6811
  },
6812
+ {
6813
+ "path": "powerState",
6814
+ "valueType": "string"
6815
+ },
6768
6816
  {
6769
6817
  "path": "saltLevel",
6770
6818
  "valueType": "enum",
@@ -10414,6 +10462,22 @@ var LOCATE_HYDRATE_MARGIN_MS = 6e4;
10414
10462
  */
10415
10463
  var WINDOW_READ_MAX_BYTES = 2e7;
10416
10464
  /** Empty "no playback" manifest. */
10465
+ /**
10466
+ * Start a footage move only while no single-location repoint holds an
10467
+ * overlapping physical root (the storage orchestrator owns that claim).
10468
+ */
10469
+ function startFootageMover(deps, engine, input) {
10470
+ const ask = deps.moverClearance;
10471
+ if (ask === void 0) return Promise.resolve(engine.start(input));
10472
+ return startMoverUnderClearance({
10473
+ clearance: ask,
10474
+ start: () => engine.start(input),
10475
+ cancel: (jobId) => {
10476
+ engine.cancel(jobId);
10477
+ },
10478
+ locationIds: [input.fromLocationId, input.toLocationId]
10479
+ });
10480
+ }
10417
10481
  function noPlayback(deviceId) {
10418
10482
  return {
10419
10483
  deviceId,
@@ -11216,13 +11280,13 @@ function buildRecordingProvider(deps) {
11216
11280
  startStorageMigrationMove: async (input) => {
11217
11281
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11218
11282
  deps.assertStorageMigrationLease?.(input.leaseId);
11219
- return { jobId: deps.relocate.start(input) };
11283
+ return { jobId: await startFootageMover(deps, deps.relocate, input) };
11220
11284
  },
11221
11285
  getStorageMigrationMoveStatus: async ({ jobId }) => deps.relocate?.list().find((job) => job.jobId === jobId) ?? null,
11222
11286
  cancelStorageMigrationMove: async ({ jobId }) => ({ cancelled: deps.relocate?.cancel(jobId) ?? false }),
11223
11287
  relocateFootage: async (input) => {
11224
11288
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11225
- const jobId = deps.relocate.start(input);
11289
+ const jobId = await startFootageMover(deps, deps.relocate, input);
11226
11290
  deps.logger.info("recorder: relocate job enqueued", {
11227
11291
  ...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
11228
11292
  meta: {
@@ -15198,6 +15262,135 @@ function startPeriodicPass(deps) {
15198
15262
  };
15199
15263
  }
15200
15264
  //#endregion
15265
+ //#region src/recorder/addon/placement-alerts.ts
15266
+ var SEVERITY = {
15267
+ unusable: "critical",
15268
+ "shared-root": "error",
15269
+ "writer-restart": "warning"
15270
+ };
15271
+ var TITLE = {
15272
+ unusable: "Recordings storage location unusable",
15273
+ "shared-root": "Recordings storage locations share one disk",
15274
+ "writer-restart": "Recording placement waiting on a writer restart"
15275
+ };
15276
+ /** Stable identity of one fault: the alert id AND its episode key. */
15277
+ function placementAlertId(locationId, reason) {
15278
+ return `recorder-placement:${locationId}:${reason}`;
15279
+ }
15280
+ var PlacementAlertPublisher = class {
15281
+ deps;
15282
+ episodes = /* @__PURE__ */ new Map();
15283
+ constructor(deps) {
15284
+ this.deps = deps;
15285
+ }
15286
+ /** Raise (or refresh) the alert of one fault. Advisory: never throws. */
15287
+ raise(input) {
15288
+ const id = placementAlertId(input.locationId, input.reason);
15289
+ const now = this.deps.now();
15290
+ const episode = this.episodes.get(id) ?? {
15291
+ createdAt: now,
15292
+ count: 0
15293
+ };
15294
+ episode.count += 1;
15295
+ this.episodes.set(id, episode);
15296
+ const alert = {
15297
+ id,
15298
+ category: "storage",
15299
+ severity: SEVERITY[input.reason],
15300
+ title: TITLE[input.reason],
15301
+ message: input.detail,
15302
+ status: "active",
15303
+ read: false,
15304
+ createdAt: episode.createdAt,
15305
+ updatedAt: now,
15306
+ source: {
15307
+ type: "addon",
15308
+ id: this.deps.sourceId
15309
+ },
15310
+ metadata: {
15311
+ locationId: input.locationId,
15312
+ nodeId: this.deps.nodeId,
15313
+ reason: input.reason,
15314
+ episodeKey: id,
15315
+ count: episode.count
15316
+ }
15317
+ };
15318
+ this.deps.port.emit(alert).catch((err) => {
15319
+ this.deps.logger.warn("recorder: storage alert emit failed (advisory)", { meta: {
15320
+ locationId: input.locationId,
15321
+ reason: input.reason,
15322
+ error: errMsg$1(err)
15323
+ } });
15324
+ });
15325
+ }
15326
+ /** The fault is gone: close its alert. A fault that never fired is a no-op. */
15327
+ resolve(locationId, reason) {
15328
+ const id = placementAlertId(locationId, reason);
15329
+ const episode = this.episodes.get(id);
15330
+ if (episode === void 0) return;
15331
+ this.episodes.delete(id);
15332
+ this.deps.logger.info("recorder: storage fault recovered — closing its alert", { meta: {
15333
+ locationId,
15334
+ reason,
15335
+ alertId: id,
15336
+ raisedTimes: episode.count
15337
+ } });
15338
+ this.deps.port.update({
15339
+ alertId: id,
15340
+ patch: {
15341
+ status: "completed",
15342
+ read: true,
15343
+ updatedAt: this.deps.now()
15344
+ }
15345
+ }).catch((err) => {
15346
+ this.deps.logger.warn("recorder: closing a recovered storage alert failed (advisory)", { meta: {
15347
+ locationId,
15348
+ reason,
15349
+ alertId: id,
15350
+ error: errMsg$1(err)
15351
+ } });
15352
+ });
15353
+ }
15354
+ };
15355
+ /**
15356
+ * Keeps the `writer-restart` alerts true to the live set of pending writer
15357
+ * moves: one alert per PLANNED location, raised while any camera is still
15358
+ * pending for it, completed when none is (the move landed, or the plan was
15359
+ * dropped - the target was drained or deleted).
15360
+ */
15361
+ var WriterRestartAlerts = class {
15362
+ publisher;
15363
+ logger;
15364
+ previous = [];
15365
+ constructor(publisher, logger) {
15366
+ this.publisher = publisher;
15367
+ this.logger = logger;
15368
+ }
15369
+ update(moves) {
15370
+ const byLocation = /* @__PURE__ */ new Map();
15371
+ for (const move of moves) byLocation.set(move.plannedFor, (byLocation.get(move.plannedFor) ?? 0) + 1);
15372
+ for (const [locationId, count] of byLocation) this.publisher.raise({
15373
+ locationId,
15374
+ reason: "writer-restart",
15375
+ detail: `${String(count)} recording profile(s) are planned for ${locationId} and will keep writing to their current location until each camera next attaches. Continuously-recording cameras never re-attach on their own.`
15376
+ });
15377
+ const stillPending = new Set(moves.map((m) => `${String(m.deviceId)}:${m.profile}>${m.plannedFor}`));
15378
+ for (const gone of this.previous) {
15379
+ if (stillPending.has(`${String(gone.deviceId)}:${gone.profile}>${gone.plannedFor}`)) continue;
15380
+ this.logger.info("recorder placement: writer-restart no longer pending for this camera", {
15381
+ tags: { deviceId: gone.deviceId },
15382
+ meta: {
15383
+ locationId: gone.plannedFor,
15384
+ profile: gone.profile,
15385
+ writingTo: gone.writingTo
15386
+ }
15387
+ });
15388
+ }
15389
+ for (const gone of this.previous) if (!byLocation.has(gone.plannedFor)) this.publisher.resolve(gone.plannedFor, "writer-restart");
15390
+ this.previous = moves;
15391
+ }
15392
+ };
15393
+ //#endregion
15201
15394
  //#region src/recorder/addon/placement-guard.ts
15202
15395
  /**
15203
15396
  * Same-root guard for recordings placement.
@@ -15842,6 +16035,9 @@ var PlacementService = class {
15842
16035
  verified = /* @__PURE__ */ new Set();
15843
16036
  /** Locations the probe declared unusable — re-probed after a change. */
15844
16037
  unusable = /* @__PURE__ */ new Set();
16038
+ /** Locations with an OPEN mount-fault alert. Survives a re-resolve (unlike
16039
+ * `unusable`): the alert closes only when the probe verifies the volume. */
16040
+ faulted = /* @__PURE__ */ new Set();
15845
16041
  /** Locations whose capacity could not be read, by reason. One line per
15846
16042
  * distinct fault (this is on the 30 s tick) and one when it clears. */
15847
16043
  capacityFaults = /* @__PURE__ */ new Map();
@@ -16356,6 +16552,7 @@ var PlacementService = class {
16356
16552
  return chosen;
16357
16553
  }
16358
16554
  this.verified.add(chosen.id);
16555
+ this.reportMountRecovered(chosen.id);
16359
16556
  if (result.outcome === "adopted" && result.foundVolumeId !== null) {
16360
16557
  try {
16361
16558
  await rememberVolumeId(this.deps.state, chosen.id, result.foundVolumeId);
@@ -16379,6 +16576,13 @@ var PlacementService = class {
16379
16576
  }
16380
16577
  return chosen;
16381
16578
  }
16579
+ /** The probe verified a location that had an open fault: close its alert. */
16580
+ reportMountRecovered(locationId) {
16581
+ if (!this.faulted.delete(locationId)) return;
16582
+ try {
16583
+ this.deps.recovered?.(locationId);
16584
+ } catch {}
16585
+ }
16382
16586
  /** One loud line + one alert + one audit row per mount fault. */
16383
16587
  reportMountFault(result, deviceId) {
16384
16588
  this.deps.logger.error("recorder placement: storage volume MISMATCH — the disk under this location is not the one it last wrote to; failing over", {
@@ -16392,8 +16596,10 @@ var PlacementService = class {
16392
16596
  }
16393
16597
  });
16394
16598
  try {
16599
+ this.faulted.add(result.locationId);
16395
16600
  this.deps.alert?.({
16396
16601
  locationId: result.locationId,
16602
+ reason: "unusable",
16397
16603
  detail: result.detail
16398
16604
  });
16399
16605
  } catch {}
@@ -23651,6 +23857,7 @@ var RecorderV2Addon = class extends BaseAddon {
23651
23857
  state: this.placementStore(),
23652
23858
  opsLog,
23653
23859
  alert: (input) => this.emitPlacementAlert(input),
23860
+ recovered: (locationId) => this.resolvePlacementAlert(locationId),
23654
23861
  capacityFor: (location) => this.locationCapacity(location.root),
23655
23862
  usedOnLocation: async (locationId) => (await this.segmentHours?.accountingForLocations(new Set([locationId])))?.bytes ?? 0,
23656
23863
  dailyBytesFor: (deviceId, profile) => this.dailyBytesFor(deviceId, profile)
@@ -23852,6 +24059,7 @@ var RecorderV2Addon = class extends BaseAddon {
23852
24059
  resumeForStorageMigration: (leaseId) => this.resumeForStorageMigration(leaseId),
23853
24060
  refreshStorageLocationsForMigration: (leaseId) => this.refreshStorageLocationsForMigration(leaseId),
23854
24061
  assertStorageMigrationLease: (leaseId) => this.assertStorageMigrationLease(leaseId),
24062
+ moverClearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
23855
24063
  placementSnapshot: () => this.placementSnapshot(),
23856
24064
  placementView: () => {
23857
24065
  const placement = this.placement;
@@ -24827,6 +25035,7 @@ var RecorderV2Addon = class extends BaseAddon {
24827
25035
  } });
24828
25036
  this.emitPlacementAlert({
24829
25037
  locationId: conflict.keptId,
25038
+ reason: "shared-root",
24830
25039
  detail: `Storage locations ${[conflict.keptId, ...conflict.demotedIds].join(", ")} all resolve to ${conflict.root}. Only ${conflict.keptId} will be written to.`
24831
25040
  });
24832
25041
  }
@@ -24965,6 +25174,7 @@ var RecorderV2Addon = class extends BaseAddon {
24965
25174
  const signature = pendingMovesSignature(moves);
24966
25175
  if (signature === this.pendingWriterMoves) return;
24967
25176
  this.pendingWriterMoves = signature;
25177
+ this.writerRestartAlerts().update(moves);
24968
25178
  if (moves.length === 0) return;
24969
25179
  for (const move of moves) this.ctx.logger.warn("recorder placement: this camera is PLANNED for another location and its writer will not move until it next attaches", {
24970
25180
  tags: { deviceId: move.deviceId },
@@ -24975,12 +25185,6 @@ var RecorderV2Addon = class extends BaseAddon {
24975
25185
  hint: "a continuously-recording camera never re-attaches on its own — toggle its recording switch, or restart the recorder, to apply the plan (each costs one gap on that camera)"
24976
25186
  }
24977
25187
  });
24978
- this.emitAlert({
24979
- severity: "warning",
24980
- title: "Recording placement waiting on a writer restart",
24981
- message: `${String(moves.length)} recording profile(s) are planned for a different storage location and will keep writing to their current one until each camera next attaches. Continuously-recording cameras never re-attach on their own.`,
24982
- locationId: moves[0]?.plannedFor ?? ""
24983
- });
24984
25188
  }
24985
25189
  /**
24986
25190
  * A placement pass that plans NOTHING says so — once per episode.
@@ -25202,38 +25406,34 @@ var RecorderV2Addon = class extends BaseAddon {
25202
25406
  /**
25203
25407
  * Surface a storage-placement fault on the liveness/alert surface. STRICTLY
25204
25408
  * best-effort: the alerts cap is a nice-to-have and an unmounted disk must
25205
- * still be shouted about in the log even when it is unreachable.
25409
+ * still be shouted about in the log even when it is unreachable. One row per
25410
+ * (location, reason) episode - see `placement-alerts.ts`.
25206
25411
  */
25207
25412
  emitPlacementAlert(input) {
25208
- this.emitAlert({
25209
- severity: "error",
25210
- title: "Recordings storage location unusable",
25211
- message: input.detail,
25212
- locationId: input.locationId
25213
- });
25214
- }
25215
- /** One storage-category alert. Advisory: the LOG line is the durable record,
25216
- * so a failed emit is swallowed rather than raised. */
25217
- emitAlert(input) {
25218
- this.ctx.api.alerts.emit.mutate({
25219
- id: randomUUID(),
25220
- category: "storage",
25221
- severity: input.severity,
25222
- title: input.title,
25223
- message: input.message,
25224
- status: "active",
25225
- read: false,
25226
- createdAt: Date.now(),
25227
- updatedAt: Date.now(),
25228
- source: {
25229
- type: "addon",
25230
- id: this.ctx.id
25413
+ this.placementAlerts().raise(input);
25414
+ }
25415
+ /** The faulted location passed its volume probe again: close its alert. */
25416
+ resolvePlacementAlert(locationId) {
25417
+ this.placementAlerts().resolve(locationId, "unusable");
25418
+ }
25419
+ placementAlertPublisher = null;
25420
+ writerRestartAlertSync = null;
25421
+ writerRestartAlerts() {
25422
+ this.writerRestartAlertSync ??= new WriterRestartAlerts(this.placementAlerts(), this.ctx.logger);
25423
+ return this.writerRestartAlertSync;
25424
+ }
25425
+ placementAlerts() {
25426
+ this.placementAlertPublisher ??= new PlacementAlertPublisher({
25427
+ port: {
25428
+ emit: (alert) => this.ctx.api.alerts.emit.mutate(alert),
25429
+ update: (input) => this.ctx.api.alerts.update.mutate(input)
25231
25430
  },
25232
- metadata: {
25233
- locationId: input.locationId,
25234
- nodeId: this.nodeId
25235
- }
25236
- }).catch(() => {});
25431
+ logger: this.ctx.logger,
25432
+ sourceId: this.ctx.id,
25433
+ nodeId: this.nodeId,
25434
+ now: () => Date.now()
25435
+ });
25436
+ return this.placementAlertPublisher;
25237
25437
  }
25238
25438
  async pauseForStorageMigration(leaseId) {
25239
25439
  if (this.storageMigrationLeaseId !== null && this.storageMigrationLeaseId !== leaseId) throw new Error("recorder is already paused by another storage migration");
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "./chunk-DnnnRqeS.mjs";
2
- import { An as errMsg } from "./dist-BmSyTuy4.mjs";
2
+ import { jn as errMsg } from "./dist-CsCflIM1.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region src/stream-broker/recorded/avcc-annexb.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-B4HyxJe1.js");
2
+ const require_dist = require("./dist-DvXRw_dM.js");
3
3
  let node_child_process = require("node:child_process");
4
4
  //#region src/stream-broker/recorded/avcc-annexb.ts
5
5
  /**
@@ -1,7 +1,7 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, m as o, n as s, o as c, p as l, r as u, s as d, t as f, u as p } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CGcD3ujP.mjs";
1
+ import { a as e, c as t, d as n, f as r, i, l as a, m as o, n as s, o as c, p as l, r as u, s as d, t as f, u as p } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-RrTYeVaZ.mjs";
2
2
  import { a as m, i as h, n as g, o as _, r as v, t as y } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
3
3
  import { n as b, r as x, t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
4
- import { t as C } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DUPQbxcH.mjs";
4
+ import { t as C } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DkA9R3eo.mjs";
5
5
  import { n as w, t as T } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
6
6
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
7
7
  var E = (...e) => e.filter((e, t, n) => !!e && e.trim() !== "" && n.indexOf(e) === t).join(" ").trim(), D = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), O = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), k = (e) => {
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.134",
6
+ version: "1.2.135",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_stream_broker_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.384",
21
+ version: "1.2.385",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.301",
36
+ version: "1.2.302",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",