@camstack/addon-pipeline 1.2.602 → 1.2.604

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--ms2wSDI.mjs → custom-model-catalog-BWxCwQ1L.mjs} +2 -2
  4. package/dist/{custom-model-catalog-CVvdKnH2.js → custom-model-catalog-BmQ6g825.js} +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-BZ34hSbo.mjs} +516 -241
  8. package/dist/{dist-B4HyxJe1.js → dist-DGzDqaBO.js} +521 -240
  9. package/dist/{model-catalogs-Bj0f2Obs.js → model-catalogs-BZ7wE3tk.js} +1 -1
  10. package/dist/{model-catalogs-OnXVUgQY.mjs → model-catalogs-EjFqjUfh.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-CJPdIMAm.mjs} +1 -1
  14. package/dist/{node-DfuKhWUZ.js → node-CVbPzQN2.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-BPqkGyDA.mjs} +1 -1
  18. package/dist/{process-memory-BZ1_q3g3.js → process-memory-C8-aiceH.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-CxA4wyuM.mjs} +1 -1
  22. package/dist/{segment-demux-js-C7fMqBGZ.js → segment-demux-js-z3asNjJ8.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-D28YdqZf.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-DdrO8p3-.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-CT3_w1iD.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,4 +1,4 @@
1
- const require_dist = require("./dist-B4HyxJe1.js");
1
+ const require_dist = require("./dist-DGzDqaBO.js");
2
2
  //#region src/detection-pipeline/registry/model-licenses.ts
3
3
  var COCO_DATA = {
4
4
  name: "COCO 2017",
@@ -1,4 +1,4 @@
1
- import { Un as hfModelUrl } from "./dist-BmSyTuy4.mjs";
1
+ import { Wn as hfModelUrl } from "./dist-BZ34hSbo.mjs";
2
2
  //#region src/detection-pipeline/registry/model-licenses.ts
3
3
  var COCO_DATA = {
4
4
  name: "COCO 2017",
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-B4HyxJe1.js");
5
+ const require_dist = require("../dist-DGzDqaBO.js");
6
6
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Mn as BaseAddon, St as evaluateZoneRules, Wn as hydrateSchema, Wt as motionDetectionCapability, kn as DeviceType } from "../dist-BmSyTuy4.mjs";
1
+ import { An as DeviceType, Gn as hydrateSchema, Nn as BaseAddon, St as evaluateZoneRules, Wt as motionDetectionCapability } from "../dist-BZ34hSbo.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -1,4 +1,4 @@
1
- import { An as errMsg, Cn as Fmp4BoxSplitter, Dn as isSoftwareDecode, wn as buildFfmpegArgs } from "./dist-BmSyTuy4.mjs";
1
+ import { On as isSoftwareDecode, Tn as buildFfmpegArgs, jn as errMsg, wn as Fmp4BoxSplitter } from "./dist-BZ34hSbo.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
@@ -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-DGzDqaBO.js");
3
3
  require("node:os");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_fs = require("node:fs");
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-B4HyxJe1.js");
7
- const require_node = require("../node-DfuKhWUZ.js");
8
- const require_custom_model_catalog = require("../custom-model-catalog-CVvdKnH2.js");
6
+ const require_dist = require("../dist-DGzDqaBO.js");
7
+ const require_node = require("../node-CVbPzQN2.js");
8
+ const require_custom_model_catalog = require("../custom-model-catalog-BmQ6g825.js");
9
9
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
10
10
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
11
11
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
@@ -1,6 +1,6 @@
1
- import { A as NativeLeaseAdmissionSchema, An as errMsg, Bn as defineCustomActions, E as FailureCounters, Et as failureContributionCapability, Ft as loadContributionCapability, In as MODEL_FORMATS, Jt as pickDetailCropConvention, M as NodeBenchRunnerInputSchema, Mn as BaseAddon, P as PIPELINE_RAMP, Pt as linkedStepOf, Rn as createEvent, V as ROOT_SUPPLIABLE_STEP_IDS, Xn as nodePin, Y as allowedSuppliedEffect, Yt as pickNativeLeaseOverride, Zt as pipelineRunnerCapability, _ as DEFAULT_DETAIL_CROP_CONVENTION, a as BackgroundRunRegistry, ar as lazy, cr as object, dn as rootSuppliesOf, dr as string, gt as deriveRootFaceRefineRect, j as NativeLeaseSettingsSchema, m as CROP_SUPPLY_SOURCES, mt as deriveDetailCropRect, nr as array, nt as buildBenchStepTree, or as literal, pr as EventCategory, rr as boolean, rt as cameraOwnsMotionViaSensor, sn as resolveModelBoundSetting, sr as number, tr as _enum, un as resolveRootSupply, x as DEVICE_RAMP, y as DEFAULT_NATIVE_LEASE_SETTINGS, zn as customAction } from "../dist-BmSyTuy4.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node--4HtlkFo.mjs";
3
- import { a as stepsForElectedPool, b as getStepDefinition, d as rootObjectGate, f as localFrameRegistry, h as resolveFrameViewGeometry, i as placeDetailStep, l as resolveDefaultDetectionModel, m as createSessionDecodeCoordinator, p as isSessionDecodeReadGone, s as resolveDeviceEngine, t as CustomModelCatalog, u as macroGateVerdict, y as getStep } from "../custom-model-catalog--ms2wSDI.mjs";
1
+ import { A as NativeLeaseAdmissionSchema, Bn as customAction, E as FailureCounters, Et as failureContributionCapability, Ft as loadContributionCapability, Jt as pickDetailCropConvention, Ln as MODEL_FORMATS, M as NodeBenchRunnerInputSchema, Nn as BaseAddon, P as PIPELINE_RAMP, Pt as linkedStepOf, V as ROOT_SUPPLIABLE_STEP_IDS, Vn as defineCustomActions, Y as allowedSuppliedEffect, Yt as pickNativeLeaseOverride, Zn as nodePin, Zt as pipelineRunnerCapability, _ as DEFAULT_DETAIL_CROP_CONVENTION, a as BackgroundRunRegistry, cr as number, dn as rootSuppliesOf, fr as string, gt as deriveRootFaceRefineRect, ir as boolean, j as NativeLeaseSettingsSchema, jn as errMsg, lr as object, m as CROP_SUPPLY_SOURCES, mr as EventCategory, mt as deriveDetailCropRect, nr as _enum, nt as buildBenchStepTree, or as lazy, rr as array, rt as cameraOwnsMotionViaSensor, sn as resolveModelBoundSetting, sr as literal, un as resolveRootSupply, x as DEVICE_RAMP, y as DEFAULT_NATIVE_LEASE_SETTINGS, zn as createEvent } from "../dist-BZ34hSbo.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CJPdIMAm.mjs";
3
+ import { a as stepsForElectedPool, b as getStepDefinition, d as rootObjectGate, f as localFrameRegistry, h as resolveFrameViewGeometry, i as placeDetailStep, l as resolveDefaultDetectionModel, m as createSessionDecodeCoordinator, p as isSessionDecodeReadGone, s as resolveDeviceEngine, t as CustomModelCatalog, u as macroGateVerdict, y as getStep } from "../custom-model-catalog-BWxCwQ1L.mjs";
4
4
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
6
6
  import { c as withDetectionIntent, o as preRollRequestOf, t as DETECTION_PRE_ROLL_REQUEST_MS } from "../restream-intent-DyGmLAU9.mjs";
@@ -1,4 +1,4 @@
1
- import { Kt as parseProcStatus } from "./dist-BmSyTuy4.mjs";
1
+ import { Kt as parseProcStatus } from "./dist-BZ34hSbo.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
4
  //#region src/shared/node-topology-platform.ts
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-B4HyxJe1.js");
2
+ const require_dist = require("./dist-DGzDqaBO.js");
3
3
  let node_fs = require("node:fs");
4
4
  node_fs = require_chunk.__toESM(node_fs);
5
5
  let node_child_process = require("node:child_process");
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-B4HyxJe1.js");
7
- const require_node = require("../node-DfuKhWUZ.js");
6
+ const require_dist = require("../dist-DGzDqaBO.js");
7
+ const require_node = require("../node-CVbPzQN2.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
9
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
10
10
  let node_crypto = require("node:crypto");
@@ -6737,6 +6737,15 @@ Object.freeze({
6737
6737
  "valueType": "boolean"
6738
6738
  }],
6739
6739
  "dishwasher": [
6740
+ {
6741
+ "path": "availability",
6742
+ "valueType": "enum",
6743
+ "enumValues": [
6744
+ "available",
6745
+ "offline",
6746
+ "description-missing"
6747
+ ]
6748
+ },
6740
6749
  {
6741
6750
  "path": "state",
6742
6751
  "valueType": "enum",
@@ -6749,12 +6758,35 @@ Object.freeze({
6749
6758
  "unavailable"
6750
6759
  ]
6751
6760
  },
6761
+ {
6762
+ "path": "operationState",
6763
+ "valueType": "enum",
6764
+ "enumValues": [
6765
+ "Inactive",
6766
+ "Ready",
6767
+ "DelayedStart",
6768
+ "Run",
6769
+ "Pause",
6770
+ "ActionRequired",
6771
+ "Finished",
6772
+ "Error",
6773
+ "Aborting"
6774
+ ]
6775
+ },
6752
6776
  {
6753
6777
  "path": "program",
6754
6778
  "valueType": "string"
6755
6779
  },
6756
6780
  {
6757
- "path": "remainingMinutes",
6781
+ "path": "selectedProgram",
6782
+ "valueType": "string"
6783
+ },
6784
+ {
6785
+ "path": "remainingSeconds",
6786
+ "valueType": "number"
6787
+ },
6788
+ {
6789
+ "path": "delayedStartSeconds",
6758
6790
  "valueType": "number"
6759
6791
  },
6760
6792
  {
@@ -6765,14 +6797,30 @@ Object.freeze({
6765
6797
  "path": "doorOpen",
6766
6798
  "valueType": "boolean"
6767
6799
  },
6800
+ {
6801
+ "path": "doorLocked",
6802
+ "valueType": "boolean"
6803
+ },
6768
6804
  {
6769
6805
  "path": "remoteStartAllowed",
6770
6806
  "valueType": "boolean"
6771
6807
  },
6808
+ {
6809
+ "path": "remoteControlActive",
6810
+ "valueType": "boolean"
6811
+ },
6812
+ {
6813
+ "path": "localControlActive",
6814
+ "valueType": "boolean"
6815
+ },
6772
6816
  {
6773
6817
  "path": "powerOn",
6774
6818
  "valueType": "boolean"
6775
6819
  },
6820
+ {
6821
+ "path": "powerState",
6822
+ "valueType": "string"
6823
+ },
6776
6824
  {
6777
6825
  "path": "saltLevel",
6778
6826
  "valueType": "enum",
@@ -10422,6 +10470,22 @@ var LOCATE_HYDRATE_MARGIN_MS = 6e4;
10422
10470
  */
10423
10471
  var WINDOW_READ_MAX_BYTES = 2e7;
10424
10472
  /** Empty "no playback" manifest. */
10473
+ /**
10474
+ * Start a footage move only while no single-location repoint holds an
10475
+ * overlapping physical root (the storage orchestrator owns that claim).
10476
+ */
10477
+ function startFootageMover(deps, engine, input) {
10478
+ const ask = deps.moverClearance;
10479
+ if (ask === void 0) return Promise.resolve(engine.start(input));
10480
+ return require_dist.startMoverUnderClearance({
10481
+ clearance: ask,
10482
+ start: () => engine.start(input),
10483
+ cancel: (jobId) => {
10484
+ engine.cancel(jobId);
10485
+ },
10486
+ locationIds: [input.fromLocationId, input.toLocationId]
10487
+ });
10488
+ }
10425
10489
  function noPlayback(deviceId) {
10426
10490
  return {
10427
10491
  deviceId,
@@ -11224,13 +11288,13 @@ function buildRecordingProvider(deps) {
11224
11288
  startStorageMigrationMove: async (input) => {
11225
11289
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11226
11290
  deps.assertStorageMigrationLease?.(input.leaseId);
11227
- return { jobId: deps.relocate.start(input) };
11291
+ return { jobId: await startFootageMover(deps, deps.relocate, input) };
11228
11292
  },
11229
11293
  getStorageMigrationMoveStatus: async ({ jobId }) => deps.relocate?.list().find((job) => job.jobId === jobId) ?? null,
11230
11294
  cancelStorageMigrationMove: async ({ jobId }) => ({ cancelled: deps.relocate?.cancel(jobId) ?? false }),
11231
11295
  relocateFootage: async (input) => {
11232
11296
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11233
- const jobId = deps.relocate.start(input);
11297
+ const jobId = await startFootageMover(deps, deps.relocate, input);
11234
11298
  deps.logger.info("recorder: relocate job enqueued", {
11235
11299
  ...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
11236
11300
  meta: {
@@ -15206,6 +15270,135 @@ function startPeriodicPass(deps) {
15206
15270
  };
15207
15271
  }
15208
15272
  //#endregion
15273
+ //#region src/recorder/addon/placement-alerts.ts
15274
+ var SEVERITY = {
15275
+ unusable: "critical",
15276
+ "shared-root": "error",
15277
+ "writer-restart": "warning"
15278
+ };
15279
+ var TITLE = {
15280
+ unusable: "Recordings storage location unusable",
15281
+ "shared-root": "Recordings storage locations share one disk",
15282
+ "writer-restart": "Recording placement waiting on a writer restart"
15283
+ };
15284
+ /** Stable identity of one fault: the alert id AND its episode key. */
15285
+ function placementAlertId(locationId, reason) {
15286
+ return `recorder-placement:${locationId}:${reason}`;
15287
+ }
15288
+ var PlacementAlertPublisher = class {
15289
+ deps;
15290
+ episodes = /* @__PURE__ */ new Map();
15291
+ constructor(deps) {
15292
+ this.deps = deps;
15293
+ }
15294
+ /** Raise (or refresh) the alert of one fault. Advisory: never throws. */
15295
+ raise(input) {
15296
+ const id = placementAlertId(input.locationId, input.reason);
15297
+ const now = this.deps.now();
15298
+ const episode = this.episodes.get(id) ?? {
15299
+ createdAt: now,
15300
+ count: 0
15301
+ };
15302
+ episode.count += 1;
15303
+ this.episodes.set(id, episode);
15304
+ const alert = {
15305
+ id,
15306
+ category: "storage",
15307
+ severity: SEVERITY[input.reason],
15308
+ title: TITLE[input.reason],
15309
+ message: input.detail,
15310
+ status: "active",
15311
+ read: false,
15312
+ createdAt: episode.createdAt,
15313
+ updatedAt: now,
15314
+ source: {
15315
+ type: "addon",
15316
+ id: this.deps.sourceId
15317
+ },
15318
+ metadata: {
15319
+ locationId: input.locationId,
15320
+ nodeId: this.deps.nodeId,
15321
+ reason: input.reason,
15322
+ episodeKey: id,
15323
+ count: episode.count
15324
+ }
15325
+ };
15326
+ this.deps.port.emit(alert).catch((err) => {
15327
+ this.deps.logger.warn("recorder: storage alert emit failed (advisory)", { meta: {
15328
+ locationId: input.locationId,
15329
+ reason: input.reason,
15330
+ error: require_dist.errMsg(err)
15331
+ } });
15332
+ });
15333
+ }
15334
+ /** The fault is gone: close its alert. A fault that never fired is a no-op. */
15335
+ resolve(locationId, reason) {
15336
+ const id = placementAlertId(locationId, reason);
15337
+ const episode = this.episodes.get(id);
15338
+ if (episode === void 0) return;
15339
+ this.episodes.delete(id);
15340
+ this.deps.logger.info("recorder: storage fault recovered — closing its alert", { meta: {
15341
+ locationId,
15342
+ reason,
15343
+ alertId: id,
15344
+ raisedTimes: episode.count
15345
+ } });
15346
+ this.deps.port.update({
15347
+ alertId: id,
15348
+ patch: {
15349
+ status: "completed",
15350
+ read: true,
15351
+ updatedAt: this.deps.now()
15352
+ }
15353
+ }).catch((err) => {
15354
+ this.deps.logger.warn("recorder: closing a recovered storage alert failed (advisory)", { meta: {
15355
+ locationId,
15356
+ reason,
15357
+ alertId: id,
15358
+ error: require_dist.errMsg(err)
15359
+ } });
15360
+ });
15361
+ }
15362
+ };
15363
+ /**
15364
+ * Keeps the `writer-restart` alerts true to the live set of pending writer
15365
+ * moves: one alert per PLANNED location, raised while any camera is still
15366
+ * pending for it, completed when none is (the move landed, or the plan was
15367
+ * dropped - the target was drained or deleted).
15368
+ */
15369
+ var WriterRestartAlerts = class {
15370
+ publisher;
15371
+ logger;
15372
+ previous = [];
15373
+ constructor(publisher, logger) {
15374
+ this.publisher = publisher;
15375
+ this.logger = logger;
15376
+ }
15377
+ update(moves) {
15378
+ const byLocation = /* @__PURE__ */ new Map();
15379
+ for (const move of moves) byLocation.set(move.plannedFor, (byLocation.get(move.plannedFor) ?? 0) + 1);
15380
+ for (const [locationId, count] of byLocation) this.publisher.raise({
15381
+ locationId,
15382
+ reason: "writer-restart",
15383
+ 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.`
15384
+ });
15385
+ const stillPending = new Set(moves.map((m) => `${String(m.deviceId)}:${m.profile}>${m.plannedFor}`));
15386
+ for (const gone of this.previous) {
15387
+ if (stillPending.has(`${String(gone.deviceId)}:${gone.profile}>${gone.plannedFor}`)) continue;
15388
+ this.logger.info("recorder placement: writer-restart no longer pending for this camera", {
15389
+ tags: { deviceId: gone.deviceId },
15390
+ meta: {
15391
+ locationId: gone.plannedFor,
15392
+ profile: gone.profile,
15393
+ writingTo: gone.writingTo
15394
+ }
15395
+ });
15396
+ }
15397
+ for (const gone of this.previous) if (!byLocation.has(gone.plannedFor)) this.publisher.resolve(gone.plannedFor, "writer-restart");
15398
+ this.previous = moves;
15399
+ }
15400
+ };
15401
+ //#endregion
15209
15402
  //#region src/recorder/addon/placement-guard.ts
15210
15403
  /**
15211
15404
  * Same-root guard for recordings placement.
@@ -15850,6 +16043,9 @@ var PlacementService = class {
15850
16043
  verified = /* @__PURE__ */ new Set();
15851
16044
  /** Locations the probe declared unusable — re-probed after a change. */
15852
16045
  unusable = /* @__PURE__ */ new Set();
16046
+ /** Locations with an OPEN mount-fault alert. Survives a re-resolve (unlike
16047
+ * `unusable`): the alert closes only when the probe verifies the volume. */
16048
+ faulted = /* @__PURE__ */ new Set();
15853
16049
  /** Locations whose capacity could not be read, by reason. One line per
15854
16050
  * distinct fault (this is on the 30 s tick) and one when it clears. */
15855
16051
  capacityFaults = /* @__PURE__ */ new Map();
@@ -16364,6 +16560,7 @@ var PlacementService = class {
16364
16560
  return chosen;
16365
16561
  }
16366
16562
  this.verified.add(chosen.id);
16563
+ this.reportMountRecovered(chosen.id);
16367
16564
  if (result.outcome === "adopted" && result.foundVolumeId !== null) {
16368
16565
  try {
16369
16566
  await rememberVolumeId(this.deps.state, chosen.id, result.foundVolumeId);
@@ -16387,6 +16584,13 @@ var PlacementService = class {
16387
16584
  }
16388
16585
  return chosen;
16389
16586
  }
16587
+ /** The probe verified a location that had an open fault: close its alert. */
16588
+ reportMountRecovered(locationId) {
16589
+ if (!this.faulted.delete(locationId)) return;
16590
+ try {
16591
+ this.deps.recovered?.(locationId);
16592
+ } catch {}
16593
+ }
16390
16594
  /** One loud line + one alert + one audit row per mount fault. */
16391
16595
  reportMountFault(result, deviceId) {
16392
16596
  this.deps.logger.error("recorder placement: storage volume MISMATCH — the disk under this location is not the one it last wrote to; failing over", {
@@ -16400,8 +16604,10 @@ var PlacementService = class {
16400
16604
  }
16401
16605
  });
16402
16606
  try {
16607
+ this.faulted.add(result.locationId);
16403
16608
  this.deps.alert?.({
16404
16609
  locationId: result.locationId,
16610
+ reason: "unusable",
16405
16611
  detail: result.detail
16406
16612
  });
16407
16613
  } catch {}
@@ -23659,6 +23865,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
23659
23865
  state: this.placementStore(),
23660
23866
  opsLog,
23661
23867
  alert: (input) => this.emitPlacementAlert(input),
23868
+ recovered: (locationId) => this.resolvePlacementAlert(locationId),
23662
23869
  capacityFor: (location) => this.locationCapacity(location.root),
23663
23870
  usedOnLocation: async (locationId) => (await this.segmentHours?.accountingForLocations(new Set([locationId])))?.bytes ?? 0,
23664
23871
  dailyBytesFor: (deviceId, profile) => this.dailyBytesFor(deviceId, profile)
@@ -23860,6 +24067,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
23860
24067
  resumeForStorageMigration: (leaseId) => this.resumeForStorageMigration(leaseId),
23861
24068
  refreshStorageLocationsForMigration: (leaseId) => this.refreshStorageLocationsForMigration(leaseId),
23862
24069
  assertStorageMigrationLease: (leaseId) => this.assertStorageMigrationLease(leaseId),
24070
+ moverClearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
23863
24071
  placementSnapshot: () => this.placementSnapshot(),
23864
24072
  placementView: () => {
23865
24073
  const placement = this.placement;
@@ -24835,6 +25043,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
24835
25043
  } });
24836
25044
  this.emitPlacementAlert({
24837
25045
  locationId: conflict.keptId,
25046
+ reason: "shared-root",
24838
25047
  detail: `Storage locations ${[conflict.keptId, ...conflict.demotedIds].join(", ")} all resolve to ${conflict.root}. Only ${conflict.keptId} will be written to.`
24839
25048
  });
24840
25049
  }
@@ -24973,6 +25182,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
24973
25182
  const signature = pendingMovesSignature(moves);
24974
25183
  if (signature === this.pendingWriterMoves) return;
24975
25184
  this.pendingWriterMoves = signature;
25185
+ this.writerRestartAlerts().update(moves);
24976
25186
  if (moves.length === 0) return;
24977
25187
  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", {
24978
25188
  tags: { deviceId: move.deviceId },
@@ -24983,12 +25193,6 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
24983
25193
  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)"
24984
25194
  }
24985
25195
  });
24986
- this.emitAlert({
24987
- severity: "warning",
24988
- title: "Recording placement waiting on a writer restart",
24989
- 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.`,
24990
- locationId: moves[0]?.plannedFor ?? ""
24991
- });
24992
25196
  }
24993
25197
  /**
24994
25198
  * A placement pass that plans NOTHING says so — once per episode.
@@ -25210,38 +25414,34 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
25210
25414
  /**
25211
25415
  * Surface a storage-placement fault on the liveness/alert surface. STRICTLY
25212
25416
  * best-effort: the alerts cap is a nice-to-have and an unmounted disk must
25213
- * still be shouted about in the log even when it is unreachable.
25417
+ * still be shouted about in the log even when it is unreachable. One row per
25418
+ * (location, reason) episode - see `placement-alerts.ts`.
25214
25419
  */
25215
25420
  emitPlacementAlert(input) {
25216
- this.emitAlert({
25217
- severity: "error",
25218
- title: "Recordings storage location unusable",
25219
- message: input.detail,
25220
- locationId: input.locationId
25221
- });
25222
- }
25223
- /** One storage-category alert. Advisory: the LOG line is the durable record,
25224
- * so a failed emit is swallowed rather than raised. */
25225
- emitAlert(input) {
25226
- this.ctx.api.alerts.emit.mutate({
25227
- id: (0, node_crypto.randomUUID)(),
25228
- category: "storage",
25229
- severity: input.severity,
25230
- title: input.title,
25231
- message: input.message,
25232
- status: "active",
25233
- read: false,
25234
- createdAt: Date.now(),
25235
- updatedAt: Date.now(),
25236
- source: {
25237
- type: "addon",
25238
- id: this.ctx.id
25421
+ this.placementAlerts().raise(input);
25422
+ }
25423
+ /** The faulted location passed its volume probe again: close its alert. */
25424
+ resolvePlacementAlert(locationId) {
25425
+ this.placementAlerts().resolve(locationId, "unusable");
25426
+ }
25427
+ placementAlertPublisher = null;
25428
+ writerRestartAlertSync = null;
25429
+ writerRestartAlerts() {
25430
+ this.writerRestartAlertSync ??= new WriterRestartAlerts(this.placementAlerts(), this.ctx.logger);
25431
+ return this.writerRestartAlertSync;
25432
+ }
25433
+ placementAlerts() {
25434
+ this.placementAlertPublisher ??= new PlacementAlertPublisher({
25435
+ port: {
25436
+ emit: (alert) => this.ctx.api.alerts.emit.mutate(alert),
25437
+ update: (input) => this.ctx.api.alerts.update.mutate(input)
25239
25438
  },
25240
- metadata: {
25241
- locationId: input.locationId,
25242
- nodeId: this.nodeId
25243
- }
25244
- }).catch(() => {});
25439
+ logger: this.ctx.logger,
25440
+ sourceId: this.ctx.id,
25441
+ nodeId: this.nodeId,
25442
+ now: () => Date.now()
25443
+ });
25444
+ return this.placementAlertPublisher;
25245
25445
  }
25246
25446
  async pauseForStorageMigration(leaseId) {
25247
25447
  if (this.storageMigrationLeaseId !== null && this.storageMigrationLeaseId !== leaseId) throw new Error("recorder is already paused by another storage migration");