@camstack/system 1.2.107 → 1.2.108
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/builtins/device-manager/device-aggregation.d.ts +2 -2
- package/dist/builtins/device-manager/device-bindings-store.d.ts +12 -6
- package/dist/builtins/device-manager/device-manager.addon.d.ts +6 -0
- package/dist/builtins/device-manager/device-manager.addon.js +1177 -824
- package/dist/builtins/device-manager/device-manager.addon.mjs +1177 -824
- package/dist/builtins/device-manager/device-meta-actions.d.ts +4 -4
- package/dist/builtins/device-manager/device-meta-store.d.ts +18 -43
- package/dist/builtins/device-manager/device-meta-types.d.ts +19 -17
- package/dist/builtins/device-manager/device-row-store.d.ts +229 -0
- package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +99 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -176
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -175
- package/dist/retired-settings-keys-Bsjf7HQ-.mjs +278 -0
- package/dist/retired-settings-keys-pNmoHPTg.js +295 -0
- package/package.json +1 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const require_dist = require("./dist-Ctfhlrtl.js");
|
|
2
|
+
//#region src/builtins/sqlite-storage/retired-settings-keys.ts
|
|
3
|
+
/**
|
|
4
|
+
* Is THIS node the one whose settings store is the cluster's authority?
|
|
5
|
+
*
|
|
6
|
+
* Mirrors `resolveNodeRole` (server/backend `node-role.ts`) — unset or `'hub'`
|
|
7
|
+
* means hub, which is the launcher's own default. It is duplicated rather than
|
|
8
|
+
* imported because `@camstack/system` is a dependency OF the backend, not the
|
|
9
|
+
* other way round; the parse is four lines and the env var is the contract.
|
|
10
|
+
*
|
|
11
|
+
* Anything else answers `false`. Skipping where the purge should have run is a
|
|
12
|
+
* missed cleanup the next hub boot fixes; running where it should not have is a
|
|
13
|
+
* write to a store this node does not own. Only one of those is recoverable.
|
|
14
|
+
*/
|
|
15
|
+
function settingsStoreIsAuthoritativeHere(env) {
|
|
16
|
+
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
17
|
+
return raw === "" || raw === "hub";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The dead keys, per row.
|
|
21
|
+
*
|
|
22
|
+
* `detection-pipeline` / `addon-settings` / `root` — every key in this row is
|
|
23
|
+
* dead; the row itself is kept because the addon writes live keys into it
|
|
24
|
+
* (`pipelineTemplates`, `videoPipelineSteps`).
|
|
25
|
+
*
|
|
26
|
+
* - `pipelineSteps` / `pipelineEngine`: the persisted GLOBAL step + engine
|
|
27
|
+
* seed. Removed with `getGlobalSteps` becoming pure — dispatch builds steps
|
|
28
|
+
* from the orchestrator's `agentSettings` plus per-camera overrides, and
|
|
29
|
+
* never consulted the seed. See `addon-pipeline/src/detection-pipeline/provider.ts`.
|
|
30
|
+
* - `engineRuntime` / `engineBackend` / `engineDevice` / `probedBestEngine`:
|
|
31
|
+
* the per-node engine CASCADE, removed with the operator-facing engine
|
|
32
|
+
* election. Each node now runs a device-array of inference pools balanced
|
|
33
|
+
* per session, and the node-default pool derives live from the node's own
|
|
34
|
+
* hardware (`resolveAutoEngine`). See
|
|
35
|
+
* `addon-pipeline/src/detection-pipeline/engine-store-keys.ts`.
|
|
36
|
+
*/
|
|
37
|
+
var RETIRED_SETTINGS_KEYS = [{
|
|
38
|
+
namespace: "detection-pipeline",
|
|
39
|
+
collection: "addon-settings",
|
|
40
|
+
row: "root",
|
|
41
|
+
keys: ["pipelineSteps", "pipelineEngine"],
|
|
42
|
+
perNodeKeys: [
|
|
43
|
+
"engineRuntime",
|
|
44
|
+
"engineBackend",
|
|
45
|
+
"engineDevice",
|
|
46
|
+
"probedBestEngine"
|
|
47
|
+
],
|
|
48
|
+
reason: "global step/engine seed and the per-node engine cascade — both removed; the live per-camera dispatch path never read either"
|
|
49
|
+
}];
|
|
50
|
+
/**
|
|
51
|
+
* The device-manager's three fleet blobs, replaced by one row per device in
|
|
52
|
+
* `device-manager:devices` (`device-row-store.ts`).
|
|
53
|
+
*
|
|
54
|
+
* 625 KB of the `addon-settings` table's 627 KB, re-read and re-parsed on every
|
|
55
|
+
* projection and rewritten in full on every rename. `deviceIndex` is listed
|
|
56
|
+
* here like the other two even though it was never migrated: `addonId` and
|
|
57
|
+
* `stableId` are columns of every row, so an addon's device list is a query,
|
|
58
|
+
* and the one bit that was not derivable — "has `registerDevice` run?" — is the
|
|
59
|
+
* `registered` column.
|
|
60
|
+
*/
|
|
61
|
+
var RETIRED_SETTINGS_ROWS = [
|
|
62
|
+
{
|
|
63
|
+
collection: "addon-settings",
|
|
64
|
+
row: "device-manager.deviceMeta",
|
|
65
|
+
owner: "device-manager",
|
|
66
|
+
evidence: { collection: "device-manager:devices" },
|
|
67
|
+
reason: "the fleet is one row per device in `device-manager:devices` since the flatten; this blob was 467 KB read and JSON.parsed on hub-main per projection"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
collection: "addon-settings",
|
|
71
|
+
row: "device-manager.deviceMetadata",
|
|
72
|
+
owner: "device-manager",
|
|
73
|
+
evidence: { collection: "device-manager:devices" },
|
|
74
|
+
reason: "hardware-identity metadata is the `metadata` column of the device row; the separate blob existed only because a blob write is whole-value"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
collection: "addon-settings",
|
|
78
|
+
row: "device-manager.deviceIndex",
|
|
79
|
+
owner: "device-manager",
|
|
80
|
+
evidence: { collection: "device-manager:devices" },
|
|
81
|
+
reason: "derivable: `addonId`/`stableId` are indexed columns, and index membership is the `registered` column"
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
/**
|
|
85
|
+
* Adapt the settings backend to {@link RetiredKeyStore}.
|
|
86
|
+
*
|
|
87
|
+
* `get` answering a non-object (missing row) is a MISS, not a fault: it maps to
|
|
88
|
+
* `null` and the purge skips the row. A backend throw propagates, which is what
|
|
89
|
+
* routes it to the "read failed, row left untouched" branch.
|
|
90
|
+
*/
|
|
91
|
+
function retiredKeyStoreOf(backend) {
|
|
92
|
+
return {
|
|
93
|
+
async read(spec) {
|
|
94
|
+
return require_dist.asJsonObject(await backend.get({
|
|
95
|
+
namespace: spec.namespace,
|
|
96
|
+
collection: spec.collection,
|
|
97
|
+
key: spec.row
|
|
98
|
+
}));
|
|
99
|
+
},
|
|
100
|
+
async write(spec, value) {
|
|
101
|
+
await backend.set({
|
|
102
|
+
namespace: spec.namespace,
|
|
103
|
+
collection: spec.collection,
|
|
104
|
+
key: spec.row,
|
|
105
|
+
value
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Nested `<mapKey>.<retiredKey>` paths that `spec` retires inside a map-shaped
|
|
112
|
+
* row. Pure. Values that are not plain objects are skipped, never rewritten.
|
|
113
|
+
*/
|
|
114
|
+
function planNestedRetiredKeyPurge(blob, spec) {
|
|
115
|
+
const nestedKeys = spec.mapValueKeys ?? [];
|
|
116
|
+
if (nestedKeys.length === 0) return [];
|
|
117
|
+
const found = [];
|
|
118
|
+
for (const [mapKey, value] of Object.entries(blob)) {
|
|
119
|
+
const inner = require_dist.asJsonObject(value);
|
|
120
|
+
if (inner === null) continue;
|
|
121
|
+
for (const retired of nestedKeys) if (retired in inner) found.push(`${mapKey}.${retired}`);
|
|
122
|
+
}
|
|
123
|
+
return found;
|
|
124
|
+
}
|
|
125
|
+
/** Keys of `blob` that `spec` retires. Pure. */
|
|
126
|
+
function planRetiredKeyPurge(blob, spec) {
|
|
127
|
+
const exact = new Set(spec.keys);
|
|
128
|
+
const found = [];
|
|
129
|
+
for (const key of Object.keys(blob)) {
|
|
130
|
+
if (exact.has(key)) {
|
|
131
|
+
found.push(key);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
for (const perNode of spec.perNodeKeys) if (key === perNode || key.startsWith(`${perNode}@`)) {
|
|
135
|
+
found.push(key);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return found;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Delete every retired key present in the declared rows — on the hub.
|
|
143
|
+
*
|
|
144
|
+
* Off the authoritative node this is a no-op that touches nothing and says
|
|
145
|
+
* nothing (see the module header). Returns one entry per row that actually
|
|
146
|
+
* changed — empty when the store is already clean, which is the steady state
|
|
147
|
+
* after the first boot that ran it.
|
|
148
|
+
*/
|
|
149
|
+
async function purgeRetiredSettingsKeys(store, logger, specs = RETIRED_SETTINGS_KEYS, env = process.env) {
|
|
150
|
+
if (!settingsStoreIsAuthoritativeHere(env)) return [];
|
|
151
|
+
const results = [];
|
|
152
|
+
for (const spec of specs) {
|
|
153
|
+
let blob;
|
|
154
|
+
try {
|
|
155
|
+
blob = await store.read(spec);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
logger.warn("retired settings keys — read failed, row left untouched", { meta: {
|
|
158
|
+
addon: spec.namespace,
|
|
159
|
+
collection: spec.collection,
|
|
160
|
+
row: spec.row,
|
|
161
|
+
error: err instanceof Error ? err.message : String(err)
|
|
162
|
+
} });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (blob === null) continue;
|
|
166
|
+
const doomed = planRetiredKeyPurge(blob, spec);
|
|
167
|
+
const doomedNested = planNestedRetiredKeyPurge(blob, spec);
|
|
168
|
+
if (doomed.length === 0 && doomedNested.length === 0) continue;
|
|
169
|
+
const doomedSet = new Set(doomed);
|
|
170
|
+
const nestedByMapKey = /* @__PURE__ */ new Map();
|
|
171
|
+
for (const path of doomedNested) {
|
|
172
|
+
const cut = path.indexOf(".");
|
|
173
|
+
const mapKey = path.slice(0, cut);
|
|
174
|
+
const inner = path.slice(cut + 1);
|
|
175
|
+
const bucket = nestedByMapKey.get(mapKey) ?? /* @__PURE__ */ new Set();
|
|
176
|
+
bucket.add(inner);
|
|
177
|
+
nestedByMapKey.set(mapKey, bucket);
|
|
178
|
+
}
|
|
179
|
+
const next = Object.fromEntries(Object.entries(blob).filter(([key]) => !doomedSet.has(key)).map(([key, value]) => {
|
|
180
|
+
const retired = nestedByMapKey.get(key);
|
|
181
|
+
if (retired === void 0) return [key, value];
|
|
182
|
+
const inner = require_dist.asJsonObject(value);
|
|
183
|
+
if (inner === null) return [key, value];
|
|
184
|
+
return [key, Object.fromEntries(Object.entries(inner).filter(([k]) => !retired.has(k)))];
|
|
185
|
+
}));
|
|
186
|
+
await store.write(spec, next);
|
|
187
|
+
const result = {
|
|
188
|
+
addon: spec.namespace,
|
|
189
|
+
collection: spec.collection,
|
|
190
|
+
row: spec.row,
|
|
191
|
+
keys: doomed,
|
|
192
|
+
nested: doomedNested
|
|
193
|
+
};
|
|
194
|
+
results.push(result);
|
|
195
|
+
logger.info(`purged ${doomed.length + doomedNested.length} retired settings keys`, { meta: {
|
|
196
|
+
...result,
|
|
197
|
+
reason: spec.reason
|
|
198
|
+
} });
|
|
199
|
+
}
|
|
200
|
+
return results;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Delete every retired ROW whose successor is populated — on the hub.
|
|
204
|
+
*
|
|
205
|
+
* Off the authoritative node this is a no-op that touches nothing and says
|
|
206
|
+
* nothing, same gate as {@link purgeRetiredSettingsKeys}.
|
|
207
|
+
*
|
|
208
|
+
* **The evidence check is the whole safety of this function.** A retired row is
|
|
209
|
+
* the only copy of its data until the migration has written the successor
|
|
210
|
+
* collection, and the migration is an OFFLINE step an operator runs — it is not
|
|
211
|
+
* part of boot. So a hub that boots the new code before being migrated must
|
|
212
|
+
* find its blobs intact, not deleted: the purge SKIPS, loudly, and the next
|
|
213
|
+
* boot after the migration cleans up. An evidence read that FAILS is treated
|
|
214
|
+
* exactly like an empty one — a fault is never permission to delete.
|
|
215
|
+
*/
|
|
216
|
+
async function purgeRetiredSettingsRows(store, logger, specs = RETIRED_SETTINGS_ROWS, env = process.env) {
|
|
217
|
+
if (!settingsStoreIsAuthoritativeHere(env)) return [];
|
|
218
|
+
const results = [];
|
|
219
|
+
/** Evidence counts, memoised per collection — the specs share successors. */
|
|
220
|
+
const evidence = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const spec of specs) {
|
|
222
|
+
let present;
|
|
223
|
+
try {
|
|
224
|
+
present = await store.hasRow(spec);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
logger.warn("retired settings row — read failed, row left untouched", { meta: {
|
|
227
|
+
addon: spec.owner,
|
|
228
|
+
collection: spec.collection,
|
|
229
|
+
row: spec.row,
|
|
230
|
+
error: errText(err)
|
|
231
|
+
} });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (!present) continue;
|
|
235
|
+
const successor = spec.evidence.collection;
|
|
236
|
+
let count = evidence.get(successor);
|
|
237
|
+
if (count === void 0) {
|
|
238
|
+
try {
|
|
239
|
+
count = await store.countRows(successor);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logger.warn("retired settings row — successor unreadable, row left untouched", { meta: {
|
|
242
|
+
addon: spec.owner,
|
|
243
|
+
row: spec.row,
|
|
244
|
+
successor,
|
|
245
|
+
error: errText(err)
|
|
246
|
+
} });
|
|
247
|
+
count = 0;
|
|
248
|
+
}
|
|
249
|
+
evidence.set(successor, count);
|
|
250
|
+
}
|
|
251
|
+
if (count === 0) {
|
|
252
|
+
logger.warn("retired settings row kept — its successor is empty, migration has not run", { meta: {
|
|
253
|
+
addon: spec.owner,
|
|
254
|
+
collection: spec.collection,
|
|
255
|
+
row: spec.row,
|
|
256
|
+
successor
|
|
257
|
+
} });
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
await store.deleteRow(spec);
|
|
261
|
+
const result = {
|
|
262
|
+
owner: spec.owner,
|
|
263
|
+
collection: spec.collection,
|
|
264
|
+
row: spec.row
|
|
265
|
+
};
|
|
266
|
+
results.push(result);
|
|
267
|
+
logger.info("purged a retired settings row", { meta: {
|
|
268
|
+
...result,
|
|
269
|
+
reason: spec.reason
|
|
270
|
+
} });
|
|
271
|
+
}
|
|
272
|
+
return results;
|
|
273
|
+
}
|
|
274
|
+
function errText(err) {
|
|
275
|
+
return err instanceof Error ? err.message : String(err);
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
Object.defineProperty(exports, "purgeRetiredSettingsKeys", {
|
|
279
|
+
enumerable: true,
|
|
280
|
+
get: function() {
|
|
281
|
+
return purgeRetiredSettingsKeys;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
Object.defineProperty(exports, "purgeRetiredSettingsRows", {
|
|
285
|
+
enumerable: true,
|
|
286
|
+
get: function() {
|
|
287
|
+
return purgeRetiredSettingsRows;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
Object.defineProperty(exports, "retiredKeyStoreOf", {
|
|
291
|
+
enumerable: true,
|
|
292
|
+
get: function() {
|
|
293
|
+
return retiredKeyStoreOf;
|
|
294
|
+
}
|
|
295
|
+
});
|