@camstack/system 1.2.169 → 1.2.171
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.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- 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-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- 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/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/data-store-dispatch.d.ts +18 -6
- package/dist/builtins/storage-orchestrator/hub-settings-forward.d.ts +68 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +27 -3
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +235 -57
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +235 -57
- 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-digPByP6.mjs → dist-1DEQFmTq.mjs} +165 -6
- package/dist/{dist-D6kBG4fg.js → dist-DsoVfJSR.js} +165 -6
- package/dist/hub-cap-forward-BdT320dq.mjs +53 -0
- package/dist/hub-cap-forward-DmHNjbB0.js +70 -0
- package/dist/index.js +18 -199
- package/dist/index.mjs +22 -203
- package/dist/kernel/capability-registry.d.ts +8 -27
- package/dist/kernel/config-manager.d.ts +0 -33
- package/dist/{manifest-system-deps-onn-Eawl.mjs → manifest-system-deps-Da6sme9C.mjs} +3 -54
- package/dist/{manifest-system-deps-BjMNGEpl.js → manifest-system-deps-ir_XTNpe.js} +2 -71
- package/dist/{retired-settings-keys-B5D2hfoN.mjs → retired-settings-keys-ChS1Q2Kv.mjs} +1 -1
- package/dist/{retired-settings-keys-BywIz4dZ.js → retired-settings-keys-DTE5k4lU.js} +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,8 @@ 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-DsoVfJSR.js");
|
|
7
|
+
const require_hub_cap_forward = require("../../hub-cap-forward-DmHNjbB0.js");
|
|
7
8
|
let zod = require("zod");
|
|
8
9
|
let node_crypto = require("node:crypto");
|
|
9
10
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1118,6 +1119,201 @@ function canProbeOccupancyLocally(locality) {
|
|
|
1118
1119
|
return locality === true;
|
|
1119
1120
|
}
|
|
1120
1121
|
//#endregion
|
|
1122
|
+
//#region src/builtins/storage-orchestrator/data-store-dispatch.ts
|
|
1123
|
+
/**
|
|
1124
|
+
* Pick the target to serve a call.
|
|
1125
|
+
*
|
|
1126
|
+
* A LOCAL engine always wins: the remote is a fallback for a node that has
|
|
1127
|
+
* none, never a policy. Two cases are refused rather than guessed:
|
|
1128
|
+
*
|
|
1129
|
+
* - **none registered, and no remote** — resolving to a no-op would make
|
|
1130
|
+
* every read answer `undefined` and every write disappear, which reads
|
|
1131
|
+
* exactly like an empty database;
|
|
1132
|
+
* - **more than one engine** — there is no routing rule yet. The `engine`
|
|
1133
|
+
* hint on `declareCollection` is what will choose, and until it exists a
|
|
1134
|
+
* silent pick would send a collection to whichever engine happened to
|
|
1135
|
+
* register first. A remote does not rescue this: the ambiguity is local.
|
|
1136
|
+
*/
|
|
1137
|
+
function resolveEngine(getEngines, getRemote) {
|
|
1138
|
+
const engines = getEngines();
|
|
1139
|
+
if (engines.length === 0) {
|
|
1140
|
+
const remote = getRemote?.() ?? null;
|
|
1141
|
+
if (remote) return remote;
|
|
1142
|
+
throw new Error("settings-store: no data-store-provider engine is registered — the data door has nothing behind it");
|
|
1143
|
+
}
|
|
1144
|
+
if (engines.length > 1) throw new Error(`settings-store: ${engines.length} data-store-provider engines are registered and there is no routing rule yet — declare the engine on the collection first`);
|
|
1145
|
+
const engine = engines[0];
|
|
1146
|
+
if (!engine) throw new Error("settings-store: data-store-provider collection yielded an empty entry");
|
|
1147
|
+
return engine;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Build the `settings-store` provider that fronts the registered engines,
|
|
1151
|
+
* with an optional remote behind it for a node that registers none.
|
|
1152
|
+
*
|
|
1153
|
+
* `getEngines` and `getRemote` are read on every call, never captured:
|
|
1154
|
+
* engines register during boot and a runner respawn replaces the instance, so
|
|
1155
|
+
* a snapshot taken at construction time would go stale exactly when an addon
|
|
1156
|
+
* crashed and came back.
|
|
1157
|
+
*/
|
|
1158
|
+
function createDataStoreDispatch(getEngines, getRemote) {
|
|
1159
|
+
const engine = async () => resolveEngine(getEngines, getRemote);
|
|
1160
|
+
return {
|
|
1161
|
+
get: async (input) => (await engine()).get(input),
|
|
1162
|
+
set: async (input) => (await engine()).set(input),
|
|
1163
|
+
query: async (input) => (await engine()).query(input),
|
|
1164
|
+
insert: async (input) => (await engine()).insert(input),
|
|
1165
|
+
insertMany: async (input) => (await engine()).insertMany(input),
|
|
1166
|
+
update: async (input) => (await engine()).update(input),
|
|
1167
|
+
delete: async (input) => (await engine()).delete(input),
|
|
1168
|
+
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
1169
|
+
updateWhere: async (input) => (await engine()).updateWhere(input),
|
|
1170
|
+
count: async (input) => (await engine()).count(input),
|
|
1171
|
+
aggregate: async (input) => (await engine()).aggregate(input),
|
|
1172
|
+
histogram: async (input) => (await engine()).histogram(input),
|
|
1173
|
+
isEmpty: async (input) => (await engine()).isEmpty(input),
|
|
1174
|
+
declareCollection: async (input) => (await engine()).declareCollection(input)
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/builtins/storage-orchestrator/hub-settings-forward.ts
|
|
1179
|
+
/**
|
|
1180
|
+
* The data door with the HUB behind it — an agent's `settings-store` provider.
|
|
1181
|
+
*
|
|
1182
|
+
* ## Why this exists
|
|
1183
|
+
*
|
|
1184
|
+
* Persistence is the hub's. An agent runs no `data-store-provider` engine
|
|
1185
|
+
* (see `resolveAgentInfraCapabilities` in `server/backend/src/agent/`), so the
|
|
1186
|
+
* `settings-store` door the storage-orchestrator registers on an agent has
|
|
1187
|
+
* nothing local behind it. Rather than refuse — which would strand the four
|
|
1188
|
+
* infra addons that boot inside agent-main (`hub-forwarder`, `native-metrics`,
|
|
1189
|
+
* `platform-probe`, the orchestrator itself) with a `ctx.settings` that throws
|
|
1190
|
+
* on every read — the door FORWARDS into the hub's own routing.
|
|
1191
|
+
*
|
|
1192
|
+
* This is not a new transport. It is the same `$hub-cap-fwd.forward` a forked
|
|
1193
|
+
* addon on an agent has always used to reach a hub-hosted cap
|
|
1194
|
+
* (`kernel/transport/hub-cap-forward.ts` names `settings-store` in its own
|
|
1195
|
+
* header); agent-main's infra addons simply never had it, because they resolve
|
|
1196
|
+
* `ctx.api.settingsStore` through `localProviderLink` against the process's own
|
|
1197
|
+
* provider registry — which is exactly the local door.
|
|
1198
|
+
*
|
|
1199
|
+
* ## The payload crosses untouched
|
|
1200
|
+
*
|
|
1201
|
+
* The engine composes its table name from `namespace` + `collection` **out of
|
|
1202
|
+
* the payload**. A forwarder that injected, defaulted or stripped a namespace
|
|
1203
|
+
* would silently rename every table on the hub. Every method here is the same
|
|
1204
|
+
* forward, and the args are the caller's object verbatim — the same rule
|
|
1205
|
+
* `data-store-dispatch.ts` states for the local path, for the same reason.
|
|
1206
|
+
*
|
|
1207
|
+
* ## No second key space
|
|
1208
|
+
*
|
|
1209
|
+
* Per-node scoping already exists, one layer up, and it is per FIELD:
|
|
1210
|
+
* `BaseAddon` persists a `perNode: true` schema field as `<key>@<nodeId>`
|
|
1211
|
+
* inside the addon's single shared blob (`packages/types/src/addon/
|
|
1212
|
+
* per-node-store.ts`). The agent therefore reads and writes the SAME row the
|
|
1213
|
+
* hub's copy of that addon reads — `<addonId>:addon-settings` / `root` — and
|
|
1214
|
+
* the two agree by construction. Introducing a second, addon-level
|
|
1215
|
+
* `(nodeId, addonId)` row here would be a second authority over one fact,
|
|
1216
|
+
* which is the shape [D224](../../../../../docs/decisions/adr-0224-the-freshness-of-a-camera-fact-lives-in-its-provider.md)
|
|
1217
|
+
* forbids: the hub's copy would keep writing the bare row and nothing would
|
|
1218
|
+
* reconcile them.
|
|
1219
|
+
*
|
|
1220
|
+
* ## A hub NEVER forwards
|
|
1221
|
+
*
|
|
1222
|
+
* {@link nodeOwnsItsDataStore} gates construction. A hub that built one would
|
|
1223
|
+
* route its own door back into itself through `$hub-cap-fwd`, which resolves
|
|
1224
|
+
* `settings-store` to… this. The gate reads the same env var, with the same
|
|
1225
|
+
* default, as `settingsStoreIsAuthoritativeHere` — unset means hub, because
|
|
1226
|
+
* that is what the launcher defaults to.
|
|
1227
|
+
*
|
|
1228
|
+
* ## When the hub is down
|
|
1229
|
+
*
|
|
1230
|
+
* Every method REJECTS. It never answers "empty": `ctx.settings`' read path
|
|
1231
|
+
* (`addon-context-factory.ts`) distinguishes a MISS from a FAILURE precisely
|
|
1232
|
+
* so a failed read cannot make the next read-modify-write persist the patch
|
|
1233
|
+
* alone and destroy every field the patch does not mention. An empty answer
|
|
1234
|
+
* would be indistinguishable from an empty store; a rejection is not. The
|
|
1235
|
+
* operator's rule for this node is the same one that already governs it —
|
|
1236
|
+
* without the hub an agent has no work — so a rejection is the whole of the
|
|
1237
|
+
* degraded behaviour, and it is loud.
|
|
1238
|
+
*/
|
|
1239
|
+
/**
|
|
1240
|
+
* Does THIS node hold its own data store?
|
|
1241
|
+
*
|
|
1242
|
+
* Unset or `'hub'` means hub — the launcher's own default, mirrored from
|
|
1243
|
+
* `resolveNodeRole` (`server/backend/src/node-role.ts`) the same way
|
|
1244
|
+
* `settingsStoreIsAuthoritativeHere` mirrors it, and duplicated rather than
|
|
1245
|
+
* imported for the same reason: `@camstack/system` is a dependency OF the
|
|
1246
|
+
* backend, not the other way round.
|
|
1247
|
+
*
|
|
1248
|
+
* Anything else is an agent, and an agent's answer is the hub's store.
|
|
1249
|
+
*/
|
|
1250
|
+
function nodeOwnsItsDataStore(env) {
|
|
1251
|
+
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
1252
|
+
return raw === "" || raw === "hub";
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Build the `settings-store` provider that forwards to the hub.
|
|
1256
|
+
*
|
|
1257
|
+
* Every method is the same forward under the CAP name — never the engine
|
|
1258
|
+
* name. The hub routes `settings-store` to its own door, which then picks its
|
|
1259
|
+
* local engine; asking for `data-store-provider` would step around the door on
|
|
1260
|
+
* the far side and lose whatever the door is for.
|
|
1261
|
+
*/
|
|
1262
|
+
function createHubSettingsForward(forward) {
|
|
1263
|
+
const send = (method, args) => forward({
|
|
1264
|
+
capName: require_dist.settingsStoreCapability.name,
|
|
1265
|
+
method,
|
|
1266
|
+
args
|
|
1267
|
+
});
|
|
1268
|
+
return {
|
|
1269
|
+
get: (input) => send("get", input),
|
|
1270
|
+
set: (input) => send("set", input),
|
|
1271
|
+
query: (input) => send("query", input),
|
|
1272
|
+
insert: (input) => send("insert", input),
|
|
1273
|
+
insertMany: (input) => send("insertMany", input),
|
|
1274
|
+
update: (input) => send("update", input),
|
|
1275
|
+
delete: (input) => send("delete", input),
|
|
1276
|
+
deleteWhere: (input) => send("deleteWhere", input),
|
|
1277
|
+
updateWhere: (input) => send("updateWhere", input),
|
|
1278
|
+
count: (input) => send("count", input),
|
|
1279
|
+
aggregate: (input) => send("aggregate", input),
|
|
1280
|
+
histogram: (input) => send("histogram", input),
|
|
1281
|
+
isEmpty: (input) => send("isEmpty", input),
|
|
1282
|
+
declareCollection: (input) => send("declareCollection", input)
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* How long an agent waits for the hub to answer a settings call before the
|
|
1287
|
+
* door rejects. Matches the agent's own `forwardToHub` budget in
|
|
1288
|
+
* `server/backend/src/agent/main.ts` — the same hop, over the same action, so
|
|
1289
|
+
* a shorter one here would only manufacture a timeout the transport did not
|
|
1290
|
+
* have.
|
|
1291
|
+
*/
|
|
1292
|
+
var HUB_SETTINGS_FORWARD_TIMEOUT_MS = 6e4;
|
|
1293
|
+
/**
|
|
1294
|
+
* The whole decision, in one place: does this node's data door have the hub
|
|
1295
|
+
* behind it, and how does it get there?
|
|
1296
|
+
*
|
|
1297
|
+
* `null` means "no remote" and the door keeps its old behaviour — it refuses
|
|
1298
|
+
* when no engine is registered. That is the answer on a hub (which IS the
|
|
1299
|
+
* store) and in any process with no cluster broker.
|
|
1300
|
+
*
|
|
1301
|
+
* Extracted from the addon so the gate is unit-testable without booting a
|
|
1302
|
+
* `BaseAddon` — the same reason `data-store-dispatch.ts` and
|
|
1303
|
+
* `provider-discovery.ts` exist.
|
|
1304
|
+
*/
|
|
1305
|
+
function resolveHubSettingsForward(options) {
|
|
1306
|
+
if (nodeOwnsItsDataStore(options.env)) return null;
|
|
1307
|
+
const broker = options.broker;
|
|
1308
|
+
if (!broker) return null;
|
|
1309
|
+
options.logger.info("storage-orchestrator: no local data-store engine on this node — the data door forwards to the hub");
|
|
1310
|
+
return createHubSettingsForward((envelope) => broker.call(require_hub_cap_forward.HUB_CAP_FWD_ACTION, {
|
|
1311
|
+
capName: envelope.capName,
|
|
1312
|
+
method: envelope.method,
|
|
1313
|
+
args: envelope.args
|
|
1314
|
+
}, { timeout: HUB_SETTINGS_FORWARD_TIMEOUT_MS }));
|
|
1315
|
+
}
|
|
1316
|
+
//#endregion
|
|
1121
1317
|
//#region src/builtins/storage-orchestrator/location-occupancy.ts
|
|
1122
1318
|
/**
|
|
1123
1319
|
* Delete-time occupancy of a location's on-disk root.
|
|
@@ -1190,57 +1386,6 @@ async function probeLocalDirectoryOccupancy(input) {
|
|
|
1190
1386
|
return "empty";
|
|
1191
1387
|
}
|
|
1192
1388
|
//#endregion
|
|
1193
|
-
//#region src/builtins/storage-orchestrator/data-store-dispatch.ts
|
|
1194
|
-
/**
|
|
1195
|
-
* Pick the engine to serve a call.
|
|
1196
|
-
*
|
|
1197
|
-
* Today the rule is "the only one there is". Two cases are refused rather
|
|
1198
|
-
* than guessed:
|
|
1199
|
-
*
|
|
1200
|
-
* - **none registered** — resolving to a no-op would make every read
|
|
1201
|
-
* answer `undefined` and every write disappear, which reads exactly
|
|
1202
|
-
* like an empty database;
|
|
1203
|
-
* - **more than one** — there is no routing rule yet. The `engine` hint
|
|
1204
|
-
* on `declareCollection` is what will choose, and until it exists a
|
|
1205
|
-
* silent pick would send a collection to whichever engine happened to
|
|
1206
|
-
* register first.
|
|
1207
|
-
*/
|
|
1208
|
-
function resolveEngine(getEngines) {
|
|
1209
|
-
const engines = getEngines();
|
|
1210
|
-
if (engines.length === 0) throw new Error("settings-store: no data-store-provider engine is registered — the data door has nothing behind it");
|
|
1211
|
-
if (engines.length > 1) throw new Error(`settings-store: ${engines.length} data-store-provider engines are registered and there is no routing rule yet — declare the engine on the collection first`);
|
|
1212
|
-
const engine = engines[0];
|
|
1213
|
-
if (!engine) throw new Error("settings-store: data-store-provider collection yielded an empty entry");
|
|
1214
|
-
return engine;
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Build the `settings-store` provider that fronts the registered engines.
|
|
1218
|
-
*
|
|
1219
|
-
* `getEngines` is read on every call, never captured: engines register
|
|
1220
|
-
* during boot and a runner respawn replaces the instance, so a snapshot
|
|
1221
|
-
* taken at construction time would go stale exactly when an addon crashed
|
|
1222
|
-
* and came back.
|
|
1223
|
-
*/
|
|
1224
|
-
function createDataStoreDispatch(getEngines) {
|
|
1225
|
-
const engine = async () => resolveEngine(getEngines);
|
|
1226
|
-
return {
|
|
1227
|
-
get: async (input) => (await engine()).get(input),
|
|
1228
|
-
set: async (input) => (await engine()).set(input),
|
|
1229
|
-
query: async (input) => (await engine()).query(input),
|
|
1230
|
-
insert: async (input) => (await engine()).insert(input),
|
|
1231
|
-
insertMany: async (input) => (await engine()).insertMany(input),
|
|
1232
|
-
update: async (input) => (await engine()).update(input),
|
|
1233
|
-
delete: async (input) => (await engine()).delete(input),
|
|
1234
|
-
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
1235
|
-
updateWhere: async (input) => (await engine()).updateWhere(input),
|
|
1236
|
-
count: async (input) => (await engine()).count(input),
|
|
1237
|
-
aggregate: async (input) => (await engine()).aggregate(input),
|
|
1238
|
-
histogram: async (input) => (await engine()).histogram(input),
|
|
1239
|
-
isEmpty: async (input) => (await engine()).isEmpty(input),
|
|
1240
|
-
declareCollection: async (input) => (await engine()).declareCollection(input)
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
//#endregion
|
|
1244
1389
|
//#region src/builtins/storage-orchestrator/location-secrets.ts
|
|
1245
1390
|
/**
|
|
1246
1391
|
* The config keys the ORCHESTRATOR itself owns, as opposed to the ones a
|
|
@@ -2838,10 +2983,32 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2838
2983
|
},
|
|
2839
2984
|
{
|
|
2840
2985
|
capability: require_dist.settingsStoreCapability,
|
|
2841
|
-
provider: createDataStoreDispatch(getEngines)
|
|
2986
|
+
provider: createDataStoreDispatch(getEngines, () => this.hubSettingsForward())
|
|
2842
2987
|
}
|
|
2843
2988
|
];
|
|
2844
2989
|
}
|
|
2990
|
+
/**
|
|
2991
|
+
* The remote behind the data door on a node that owns no engine.
|
|
2992
|
+
*
|
|
2993
|
+
* `null` on a hub — the hub IS the store, and a hub that forwarded would
|
|
2994
|
+
* route its own door back into itself through `$hub-cap-fwd`. `null` too
|
|
2995
|
+
* when this process has no cluster broker (a forked worker, or a test
|
|
2996
|
+
* harness); the door then behaves exactly as it always did and refuses.
|
|
2997
|
+
*
|
|
2998
|
+
* Built once and memoised. It is only consulted when `getEngines()` is
|
|
2999
|
+
* empty, so on a hub — and on an agent rolled back to a local engine via
|
|
3000
|
+
* `CAMSTACK_AGENT_LOCAL_DATA_STORE` — the resolve never runs a second time.
|
|
3001
|
+
*/
|
|
3002
|
+
hubForward = null;
|
|
3003
|
+
hubSettingsForward() {
|
|
3004
|
+
if (this.hubForward) return this.hubForward;
|
|
3005
|
+
this.hubForward = resolveHubSettingsForward({
|
|
3006
|
+
env: process.env,
|
|
3007
|
+
broker: this.ctx.kernel.cluster?.broker,
|
|
3008
|
+
logger: this.ctx.logger
|
|
3009
|
+
});
|
|
3010
|
+
return this.hubForward;
|
|
3011
|
+
}
|
|
2845
3012
|
async onShutdown() {
|
|
2846
3013
|
for (const dispose of this.seedSubscriptionDisposers) try {
|
|
2847
3014
|
dispose();
|
|
@@ -2862,9 +3029,19 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2862
3029
|
/**
|
|
2863
3030
|
* Resolve the live engine from the capability registry and wrap it in a
|
|
2864
3031
|
* `SqliteLocationStore`. Returns `null` when no engine has registered
|
|
2865
|
-
* yet — the service falls back to in-memory only.
|
|
2866
|
-
*
|
|
2867
|
-
*
|
|
3032
|
+
* yet — the service falls back to in-memory only. On a HUB that is a
|
|
3033
|
+
* boot-order window: `data-store-provider` precedes `storage` in
|
|
3034
|
+
* `INFRA_CAPABILITIES`, so it is the rare path.
|
|
3035
|
+
*
|
|
3036
|
+
* On an AGENT it is permanent and intended, and the line says so. An agent
|
|
3037
|
+
* boots no engine (`resolveAgentInfraCapabilities`), and this store must NOT
|
|
3038
|
+
* be pointed at the hub-forwarding data door: `storage_locations` is keyed by
|
|
3039
|
+
* a bare location id (`recordings:default`, `eventMedia:default`, …) which
|
|
3040
|
+
* every node in the cluster shares, so an agent writing through the door
|
|
3041
|
+
* would land on the HUB's rows. The locations an agent needs are re-seeded
|
|
3042
|
+
* from its addon declarations at every boot; the operator's real, node-pinned
|
|
3043
|
+
* locations live in the hub's own table (`__nodeId` in the config blob) and
|
|
3044
|
+
* are dispatched to the agent's `storage-provider` from there.
|
|
2868
3045
|
*
|
|
2869
3046
|
* Reads the ENGINE, not the `settings-store` singleton: since D44 this
|
|
2870
3047
|
* addon *is* that singleton, so asking the registry for it would hand
|
|
@@ -2876,7 +3053,8 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2876
3053
|
if (!reg) return null;
|
|
2877
3054
|
const engine = reg.getCollection(DATA_STORE_PROVIDER_CAP_NAME)[0];
|
|
2878
3055
|
if (!engine) {
|
|
2879
|
-
if (!quiet) this.ctx.logger.warn("storage-orchestrator: no data-store-provider engine yet — running in-memory until one registers");
|
|
3056
|
+
if (!quiet) if (nodeOwnsItsDataStore(process.env)) this.ctx.logger.warn("storage-orchestrator: no data-store-provider engine yet — running in-memory until one registers");
|
|
3057
|
+
else this.ctx.logger.info("storage-orchestrator: this node boots no data-store engine — storage locations are seeded from addon declarations at every boot, and node-pinned locations live on the hub");
|
|
2880
3058
|
return null;
|
|
2881
3059
|
}
|
|
2882
3060
|
return new SqliteLocationStore(engine);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { $t as parseJsonObject, C as StorageMigrationJobSchema, Ct as settingsStoreCapability, Et as storageMigrationCapability, Nt as BaseAddon, P as collectSecretConfigKeys, S as StorageLocationTypeSchema, Tt as storageCapability, m as REDACTED_SECRET, x as StorageCleanupJobSchema } from "../../dist-
|
|
1
|
+
import { $t as parseJsonObject, C as StorageMigrationJobSchema, Ct as settingsStoreCapability, Et as storageMigrationCapability, Nt as BaseAddon, P as collectSecretConfigKeys, S as StorageLocationTypeSchema, Tt as storageCapability, m as REDACTED_SECRET, x as StorageCleanupJobSchema } from "../../dist-1DEQFmTq.mjs";
|
|
2
|
+
import { t as HUB_CAP_FWD_ACTION } from "../../hub-cap-forward-BdT320dq.mjs";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import * as fs from "node:fs/promises";
|
|
@@ -1113,6 +1114,201 @@ function canProbeOccupancyLocally(locality) {
|
|
|
1113
1114
|
return locality === true;
|
|
1114
1115
|
}
|
|
1115
1116
|
//#endregion
|
|
1117
|
+
//#region src/builtins/storage-orchestrator/data-store-dispatch.ts
|
|
1118
|
+
/**
|
|
1119
|
+
* Pick the target to serve a call.
|
|
1120
|
+
*
|
|
1121
|
+
* A LOCAL engine always wins: the remote is a fallback for a node that has
|
|
1122
|
+
* none, never a policy. Two cases are refused rather than guessed:
|
|
1123
|
+
*
|
|
1124
|
+
* - **none registered, and no remote** — resolving to a no-op would make
|
|
1125
|
+
* every read answer `undefined` and every write disappear, which reads
|
|
1126
|
+
* exactly like an empty database;
|
|
1127
|
+
* - **more than one engine** — there is no routing rule yet. The `engine`
|
|
1128
|
+
* hint on `declareCollection` is what will choose, and until it exists a
|
|
1129
|
+
* silent pick would send a collection to whichever engine happened to
|
|
1130
|
+
* register first. A remote does not rescue this: the ambiguity is local.
|
|
1131
|
+
*/
|
|
1132
|
+
function resolveEngine(getEngines, getRemote) {
|
|
1133
|
+
const engines = getEngines();
|
|
1134
|
+
if (engines.length === 0) {
|
|
1135
|
+
const remote = getRemote?.() ?? null;
|
|
1136
|
+
if (remote) return remote;
|
|
1137
|
+
throw new Error("settings-store: no data-store-provider engine is registered — the data door has nothing behind it");
|
|
1138
|
+
}
|
|
1139
|
+
if (engines.length > 1) throw new Error(`settings-store: ${engines.length} data-store-provider engines are registered and there is no routing rule yet — declare the engine on the collection first`);
|
|
1140
|
+
const engine = engines[0];
|
|
1141
|
+
if (!engine) throw new Error("settings-store: data-store-provider collection yielded an empty entry");
|
|
1142
|
+
return engine;
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Build the `settings-store` provider that fronts the registered engines,
|
|
1146
|
+
* with an optional remote behind it for a node that registers none.
|
|
1147
|
+
*
|
|
1148
|
+
* `getEngines` and `getRemote` are read on every call, never captured:
|
|
1149
|
+
* engines register during boot and a runner respawn replaces the instance, so
|
|
1150
|
+
* a snapshot taken at construction time would go stale exactly when an addon
|
|
1151
|
+
* crashed and came back.
|
|
1152
|
+
*/
|
|
1153
|
+
function createDataStoreDispatch(getEngines, getRemote) {
|
|
1154
|
+
const engine = async () => resolveEngine(getEngines, getRemote);
|
|
1155
|
+
return {
|
|
1156
|
+
get: async (input) => (await engine()).get(input),
|
|
1157
|
+
set: async (input) => (await engine()).set(input),
|
|
1158
|
+
query: async (input) => (await engine()).query(input),
|
|
1159
|
+
insert: async (input) => (await engine()).insert(input),
|
|
1160
|
+
insertMany: async (input) => (await engine()).insertMany(input),
|
|
1161
|
+
update: async (input) => (await engine()).update(input),
|
|
1162
|
+
delete: async (input) => (await engine()).delete(input),
|
|
1163
|
+
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
1164
|
+
updateWhere: async (input) => (await engine()).updateWhere(input),
|
|
1165
|
+
count: async (input) => (await engine()).count(input),
|
|
1166
|
+
aggregate: async (input) => (await engine()).aggregate(input),
|
|
1167
|
+
histogram: async (input) => (await engine()).histogram(input),
|
|
1168
|
+
isEmpty: async (input) => (await engine()).isEmpty(input),
|
|
1169
|
+
declareCollection: async (input) => (await engine()).declareCollection(input)
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
//#endregion
|
|
1173
|
+
//#region src/builtins/storage-orchestrator/hub-settings-forward.ts
|
|
1174
|
+
/**
|
|
1175
|
+
* The data door with the HUB behind it — an agent's `settings-store` provider.
|
|
1176
|
+
*
|
|
1177
|
+
* ## Why this exists
|
|
1178
|
+
*
|
|
1179
|
+
* Persistence is the hub's. An agent runs no `data-store-provider` engine
|
|
1180
|
+
* (see `resolveAgentInfraCapabilities` in `server/backend/src/agent/`), so the
|
|
1181
|
+
* `settings-store` door the storage-orchestrator registers on an agent has
|
|
1182
|
+
* nothing local behind it. Rather than refuse — which would strand the four
|
|
1183
|
+
* infra addons that boot inside agent-main (`hub-forwarder`, `native-metrics`,
|
|
1184
|
+
* `platform-probe`, the orchestrator itself) with a `ctx.settings` that throws
|
|
1185
|
+
* on every read — the door FORWARDS into the hub's own routing.
|
|
1186
|
+
*
|
|
1187
|
+
* This is not a new transport. It is the same `$hub-cap-fwd.forward` a forked
|
|
1188
|
+
* addon on an agent has always used to reach a hub-hosted cap
|
|
1189
|
+
* (`kernel/transport/hub-cap-forward.ts` names `settings-store` in its own
|
|
1190
|
+
* header); agent-main's infra addons simply never had it, because they resolve
|
|
1191
|
+
* `ctx.api.settingsStore` through `localProviderLink` against the process's own
|
|
1192
|
+
* provider registry — which is exactly the local door.
|
|
1193
|
+
*
|
|
1194
|
+
* ## The payload crosses untouched
|
|
1195
|
+
*
|
|
1196
|
+
* The engine composes its table name from `namespace` + `collection` **out of
|
|
1197
|
+
* the payload**. A forwarder that injected, defaulted or stripped a namespace
|
|
1198
|
+
* would silently rename every table on the hub. Every method here is the same
|
|
1199
|
+
* forward, and the args are the caller's object verbatim — the same rule
|
|
1200
|
+
* `data-store-dispatch.ts` states for the local path, for the same reason.
|
|
1201
|
+
*
|
|
1202
|
+
* ## No second key space
|
|
1203
|
+
*
|
|
1204
|
+
* Per-node scoping already exists, one layer up, and it is per FIELD:
|
|
1205
|
+
* `BaseAddon` persists a `perNode: true` schema field as `<key>@<nodeId>`
|
|
1206
|
+
* inside the addon's single shared blob (`packages/types/src/addon/
|
|
1207
|
+
* per-node-store.ts`). The agent therefore reads and writes the SAME row the
|
|
1208
|
+
* hub's copy of that addon reads — `<addonId>:addon-settings` / `root` — and
|
|
1209
|
+
* the two agree by construction. Introducing a second, addon-level
|
|
1210
|
+
* `(nodeId, addonId)` row here would be a second authority over one fact,
|
|
1211
|
+
* which is the shape [D224](../../../../../docs/decisions/adr-0224-the-freshness-of-a-camera-fact-lives-in-its-provider.md)
|
|
1212
|
+
* forbids: the hub's copy would keep writing the bare row and nothing would
|
|
1213
|
+
* reconcile them.
|
|
1214
|
+
*
|
|
1215
|
+
* ## A hub NEVER forwards
|
|
1216
|
+
*
|
|
1217
|
+
* {@link nodeOwnsItsDataStore} gates construction. A hub that built one would
|
|
1218
|
+
* route its own door back into itself through `$hub-cap-fwd`, which resolves
|
|
1219
|
+
* `settings-store` to… this. The gate reads the same env var, with the same
|
|
1220
|
+
* default, as `settingsStoreIsAuthoritativeHere` — unset means hub, because
|
|
1221
|
+
* that is what the launcher defaults to.
|
|
1222
|
+
*
|
|
1223
|
+
* ## When the hub is down
|
|
1224
|
+
*
|
|
1225
|
+
* Every method REJECTS. It never answers "empty": `ctx.settings`' read path
|
|
1226
|
+
* (`addon-context-factory.ts`) distinguishes a MISS from a FAILURE precisely
|
|
1227
|
+
* so a failed read cannot make the next read-modify-write persist the patch
|
|
1228
|
+
* alone and destroy every field the patch does not mention. An empty answer
|
|
1229
|
+
* would be indistinguishable from an empty store; a rejection is not. The
|
|
1230
|
+
* operator's rule for this node is the same one that already governs it —
|
|
1231
|
+
* without the hub an agent has no work — so a rejection is the whole of the
|
|
1232
|
+
* degraded behaviour, and it is loud.
|
|
1233
|
+
*/
|
|
1234
|
+
/**
|
|
1235
|
+
* Does THIS node hold its own data store?
|
|
1236
|
+
*
|
|
1237
|
+
* Unset or `'hub'` means hub — the launcher's own default, mirrored from
|
|
1238
|
+
* `resolveNodeRole` (`server/backend/src/node-role.ts`) the same way
|
|
1239
|
+
* `settingsStoreIsAuthoritativeHere` mirrors it, and duplicated rather than
|
|
1240
|
+
* imported for the same reason: `@camstack/system` is a dependency OF the
|
|
1241
|
+
* backend, not the other way round.
|
|
1242
|
+
*
|
|
1243
|
+
* Anything else is an agent, and an agent's answer is the hub's store.
|
|
1244
|
+
*/
|
|
1245
|
+
function nodeOwnsItsDataStore(env) {
|
|
1246
|
+
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
1247
|
+
return raw === "" || raw === "hub";
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Build the `settings-store` provider that forwards to the hub.
|
|
1251
|
+
*
|
|
1252
|
+
* Every method is the same forward under the CAP name — never the engine
|
|
1253
|
+
* name. The hub routes `settings-store` to its own door, which then picks its
|
|
1254
|
+
* local engine; asking for `data-store-provider` would step around the door on
|
|
1255
|
+
* the far side and lose whatever the door is for.
|
|
1256
|
+
*/
|
|
1257
|
+
function createHubSettingsForward(forward) {
|
|
1258
|
+
const send = (method, args) => forward({
|
|
1259
|
+
capName: settingsStoreCapability.name,
|
|
1260
|
+
method,
|
|
1261
|
+
args
|
|
1262
|
+
});
|
|
1263
|
+
return {
|
|
1264
|
+
get: (input) => send("get", input),
|
|
1265
|
+
set: (input) => send("set", input),
|
|
1266
|
+
query: (input) => send("query", input),
|
|
1267
|
+
insert: (input) => send("insert", input),
|
|
1268
|
+
insertMany: (input) => send("insertMany", input),
|
|
1269
|
+
update: (input) => send("update", input),
|
|
1270
|
+
delete: (input) => send("delete", input),
|
|
1271
|
+
deleteWhere: (input) => send("deleteWhere", input),
|
|
1272
|
+
updateWhere: (input) => send("updateWhere", input),
|
|
1273
|
+
count: (input) => send("count", input),
|
|
1274
|
+
aggregate: (input) => send("aggregate", input),
|
|
1275
|
+
histogram: (input) => send("histogram", input),
|
|
1276
|
+
isEmpty: (input) => send("isEmpty", input),
|
|
1277
|
+
declareCollection: (input) => send("declareCollection", input)
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* How long an agent waits for the hub to answer a settings call before the
|
|
1282
|
+
* door rejects. Matches the agent's own `forwardToHub` budget in
|
|
1283
|
+
* `server/backend/src/agent/main.ts` — the same hop, over the same action, so
|
|
1284
|
+
* a shorter one here would only manufacture a timeout the transport did not
|
|
1285
|
+
* have.
|
|
1286
|
+
*/
|
|
1287
|
+
var HUB_SETTINGS_FORWARD_TIMEOUT_MS = 6e4;
|
|
1288
|
+
/**
|
|
1289
|
+
* The whole decision, in one place: does this node's data door have the hub
|
|
1290
|
+
* behind it, and how does it get there?
|
|
1291
|
+
*
|
|
1292
|
+
* `null` means "no remote" and the door keeps its old behaviour — it refuses
|
|
1293
|
+
* when no engine is registered. That is the answer on a hub (which IS the
|
|
1294
|
+
* store) and in any process with no cluster broker.
|
|
1295
|
+
*
|
|
1296
|
+
* Extracted from the addon so the gate is unit-testable without booting a
|
|
1297
|
+
* `BaseAddon` — the same reason `data-store-dispatch.ts` and
|
|
1298
|
+
* `provider-discovery.ts` exist.
|
|
1299
|
+
*/
|
|
1300
|
+
function resolveHubSettingsForward(options) {
|
|
1301
|
+
if (nodeOwnsItsDataStore(options.env)) return null;
|
|
1302
|
+
const broker = options.broker;
|
|
1303
|
+
if (!broker) return null;
|
|
1304
|
+
options.logger.info("storage-orchestrator: no local data-store engine on this node — the data door forwards to the hub");
|
|
1305
|
+
return createHubSettingsForward((envelope) => broker.call(HUB_CAP_FWD_ACTION, {
|
|
1306
|
+
capName: envelope.capName,
|
|
1307
|
+
method: envelope.method,
|
|
1308
|
+
args: envelope.args
|
|
1309
|
+
}, { timeout: HUB_SETTINGS_FORWARD_TIMEOUT_MS }));
|
|
1310
|
+
}
|
|
1311
|
+
//#endregion
|
|
1116
1312
|
//#region src/builtins/storage-orchestrator/location-occupancy.ts
|
|
1117
1313
|
/**
|
|
1118
1314
|
* Delete-time occupancy of a location's on-disk root.
|
|
@@ -1185,57 +1381,6 @@ async function probeLocalDirectoryOccupancy(input) {
|
|
|
1185
1381
|
return "empty";
|
|
1186
1382
|
}
|
|
1187
1383
|
//#endregion
|
|
1188
|
-
//#region src/builtins/storage-orchestrator/data-store-dispatch.ts
|
|
1189
|
-
/**
|
|
1190
|
-
* Pick the engine to serve a call.
|
|
1191
|
-
*
|
|
1192
|
-
* Today the rule is "the only one there is". Two cases are refused rather
|
|
1193
|
-
* than guessed:
|
|
1194
|
-
*
|
|
1195
|
-
* - **none registered** — resolving to a no-op would make every read
|
|
1196
|
-
* answer `undefined` and every write disappear, which reads exactly
|
|
1197
|
-
* like an empty database;
|
|
1198
|
-
* - **more than one** — there is no routing rule yet. The `engine` hint
|
|
1199
|
-
* on `declareCollection` is what will choose, and until it exists a
|
|
1200
|
-
* silent pick would send a collection to whichever engine happened to
|
|
1201
|
-
* register first.
|
|
1202
|
-
*/
|
|
1203
|
-
function resolveEngine(getEngines) {
|
|
1204
|
-
const engines = getEngines();
|
|
1205
|
-
if (engines.length === 0) throw new Error("settings-store: no data-store-provider engine is registered — the data door has nothing behind it");
|
|
1206
|
-
if (engines.length > 1) throw new Error(`settings-store: ${engines.length} data-store-provider engines are registered and there is no routing rule yet — declare the engine on the collection first`);
|
|
1207
|
-
const engine = engines[0];
|
|
1208
|
-
if (!engine) throw new Error("settings-store: data-store-provider collection yielded an empty entry");
|
|
1209
|
-
return engine;
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Build the `settings-store` provider that fronts the registered engines.
|
|
1213
|
-
*
|
|
1214
|
-
* `getEngines` is read on every call, never captured: engines register
|
|
1215
|
-
* during boot and a runner respawn replaces the instance, so a snapshot
|
|
1216
|
-
* taken at construction time would go stale exactly when an addon crashed
|
|
1217
|
-
* and came back.
|
|
1218
|
-
*/
|
|
1219
|
-
function createDataStoreDispatch(getEngines) {
|
|
1220
|
-
const engine = async () => resolveEngine(getEngines);
|
|
1221
|
-
return {
|
|
1222
|
-
get: async (input) => (await engine()).get(input),
|
|
1223
|
-
set: async (input) => (await engine()).set(input),
|
|
1224
|
-
query: async (input) => (await engine()).query(input),
|
|
1225
|
-
insert: async (input) => (await engine()).insert(input),
|
|
1226
|
-
insertMany: async (input) => (await engine()).insertMany(input),
|
|
1227
|
-
update: async (input) => (await engine()).update(input),
|
|
1228
|
-
delete: async (input) => (await engine()).delete(input),
|
|
1229
|
-
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
1230
|
-
updateWhere: async (input) => (await engine()).updateWhere(input),
|
|
1231
|
-
count: async (input) => (await engine()).count(input),
|
|
1232
|
-
aggregate: async (input) => (await engine()).aggregate(input),
|
|
1233
|
-
histogram: async (input) => (await engine()).histogram(input),
|
|
1234
|
-
isEmpty: async (input) => (await engine()).isEmpty(input),
|
|
1235
|
-
declareCollection: async (input) => (await engine()).declareCollection(input)
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
//#endregion
|
|
1239
1384
|
//#region src/builtins/storage-orchestrator/location-secrets.ts
|
|
1240
1385
|
/**
|
|
1241
1386
|
* The config keys the ORCHESTRATOR itself owns, as opposed to the ones a
|
|
@@ -2833,10 +2978,32 @@ var StorageOrchestratorAddon = class extends BaseAddon {
|
|
|
2833
2978
|
},
|
|
2834
2979
|
{
|
|
2835
2980
|
capability: settingsStoreCapability,
|
|
2836
|
-
provider: createDataStoreDispatch(getEngines)
|
|
2981
|
+
provider: createDataStoreDispatch(getEngines, () => this.hubSettingsForward())
|
|
2837
2982
|
}
|
|
2838
2983
|
];
|
|
2839
2984
|
}
|
|
2985
|
+
/**
|
|
2986
|
+
* The remote behind the data door on a node that owns no engine.
|
|
2987
|
+
*
|
|
2988
|
+
* `null` on a hub — the hub IS the store, and a hub that forwarded would
|
|
2989
|
+
* route its own door back into itself through `$hub-cap-fwd`. `null` too
|
|
2990
|
+
* when this process has no cluster broker (a forked worker, or a test
|
|
2991
|
+
* harness); the door then behaves exactly as it always did and refuses.
|
|
2992
|
+
*
|
|
2993
|
+
* Built once and memoised. It is only consulted when `getEngines()` is
|
|
2994
|
+
* empty, so on a hub — and on an agent rolled back to a local engine via
|
|
2995
|
+
* `CAMSTACK_AGENT_LOCAL_DATA_STORE` — the resolve never runs a second time.
|
|
2996
|
+
*/
|
|
2997
|
+
hubForward = null;
|
|
2998
|
+
hubSettingsForward() {
|
|
2999
|
+
if (this.hubForward) return this.hubForward;
|
|
3000
|
+
this.hubForward = resolveHubSettingsForward({
|
|
3001
|
+
env: process.env,
|
|
3002
|
+
broker: this.ctx.kernel.cluster?.broker,
|
|
3003
|
+
logger: this.ctx.logger
|
|
3004
|
+
});
|
|
3005
|
+
return this.hubForward;
|
|
3006
|
+
}
|
|
2840
3007
|
async onShutdown() {
|
|
2841
3008
|
for (const dispose of this.seedSubscriptionDisposers) try {
|
|
2842
3009
|
dispose();
|
|
@@ -2857,9 +3024,19 @@ var StorageOrchestratorAddon = class extends BaseAddon {
|
|
|
2857
3024
|
/**
|
|
2858
3025
|
* Resolve the live engine from the capability registry and wrap it in a
|
|
2859
3026
|
* `SqliteLocationStore`. Returns `null` when no engine has registered
|
|
2860
|
-
* yet — the service falls back to in-memory only.
|
|
2861
|
-
*
|
|
2862
|
-
*
|
|
3027
|
+
* yet — the service falls back to in-memory only. On a HUB that is a
|
|
3028
|
+
* boot-order window: `data-store-provider` precedes `storage` in
|
|
3029
|
+
* `INFRA_CAPABILITIES`, so it is the rare path.
|
|
3030
|
+
*
|
|
3031
|
+
* On an AGENT it is permanent and intended, and the line says so. An agent
|
|
3032
|
+
* boots no engine (`resolveAgentInfraCapabilities`), and this store must NOT
|
|
3033
|
+
* be pointed at the hub-forwarding data door: `storage_locations` is keyed by
|
|
3034
|
+
* a bare location id (`recordings:default`, `eventMedia:default`, …) which
|
|
3035
|
+
* every node in the cluster shares, so an agent writing through the door
|
|
3036
|
+
* would land on the HUB's rows. The locations an agent needs are re-seeded
|
|
3037
|
+
* from its addon declarations at every boot; the operator's real, node-pinned
|
|
3038
|
+
* locations live in the hub's own table (`__nodeId` in the config blob) and
|
|
3039
|
+
* are dispatched to the agent's `storage-provider` from there.
|
|
2863
3040
|
*
|
|
2864
3041
|
* Reads the ENGINE, not the `settings-store` singleton: since D44 this
|
|
2865
3042
|
* addon *is* that singleton, so asking the registry for it would hand
|
|
@@ -2871,7 +3048,8 @@ var StorageOrchestratorAddon = class extends BaseAddon {
|
|
|
2871
3048
|
if (!reg) return null;
|
|
2872
3049
|
const engine = reg.getCollection(DATA_STORE_PROVIDER_CAP_NAME)[0];
|
|
2873
3050
|
if (!engine) {
|
|
2874
|
-
if (!quiet) this.ctx.logger.warn("storage-orchestrator: no data-store-provider engine yet — running in-memory until one registers");
|
|
3051
|
+
if (!quiet) if (nodeOwnsItsDataStore(process.env)) this.ctx.logger.warn("storage-orchestrator: no data-store-provider engine yet — running in-memory until one registers");
|
|
3052
|
+
else this.ctx.logger.info("storage-orchestrator: this node boots no data-store engine — storage locations are seeded from addon declarations at every boot, and node-pinned locations live on the hub");
|
|
2875
3053
|
return null;
|
|
2876
3054
|
}
|
|
2877
3055
|
return new SqliteLocationStore(engine);
|
|
@@ -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-DsoVfJSR.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.
|