@camstack/addon-pipeline 1.2.601 → 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-DgyafyuW.js → custom-model-catalog-BAnOZBOf.js} +2 -2
  4. package/dist/{custom-model-catalog-CK9GK3N5.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-BaHsDOWa.mjs → dist-CsCflIM1.mjs} +558 -16
  8. package/dist/{dist-BqI10eLV.js → dist-DvXRw_dM.js} +563 -15
  9. package/dist/{model-catalogs-DhGVt_sH.js → model-catalogs-B95QZeYt.js} +1 -1
  10. package/dist/{model-catalogs-DM5sVj1T.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-B0a0dXhg.mjs → node-DAp5MmRJ.mjs} +1 -1
  14. package/dist/{node-CgwqjGz0.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-sfdcLVkW.mjs → process-memory-1_2L6U3Q.mjs} +1 -1
  18. package/dist/{process-memory-C0zbWvwD.js → process-memory-CmWDOMmE.js} +1 -1
  19. package/dist/recorder/index.js +352 -51
  20. package/dist/recorder/index.mjs +352 -51
  21. package/dist/{segment-demux-js-BB4CUAB5.mjs → segment-demux-js-DQo9qSHm.mjs} +1 -1
  22. package/dist/{segment-demux-js-B9lL2VCC.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-CbN9DjoK.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-BSYHAfPt.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-Boq8JqyD.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-B25q1OKG.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-BaHsDOWa.mjs";
2
- import { i as NO_COST_CLAIM, o as physicalRootOf, t as ChildCostRegistry } from "../node-B0a0dXhg.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";
@@ -6728,6 +6728,142 @@ Object.freeze({
6728
6728
  "path": "online",
6729
6729
  "valueType": "boolean"
6730
6730
  }],
6731
+ "dishwasher": [
6732
+ {
6733
+ "path": "availability",
6734
+ "valueType": "enum",
6735
+ "enumValues": [
6736
+ "available",
6737
+ "offline",
6738
+ "description-missing"
6739
+ ]
6740
+ },
6741
+ {
6742
+ "path": "state",
6743
+ "valueType": "enum",
6744
+ "enumValues": [
6745
+ "idle",
6746
+ "running",
6747
+ "paused",
6748
+ "finished",
6749
+ "error",
6750
+ "unavailable"
6751
+ ]
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
+ },
6768
+ {
6769
+ "path": "program",
6770
+ "valueType": "string"
6771
+ },
6772
+ {
6773
+ "path": "selectedProgram",
6774
+ "valueType": "string"
6775
+ },
6776
+ {
6777
+ "path": "remainingSeconds",
6778
+ "valueType": "number"
6779
+ },
6780
+ {
6781
+ "path": "delayedStartSeconds",
6782
+ "valueType": "number"
6783
+ },
6784
+ {
6785
+ "path": "progressPercent",
6786
+ "valueType": "number"
6787
+ },
6788
+ {
6789
+ "path": "doorOpen",
6790
+ "valueType": "boolean"
6791
+ },
6792
+ {
6793
+ "path": "doorLocked",
6794
+ "valueType": "boolean"
6795
+ },
6796
+ {
6797
+ "path": "remoteStartAllowed",
6798
+ "valueType": "boolean"
6799
+ },
6800
+ {
6801
+ "path": "remoteControlActive",
6802
+ "valueType": "boolean"
6803
+ },
6804
+ {
6805
+ "path": "localControlActive",
6806
+ "valueType": "boolean"
6807
+ },
6808
+ {
6809
+ "path": "powerOn",
6810
+ "valueType": "boolean"
6811
+ },
6812
+ {
6813
+ "path": "powerState",
6814
+ "valueType": "string"
6815
+ },
6816
+ {
6817
+ "path": "saltLevel",
6818
+ "valueType": "enum",
6819
+ "enumValues": [
6820
+ "ok",
6821
+ "low",
6822
+ "empty"
6823
+ ]
6824
+ },
6825
+ {
6826
+ "path": "rinseAidLevel",
6827
+ "valueType": "enum",
6828
+ "enumValues": [
6829
+ "ok",
6830
+ "low",
6831
+ "empty"
6832
+ ]
6833
+ },
6834
+ {
6835
+ "path": "halfLoad",
6836
+ "valueType": "boolean"
6837
+ },
6838
+ {
6839
+ "path": "hygienePlus",
6840
+ "valueType": "boolean"
6841
+ },
6842
+ {
6843
+ "path": "extraDry",
6844
+ "valueType": "boolean"
6845
+ },
6846
+ {
6847
+ "path": "intensiveZone",
6848
+ "valueType": "boolean"
6849
+ },
6850
+ {
6851
+ "path": "varioSpeedPlus",
6852
+ "valueType": "boolean"
6853
+ },
6854
+ {
6855
+ "path": "childLock",
6856
+ "valueType": "boolean"
6857
+ },
6858
+ {
6859
+ "path": "errorCode",
6860
+ "valueType": "number"
6861
+ },
6862
+ {
6863
+ "path": "errorLabel",
6864
+ "valueType": "string"
6865
+ }
6866
+ ],
6731
6867
  "doorbell": [{
6732
6868
  "path": "lastPressedAt",
6733
6869
  "valueType": "number"
@@ -6818,6 +6954,10 @@ Object.freeze({
6818
6954
  "path": "detected",
6819
6955
  "valueType": "boolean"
6820
6956
  }],
6957
+ "heating-valve": [{
6958
+ "path": "position",
6959
+ "valueType": "number"
6960
+ }],
6821
6961
  "humidifier": [
6822
6962
  {
6823
6963
  "path": "on",
@@ -10322,6 +10462,22 @@ var LOCATE_HYDRATE_MARGIN_MS = 6e4;
10322
10462
  */
10323
10463
  var WINDOW_READ_MAX_BYTES = 2e7;
10324
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
+ }
10325
10481
  function noPlayback(deviceId) {
10326
10482
  return {
10327
10483
  deviceId,
@@ -11124,13 +11280,13 @@ function buildRecordingProvider(deps) {
11124
11280
  startStorageMigrationMove: async (input) => {
11125
11281
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11126
11282
  deps.assertStorageMigrationLease?.(input.leaseId);
11127
- return { jobId: deps.relocate.start(input) };
11283
+ return { jobId: await startFootageMover(deps, deps.relocate, input) };
11128
11284
  },
11129
11285
  getStorageMigrationMoveStatus: async ({ jobId }) => deps.relocate?.list().find((job) => job.jobId === jobId) ?? null,
11130
11286
  cancelStorageMigrationMove: async ({ jobId }) => ({ cancelled: deps.relocate?.cancel(jobId) ?? false }),
11131
11287
  relocateFootage: async (input) => {
11132
11288
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11133
- const jobId = deps.relocate.start(input);
11289
+ const jobId = await startFootageMover(deps, deps.relocate, input);
11134
11290
  deps.logger.info("recorder: relocate job enqueued", {
11135
11291
  ...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
11136
11292
  meta: {
@@ -11747,13 +11903,10 @@ var DurableLedger = class {
11747
11903
  const body = this.spec.toValue(row);
11748
11904
  if (this.spec.writeMode === "write-behind") {
11749
11905
  this.mirror.set(key, row);
11750
- await this.persistRow(key, body);
11906
+ await this.persistRow(key, body, row);
11751
11907
  return;
11752
11908
  }
11753
- await this.kind.put({
11754
- id: key,
11755
- data: body
11756
- });
11909
+ await this.kind.put(this.buildPut(key, body, row));
11757
11910
  this.mirror.set(key, row);
11758
11911
  }
11759
11912
  /**
@@ -11854,15 +12007,27 @@ var DurableLedger = class {
11854
12007
  */
11855
12008
  persist(row) {
11856
12009
  const key = this.spec.keyOf(row);
11857
- this.persistRow(key, this.spec.toValue(row));
12010
+ this.persistRow(key, this.spec.toValue(row), row);
11858
12011
  }
11859
- async persistRow(key, body) {
11860
- const deviceId = this.deviceIdFromBody(body);
12012
+ buildPut(key, body, row) {
12013
+ const deviceId = this.resolveRecordDeviceId(row, body);
12014
+ return {
12015
+ id: key,
12016
+ data: body,
12017
+ ...deviceId !== void 0 ? { deviceId } : {}
12018
+ };
12019
+ }
12020
+ resolveRecordDeviceId(row, body) {
12021
+ if (row !== void 0) {
12022
+ const fromSpec = this.spec.deviceIdOf?.(row);
12023
+ if (fromSpec !== void 0 && Number.isFinite(fromSpec)) return fromSpec;
12024
+ }
12025
+ return this.deviceIdFromBody(body);
12026
+ }
12027
+ async persistRow(key, body, row) {
12028
+ const deviceId = this.resolveRecordDeviceId(row, body);
11861
12029
  try {
11862
- await this.kind.put({
11863
- id: key,
11864
- data: body
11865
- });
12030
+ await this.kind.put(this.buildPut(key, body, row));
11866
12031
  } catch (err) {
11867
12032
  this.logger.warn("durable persist failed — this key may re-seed on boot", {
11868
12033
  ...deviceId !== void 0 ? { tags: { deviceId } } : {},
@@ -15097,6 +15262,135 @@ function startPeriodicPass(deps) {
15097
15262
  };
15098
15263
  }
15099
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
15100
15394
  //#region src/recorder/addon/placement-guard.ts
15101
15395
  /**
15102
15396
  * Same-root guard for recordings placement.
@@ -15741,6 +16035,9 @@ var PlacementService = class {
15741
16035
  verified = /* @__PURE__ */ new Set();
15742
16036
  /** Locations the probe declared unusable — re-probed after a change. */
15743
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();
15744
16041
  /** Locations whose capacity could not be read, by reason. One line per
15745
16042
  * distinct fault (this is on the 30 s tick) and one when it clears. */
15746
16043
  capacityFaults = /* @__PURE__ */ new Map();
@@ -16255,6 +16552,7 @@ var PlacementService = class {
16255
16552
  return chosen;
16256
16553
  }
16257
16554
  this.verified.add(chosen.id);
16555
+ this.reportMountRecovered(chosen.id);
16258
16556
  if (result.outcome === "adopted" && result.foundVolumeId !== null) {
16259
16557
  try {
16260
16558
  await rememberVolumeId(this.deps.state, chosen.id, result.foundVolumeId);
@@ -16278,6 +16576,13 @@ var PlacementService = class {
16278
16576
  }
16279
16577
  return chosen;
16280
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
+ }
16281
16586
  /** One loud line + one alert + one audit row per mount fault. */
16282
16587
  reportMountFault(result, deviceId) {
16283
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", {
@@ -16291,8 +16596,10 @@ var PlacementService = class {
16291
16596
  }
16292
16597
  });
16293
16598
  try {
16599
+ this.faulted.add(result.locationId);
16294
16600
  this.deps.alert?.({
16295
16601
  locationId: result.locationId,
16602
+ reason: "unusable",
16296
16603
  detail: result.detail
16297
16604
  });
16298
16605
  } catch {}
@@ -23550,6 +23857,7 @@ var RecorderV2Addon = class extends BaseAddon {
23550
23857
  state: this.placementStore(),
23551
23858
  opsLog,
23552
23859
  alert: (input) => this.emitPlacementAlert(input),
23860
+ recovered: (locationId) => this.resolvePlacementAlert(locationId),
23553
23861
  capacityFor: (location) => this.locationCapacity(location.root),
23554
23862
  usedOnLocation: async (locationId) => (await this.segmentHours?.accountingForLocations(new Set([locationId])))?.bytes ?? 0,
23555
23863
  dailyBytesFor: (deviceId, profile) => this.dailyBytesFor(deviceId, profile)
@@ -23751,6 +24059,7 @@ var RecorderV2Addon = class extends BaseAddon {
23751
24059
  resumeForStorageMigration: (leaseId) => this.resumeForStorageMigration(leaseId),
23752
24060
  refreshStorageLocationsForMigration: (leaseId) => this.refreshStorageLocationsForMigration(leaseId),
23753
24061
  assertStorageMigrationLease: (leaseId) => this.assertStorageMigrationLease(leaseId),
24062
+ moverClearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
23754
24063
  placementSnapshot: () => this.placementSnapshot(),
23755
24064
  placementView: () => {
23756
24065
  const placement = this.placement;
@@ -24726,6 +25035,7 @@ var RecorderV2Addon = class extends BaseAddon {
24726
25035
  } });
24727
25036
  this.emitPlacementAlert({
24728
25037
  locationId: conflict.keptId,
25038
+ reason: "shared-root",
24729
25039
  detail: `Storage locations ${[conflict.keptId, ...conflict.demotedIds].join(", ")} all resolve to ${conflict.root}. Only ${conflict.keptId} will be written to.`
24730
25040
  });
24731
25041
  }
@@ -24864,6 +25174,7 @@ var RecorderV2Addon = class extends BaseAddon {
24864
25174
  const signature = pendingMovesSignature(moves);
24865
25175
  if (signature === this.pendingWriterMoves) return;
24866
25176
  this.pendingWriterMoves = signature;
25177
+ this.writerRestartAlerts().update(moves);
24867
25178
  if (moves.length === 0) return;
24868
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", {
24869
25180
  tags: { deviceId: move.deviceId },
@@ -24874,12 +25185,6 @@ var RecorderV2Addon = class extends BaseAddon {
24874
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)"
24875
25186
  }
24876
25187
  });
24877
- this.emitAlert({
24878
- severity: "warning",
24879
- title: "Recording placement waiting on a writer restart",
24880
- 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.`,
24881
- locationId: moves[0]?.plannedFor ?? ""
24882
- });
24883
25188
  }
24884
25189
  /**
24885
25190
  * A placement pass that plans NOTHING says so — once per episode.
@@ -25101,38 +25406,34 @@ var RecorderV2Addon = class extends BaseAddon {
25101
25406
  /**
25102
25407
  * Surface a storage-placement fault on the liveness/alert surface. STRICTLY
25103
25408
  * best-effort: the alerts cap is a nice-to-have and an unmounted disk must
25104
- * 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`.
25105
25411
  */
25106
25412
  emitPlacementAlert(input) {
25107
- this.emitAlert({
25108
- severity: "error",
25109
- title: "Recordings storage location unusable",
25110
- message: input.detail,
25111
- locationId: input.locationId
25112
- });
25113
- }
25114
- /** One storage-category alert. Advisory: the LOG line is the durable record,
25115
- * so a failed emit is swallowed rather than raised. */
25116
- emitAlert(input) {
25117
- this.ctx.api.alerts.emit.mutate({
25118
- id: randomUUID(),
25119
- category: "storage",
25120
- severity: input.severity,
25121
- title: input.title,
25122
- message: input.message,
25123
- status: "active",
25124
- read: false,
25125
- createdAt: Date.now(),
25126
- updatedAt: Date.now(),
25127
- source: {
25128
- type: "addon",
25129
- 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)
25130
25430
  },
25131
- metadata: {
25132
- locationId: input.locationId,
25133
- nodeId: this.nodeId
25134
- }
25135
- }).catch(() => {});
25431
+ logger: this.ctx.logger,
25432
+ sourceId: this.ctx.id,
25433
+ nodeId: this.nodeId,
25434
+ now: () => Date.now()
25435
+ });
25436
+ return this.placementAlertPublisher;
25136
25437
  }
25137
25438
  async pauseForStorageMigration(leaseId) {
25138
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-BaHsDOWa.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-BqI10eLV.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-B25q1OKG.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-Boq8JqyD.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.133",
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.381",
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.299",
36
+ version: "1.2.302",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",