@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,4 +1,4 @@
1
- const require_dist = require("./dist-BqI10eLV.js");
1
+ const require_dist = require("./dist-DvXRw_dM.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-BaHsDOWa.mjs";
1
+ import { Wn as hfModelUrl } from "./dist-CsCflIM1.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-BqI10eLV.js");
5
+ const require_dist = require("../dist-DvXRw_dM.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-BaHsDOWa.mjs";
1
+ import { An as DeviceType, Gn as hydrateSchema, Nn as BaseAddon, St as evaluateZoneRules, Wt as motionDetectionCapability } from "../dist-CsCflIM1.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-BaHsDOWa.mjs";
1
+ import { On as isSoftwareDecode, Tn as buildFfmpegArgs, jn as errMsg, wn as Fmp4BoxSplitter } from "./dist-CsCflIM1.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-BqI10eLV.js");
2
+ const require_dist = require("./dist-DvXRw_dM.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-BqI10eLV.js");
7
- const require_node = require("../node-CgwqjGz0.js");
8
- const require_custom_model_catalog = require("../custom-model-catalog-DgyafyuW.js");
6
+ const require_dist = require("../dist-DvXRw_dM.js");
7
+ const require_node = require("../node-DxAtLKEn.js");
8
+ const require_custom_model_catalog = require("../custom-model-catalog-BAnOZBOf.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-BaHsDOWa.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-B0a0dXhg.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-CK9GK3N5.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-CsCflIM1.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-DAp5MmRJ.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-Dv09Pvq_.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-BaHsDOWa.mjs";
1
+ import { Kt as parseProcStatus } from "./dist-CsCflIM1.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-BqI10eLV.js");
2
+ const require_dist = require("./dist-DvXRw_dM.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-BqI10eLV.js");
7
- const require_node = require("../node-CgwqjGz0.js");
6
+ const require_dist = require("../dist-DvXRw_dM.js");
7
+ const require_node = require("../node-DxAtLKEn.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");
@@ -6736,6 +6736,142 @@ Object.freeze({
6736
6736
  "path": "online",
6737
6737
  "valueType": "boolean"
6738
6738
  }],
