@camstack/system 1.2.8 → 1.2.10

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 (56) hide show
  1. package/dist/addon-runner.js +6 -6
  2. package/dist/addon-runner.mjs +6 -6
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +1 -1
  10. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  23. package/dist/builtins/local-network/local-network.addon.js +1 -1
  24. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  25. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  26. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  27. package/dist/builtins/platform-probe/index.js +1 -1
  28. package/dist/builtins/platform-probe/index.mjs +1 -1
  29. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  30. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  31. package/dist/builtins/snapshot/index.js +29 -1
  32. package/dist/builtins/snapshot/index.mjs +29 -1
  33. package/dist/builtins/snapshot/snapshot.addon.d.ts +6 -0
  34. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  35. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  36. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  37. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  38. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +12 -0
  39. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +48 -7
  40. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +48 -7
  41. package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +27 -0
  42. package/dist/builtins/system-config/system-config.addon.js +1 -1
  43. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  44. package/dist/builtins/winston-logging/index.js +1 -1
  45. package/dist/builtins/winston-logging/index.mjs +1 -1
  46. package/dist/{custom-action-registry-BEXwC-oo.mjs → custom-action-registry-F__gp_VX.mjs} +1 -1
  47. package/dist/{custom-action-registry-vLYEFTtv.js → custom-action-registry-jY0NOZK8.js} +1 -1
  48. package/dist/{dist-CdgScQ3H.js → dist-BQOCTD5b.js} +1664 -1434
  49. package/dist/{dist-CxM20CkF.mjs → dist-DpKeSZel.mjs} +1659 -1435
  50. package/dist/index.js +3 -3
  51. package/dist/index.mjs +3 -3
  52. package/dist/kernel/custom-action-registry.d.ts +3 -3
  53. package/dist/kernel/transport/child-cap-protocol.d.ts +11 -1
  54. package/dist/{manifest-python-deps-CUHn5wqq.js → manifest-python-deps-1I7WdPDD.js} +5 -3
  55. package/dist/{manifest-python-deps-CcW0WpfS.mjs → manifest-python-deps-I2MGvDvo.mjs} +5 -3
  56. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { Q as storageCapability, at as BaseAddon, u as StorageLocationTypeSchema, wt as parseJsonObject } from "../../dist-CxM20CkF.mjs";
1
+ import { $ as storageCapability, Tt as parseJsonObject, d as StorageLocationTypeSchema, ot as BaseAddon } from "../../dist-DpKeSZel.mjs";
2
2
  import * as path$1 from "node:path";
3
3
  import * as fs from "node:fs/promises";
