@camstack/system 1.2.157 → 1.2.160
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 +17 -16
- package/dist/addon-runner.mjs +2 -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 +10 -7
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +50 -12
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +50 -12
- package/dist/builtins/backup-orchestrator/write-targets.d.ts +63 -0
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-bindings-store.d.ts +56 -10
- package/dist/builtins/device-manager/device-manager.addon.d.ts +11 -0
- package/dist/builtins/device-manager/device-manager.addon.js +216 -81
- package/dist/builtins/device-manager/device-manager.addon.mjs +216 -81
- package/dist/builtins/device-manager/device-meta-actions.d.ts +9 -3
- package/dist/builtins/device-manager/device-meta-store.d.ts +32 -5
- package/dist/builtins/device-manager/device-queries.d.ts +7 -3
- package/dist/builtins/device-manager/write-lock.d.ts +17 -0
- 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/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.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.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- 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/filter-compiler.d.ts +41 -2
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +15 -7
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/sqlite-storage/vector-index-shared.d.ts +30 -5
- package/dist/builtins/sqlite-storage/vector-index-vec.d.ts +28 -6
- package/dist/builtins/storage-orchestrator/access-guards.d.ts +32 -0
- package/dist/builtins/storage-orchestrator/location-secrets.d.ts +33 -0
- package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +74 -3
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +26 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +552 -15
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +552 -15
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +56 -1
- 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-CDgIzo82.js → dist-CcvXUhHK.js} +537 -17
- package/dist/{dist-LvKus-mT.mjs → dist-DKs---Vx.mjs} +520 -18
- package/dist/index.js +141 -139
- package/dist/index.mjs +3 -3
- package/dist/kernel/deps/manifest-system-deps.d.ts +15 -0
- package/dist/kernel/index.d.ts +4 -2
- package/dist/kernel/moleculer/cap-usage-registry.d.ts +132 -7
- package/dist/kernel/storage-location-registry.d.ts +16 -1
- package/dist/kernel/transport/local-child-registry.d.ts +57 -0
- package/dist/{manifest-python-deps-CD14Pv0-.mjs → manifest-system-deps-DnoPpLGp.mjs} +466 -83
- package/dist/{manifest-python-deps-DVODn-qc.js → manifest-system-deps-oJMkWSX-.js} +477 -82
- package/dist/{retired-settings-keys-CV4QVdXj.mjs → retired-settings-keys-C3eONTvV.mjs} +1 -1
- package/dist/{retired-settings-keys-BfAzWvPC.js → retired-settings-keys-DXZ2xe7C.js} +1 -1
- package/package.json +2 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const
|
|
2
|
+
const require_manifest_system_deps = require("./manifest-system-deps-oJMkWSX-.js");
|
|
3
3
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -420,7 +420,7 @@ async function loadAddonModule(addonId, addonDir) {
|
|
|
420
420
|
if (!node_fs.existsSync(entryPath)) throw new Error(`Entry point not found: ${entryPath}`);
|
|
421
421
|
const modUnknown = await import(entryPath);
|
|
422
422
|
const mod = isRecord(modUnknown) ? modUnknown : {};
|
|
423
|
-
const AddonClass =
|
|
423
|
+
const AddonClass = require_manifest_system_deps.resolveAddonClass(mod);
|
|
424
424
|
if (!AddonClass) throw new Error(`No addon class exported from ${entryPath}`);
|
|
425
425
|
return {
|
|
426
426
|
declaration,
|
|
@@ -554,13 +554,13 @@ async function main() {
|
|
|
554
554
|
*/
|
|
555
555
|
const customActionRegistry = new require_custom_action_registry.CustomActionRegistry();
|
|
556
556
|
let shuttingDown = false;
|
|
557
|
-
const udsChildClient = new
|
|
557
|
+
const udsChildClient = new require_manifest_system_deps.LocalChildClient({
|
|
558
558
|
nodeId: parentNodeId,
|
|
559
559
|
childId: runnerId,
|
|
560
|
-
caps: buildChildCapDescriptors(runnerManifest,
|
|
560
|
+
caps: buildChildCapDescriptors(runnerManifest, require_manifest_system_deps.getWorkerNativeCapSnapshot()),
|
|
561
561
|
dispatch: createChildCapDispatch({
|
|
562
562
|
getSingletonProvider: (capName, addonId) => resolveSingletonProvider(capName, addonId),
|
|
563
|
-
getDeviceProvider: (capName, deviceId) =>
|
|
563
|
+
getDeviceProvider: (capName, deviceId) => require_manifest_system_deps.getWorkerNativeCapProvider(capName, deviceId) ?? null,
|
|
564
564
|
isCollectionListMethod: (capName, method) => (0, _camstack_types_addon.isCollectionArrayMethodName)(capName, method)
|
|
565
565
|
})
|
|
566
566
|
});
|
|
@@ -570,7 +570,7 @@ async function main() {
|
|
|
570
570
|
getAddonInstance: (addonId) => loadedAddonInstances.get(addonId) ?? null,
|
|
571
571
|
getDataPlanes: (addonId) => addonDataPlanes.get(addonId) ?? []
|
|
572
572
|
}));
|
|
573
|
-
const { logger: runnerLog, control: runnerLogControl } =
|
|
573
|
+
const { logger: runnerLog, control: runnerLogControl } = require_manifest_system_deps.createUdsLoggerWithControl(udsChildClient, runnerId, nodeId, "addon-runner");
|
|
574
574
|
udsChildClient.onSetLogLevel((level) => {
|
|
575
575
|
runnerLogControl.setLevel(level);
|
|
576
576
|
runnerLog.info(`Log level changed to "${level}" via UDS`);
|
|
@@ -585,13 +585,13 @@ async function main() {
|
|
|
585
585
|
if (storageProvider) runnerLog.info(`"${nodeId}" storage provider rooted at ${dataDir}`);
|
|
586
586
|
else runnerLog.warn(`"${nodeId}" failed to load FilesystemStorageProvider`, { meta: { error: (0, _camstack_types_addon.errMsg)(storageProviderError) } });
|
|
587
587
|
const refreshChildCaps = (reason) => {
|
|
588
|
-
const freshDescriptors = buildChildCapDescriptors(runnerManifest,
|
|
588
|
+
const freshDescriptors = buildChildCapDescriptors(runnerManifest, require_manifest_system_deps.getWorkerNativeCapSnapshot());
|
|
589
589
|
udsChildClient.updateCaps(freshDescriptors).catch((err) => {
|
|
590
590
|
runnerLog.warn(`UDS updateCaps failed (${reason})`, { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
591
591
|
});
|
|
592
592
|
};
|
|
593
593
|
let capRefreshTimer;
|
|
594
|
-
|
|
594
|
+
require_manifest_system_deps.setWorkerNativeCapsChangeListener(() => {
|
|
595
595
|
if (capRefreshTimer !== void 0) return;
|
|
596
596
|
capRefreshTimer = setTimeout(() => {
|
|
597
597
|
capRefreshTimer = void 0;
|
|
@@ -603,7 +603,7 @@ async function main() {
|
|
|
603
603
|
const deviceRestorePromises = [];
|
|
604
604
|
for (const { spec, declaration, AddonClass } of loadedModules) try {
|
|
605
605
|
const addon = new AddonClass();
|
|
606
|
-
const context = await
|
|
606
|
+
const context = await require_manifest_system_deps.createUdsAddonContext(udsChildClient, nodeId, declaration, dataDir, {
|
|
607
607
|
...storageProvider ? { storageProvider } : {},
|
|
608
608
|
dataPlaneSink: { set: (addonId, endpoints) => {
|
|
609
609
|
addonDataPlanes.set(addonId, endpoints);
|
|
@@ -611,12 +611,13 @@ async function main() {
|
|
|
611
611
|
});
|
|
612
612
|
try {
|
|
613
613
|
const pkgRaw = JSON.parse(node_fs.readFileSync(node_path.join(spec.addonDir, "package.json"), "utf-8"));
|
|
614
|
-
await
|
|
614
|
+
await require_manifest_system_deps.installManifestNativeDeps(spec.addonDir, pkgRaw, context.logger);
|
|
615
615
|
} catch (nativeErr) {
|
|
616
616
|
console.error(`[addon-runner] native deps install failed for "${spec.addonId}": ${(0, _camstack_types_addon.errMsg)(nativeErr)}`);
|
|
617
617
|
process.exit(1);
|
|
618
618
|
}
|
|
619
|
-
await
|
|
619
|
+
await require_manifest_system_deps.installManifestPythonDeps(declaration, spec.addonDir, context.deps, context.logger);
|
|
620
|
+
await require_manifest_system_deps.installManifestSystemDeps(declaration, context.logger);
|
|
620
621
|
const registeredProviders = /* @__PURE__ */ new Map();
|
|
621
622
|
const initWatchdog = startInitWatchdog({
|
|
622
623
|
addonId: spec.addonId,
|
|
@@ -651,7 +652,7 @@ async function main() {
|
|
|
651
652
|
return fn(input, caller);
|
|
652
653
|
});
|
|
653
654
|
}
|
|
654
|
-
|
|
655
|
+
require_manifest_system_deps.validateProviderRegistrations(spec.addonId, declaration, registeredProviders, context.logger);
|
|
655
656
|
const capNames = [...registeredProviders.keys()];
|
|
656
657
|
context.logger.info(`"${spec.addonId}" caps: ${capNames.length > 0 ? capNames.join(", ") : "(none)"}`);
|
|
657
658
|
runnerManifest.push({
|
|
@@ -688,10 +689,10 @@ async function main() {
|
|
|
688
689
|
fireHubReachableOnce();
|
|
689
690
|
runnerLog.info(`"${nodeId}" started — ${loaded.length} addon(s) ready (UDS connected)`);
|
|
690
691
|
process.stdout.write(`READY:${nodeId}\n`);
|
|
691
|
-
const stopHeapWatch =
|
|
692
|
+
const stopHeapWatch = require_manifest_system_deps.startRunnerHeapWatch({
|
|
692
693
|
heapProfile: process.env["CAMSTACK_RUNNER_HEAP_PROFILE"],
|
|
693
694
|
reclaimSwitch: process.env["CAMSTACK_RUNNER_HEAP_RECLAIM"],
|
|
694
|
-
rssBudgetMb: process.env[
|
|
695
|
+
rssBudgetMb: process.env[require_manifest_system_deps.RUNNER_RSS_BUDGET_ENV],
|
|
695
696
|
label: nodeId,
|
|
696
697
|
sink: {
|
|
697
698
|
info: (line) => {
|
|
@@ -703,7 +704,7 @@ async function main() {
|
|
|
703
704
|
}
|
|
704
705
|
});
|
|
705
706
|
process.on("SIGUSR2", () => {
|
|
706
|
-
|
|
707
|
+
require_manifest_system_deps.emitHeapDiagnosticReport({
|
|
707
708
|
label: nodeId,
|
|
708
709
|
addons: loaded.map((e) => e.addonId),
|
|
709
710
|
sink: {
|
|
@@ -720,7 +721,7 @@ async function main() {
|
|
|
720
721
|
const shutdown = async () => {
|
|
721
722
|
shuttingDown = true;
|
|
722
723
|
stopHeapWatch?.();
|
|
723
|
-
|
|
724
|
+
require_manifest_system_deps.setWorkerNativeCapsChangeListener(null);
|
|
724
725
|
if (capRefreshTimer !== void 0) {
|
|
725
726
|
clearTimeout(capRefreshTimer);
|
|
726
727
|
capRefreshTimer = void 0;
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ct as resolveAddonClass, N as createUdsLoggerWithControl, P as LocalChildClient, Rt as RUNNER_RSS_BUDGET_ENV, St as installManifestNativeDeps, Zt as startRunnerHeapWatch, a as createUdsAddonContext, at as getWorkerNativeCapSnapshot, it as getWorkerNativeCapProvider, lt as validateProviderRegistrations, n as installManifestPythonDeps, nn as emitHeapDiagnosticReport, st as setWorkerNativeCapsChangeListener, t as installManifestSystemDeps } from "./manifest-system-deps-DnoPpLGp.mjs";
|
|
2
2
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
3
3
|
import { register } from "node:module";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -614,6 +614,7 @@ async function main() {
|
|
|
614
614
|
process.exit(1);
|
|
615
615
|
}
|
|
616
616
|
await installManifestPythonDeps(declaration, spec.addonDir, context.deps, context.logger);
|
|
617
|
+
await installManifestSystemDeps(declaration, context.logger);
|
|
617
618
|
const registeredProviders = /* @__PURE__ */ new Map();
|
|
618
619
|
const initWatchdog = startInitWatchdog({
|
|
619
620
|
addonId: spec.addonId,
|
|
@@ -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-CcvXUhHK.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as BaseAddon, in as EventCategory, jt as errMsg, w as addonPagesCapability } from "../../dist-DKs---Vx.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 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-CcvXUhHK.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as addonWidgetsCapability, Mt as BaseAddon, in as EventCategory, jt as errMsg } from "../../dist-DKs---Vx.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -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-CcvXUhHK.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as alertsCapability, Gt as createEvent, Mt as BaseAddon, in as EventCategory, jt as errMsg, n as AlertSchema } from "../../dist-DKs---Vx.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -120,13 +120,16 @@ export declare class BackupOrchestratorAddon extends BaseAddon<Record<string, ne
|
|
|
120
120
|
*/
|
|
121
121
|
private scheduleRestore;
|
|
122
122
|
/**
|
|
123
|
-
* Resolve a target destination set
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
123
|
+
* Resolve a target destination set — see {@link selectBackupTargets} for the
|
|
124
|
+
* rule. Caller-provided list wins over the per-destination policy flag;
|
|
125
|
+
* NEITHER wins over `StorageLocation.enabled`, which is the operator's
|
|
126
|
+
* switch on the storage screen and the only one that means "this disk takes
|
|
127
|
+
* no writes" (D62).
|
|
128
|
+
*
|
|
129
|
+
* Reads locations + policies directly rather than going through
|
|
130
|
+
* `listDestinations()`: that method also reads every destination's
|
|
131
|
+
* `manifests.json` to hydrate `lastSuccess`, which target resolution has no
|
|
132
|
+
* use for and which cost one round-trip per destination on every trigger.
|
|
130
133
|
*/
|
|
131
134
|
private resolveTargets;
|
|
132
135
|
private startScheduleTimer;
|
|
@@ -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-CcvXUhHK.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let buffer = require("buffer");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -4777,6 +4777,28 @@ function mapRowToSchedule(row) {
|
|
|
4777
4777
|
};
|
|
4778
4778
|
}
|
|
4779
4779
|
//#endregion
|
|
4780
|
+
//#region src/builtins/backup-orchestrator/write-targets.ts
|
|
4781
|
+
/**
|
|
4782
|
+
* Pick the destinations an archive may be fanned out to.
|
|
4783
|
+
*
|
|
4784
|
+
* An explicit `requested` list selects from the candidates by id and bypasses
|
|
4785
|
+
* the policy flag (that is today's behaviour and it is right: naming a
|
|
4786
|
+
* destination IS the opt-in). A requested id that is not a `backups` location
|
|
4787
|
+
* at all is simply absent from the candidates and drops out here, exactly as
|
|
4788
|
+
* before.
|
|
4789
|
+
*
|
|
4790
|
+
* The location flag is applied LAST and to both paths, so there is one place
|
|
4791
|
+
* where "disabled" is honoured rather than one per caller.
|
|
4792
|
+
*/
|
|
4793
|
+
function selectBackupTargets(input) {
|
|
4794
|
+
const requested = input.requested;
|
|
4795
|
+
const pool = requested && requested.length > 0 ? input.candidates.filter((c) => requested.includes(c.id)) : input.candidates.filter((c) => c.policyEnabled);
|
|
4796
|
+
return {
|
|
4797
|
+
targets: pool.filter((c) => c.locationEnabled),
|
|
4798
|
+
skippedDisabled: pool.filter((c) => !c.locationEnabled).map((c) => c.id)
|
|
4799
|
+
};
|
|
4800
|
+
}
|
|
4801
|
+
//#endregion
|
|
4780
4802
|
//#region src/builtins/backup-orchestrator/backup-orchestrator.addon.ts
|
|
4781
4803
|
/**
|
|
4782
4804
|
* `backup-orchestrator` builtin — singleton owner of the `backup` cap.
|
|
@@ -5002,7 +5024,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
5002
5024
|
subId,
|
|
5003
5025
|
displayName,
|
|
5004
5026
|
kind: kindFromProviderId(loc.providerId),
|
|
5005
|
-
triggerSupported: effective.enabled,
|
|
5027
|
+
triggerSupported: effective.enabled && loc.enabled !== false,
|
|
5006
5028
|
restoreSupported: true,
|
|
5007
5029
|
enabled: effective.enabled,
|
|
5008
5030
|
retentionCount: effective.retentionCount,
|
|
@@ -5367,18 +5389,34 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
5367
5389
|
});
|
|
5368
5390
|
}
|
|
5369
5391
|
/**
|
|
5370
|
-
* Resolve a target destination set
|
|
5371
|
-
*
|
|
5372
|
-
*
|
|
5373
|
-
*
|
|
5374
|
-
*
|
|
5375
|
-
*
|
|
5376
|
-
*
|
|
5392
|
+
* Resolve a target destination set — see {@link selectBackupTargets} for the
|
|
5393
|
+
* rule. Caller-provided list wins over the per-destination policy flag;
|
|
5394
|
+
* NEITHER wins over `StorageLocation.enabled`, which is the operator's
|
|
5395
|
+
* switch on the storage screen and the only one that means "this disk takes
|
|
5396
|
+
* no writes" (D62).
|
|
5397
|
+
*
|
|
5398
|
+
* Reads locations + policies directly rather than going through
|
|
5399
|
+
* `listDestinations()`: that method also reads every destination's
|
|
5400
|
+
* `manifests.json` to hydrate `lastSuccess`, which target resolution has no
|
|
5401
|
+
* use for and which cost one round-trip per destination on every trigger.
|
|
5377
5402
|
*/
|
|
5378
5403
|
async resolveTargets(requested) {
|
|
5379
|
-
const
|
|
5380
|
-
|
|
5381
|
-
|
|
5404
|
+
const locations = await this.fetchBackupLocations();
|
|
5405
|
+
const policies = await this.loadPoliciesIndexed(locations.map((l) => l.id));
|
|
5406
|
+
const selection = selectBackupTargets({
|
|
5407
|
+
candidates: locations.map((l) => ({
|
|
5408
|
+
id: l.id,
|
|
5409
|
+
locationEnabled: l.enabled !== false,
|
|
5410
|
+
policyEnabled: (policies.get(l.id) ?? this.fallbackPolicy(l.id)).enabled
|
|
5411
|
+
})),
|
|
5412
|
+
...requested && requested.length > 0 ? { requested: [...requested] } : {}
|
|
5413
|
+
});
|
|
5414
|
+
if (selection.skippedDisabled.length > 0) this.ctx.logger.warn("backup: destination skipped — its storage location is disabled by the operator", { meta: {
|
|
5415
|
+
destinationIds: [...selection.skippedDisabled],
|
|
5416
|
+
requestedExplicitly: requested !== void 0 && requested.length > 0
|
|
5417
|
+
} });
|
|
5418
|
+
if (selection.targets.length === 0 && selection.skippedDisabled.length > 0) throw new Error(`every selected backup destination is disabled (${selection.skippedDisabled.join(", ")}) — enable its storage location on the Storage screen, or choose another destination. Refusing to write an archive to a location the operator turned off.`);
|
|
5419
|
+
return selection.targets.map((t) => ({ id: t.id }));
|
|
5382
5420
|
}
|
|
5383
5421
|
startScheduleTimer() {
|
|
5384
5422
|
if (this.scheduleTimer) return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as BaseAddon, in as EventCategory, k as backupCapability } from "../../dist-DKs---Vx.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { Buffer as Buffer$1 } from "buffer";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
@@ -4762,6 +4762,28 @@ function mapRowToSchedule(row) {
|
|
|
4762
4762
|
};
|
|
4763
4763
|
}
|
|
4764
4764
|
//#endregion
|
|
4765
|
+
//#region src/builtins/backup-orchestrator/write-targets.ts
|
|
4766
|
+
/**
|
|
4767
|
+
* Pick the destinations an archive may be fanned out to.
|
|
4768
|
+
*
|
|
4769
|
+
* An explicit `requested` list selects from the candidates by id and bypasses
|
|
4770
|
+
* the policy flag (that is today's behaviour and it is right: naming a
|
|
4771
|
+
* destination IS the opt-in). A requested id that is not a `backups` location
|
|
4772
|
+
* at all is simply absent from the candidates and drops out here, exactly as
|
|
4773
|
+
* before.
|
|
4774
|
+
*
|
|
4775
|
+
* The location flag is applied LAST and to both paths, so there is one place
|
|
4776
|
+
* where "disabled" is honoured rather than one per caller.
|
|
4777
|
+
*/
|
|
4778
|
+
function selectBackupTargets(input) {
|
|
4779
|
+
const requested = input.requested;
|
|
4780
|
+
const pool = requested && requested.length > 0 ? input.candidates.filter((c) => requested.includes(c.id)) : input.candidates.filter((c) => c.policyEnabled);
|
|
4781
|
+
return {
|
|
4782
|
+
targets: pool.filter((c) => c.locationEnabled),
|
|
4783
|
+
skippedDisabled: pool.filter((c) => !c.locationEnabled).map((c) => c.id)
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
//#endregion
|
|
4765
4787
|
//#region src/builtins/backup-orchestrator/backup-orchestrator.addon.ts
|
|
4766
4788
|
/**
|
|
4767
4789
|
* `backup-orchestrator` builtin — singleton owner of the `backup` cap.
|
|
@@ -4987,7 +5009,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
4987
5009
|
subId,
|
|
4988
5010
|
displayName,
|
|
4989
5011
|
kind: kindFromProviderId(loc.providerId),
|
|
4990
|
-
triggerSupported: effective.enabled,
|
|
5012
|
+
triggerSupported: effective.enabled && loc.enabled !== false,
|
|
4991
5013
|
restoreSupported: true,
|
|
4992
5014
|
enabled: effective.enabled,
|
|
4993
5015
|
retentionCount: effective.retentionCount,
|
|
@@ -5352,18 +5374,34 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
5352
5374
|
});
|
|
5353
5375
|
}
|
|
5354
5376
|
/**
|
|
5355
|
-
* Resolve a target destination set
|
|
5356
|
-
*
|
|
5357
|
-
*
|
|
5358
|
-
*
|
|
5359
|
-
*
|
|
5360
|
-
*
|
|
5361
|
-
*
|
|
5377
|
+
* Resolve a target destination set — see {@link selectBackupTargets} for the
|
|
5378
|
+
* rule. Caller-provided list wins over the per-destination policy flag;
|
|
5379
|
+
* NEITHER wins over `StorageLocation.enabled`, which is the operator's
|
|
5380
|
+
* switch on the storage screen and the only one that means "this disk takes
|
|
5381
|
+
* no writes" (D62).
|
|
5382
|
+
*
|
|
5383
|
+
* Reads locations + policies directly rather than going through
|
|
5384
|
+
* `listDestinations()`: that method also reads every destination's
|
|
5385
|
+
* `manifests.json` to hydrate `lastSuccess`, which target resolution has no
|
|
5386
|
+
* use for and which cost one round-trip per destination on every trigger.
|
|
5362
5387
|
*/
|
|
5363
5388
|
async resolveTargets(requested) {
|
|
5364
|
-
const
|
|
5365
|
-
|
|
5366
|
-
|
|
5389
|
+
const locations = await this.fetchBackupLocations();
|
|
5390
|
+
const policies = await this.loadPoliciesIndexed(locations.map((l) => l.id));
|
|
5391
|
+
const selection = selectBackupTargets({
|
|
5392
|
+
candidates: locations.map((l) => ({
|
|
5393
|
+
id: l.id,
|
|
5394
|
+
locationEnabled: l.enabled !== false,
|
|
5395
|
+
policyEnabled: (policies.get(l.id) ?? this.fallbackPolicy(l.id)).enabled
|
|
5396
|
+
})),
|
|
5397
|
+
...requested && requested.length > 0 ? { requested: [...requested] } : {}
|
|
5398
|
+
});
|
|
5399
|
+
if (selection.skippedDisabled.length > 0) this.ctx.logger.warn("backup: destination skipped — its storage location is disabled by the operator", { meta: {
|
|
5400
|
+
destinationIds: [...selection.skippedDisabled],
|
|
5401
|
+
requestedExplicitly: requested !== void 0 && requested.length > 0
|
|
5402
|
+
} });
|
|
5403
|
+
if (selection.targets.length === 0 && selection.skippedDisabled.length > 0) throw new Error(`every selected backup destination is disabled (${selection.skippedDisabled.join(", ")}) — enable its storage location on the Storage screen, or choose another destination. Refusing to write an archive to a location the operator turned off.`);
|
|
5404
|
+
return selection.targets.map((t) => ({ id: t.id }));
|
|
5367
5405
|
}
|
|
5368
5406
|
startScheduleTimer() {
|
|
5369
5407
|
if (this.scheduleTimer) return;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which `backups` locations tonight's archive may land on.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is its own module
|
|
5
|
+
*
|
|
6
|
+
* The orchestrator had **two** enabled switches for one function: the
|
|
7
|
+
* operator-visible `StorageLocation.enabled` on the storage screen, and
|
|
8
|
+
* `backup_destination_policies.enabled`, seeded `true` for every newly-seen
|
|
9
|
+
* location. Only the second one was consulted, so a location the storage
|
|
10
|
+
* screen showed as disabled still received the archive — the exact shape D62
|
|
11
|
+
* exists to forbid ("a second switch that disagrees with the first is worse
|
|
12
|
+
* than no switch").
|
|
13
|
+
*
|
|
14
|
+
* The location flag is now the OUTER gate for every path, and the policy flag
|
|
15
|
+
* keeps its narrower meaning: *this destination takes part in the unattended
|
|
16
|
+
* fan-out*. An explicit request (an operator pressing Back up now with a
|
|
17
|
+
* destination selected, or a schedule naming `locationIds`) still overrides
|
|
18
|
+
* the policy flag — it never overrides the location flag, because that one is
|
|
19
|
+
* the operator saying "do not write here" about the disk itself.
|
|
20
|
+
*
|
|
21
|
+
* Kept pure and separate so the decision can be read in one screen and the
|
|
22
|
+
* addon is left with fetch → decide → log → upload.
|
|
23
|
+
*/
|
|
24
|
+
/** One `backups` location, reduced to the two flags that decide a write. */
|
|
25
|
+
export interface BackupTargetCandidate {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
/**
|
|
28
|
+
* `StorageLocation.enabled`. ABSENT MEANS ACTIVE — the caller maps
|
|
29
|
+
* `enabled !== false`, so a legacy location persisted before the flag
|
|
30
|
+
* existed stays a destination.
|
|
31
|
+
*/
|
|
32
|
+
readonly locationEnabled: boolean;
|
|
33
|
+
/** `backup_destination_policies.enabled` — the unattended-fan-out opt-in. */
|
|
34
|
+
readonly policyEnabled: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface BackupTargetSelection {
|
|
37
|
+
/** Destinations the archive may be written to. */
|
|
38
|
+
readonly targets: readonly BackupTargetCandidate[];
|
|
39
|
+
/**
|
|
40
|
+
* Ids dropped because the OPERATOR disabled the storage location. Returned
|
|
41
|
+
* rather than silently filtered: a fan-out that drops a destination must say
|
|
42
|
+
* so, and the caller decides between "log and continue" and "refuse".
|
|
43
|
+
*/
|
|
44
|
+
readonly skippedDisabled: readonly string[];
|
|
45
|
+
}
|
|
46
|
+
export interface BackupTargetSelectionInput {
|
|
47
|
+
readonly candidates: readonly BackupTargetCandidate[];
|
|
48
|
+
/** Explicitly named destinations (cap input, or a schedule's `locationIds`). */
|
|
49
|
+
readonly requested?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Pick the destinations an archive may be fanned out to.
|
|
53
|
+
*
|
|
54
|
+
* An explicit `requested` list selects from the candidates by id and bypasses
|
|
55
|
+
* the policy flag (that is today's behaviour and it is right: naming a
|
|
56
|
+
* destination IS the opt-in). A requested id that is not a `backups` location
|
|
57
|
+
* at all is simply absent from the candidates and drops out here, exactly as
|
|
58
|
+
* before.
|
|
59
|
+
*
|
|
60
|
+
* The location flag is applied LAST and to both paths, so there is one place
|
|
61
|
+
* where "disabled" is honoured rather than one per caller.
|
|
62
|
+
*/
|
|
63
|
+
export declare function selectBackupTargets(input: BackupTargetSelectionInput): BackupTargetSelection;
|
|
@@ -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-CcvXUhHK.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as BaseAddon, ot as logDestinationCapability } from "../../dist-DKs---Vx.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-CcvXUhHK.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { F as coreBlockIdFromAddonId, I as coreBlocksCapability, Mt as BaseAddon, P as coreBlockAddonId, c as CoreBlockSchema, jt as errMsg, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, u as DeclaredDevices } from "../../dist-DKs---Vx.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AddonContext, CapabilityDefinition, DeviceBindingEntry, ICapabilityRegistry } from '@camstack/types';
|
|
2
2
|
import { DeviceBindingsStore } from './device-meta-types.js';
|
|
3
3
|
import { DeviceRow, DeviceRowStore } from './device-row-store.js';
|
|
4
|
+
import { WriteLock } from './write-lock.js';
|
|
4
5
|
/** Push-fed remote native-cap cache shape (numeric deviceId → capName → owner). */
|
|
5
6
|
export type RemoteNativeCaps = Map<number, Map<string, {
|
|
6
7
|
addonId: string;
|
|
@@ -18,6 +19,27 @@ export interface BindingsDeps {
|
|
|
18
19
|
/** Row access for `device-manager:devices` — the device ledger the type and
|
|
19
20
|
* presence gates below consult. */
|
|
20
21
|
readonly rows: DeviceRowStore;
|
|
22
|
+
/**
|
|
23
|
+
* Ordering for every read-modify-write of this addon's `addon-settings` key
|
|
24
|
+
* range — `deviceBindings` above all, but also `locations`, `nextDeviceId`
|
|
25
|
+
* and `roleDisplayDefaults`.
|
|
26
|
+
*
|
|
27
|
+
* `deviceBindings` is ONE fleet-wide blob keyed by device, so a writer that
|
|
28
|
+
* only means to touch camera 615 rewrites every camera's row. Nothing
|
|
29
|
+
* ordered those writers, and the window between the read and the write is a
|
|
30
|
+
* settings-door round trip — an RPC to the isolated `sqlite-settings` child,
|
|
31
|
+
* measured at 2.9 s on the live hub mid-drain (D181). Two toggles on
|
|
32
|
+
* DIFFERENT cameras therefore lost each other, and the value that went
|
|
33
|
+
* missing was `{ wrapperAddonId: null }`: the marker that keeps a
|
|
34
|
+
* `defaultActive` wrapper from re-binding. The operator's OFF came back ON
|
|
35
|
+
* (D62).
|
|
36
|
+
*
|
|
37
|
+
* **Writers only.** No read path takes this lock: `getBindings`,
|
|
38
|
+
* `getBindingsBatch` and `getAllBindings` must never queue behind a write,
|
|
39
|
+
* and putting a read under it would make a 1 019-device sweep wait on
|
|
40
|
+
* whatever toggle happens to be in flight.
|
|
41
|
+
*/
|
|
42
|
+
readonly withAddonStoreWriteLock: WriteLock;
|
|
21
43
|
}
|
|
22
44
|
/**
|
|
23
45
|
* Wire the push-fed cross-process native-cap cache (`remoteNativeCaps`).
|
|
@@ -107,8 +129,15 @@ export type DevicePresence = 'present' | 'absent' | 'unknown';
|
|
|
107
129
|
* same reason `getAllBindings` warns instead of reporting zero devices. That is
|
|
108
130
|
* the only reason this is async: the `COUNT(*)` runs solely on the miss path,
|
|
109
131
|
* where the alternative is to call a device gone because the store is empty.
|
|
132
|
+
*
|
|
133
|
+
* `ledgerNonEmpty` is passed IN rather than defaulted, deliberately. A default
|
|
134
|
+
* that issued its own `rows.count()` would be a per-device read hiding behind
|
|
135
|
+
* an optional argument — the exact shape this file just removed from the
|
|
136
|
+
* activations blob — and nothing would fail when a future caller took it.
|
|
110
137
|
*/
|
|
111
|
-
export declare function resolveDevicePresence(deps: BindingsDeps, row: DeviceRow | null, deviceId: number): Promise<DevicePresence>;
|
|
138
|
+
export declare function resolveDevicePresence(deps: BindingsDeps, row: DeviceRow | null, deviceId: number, ledgerNonEmpty: LedgerNonEmpty): Promise<DevicePresence>;
|
|
139
|
+
/** "Does the ledger hold any row at all?", asked at most once per pass. */
|
|
140
|
+
export type LedgerNonEmpty = () => Promise<boolean>;
|
|
112
141
|
/**
|
|
113
142
|
* Does a capability apply to a device of type `deviceType`?
|
|
114
143
|
*
|
|
@@ -138,15 +167,23 @@ export declare function getBindings(deps: BindingsDeps, input: {
|
|
|
138
167
|
* `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
|
|
139
168
|
*
|
|
140
169
|
* Sequential on purpose, like `getAllBindings`: `resolveBindingsForDevice`'s
|
|
141
|
-
* remaining per-device work
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
170
|
+
* remaining per-device work is the routing rules, which run on this process's
|
|
171
|
+
* own event loop, so concurrency buys nothing there and a fan-out of
|
|
172
|
+
* `Promise.all` over hundreds of ids only makes the queue less fair to
|
|
173
|
+
* everything else on it. Both parts that DO amortise across the whole set are
|
|
174
|
+
* fetched ONCE, ahead of the loop, and in parallel with each other:
|
|
175
|
+
*
|
|
176
|
+
* - the device rows ({@link DeviceRowStore.getMany}) rather than once per
|
|
177
|
+
* iteration — measured on the live hub 2026-08-25, `get
|
|
178
|
+
* device-manager:devices` ran 1 656 calls/min steady-state, one row per
|
|
179
|
+
* call, which is exactly what looping `getBindings`'s own
|
|
180
|
+
* `rows.get(deviceId)` here produced;
|
|
181
|
+
* - the wrapper-activation blob, which the per-device resolver used to read
|
|
182
|
+
* itself — a second settings-door round trip per id, on the call that
|
|
183
|
+
* exists precisely so a caller needing many devices does not pay per
|
|
184
|
+
* device. Measured on the live hub 2026-08-30, mid-drain:
|
|
185
|
+
* `getBindingsBatch` of 1 id = 5.78 s, of 8 ids = 17.16 s — a slope of
|
|
186
|
+
* 1.63 s PER ADDITIONAL DEVICE, on a call that ships 4.5 KB a row.
|
|
150
187
|
*/
|
|
151
188
|
export declare function getBindingsBatch(deps: BindingsDeps, input: {
|
|
152
189
|
deviceIds: readonly number[];
|
|
@@ -164,6 +201,15 @@ export declare function getBindingsBatch(deps: BindingsDeps, input: {
|
|
|
164
201
|
* clients invalidate via the existing
|
|
165
202
|
* `capability.binding-changed` event rather than re-fetching this
|
|
166
203
|
* payload periodically.
|
|
204
|
+
*
|
|
205
|
+
* **"Single round-trip" was a claim, not a fact, until 2026-08-30.** The
|
|
206
|
+
* per-device resolver read the wrapper-activation blob itself, so this call
|
|
207
|
+
* issued one settings-door RPC per device — 1 020 of them on the live
|
|
208
|
+
* 1 019-device hub, at ~2.9 s each while the storage drain ran. It did not
|
|
209
|
+
* return in 240 s, and `SystemMirror.init` gives it 15 s, so the admin UI
|
|
210
|
+
* could not boot at all. Now the whole pass costs exactly TWO reads (the fleet
|
|
211
|
+
* listing and the blob), issued in parallel, and nothing in the loop awaits a
|
|
212
|
+
* store.
|
|
167
213
|
*/
|
|
168
214
|
export declare function getAllBindings(deps: BindingsDeps): Promise<Array<{
|
|
169
215
|
deviceId: number;
|
|
@@ -83,6 +83,17 @@ export declare class DeviceManagerAddon extends BaseAddon {
|
|
|
83
83
|
* it through {@link bindingsDeps} or the `ProviderContext`.
|
|
84
84
|
*/
|
|
85
85
|
private deviceRows;
|
|
86
|
+
/**
|
|
87
|
+
* The ONE lock ordering every read-modify-write of this addon's
|
|
88
|
+
* `addon-settings` key range (`deviceBindings`, `locations`, `nextDeviceId`,
|
|
89
|
+
* `roleDisplayDefaults`).
|
|
90
|
+
*
|
|
91
|
+
* It lives on the addon rather than on `DeviceMetaStore` because the writers
|
|
92
|
+
* do not all go through that class — `setWrapperActive` and `removeDevice`'s
|
|
93
|
+
* bindings purge reach the store through `BindingsDeps` — and two locks over
|
|
94
|
+
* one key range is the same defect one level up. Handed to both.
|
|
95
|
+
*/
|
|
96
|
+
private readonly addonStoreWriteLock;
|
|
86
97
|
/** Build the dependency context the extracted binding resolvers consume. */
|
|
87
98
|
private get bindingsDeps();
|
|
88
99
|
getBindings(input: {
|