6739
+ "dishwasher": [
6740
+ {
6741
+ "path": "availability",
6742
+ "valueType": "enum",
6743
+ "enumValues": [
6744
+ "available",
6745
+ "offline",
6746
+ "description-missing"
6747
+ ]
6748
+ },
6749
+ {
6750
+ "path": "state",
6751
+ "valueType": "enum",
6752
+ "enumValues": [
6753
+ "idle",
6754
+ "running",
6755
+ "paused",
6756
+ "finished",
6757
+ "error",
6758
+ "unavailable"
6759
+ ]
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
+ },
6776
+ {
6777
+ "path": "program",
6778
+ "valueType": "string"
6779
+ },
6780
+ {
6781
+ "path": "selectedProgram",
6782
+ "valueType": "string"
6783
+ },
6784
+ {
6785
+ "path": "remainingSeconds",
6786
+ "valueType": "number"
6787
+ },
6788
+ {
6789
+ "path": "delayedStartSeconds",
6790
+ "valueType": "number"
6791
+ },
6792
+ {
6793
+ "path": "progressPercent",
6794
+ "valueType": "number"
6795
+ },
6796
+ {
6797
+ "path": "doorOpen",
6798
+ "valueType": "boolean"
6799
+ },
6800
+ {
6801
+ "path": "doorLocked",
6802
+ "valueType": "boolean"
6803
+ },
6804
+ {
6805
+ "path": "remoteStartAllowed",
6806
+ "valueType": "boolean"
6807
+ },
6808
+ {
6809
+ "path": "remoteControlActive",
6810
+ "valueType": "boolean"
6811
+ },
6812
+ {
6813
+ "path": "localControlActive",
6814
+ "valueType": "boolean"
6815
+ },
6816
+ {
6817
+ "path": "powerOn",
6818
+ "valueType": "boolean"
6819
+ },
6820
+ {
6821
+ "path": "powerState",
6822
+ "valueType": "string"
6823
+ },
6824
+ {
6825
+ "path": "saltLevel",
6826
+ "valueType": "enum",
6827
+ "enumValues": [
6828
+ "ok",
6829
+ "low",
6830
+ "empty"
6831
+ ]
6832
+ },
6833
+ {
6834
+ "path": "rinseAidLevel",
6835
+ "valueType": "enum",
6836
+ "enumValues": [
6837
+ "ok",
6838
+ "low",
6839
+ "empty"
6840
+ ]
6841
+ },
6842
+ {
6843
+ "path": "halfLoad",
6844
+ "valueType": "boolean"
6845
+ },
6846
+ {
6847
+ "path": "hygienePlus",
6848
+ "valueType": "boolean"
6849
+ },
6850
+ {
6851
+ "path": "extraDry",
6852
+ "valueType": "boolean"
6853
+ },
6854
+ {
6855
+ "path": "intensiveZone",
6856
+ "valueType": "boolean"
6857
+ },
6858
+ {
6859
+ "path": "varioSpeedPlus",
6860
+ "valueType": "boolean"
6861
+ },
6862
+ {
6863
+ "path": "childLock",
6864
+ "valueType": "boolean"
6865
+ },
6866
+ {
6867
+ "path": "errorCode",
6868
+ "valueType": "number"
6869
+ },
6870
+ {
6871
+ "path": "errorLabel",
6872
+ "valueType": "string"
6873
+ }
6874
+ ],
6739
6875
  "doorbell": [{
6740
6876
  "path": "lastPressedAt",
6741
6877
  "valueType": "number"
@@ -6826,6 +6962,10 @@ Object.freeze({
6826
6962
  "path": "detected",
6827
6963
  "valueType": "boolean"
6828
6964
  }],
6965
+ "heating-valve": [{
6966
+ "path": "position",
6967
+ "valueType": "number"
6968
+ }],
6829
6969
  "humidifier": [
6830
6970
  {
6831
6971
  "path": "on",
@@ -10330,6 +10470,22 @@ var LOCATE_HYDRATE_MARGIN_MS = 6e4;
10330
10470
  */
10331
10471
  var WINDOW_READ_MAX_BYTES = 2e7;
10332
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
+ }
10333
10489
  function noPlayback(deviceId) {
10334
10490
  return {
10335
10491
  deviceId,
@@ -11132,13 +11288,13 @@ function buildRecordingProvider(deps) {
11132
11288
  startStorageMigrationMove: async (input) => {
11133
11289
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11134
11290
  deps.assertStorageMigrationLease?.(input.leaseId);
11135
- return { jobId: deps.relocate.start(input) };
11291
+ return { jobId: await startFootageMover(deps, deps.relocate, input) };
11136
11292
  },
11137
11293
  getStorageMigrationMoveStatus: async ({ jobId }) => deps.relocate?.list().find((job) => job.jobId === jobId) ?? null,
11138
11294
  cancelStorageMigrationMove: async ({ jobId }) => ({ cancelled: deps.relocate?.cancel(jobId) ?? false }),
11139
11295
  relocateFootage: async (input) => {
11140
11296
  if (!deps.relocate) throw new Error("relocation unavailable on this node");
11141
- const jobId = deps.relocate.start(input);
11297
+ const jobId = await startFootageMover(deps, deps.relocate, input);
11142
11298
  deps.logger.info("recorder: relocate job enqueued", {
11143
11299
  ...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
11144
11300
  meta: {
@@ -11755,13 +11911,10 @@ var DurableLedger = class {
11755
11911
  const body = this.spec.toValue(row);
11756
11912
  if (this.spec.writeMode === "write-behind") {
11757
11913
  this.mirror.set(key, row);
11758
- await this.persistRow(key, body);
11914
+ await this.persistRow(key, body, row);
11759
11915
  return;
11760
11916
  }
11761
- await this.kind.put({
11762
- id: key,
11763
- data: body
11764
- });
11917
+ await this.kind.put(this.buildPut(key, body, row));
11765
11918
  this.mirror.set(key, row);
11766
11919
  }
11767
11920
  /**
@@ -11862,15 +12015,27 @@ var DurableLedger = class {
11862
12015
  */
11863
12016
  persist(row) {
11864
12017
  const key = this.spec.keyOf(row);
11865
- this.persistRow(key, this.spec.toValue(row));
12018
+ this.persistRow(key, this.spec.toValue(row), row);
11866
12019
  }
11867
- async persistRow(key, body) {
11868
- const deviceId = this.deviceIdFromBody(body);
12020
+ buildPut(key, body, row) {
12021
+ const deviceId = this.resolveRecordDeviceId(row, body);
12022
+ return {
12023
+ id: key,
12024
+ data: body,
12025
+ ...deviceId !== void 0 ? { deviceId } : {}
12026
+ };
12027
+ }
12028
+ resolveRecordDeviceId(row, body) {
12029
+ if (row !== void 0) {
12030
+ const fromSpec = this.spec.deviceIdOf?.(row);
12031
+ if (fromSpec !== void 0 && Number.isFinite(fromSpec)) return fromSpec;
12032
+ }
12033
+ return this.deviceIdFromBody(body);
12034
+ }
12035
+ async persistRow(key, body, row) {
12036
+ const deviceId = this.resolveRecordDeviceId(row, body);
11869
12037
  try {
11870
- await this.kind.put({
11871
- id: key,
11872
- data: body
11873
- });
12038
+ await this.kind.put(this.buildPut(key, body, row));
11874
12039
  } catch (err) {
11875
12040
  this.logger.warn("durable persist failed — this key may re-seed on boot", {
11876
12041
  ...deviceId !== void 0 ? { tags: { deviceId } } : {},
@@ -15105,6 +15270,135 @@ function startPeriodicPass(deps) {
15105
15270
  };
15106
15271
  }
15107
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
15108
15402
  //#region src/recorder/addon/placement-guard.ts
15109
15403
  /**
15110
15404
  * Same-root guard for recordings placement.
@@ -15749,6 +16043,9 @@ var PlacementService = class {
15749
16043
  verified = /* @__PURE__ */ new Set();
15750
16044
  /** Locations the probe declared unusable — re-probed after a change. */
15751
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();
15752
16049
  /** Locations whose capacity could not be read, by reason. One line per
15753
16050
  * distinct fault (this is on the 30 s tick) and one when it clears. */
15754
16051
  capacityFaults = /* @__PURE__ */ new Map();
@@ -16263,6 +16560,7 @@ var PlacementService = class {
16263
16560
  return chosen;
16264
16561
  }
16265
16562
  this.verified.add(chosen.id);
16563
+ this.reportMountRecovered(chosen.id);
16266
16564
  if (result.outcome === "adopted" && result.foundVolumeId !== null) {
16267
16565
  try {
16268
16566
  await rememberVolumeId(this.deps.state, chosen.id, result.foundVolumeId);
@@ -16286,6 +16584,13 @@ var PlacementService = class {
16286
16584
  }
16287
16585
  return chosen;
16288
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
+ }
16289
16594
  /** One loud line + one alert + one audit row per mount fault. */
16290
16595
  reportMountFault(result, deviceId) {
16291
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", {
@@ -16299,8 +16604,10 @@ var PlacementService = class {
16299
16604
  }
16300
16605
  });
16301
16606
  try {
16607
+ this.faulted.add(result.locationId);
16302
16608
  this.deps.alert?.({
16303
16609
  locationId: result.locationId,
16610
+ reason: "unusable",
16304
16611
  detail: result.detail
16305
16612
  });
16306
16613
  } catch {}
@@ -23558,6 +23865,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
23558
23865
  state: this.placementStore(),
23559
23866
  opsLog,
23560
23867
  alert: (input) => this.emitPlacementAlert(input),
23868
+ recovered: (locationId) => this.resolvePlacementAlert(locationId),
23561
23869
  capacityFor: (location) => this.locationCapacity(location.root),
23562
23870
  usedOnLocation: async (locationId) => (await this.segmentHours?.accountingForLocations(new Set([locationId])))?.bytes ?? 0,
23563
23871
  dailyBytesFor: (deviceId, profile) => this.dailyBytesFor(deviceId, profile)
@@ -23759,6 +24067,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
23759
24067
  resumeForStorageMigration: (leaseId) => this.resumeForStorageMigration(leaseId),
23760
24068
  refreshStorageLocationsForMigration: (leaseId) => this.refreshStorageLocationsForMigration(leaseId),
23761
24069
  assertStorageMigrationLease: (leaseId) => this.assertStorageMigrationLease(leaseId),
24070
+ moverClearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
23762
24071
  placementSnapshot: () => this.placementSnapshot(),
23763
24072
  placementView: () => {
23764
24073
  const placement = this.placement;
@@ -24734,6 +25043,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
24734
25043
  } });
24735
25044
  this.emitPlacementAlert({
24736
25045
  locationId: conflict.keptId,
25046
+ reason: "shared-root",
24737
25047
  detail: `Storage locations ${[conflict.keptId, ...conflict.demotedIds].join(", ")} all resolve to ${conflict.root}. Only ${conflict.keptId} will be written to.`
24738
25048
  });
24739
25049
  }
@@ -24872,6 +25182,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
24872
25182
  const signature = pendingMovesSignature(moves);
24873
25183
  if (signature === this.pendingWriterMoves) return;
24874
25184
  this.pendingWriterMoves = signature;
25185
+ this.writerRestartAlerts().update(moves);
24875
25186
  if (moves.length === 0) return;
24876
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", {
24877
25188
  tags: { deviceId: move.deviceId },
@@ -24882,12 +25193,6 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
24882
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)"
24883
25194
  }
24884
25195
  });
24885
- this.emitAlert({
24886
- severity: "warning",
24887
- title: "Recording placement waiting on a writer restart",
24888
- 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.`,
24889
- locationId: moves[0]?.plannedFor ?? ""
24890
- });
24891
25196
  }
24892
25197
  /**
24893
25198
  * A placement pass that plans NOTHING says so — once per episode.
@@ -25109,38 +25414,34 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
25109
25414
  /**
25110
25415
  * Surface a storage-placement fault on the liveness/alert surface. STRICTLY
25111
25416
  * best-effort: the alerts cap is a nice-to-have and an unmounted disk must
25112
- * 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`.
25113
25419
  */
25114
25420
  emitPlacementAlert(input) {
25115
- this.emitAlert({
25116
- severity: "error",
25117
- title: "Recordings storage location unusable",
25118
- message: input.detail,
25119
- locationId: input.locationId
25120
- });
25121
- }
25122
- /** One storage-category alert. Advisory: the LOG line is the durable record,
25123
- * so a failed emit is swallowed rather than raised. */
25124
- emitAlert(input) {
25125
- this.ctx.api.alerts.emit.mutate({
25126
- id: (0, node_crypto.randomUUID)(),
25127
- category: "storage",
25128
- severity: input.severity,
25129
- title: input.title,
25130
- message: input.message,
25131
- status: "active",
25132
- read: false,
25133
- createdAt: Date.now(),
25134
- updatedAt: Date.now(),
25135
- source: {
25136
- type: "addon",
25137
- 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)
25138
25438
  },
25139
- metadata: {
25140
- locationId: input.locationId,
25141
- nodeId: this.nodeId
25142
- }
25143
- }).catch(() => {});
25439
+ logger: this.ctx.logger,
25440
+ sourceId: this.ctx.id,
25441
+ nodeId: this.nodeId,
25442
+ now: () => Date.now()
25443
+ });
25444
+ return this.placementAlertPublisher;
25144
25445
  }
25145
25446
  async pauseForStorageMigration(leaseId) {
25146
25447
  if (this.storageMigrationLeaseId !== null && this.storageMigrationLeaseId !== leaseId) throw new Error("recorder is already paused by another storage migration");