4
4
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -607,10 +607,19 @@ var StoragePressureManager = class {
607
607
  /** Relieve every managed location currently under its threshold. */
608
608
  async sweep() {
609
609
  const thresholds = await this.deps.getLocationThresholds();
610
+ const relievedDevices = /* @__PURE__ */ new Set();
610
611
  for (const [locationId, threshold] of thresholds) {
611
612
  if (threshold <= 0) continue;
613
+ const deviceKey = await this.deps.getLocationDeviceKey?.(locationId) ?? null;
614
+ if (deviceKey !== null && relievedDevices.has(deviceKey)) {
615
+ this.deps.logger.debug("storage pressure: skipping location — physical device already relieved this sweep", { meta: {
616
+ locationId,
617
+ deviceKey
618
+ } });
619
+ continue;
620
+ }
612
621
  try {
613
- await this.relieveLocation(locationId, threshold);
622
+ if (await this.relieveLocation(locationId, threshold) && deviceKey !== null) relievedDevices.add(deviceKey);
614
623
  } catch (err) {
615
624
  this.deps.logger.warn("storage pressure: relief failed for location", { meta: {
616
625
  locationId,
@@ -619,16 +628,25 @@ var StoragePressureManager = class {
619
628
  }
620
629
  }
621
630
  }
631
+ /**
632
+ * Relieve one location until it recovers, nothing is evictable, or a round
633
+ * makes no progress. Returns `true` when at least one relief round actually
634
+ * reclaimed bytes (so the caller marks the location's physical device relieved
635
+ * for this sweep — the trigger for the over-eviction dedup: freed bytes mean a
636
+ * sibling alias may re-read a not-yet-settled `statfs`), `false` when nothing
637
+ * was freed (never breached, or breached with no evictable data).
638
+ */
622
639
  async relieveLocation(locationId, threshold) {
623
640
  const maxRounds = this.deps.maxRounds ?? DEFAULT_MAX_ROUNDS;
624
641
  const hysteresis = this.deps.hysteresisPercent ?? DEFAULT_HYSTERESIS_PERCENT;
642
+ let reclaimedAny = false;
625
643
  for (let round = 0; round < maxRounds; round++) {
626
644
  const free = await this.deps.getFreePercent(locationId);
627
- if (free >= threshold) return;
645
+ if (free >= threshold) return reclaimedAny;
628
646
  const total = await this.deps.getVolumeTotalBytes(locationId);
629
647
  const deficitPercent = threshold - free + hysteresis;
630
648
  const targetBytes = Math.ceil(total * deficitPercent / 100);
631
- if (targetBytes <= 0) return;
649
+ if (targetBytes <= 0) return reclaimedAny;
632
650
  const providers = this.deps.getEvictableProviders();
633
651
  const usages = await Promise.all(providers.map(async (p) => ({
634
652
  p,
@@ -641,7 +659,7 @@ var StoragePressureManager = class {
641
659
  freePercent: free,
642
660
  threshold
643
661
  } });
644
- return;
662
+ return reclaimedAny;
645
663
  }
646
664
  let reclaimed = 0;
647
665
  for (const u of usages) {
@@ -653,6 +671,7 @@ var StoragePressureManager = class {
653
671
  });
654
672
  reclaimed += res.reclaimedBytes;
655
673
  }
674
+ if (reclaimed > 0) reclaimedAny = true;
656
675
  this.deps.logger.info("storage pressure: relief round", { meta: {
657
676
  locationId,
658
677
  round,
@@ -661,8 +680,9 @@ var StoragePressureManager = class {
661
680
  targetBytes,
662
681
  reclaimedBytes: reclaimed
663
682
  } });
664
- if (reclaimed <= 0) return;
683
+ if (reclaimed <= 0) return reclaimedAny;
665
684
  }
685
+ return reclaimedAny;
666
686
  }
667
687
  };
668
688
  //#endregion
@@ -916,7 +936,8 @@ var StorageOrchestratorAddon = class extends BaseAddon {
916
936
  getFreePercent: (id) => this.locationFreePercent(id),
917
937
  getVolumeTotalBytes: (id) => this.locationVolumeTotalBytes(id),
918
938
  getEvictableProviders: () => this.ctx.kernel.capabilityRegistry?.getCollection(STORAGE_EVICTABLE_CAP_NAME) ?? [],
919
- getLocationThresholds: () => this.resolveLocationThresholds()
939
+ getLocationThresholds: () => this.resolveLocationThresholds(),
940
+ getLocationDeviceKey: (id) => this.locationDeviceKey(id)
920
941
  });
921
942
  this.pressureTimer = setInterval(() => {
922
943
  this.runPressureSweep();
@@ -1029,6 +1050,26 @@ var StorageOrchestratorAddon = class extends BaseAddon {
1029
1050
  const bp = (this.service?.listLocations().find((l) => l.id === locationId))?.config["basePath"];
1030
1051
  return typeof bp === "string" && bp.length > 0 ? bp : null;
1031
1052
  }
1053
+ /**
1054
+ * A stable key for the PHYSICAL storage device behind a location — the
1055
+ * `realpath` of its `basePath`, so symlinked / bind-mounted aliases and
1056
+ * same-root managed locations (`recordings:default` + `recordingsLow:default`,
1057
+ * both at `/recordings`) collapse to one key. Feeds the pressure manager's
1058
+ * in-sweep dedup so a shared disk is relieved at most once per tick (closing
1059
+ * the bounded over-eviction window; see `getLocationDeviceKey`). Falls back to
1060
+ * the lexically-resolved path when realpath throws (root missing at boot),
1061
+ * `null` when the location has no basePath — either way dedup degrades to
1062
+ * per-location, never merging two distinct disks.
1063
+ */
1064
+ async locationDeviceKey(locationId) {
1065
+ const bp = this.locationBasePath(locationId);
1066
+ if (!bp) return null;
1067
+ try {
1068
+ return await fs.realpath(bp);
1069
+ } catch {
1070
+ return path$1.resolve(bp);
1071
+ }
1072
+ }
1032
1073
  /** Free capacity (%) on a location's volume via `statfs`; 100 (guard inert) when unstattable. */
1033
1074
  async locationFreePercent(locationId) {
1034
1075
  const bp = this.locationBasePath(locationId);
@@ -28,11 +28,38 @@ export interface PressureManagerDeps {
28
28
  readonly maxRounds?: number;
29
29
  /** Extra headroom % freed beyond the threshold to avoid re-breaching immediately (default 1). */
30
30
  readonly hysteresisPercent?: number;
31
+ /**
32
+ * Optional: map a locationId to a stable key identifying its PHYSICAL storage
33
+ * device — the `realpath` of the location's `basePath`, so aliases (symlinks /
34
+ * bind-mounts) and same-root managed locations (`recordings:default` +
35
+ * `recordingsLow:default`, both at `/recordings`) share one key.
36
+ *
37
+ * Powers an in-sweep dedup: once a physical device has actually been RELIEVED
38
+ * this sweep, other locations resolving to the SAME device are skipped for the
39
+ * rest of the tick. This closes a bounded OVER-eviction window — after the
40
+ * first alias frees the shared disk, a second alias re-reading not-yet-settled
41
+ * `statfs` free% (lazy fs accounting, open fds) could see it still breached and
42
+ * evict the oldest footage a second time. A fresh sweep on the next timer tick
43
+ * re-checks the settled free% and finishes any genuinely-residual pressure, so
44
+ * skipping can only ever DEFER relief (self-correcting), never over-delete.
45
+ *
46
+ * Omitted, or returning `null`, → no dedup (each location relieved
47
+ * independently), preserving prior single-disk behaviour.
48
+ */
49
+ readonly getLocationDeviceKey?: (locationId: string) => Promise<string | null>;
31
50
  }
32
51
  export declare class StoragePressureManager {
33
52
  private readonly deps;
34
53
  constructor(deps: PressureManagerDeps);
35
54
  /** Relieve every managed location currently under its threshold. */
36
55
  sweep(): Promise<void>;
56
+ /**
57
+ * Relieve one location until it recovers, nothing is evictable, or a round
58
+ * makes no progress. Returns `true` when at least one relief round actually
59
+ * reclaimed bytes (so the caller marks the location's physical device relieved
60
+ * for this sweep — the trigger for the over-eviction dedup: freed bytes mean a
61
+ * sibling alias may re-read a not-yet-settled `statfs`), `false` when nothing
62
+ * was freed (never breached, or breached with no evictable data).
63
+ */
37
64
  private relieveLocation;
38
65
  }
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CdgScQ3H.js");
6
+ const require_dist = require("../../dist-BQOCTD5b.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { St as hydrateSchema, at as BaseAddon, it as errMsg } from "../../dist-CxM20CkF.mjs";
1
+ import { Ct as hydrateSchema, at as errMsg, ot as BaseAddon } from "../../dist-DpKeSZel.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CdgScQ3H.js");
6
+ const require_dist = require("../../dist-BQOCTD5b.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { R as logDestinationCapability, at as BaseAddon } from "../../dist-CxM20CkF.mjs";
1
+ import { ot as BaseAddon, z as logDestinationCapability } from "../../dist-DpKeSZel.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -16,7 +16,7 @@ var CustomActionRegistry = class {
16
16
  if (scope.kind !== "system") throw new Error(`custom action '${addonId}.${name}' declared scope '${scope.kind}' — not yet implemented`);
17
17
  actions.set(name, {
18
18
  spec,
19
- handler: (input) => handler(name, input)
19
+ handler: (input, caller) => handler(name, input, caller)
20
20
  });
21
21
  }
22
22
  this.byAddon.set(addonId, actions);
@@ -16,7 +16,7 @@ var CustomActionRegistry = class {
16
16
  if (scope.kind !== "system") throw new Error(`custom action '${addonId}.${name}' declared scope '${scope.kind}' — not yet implemented`);
17
17
  actions.set(name, {
18
18
  spec,
19
- handler: (input) => handler(name, input)
19
+ handler: (input, caller) => handler(name, input, caller)
20
20
  });
21
21
  }
22
22
  this.byAddon.set(addonId, actions);