@camstack/system 1.2.22 → 1.2.24
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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +0 -9
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -40
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -40
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.d.ts +16 -0
- package/dist/builtins/local-network/local-network.addon.js +30 -2
- package/dist/builtins/local-network/local-network.addon.mjs +30 -2
- package/dist/builtins/loki-logging/index.js +34 -12
- package/dist/builtins/loki-logging/index.mjs +34 -12
- package/dist/builtins/loki-logging/loki-logging.addon.d.ts +19 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +7 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +108 -4
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +108 -4
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +11 -1
- package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +5 -0
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-C9_XdXl5.mjs → dist-CIek6F9X.mjs} +436 -72
- package/dist/{dist-j49ewCPY.js → dist-CS0pdC8l.js} +436 -72
- package/dist/index.js +58 -8
- package/dist/index.mjs +58 -8
- package/dist/kernel/capability-registry.d.ts +9 -0
- package/dist/kernel/transport/local-child-registry.d.ts +38 -0
- package/dist/kernel/transport/local-transport.d.ts +11 -1
- package/dist/kernel/transport/parent-unowned-call.d.ts +22 -0
- package/dist/kernel/transport/socket-channel.d.ts +28 -1
- package/dist/logging/log-manager.d.ts +32 -1
- package/dist/logging/partitioned-log-buffer.d.ts +15 -3
- package/dist/{manifest-python-deps-CnRW2Utv.js → manifest-python-deps-BbOZZbVt.js} +56 -12
- package/dist/{manifest-python-deps-B30qVAuW.mjs → manifest-python-deps-C9qhvMUi.mjs} +57 -13
- package/package.json +1 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { LogLevel, ProviderRegistration, BaseAddon } from '@camstack/types';
|
|
2
2
|
import { LokiDestination } from './loki-destination.js';
|
|
3
3
|
interface LokiAddonConfig {
|
|
4
|
+
/**
|
|
5
|
+
* RETIRED (2026-07-30). Enablement is the orchestrator's per-destination
|
|
6
|
+
* toggle now — the ONE truth for every log destination. Kept only so a
|
|
7
|
+
* stored `false` can be migrated ONCE into a registry disable: without that,
|
|
8
|
+
* an operator who had Loki off would silently start shipping logs after this
|
|
9
|
+
* update. `migratedEnabled` marks the migration done; the field is then dead.
|
|
10
|
+
*/
|
|
4
11
|
readonly enabled: boolean;
|
|
12
|
+
readonly migratedEnabled: boolean;
|
|
5
13
|
readonly url: string;
|
|
6
14
|
readonly job: string;
|
|
7
15
|
readonly level: LogLevel;
|
|
@@ -17,6 +25,17 @@ export declare class LokiLoggingAddon extends BaseAddon<LokiAddonConfig> {
|
|
|
17
25
|
private destination;
|
|
18
26
|
constructor();
|
|
19
27
|
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
28
|
+
/**
|
|
29
|
+
* ONE-SHOT: carry a stored `enabled: false` over to the orchestrator's
|
|
30
|
+
* per-destination toggle, then never look at the flag again.
|
|
31
|
+
*
|
|
32
|
+
* Without this, retiring the private setting would silently RE-ENABLE Loki
|
|
33
|
+
* for every operator who had turned it off — shipping their logs to a host
|
|
34
|
+
* they may no longer control. Best-effort: if the toggle call fails the
|
|
35
|
+
* migration is simply retried next boot (the flag is only cleared on
|
|
36
|
+
* success).
|
|
37
|
+
*/
|
|
38
|
+
private migrateRetiredEnabledFlag;
|
|
20
39
|
protected onShutdown(): Promise<void>;
|
|
21
40
|
/**
|
|
22
41
|
* React to a settings change (enable/disable, URL, level, batching) without a
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_chunk.__toESM(node_fs);
|
|
9
9
|
let node_child_process = require("node:child_process");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as metricsProviderCapability, at as BaseAddon, jt as EventCategory, vt as createEvent } from "../../dist-
|
|
1
|
+
import { H as metricsProviderCapability, at as BaseAddon, jt as EventCategory, vt as createEvent } from "../../dist-CIek6F9X.mjs";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { execFile, execFileSync } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_chunk.__toESM(node_fs);
|
|
9
9
|
let node_child_process = require("node:child_process");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { J as scoreRuntimes, K as platformProbeCapability, T as enumerateInferenceDevices, at as BaseAddon, bt as emitReadiness, it as errMsg, jt as EventCategory } from "../../dist-
|
|
2
|
+
import { J as scoreRuntimes, K as platformProbeCapability, T as enumerateInferenceDevices, at as BaseAddon, bt as emitReadiness, it as errMsg, jt as EventCategory } from "../../dist-CIek6F9X.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
5
|
import { promisify } from "node:util";
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.js");
|
|
7
7
|
//#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
|
|
8
8
|
/**
|
|
9
9
|
* Reconcile the durable `enabledProviders` set against authoritative
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { at as BaseAddon, jt as EventCategory } from "../../dist-
|
|
1
|
+
import { at as BaseAddon, jt as EventCategory } from "../../dist-CIek6F9X.mjs";
|
|
2
2
|
//#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
|
|
3
3
|
/**
|
|
4
4
|
* Reconcile the durable `enabledProviders` set against authoritative
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.js");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
//#region src/builtins/snapshot/snapshot-coalescing.ts
|
|
9
9
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Z as snapshotCapability, at as BaseAddon, dt as DeviceType, et as streamQualityLabel, i as BatteryStatusSchema, it as errMsg, lt as DeviceFeature, wt as nodePin } from "../../dist-
|
|
1
|
+
import { Z as snapshotCapability, at as BaseAddon, dt as DeviceType, et as streamQualityLabel, i as BatteryStatusSchema, it as errMsg, lt as DeviceFeature, wt as nodePin } from "../../dist-CIek6F9X.mjs";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
//#region src/builtins/snapshot/snapshot-coalescing.ts
|
|
4
4
|
/**
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_chunk.__toESM(node_fs);
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as storageProviderCapability, A as filesystemBrowseCapability, at as BaseAddon } from "../../dist-
|
|
1
|
+
import { $ as storageProviderCapability, A as filesystemBrowseCapability, at as BaseAddon } from "../../dist-CIek6F9X.mjs";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let better_sqlite3 = require("better-sqlite3");
|
|
9
9
|
better_sqlite3 = require_chunk.__toESM(better_sqlite3);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Et as parseJsonUnknown, X as settingsStoreCapability, at as BaseAddon, c as RUNTIME_DEFAULTS, ht as asJsonObject, it as errMsg } from "../../dist-
|
|
1
|
+
import { Et as parseJsonUnknown, X as settingsStoreCapability, at as BaseAddon, c as RUNTIME_DEFAULTS, ht as asJsonObject, it as errMsg } from "../../dist-CIek6F9X.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
//#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
|
|
@@ -64,6 +64,13 @@ export declare class StorageOrchestratorAddon extends BaseAddon<StorageOrchestra
|
|
|
64
64
|
* the recorder so disk-pressure is a single core concern.
|
|
65
65
|
*/
|
|
66
66
|
private resolveLocationThresholds;
|
|
67
|
+
/** statfs capacity of a location's basePath, walking up to the nearest
|
|
68
|
+
* existing ancestor. Null for remote-node locations or unstattable paths. */
|
|
69
|
+
private localCapacityOf;
|
|
70
|
+
/** Per-location cap on EVICTABLE bytes (`config.maxUsedGb`): the operator's
|
|
71
|
+
* "recordings may use at most N GB in total" — enforced by the pressure
|
|
72
|
+
* manager alongside the free-percent guard. */
|
|
73
|
+
private resolveLocationMaxUsedBytes;
|
|
67
74
|
private locationBasePath;
|
|
68
75
|
/**
|
|
69
76
|
* A stable key for the PHYSICAL storage device behind a location — the
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.js");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_chunk.__toESM(node_path);
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -14,6 +14,8 @@ var StorageOrchestratorService = class {
|
|
|
14
14
|
logger;
|
|
15
15
|
getProviders;
|
|
16
16
|
locationStore;
|
|
17
|
+
localNodeId;
|
|
18
|
+
getRemoteProvider;
|
|
17
19
|
locations = /* @__PURE__ */ new Map();
|
|
18
20
|
/**
|
|
19
21
|
* Declaration-driven cardinality source, injected by the addon after
|
|
@@ -31,10 +33,12 @@ var StorageOrchestratorService = class {
|
|
|
31
33
|
* early-boot path that predates the resolver wiring.
|
|
32
34
|
*/
|
|
33
35
|
nodeLocalResolver = null;
|
|
34
|
-
constructor(logger, getProviders, locationStore = null) {
|
|
36
|
+
constructor(logger, getProviders, locationStore = null, localNodeId = "hub", getRemoteProvider) {
|
|
35
37
|
this.logger = logger;
|
|
36
38
|
this.getProviders = getProviders;
|
|
37
39
|
this.locationStore = locationStore;
|
|
40
|
+
this.localNodeId = localNodeId;
|
|
41
|
+
this.getRemoteProvider = getRemoteProvider;
|
|
38
42
|
}
|
|
39
43
|
/** True once a persistence store is wired (constructor or {@link attachStore}). */
|
|
40
44
|
hasStore() {
|
|
@@ -349,6 +353,12 @@ var StorageOrchestratorService = class {
|
|
|
349
353
|
* the right place to fix the config.
|
|
350
354
|
*/
|
|
351
355
|
async getProviderFor(location) {
|
|
356
|
+
const nodeId = location.nodeId;
|
|
357
|
+
if (nodeId !== void 0 && nodeId !== this.localNodeId) {
|
|
358
|
+
const remote = this.getRemoteProvider?.(nodeId);
|
|
359
|
+
if (remote) return remote;
|
|
360
|
+
throw new Error(`storage-provider for location "${location.id}" lives on node "${nodeId}" which is not reachable`);
|
|
361
|
+
}
|
|
352
362
|
const providers = this.getProviders();
|
|
353
363
|
for (const p of providers) if ((await p.getProviderInfo()).providerId === location.providerId) return p;
|
|
354
364
|
throw new Error(`No storage-provider registered for providerId "${location.providerId}" (location "${location.id}")`);
|
|
@@ -634,6 +644,53 @@ var StoragePressureManager = class {
|
|
|
634
644
|
} });
|
|
635
645
|
}
|
|
636
646
|
}
|
|
647
|
+
const caps = await this.deps.getLocationMaxUsedBytes?.() ?? /* @__PURE__ */ new Map();
|
|
648
|
+
for (const [locationId, maxBytes] of caps) {
|
|
649
|
+
if (relievedDevices.size > 0) {
|
|
650
|
+
const deviceKey = await this.deps.getLocationDeviceKey?.(locationId) ?? null;
|
|
651
|
+
if (deviceKey !== null && relievedDevices.has(deviceKey)) continue;
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
await this.relieveToUsageCap(locationId, maxBytes);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
this.deps.logger.warn("storage pressure: usage-cap relief failed", { meta: {
|
|
657
|
+
locationId,
|
|
658
|
+
error: err instanceof Error ? err.message : String(err)
|
|
659
|
+
} });
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/** Evict oldest-first until the location's evictable usage fits `maxBytes`. */
|
|
664
|
+
async relieveToUsageCap(locationId, maxBytes) {
|
|
665
|
+
const maxRounds = this.deps.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
666
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
667
|
+
const providers = this.deps.getEvictableProviders();
|
|
668
|
+
const usages = await Promise.all(providers.map(async (p) => ({
|
|
669
|
+
p,
|
|
670
|
+
bytes: (await p.getEvictableUsage({ locationId })).bytes
|
|
671
|
+
})));
|
|
672
|
+
const totalUsed = usages.reduce((s, u) => s + u.bytes, 0);
|
|
673
|
+
const excess = totalUsed - maxBytes;
|
|
674
|
+
if (excess <= 0) return;
|
|
675
|
+
let reclaimed = 0;
|
|
676
|
+
for (const u of usages) {
|
|
677
|
+
if (u.bytes <= 0) continue;
|
|
678
|
+
const share = Math.max(1, Math.ceil(excess * u.bytes / totalUsed));
|
|
679
|
+
const res = await u.p.evict({
|
|
680
|
+
locationId,
|
|
681
|
+
targetBytes: share
|
|
682
|
+
});
|
|
683
|
+
reclaimed += res.reclaimedBytes;
|
|
684
|
+
}
|
|
685
|
+
this.deps.logger.info("storage pressure: usage-cap relief round", { meta: {
|
|
686
|
+
locationId,
|
|
687
|
+
round,
|
|
688
|
+
totalUsed,
|
|
689
|
+
maxBytes,
|
|
690
|
+
reclaimedBytes: reclaimed
|
|
691
|
+
} });
|
|
692
|
+
if (reclaimed <= 0) return;
|
|
693
|
+
}
|
|
637
694
|
}
|
|
638
695
|
/**
|
|
639
696
|
* Relieve one location until it recovers, nothing is evictable, or a round
|
|
@@ -772,13 +829,21 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
772
829
|
return reg.getCollection(STORAGE_PROVIDER_CAP_NAME);
|
|
773
830
|
};
|
|
774
831
|
const locationStore = this.resolveLocationStore();
|
|
775
|
-
const service = new StorageOrchestratorService(this.ctx.logger, getProviders, locationStore)
|
|
832
|
+
const service = new StorageOrchestratorService(this.ctx.logger, getProviders, locationStore, this.ctx.kernel.localNodeId ?? "hub", (nodeId) => {
|
|
833
|
+
const proxy = this.ctx.kernel.resolveNodeCapability?.("storage-provider", nodeId);
|
|
834
|
+
if (!proxy) return null;
|
|
835
|
+
return proxy;
|
|
836
|
+
});
|
|
776
837
|
this.service = service;
|
|
777
838
|
service.setNodeLocalResolver((providerId) => this.nodeLocalByProvider.get(providerId));
|
|
778
839
|
await service.initialize();
|
|
779
840
|
const provider = {
|
|
780
841
|
listLocations: async ({ type }) => {
|
|
781
|
-
|
|
842
|
+
const rows = type !== void 0 ? service.listLocations({ type }) : service.listLocations();
|
|
843
|
+
return Promise.all(rows.map(async (loc) => ({
|
|
844
|
+
...loc,
|
|
845
|
+
capacity: await this.localCapacityOf(loc)
|
|
846
|
+
})));
|
|
782
847
|
},
|
|
783
848
|
getDefaultLocation: async ({ type }) => service.getDefaultLocation(type),
|
|
784
849
|
listLocationDeclarations: async () => service.listDeclarations(),
|
|
@@ -944,6 +1009,7 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
944
1009
|
getVolumeTotalBytes: (id) => this.locationVolumeTotalBytes(id),
|
|
945
1010
|
getEvictableProviders: () => this.ctx.kernel.capabilityRegistry?.getCollection(STORAGE_EVICTABLE_CAP_NAME) ?? [],
|
|
946
1011
|
getLocationThresholds: () => this.resolveLocationThresholds(),
|
|
1012
|
+
getLocationMaxUsedBytes: () => this.resolveLocationMaxUsedBytes(),
|
|
947
1013
|
getLocationDeviceKey: (id) => this.locationDeviceKey(id)
|
|
948
1014
|
});
|
|
949
1015
|
this.pressureTimer = setInterval(() => {
|
|
@@ -1053,6 +1119,44 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
1053
1119
|
}
|
|
1054
1120
|
return out;
|
|
1055
1121
|
}
|
|
1122
|
+
/** statfs capacity of a location's basePath, walking up to the nearest
|
|
1123
|
+
* existing ancestor. Null for remote-node locations or unstattable paths. */
|
|
1124
|
+
async localCapacityOf(loc) {
|
|
1125
|
+
if (loc.nodeId !== void 0 && loc.nodeId !== "hub" && loc.nodeId !== this.ctx.nodeId) return null;
|
|
1126
|
+
const bp = loc.config["basePath"];
|
|
1127
|
+
if (typeof bp !== "string" || bp.length === 0) return null;
|
|
1128
|
+
try {
|
|
1129
|
+
let target = node_path.resolve(bp);
|
|
1130
|
+
for (;;) try {
|
|
1131
|
+
await node_fs_promises.access(target);
|
|
1132
|
+
break;
|
|
1133
|
+
} catch {
|
|
1134
|
+
const parent = node_path.dirname(target);
|
|
1135
|
+
if (!parent || parent === target) return null;
|
|
1136
|
+
target = parent;
|
|
1137
|
+
}
|
|
1138
|
+
const st = await node_fs_promises.statfs(target);
|
|
1139
|
+
return {
|
|
1140
|
+
totalBytes: st.blocks * st.bsize,
|
|
1141
|
+
availableBytes: st.bavail * st.bsize
|
|
1142
|
+
};
|
|
1143
|
+
} catch {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
/** Per-location cap on EVICTABLE bytes (`config.maxUsedGb`): the operator's
|
|
1148
|
+
* "recordings may use at most N GB in total" — enforced by the pressure
|
|
1149
|
+
* manager alongside the free-percent guard. */
|
|
1150
|
+
async resolveLocationMaxUsedBytes() {
|
|
1151
|
+
const out = /* @__PURE__ */ new Map();
|
|
1152
|
+
const svc = this.service;
|
|
1153
|
+
if (!svc) return out;
|
|
1154
|
+
for (const loc of svc.listLocations()) {
|
|
1155
|
+
const raw = loc.config["maxUsedGb"];
|
|
1156
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) out.set(loc.id, Math.floor(raw * 1024 ** 3));
|
|
1157
|
+
}
|
|
1158
|
+
return out;
|
|
1159
|
+
}
|
|
1056
1160
|
locationBasePath(locationId) {
|
|
1057
1161
|
const bp = (this.service?.listLocations().find((l) => l.id === locationId))?.config["basePath"];
|
|
1058
1162
|
return typeof bp === "string" && bp.length > 0 ? bp : null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Q as storageCapability, Tt as parseJsonObject, at as BaseAddon, d as StorageLocationTypeSchema } from "../../dist-
|
|
1
|
+
import { Q as storageCapability, Tt as parseJsonObject, at as BaseAddon, d as StorageLocationTypeSchema } from "../../dist-CIek6F9X.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";
|
|
@@ -7,6 +7,8 @@ var StorageOrchestratorService = class {
|
|
|
7
7
|
logger;
|
|
8
8
|
getProviders;
|
|
9
9
|
locationStore;
|
|
10
|
+
localNodeId;
|
|
11
|
+
getRemoteProvider;
|
|
10
12
|
locations = /* @__PURE__ */ new Map();
|
|
11
13
|
/**
|
|
12
14
|
* Declaration-driven cardinality source, injected by the addon after
|
|
@@ -24,10 +26,12 @@ var StorageOrchestratorService = class {
|
|
|
24
26
|
* early-boot path that predates the resolver wiring.
|
|
25
27
|
*/
|
|
26
28
|
nodeLocalResolver = null;
|
|
27
|
-
constructor(logger, getProviders, locationStore = null) {
|
|
29
|
+
constructor(logger, getProviders, locationStore = null, localNodeId = "hub", getRemoteProvider) {
|
|
28
30
|
this.logger = logger;
|
|
29
31
|
this.getProviders = getProviders;
|
|
30
32
|
this.locationStore = locationStore;
|
|
33
|
+
this.localNodeId = localNodeId;
|
|
34
|
+
this.getRemoteProvider = getRemoteProvider;
|
|
31
35
|
}
|
|
32
36
|
/** True once a persistence store is wired (constructor or {@link attachStore}). */
|
|
33
37
|
hasStore() {
|
|
@@ -342,6 +346,12 @@ var StorageOrchestratorService = class {
|
|
|
342
346
|
* the right place to fix the config.
|
|
343
347
|
*/
|
|
344
348
|
async getProviderFor(location) {
|
|
349
|
+
const nodeId = location.nodeId;
|
|
350
|
+
if (nodeId !== void 0 && nodeId !== this.localNodeId) {
|
|
351
|
+
const remote = this.getRemoteProvider?.(nodeId);
|
|
352
|
+
if (remote) return remote;
|
|
353
|
+
throw new Error(`storage-provider for location "${location.id}" lives on node "${nodeId}" which is not reachable`);
|
|
354
|
+
}
|
|
345
355
|
const providers = this.getProviders();
|
|
346
356
|
for (const p of providers) if ((await p.getProviderInfo()).providerId === location.providerId) return p;
|
|
347
357
|
throw new Error(`No storage-provider registered for providerId "${location.providerId}" (location "${location.id}")`);
|
|
@@ -627,6 +637,53 @@ var StoragePressureManager = class {
|
|
|
627
637
|
} });
|
|
628
638
|
}
|
|
629
639
|
}
|
|
640
|
+
const caps = await this.deps.getLocationMaxUsedBytes?.() ?? /* @__PURE__ */ new Map();
|
|
641
|
+
for (const [locationId, maxBytes] of caps) {
|
|
642
|
+
if (relievedDevices.size > 0) {
|
|
643
|
+
const deviceKey = await this.deps.getLocationDeviceKey?.(locationId) ?? null;
|
|
644
|
+
if (deviceKey !== null && relievedDevices.has(deviceKey)) continue;
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
await this.relieveToUsageCap(locationId, maxBytes);
|
|
648
|
+
} catch (err) {
|
|
649
|
+
this.deps.logger.warn("storage pressure: usage-cap relief failed", { meta: {
|
|
650
|
+
locationId,
|
|
651
|
+
error: err instanceof Error ? err.message : String(err)
|
|
652
|
+
} });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/** Evict oldest-first until the location's evictable usage fits `maxBytes`. */
|
|
657
|
+
async relieveToUsageCap(locationId, maxBytes) {
|
|
658
|
+
const maxRounds = this.deps.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
659
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
660
|
+
const providers = this.deps.getEvictableProviders();
|
|
661
|
+
const usages = await Promise.all(providers.map(async (p) => ({
|
|
662
|
+
p,
|
|
663
|
+
bytes: (await p.getEvictableUsage({ locationId })).bytes
|
|
664
|
+
})));
|
|
665
|
+
const totalUsed = usages.reduce((s, u) => s + u.bytes, 0);
|
|
666
|
+
const excess = totalUsed - maxBytes;
|
|
667
|
+
if (excess <= 0) return;
|
|
668
|
+
let reclaimed = 0;
|
|
669
|
+
for (const u of usages) {
|
|
670
|
+
if (u.bytes <= 0) continue;
|
|
671
|
+
const share = Math.max(1, Math.ceil(excess * u.bytes / totalUsed));
|
|
672
|
+
const res = await u.p.evict({
|
|
673
|
+
locationId,
|
|
674
|
+
targetBytes: share
|
|
675
|
+
});
|
|
676
|
+
reclaimed += res.reclaimedBytes;
|
|
677
|
+
}
|
|
678
|
+
this.deps.logger.info("storage pressure: usage-cap relief round", { meta: {
|
|
679
|
+
locationId,
|
|
680
|
+
round,
|
|
681
|
+
totalUsed,
|
|
682
|
+
maxBytes,
|
|
683
|
+
reclaimedBytes: reclaimed
|
|
684
|
+
} });
|
|
685
|
+
if (reclaimed <= 0) return;
|
|
686
|
+
}
|
|
630
687
|
}
|
|
631
688
|
/**
|
|
632
689
|
* Relieve one location until it recovers, nothing is evictable, or a round
|
|
@@ -765,13 +822,21 @@ var StorageOrchestratorAddon = class extends BaseAddon {
|
|
|
765
822
|
return reg.getCollection(STORAGE_PROVIDER_CAP_NAME);
|
|
766
823
|
};
|
|
767
824
|
const locationStore = this.resolveLocationStore();
|
|
768
|
-
const service = new StorageOrchestratorService(this.ctx.logger, getProviders, locationStore)
|
|
825
|
+
const service = new StorageOrchestratorService(this.ctx.logger, getProviders, locationStore, this.ctx.kernel.localNodeId ?? "hub", (nodeId) => {
|
|
826
|
+
const proxy = this.ctx.kernel.resolveNodeCapability?.("storage-provider", nodeId);
|
|
827
|
+
if (!proxy) return null;
|
|
828
|
+
return proxy;
|
|
829
|
+
});
|
|
769
830
|
this.service = service;
|
|
770
831
|
service.setNodeLocalResolver((providerId) => this.nodeLocalByProvider.get(providerId));
|
|
771
832
|
await service.initialize();
|
|
772
833
|
const provider = {
|
|
773
834
|
listLocations: async ({ type }) => {
|
|
774
|
-
|
|
835
|
+
const rows = type !== void 0 ? service.listLocations({ type }) : service.listLocations();
|
|
836
|
+
return Promise.all(rows.map(async (loc) => ({
|
|
837
|
+
...loc,
|
|
838
|
+
capacity: await this.localCapacityOf(loc)
|
|
839
|
+
})));
|
|
775
840
|
},
|
|
776
841
|
getDefaultLocation: async ({ type }) => service.getDefaultLocation(type),
|
|
777
842
|
listLocationDeclarations: async () => service.listDeclarations(),
|
|
@@ -937,6 +1002,7 @@ var StorageOrchestratorAddon = class extends BaseAddon {
|
|
|
937
1002
|
getVolumeTotalBytes: (id) => this.locationVolumeTotalBytes(id),
|
|
938
1003
|
getEvictableProviders: () => this.ctx.kernel.capabilityRegistry?.getCollection(STORAGE_EVICTABLE_CAP_NAME) ?? [],
|
|
939
1004
|
getLocationThresholds: () => this.resolveLocationThresholds(),
|
|
1005
|
+
getLocationMaxUsedBytes: () => this.resolveLocationMaxUsedBytes(),
|
|
940
1006
|
getLocationDeviceKey: (id) => this.locationDeviceKey(id)
|
|
941
1007
|
});
|
|
942
1008
|
this.pressureTimer = setInterval(() => {
|
|
@@ -1046,6 +1112,44 @@ var StorageOrchestratorAddon = class extends BaseAddon {
|
|
|
1046
1112
|
}
|
|
1047
1113
|
return out;
|
|
1048
1114
|
}
|
|
1115
|
+
/** statfs capacity of a location's basePath, walking up to the nearest
|
|
1116
|
+
* existing ancestor. Null for remote-node locations or unstattable paths. */
|
|
1117
|
+
async localCapacityOf(loc) {
|
|
1118
|
+
if (loc.nodeId !== void 0 && loc.nodeId !== "hub" && loc.nodeId !== this.ctx.nodeId) return null;
|
|
1119
|
+
const bp = loc.config["basePath"];
|
|
1120
|
+
if (typeof bp !== "string" || bp.length === 0) return null;
|
|
1121
|
+
try {
|
|
1122
|
+
let target = path$1.resolve(bp);
|
|
1123
|
+
for (;;) try {
|
|
1124
|
+
await fs.access(target);
|
|
1125
|
+
break;
|
|
1126
|
+
} catch {
|
|
1127
|
+
const parent = path$1.dirname(target);
|
|
1128
|
+
if (!parent || parent === target) return null;
|
|
1129
|
+
target = parent;
|
|
1130
|
+
}
|
|
1131
|
+
const st = await fs.statfs(target);
|
|
1132
|
+
return {
|
|
1133
|
+
totalBytes: st.blocks * st.bsize,
|
|
1134
|
+
availableBytes: st.bavail * st.bsize
|
|
1135
|
+
};
|
|
1136
|
+
} catch {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
/** Per-location cap on EVICTABLE bytes (`config.maxUsedGb`): the operator's
|
|
1141
|
+
* "recordings may use at most N GB in total" — enforced by the pressure
|
|
1142
|
+
* manager alongside the free-percent guard. */
|
|
1143
|
+
async resolveLocationMaxUsedBytes() {
|
|
1144
|
+
const out = /* @__PURE__ */ new Map();
|
|
1145
|
+
const svc = this.service;
|
|
1146
|
+
if (!svc) return out;
|
|
1147
|
+
for (const loc of svc.listLocations()) {
|
|
1148
|
+
const raw = loc.config["maxUsedGb"];
|
|
1149
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) out.set(loc.id, Math.floor(raw * 1024 ** 3));
|
|
1150
|
+
}
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
|
1049
1153
|
locationBasePath(locationId) {
|
|
1050
1154
|
const bp = (this.service?.listLocations().find((l) => l.id === locationId))?.config["basePath"];
|
|
1051
1155
|
return typeof bp === "string" && bp.length > 0 ? bp : null;
|
|
@@ -54,6 +54,11 @@ export declare class StorageOrchestratorService {
|
|
|
54
54
|
* the provider appears. See {@link attachStore}.
|
|
55
55
|
*/
|
|
56
56
|
private locationStore;
|
|
57
|
+
/** This node's id — locations pinned to OTHER nodes dispatch remotely. */
|
|
58
|
+
private readonly localNodeId;
|
|
59
|
+
/** Cross-node provider resolver (hub wiring: a node-routed capability
|
|
60
|
+
* proxy over `storage-provider`). Absent → remote locations error. */
|
|
61
|
+
private readonly getRemoteProvider?;
|
|
57
62
|
private readonly locations;
|
|
58
63
|
/**
|
|
59
64
|
* Declaration-driven cardinality source, injected by the addon after
|
|
@@ -85,7 +90,12 @@ export declare class StorageOrchestratorService {
|
|
|
85
90
|
* service starts store-less and the addon calls {@link attachStore} once
|
|
86
91
|
* the provider appears. See {@link attachStore}.
|
|
87
92
|
*/
|
|
88
|
-
locationStore?: ILocationStore | null
|
|
93
|
+
locationStore?: ILocationStore | null,
|
|
94
|
+
/** This node's id — locations pinned to OTHER nodes dispatch remotely. */
|
|
95
|
+
localNodeId?: string,
|
|
96
|
+
/** Cross-node provider resolver (hub wiring: a node-routed capability
|
|
97
|
+
* proxy over `storage-provider`). Absent → remote locations error. */
|
|
98
|
+
getRemoteProvider?: ((nodeId: string) => IStorageProviderImpl | null) | undefined);
|
|
89
99
|
/** True once a persistence store is wired (constructor or {@link attachStore}). */
|
|
90
100
|
hasStore(): boolean;
|
|
91
101
|
/**
|
|
@@ -24,6 +24,9 @@ export interface PressureManagerDeps {
|
|
|
24
24
|
readonly getEvictableProviders: () => readonly IStorageEvictableProvider[];
|
|
25
25
|
/** locationId → minFreePercent threshold; entries with `<= 0` have the guard off. */
|
|
26
26
|
readonly getLocationThresholds: () => Promise<ReadonlyMap<string, number>>;
|
|
27
|
+
/** locationId → hard cap on EVICTABLE bytes (`config.maxUsedGb`, operator's
|
|
28
|
+
* "recordings may use at most N GB total"). Optional — absent = no caps. */
|
|
29
|
+
readonly getLocationMaxUsedBytes?: () => Promise<ReadonlyMap<string, number>>;
|
|
27
30
|
/** Max relief rounds per location per sweep (backstop, default 8). */
|
|
28
31
|
readonly maxRounds?: number;
|
|
29
32
|
/** Extra headroom % freed beyond the threshold to avoid re-breaching immediately (default 1). */
|
|
@@ -53,6 +56,8 @@ export declare class StoragePressureManager {
|
|
|
53
56
|
constructor(deps: PressureManagerDeps);
|
|
54
57
|
/** Relieve every managed location currently under its threshold. */
|
|
55
58
|
sweep(): Promise<void>;
|
|
59
|
+
/** Evict oldest-first until the location's evictable usage fits `maxBytes`. */
|
|
60
|
+
private relieveToUsageCap;
|
|
56
61
|
/**
|
|
57
62
|
* Relieve one location until it recovers, nothing is evictable, or a round
|
|
58
63
|
* makes no progress. Returns `true` when at least one relief round actually
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.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-
|
|
1
|
+
import { St as hydrateSchema, at as BaseAddon, it as errMsg } from "../../dist-CIek6F9X.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-
|
|
6
|
+
const require_dist = require("../../dist-CS0pdC8l.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 { at as BaseAddon, z as logDestinationCapability } from "../../dist-
|
|
1
|
+
import { at as BaseAddon, z as logDestinationCapability } from "../../dist-CIek6F9X.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";
|