@camstack/server 1.2.610 → 1.2.612

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.
@@ -32,6 +32,7 @@ under [Non-commercial models](#non-commercial-models).
32
32
 
33
33
  - © Ultralytics, AGPL-3.0. Corresponding source: the upstream Ultralytics checkpoint and build-camstack-models.py (with convert_lib.py) published at https://huggingface.co/camstack/camstack-models/tree/main/source. https://github.com/ultralytics/ultralytics
34
34
  - Coral Edge TPU models and AIY Vision Birds V1 © Google LLC, Apache-2.0. https://github.com/google-coral/test_data
35
+ - YOLOv9-s EdgeTPU weights © 2025 Dan Brown (fine-tuned to 17 COCO classes, INT8, compiled for the Edge TPU; https://github.com/dbro/frigate-detector-edgetpu-yolo9, repository MIT). Derived from the YOLOv9 code and yolov9-s checkpoint of Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the fine-tuning checkpoint (yolov9-s-relu6-best.pt), the fine-tuning and conversion notebooks and the publisher LICENSE, mirrored at https://huggingface.co/camstack/camstack-models/tree/main/source/yolov9-edgetpu-dbro. https://github.com/dbro/frigate-detector-edgetpu-yolo9
35
36
  - © Ultralytics, AGPL-3.0 (Ultralytics YOLOv9 weights). YOLOv9 architecture: Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the upstream Ultralytics checkpoint and build-camstack-models.py (with convert_lib.py) published at https://huggingface.co/camstack/camstack-models/tree/main/source. https://github.com/ultralytics/ultralytics
36
37
  - RF-DETR © Roboflow, Apache-2.0. https://github.com/roboflow/rf-detr
37
38
  - YuNet weights Copyright (c) 2020 Shiqi Yu, MIT. Training code: ShiqiYu/libfacedetection.train, BSD-3-Clause. https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet
@@ -48,7 +49,7 @@ under [Non-commercial models](#non-commercial-models).
48
49
 
49
50
  Training data under attribution terms:
50
51
 
51
- - **COCO 2017** (training data of `yolo26n-seg` and 50 more): annotations CC-BY-4.0; images under their Flickr owners' terms. https://cocodataset.org/#termsofuse
52
+ - **COCO 2017** (training data of `yolo26n-seg` and 52 more): annotations CC-BY-4.0; images under their Flickr owners' terms. https://cocodataset.org/#termsofuse
52
53
  - **WIDER FACE** (training data of `yunet-2023mar`): research-oriented terms; images CC BY-NC-ND. http://shuoyang1213.me/WIDERFACE/
53
54
  - **Open Images V7** (training data of `animal-classifier`): annotations CC-BY-4.0 (Google LLC); images listed as CC-BY-2.0, no warranty. https://storage.googleapis.com/openimages/web/factsfigures_v7.html
54
55
  - **DrBimmer/vehicle-classification** (training data of `vehicle-type-v1`): CC-BY-NC-4.0 — treated as binding the trained weights (conservative reading). https://huggingface.co/datasets/DrBimmer/vehicle-classification
@@ -123,6 +124,7 @@ entry kept resolvable for a saved selection but no longer offered.
123
124
  | `yolo26n-seg`, `yolo26s-seg`, `yolo26m-seg` | [Ultralytics](https://github.com/ultralytics/ultralytics) | AGPL-3.0 / AGPL-3.0 (`AGPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | exported to ONNX, OpenVINO IR and CoreML | mirror `segmentation/yolo26-seg` |
124
125
  | `yolo26n` (default), `yolo26s`, `yolo26m`, `yolo26l`, `yolo26x` (hidden), `yolo26n-fp16` (hidden), `yolo26n-int8`, `yolo26s-fp16` (hidden), `yolo26s-int8`, `yolo26m-fp16` (hidden), `yolo26m-int8`, `yolo26l-fp16` (hidden), `yolo26l-int8`, `yolo26n-320`, `yolo26n-320-int8`, `yolo26n-256`, `yolo26n-256-int8`, `yolo26s-320`, `yolo26s-320-int8`, `yolo26s-256`, `yolo26s-256-int8`, `yolo26m-320`, `yolo26m-320-int8`, `yolo26m-256`, `yolo26m-256-int8`, `yolo26l-320`, `yolo26l-320-int8`, `yolo26l-256`, `yolo26l-256-int8`, `yolo26x-fp16` (hidden), `yolo26x-int8` (hidden) | [Ultralytics](https://github.com/ultralytics/ultralytics) | AGPL-3.0 / AGPL-3.0 (`AGPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | exported to ONNX, OpenVINO IR and CoreML (scripts/build-camstack-models.py); OpenVINO FP16 export; exported to OpenVINO IR, then OpenVINO NNCF INT8 post-training quantisation; re-exported at 320×320 to ONNX, OpenVINO IR and CoreML; re-exported at 320×320 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation; re-exported at 256×256 to ONNX, OpenVINO IR and CoreML; re-exported at 256×256 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation | mirror `objectDetection/yolo26` |
125
126
  | `ssd-mobilenet-v2-coco-edgetpu` (default) | [Google Coral test_data](https://github.com/google-coral/test_data) | Apache-2.0 / Apache-2.0 (`Apache-2.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | none | https://github.com/google-coral/test_data |
127
+ | `yolov9s-320-edgetpu`, `yolov9s-512-edgetpu` | [YOLOv9-s EdgeTPU (Dan Brown)](https://github.com/dbro/frigate-detector-edgetpu-yolo9) | GPL-3.0 / GPL-3.0 (`GPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | none: the publisher release files are mirrored byte-identical | mirror `objectDetection/yolov9` |
126
128
  | `yolov9t-320`, `yolov9t-320-int8`, `yolov9t-640`, `yolov9t-640-int8`, `yolov9s-320`, `yolov9s-320-int8`, `yolov9s-640`, `yolov9s-640-int8`, `yolov9m-320`, `yolov9m-320-int8`, `yolov9m-640`, `yolov9m-640-int8`, `yolov9c-320`, `yolov9c-320-int8`, `yolov9c-640`, `yolov9c-640-int8` | [Ultralytics](https://github.com/ultralytics/ultralytics) | AGPL-3.0 / AGPL-3.0 (`AGPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | exported at 320×320 to ONNX, OpenVINO IR FP16 and CoreML; exported at 320×320 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation; exported at 640×640 to ONNX, OpenVINO IR FP16 and CoreML; exported at 640×640 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation | mirror `objectDetection/yolov9` |
127
129
 
128
130
  ### Package detection
@@ -11068,15 +11068,6 @@ function createCapRouter_storageAdmin(getProvider, createRemoteProxy) {
11068
11068
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11069
11069
  return p.listTables(methodInput);
11070
11070
  }),
11071
- dropCollection: trpc_middleware_js_1.adminProcedure
11072
- .input(types_125.storageAdminCapability.methods.dropCollection.input.loose())
11073
- .output(types_125.storageAdminCapability.methods.dropCollection.output)
11074
- .mutation(async ({ input, ctx }) => {
11075
- const { nodeId, ...methodInput } = input;
11076
- const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
11077
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11078
- return p.dropCollection(methodInput);
11079
- }),
11080
11071
  listKinds: trpc_middleware_js_1.adminProcedure
11081
11072
  .input(types_125.storageAdminCapability.methods.listKinds.input.loose())
11082
11073
  .output(types_125.storageAdminCapability.methods.listKinds.output)
@@ -248,17 +248,30 @@ function isShareViewKind(value) {
248
248
  // new `getAddonSettings / getGlobalSettings / getDeviceSettings`
249
249
  // endpoints on the `addon-settings` singleton capability.
250
250
  /**
251
- * Whether the `capability:provider-registered` hook may emit `ready` for a
252
- * capability. `platform-probe` OWNS its readiness protocol: its provider
253
- * registers synchronously from `onInitialize` while the REAL probe
254
- * (hardware + embedded-Python EP scoring) is still running async — the
255
- * registration-time emit would flip it `ready` prematurely and probe-gated
256
- * consumers (detection-pipeline engine auto-pick) would read
257
- * accelerator-blind results. The addon emits `starting`/`ready`/`down`
258
- * itself (`autoEmitReadiness=false`, ready on probe-done).
251
+ * Capabilities whose provider OWNS its readiness protocol
252
+ * (`autoEmitReadiness=false`): it registers before it can answer, and emits
253
+ * `starting`/`ready`/`down` itself. The registration-time emit would flip
254
+ * them `ready` early.
255
+ *
256
+ * - `platform-probe` registers synchronously from `onInitialize` while the
257
+ * REAL probe (hardware + embedded-Python EP scoring) still runs async;
258
+ * probe-gated consumers (detection-pipeline engine auto-pick) would read
259
+ * accelerator-blind results.
260
+ * - `device-history` is `ready` when its STORE is open (`onHubReachable`),
261
+ * not when it registered; every producer that declares on `ready`
262
+ * (metrics-native, pipeline-analytics) declared into a closed store and
263
+ * logged a declare failure on each boot.
264
+ *
265
+ * Only single-provider caps belong here: the hook is per CAPABILITY, so a cap
266
+ * other addons also provide (`device-removal-hook`) would lose its ready.
259
267
  */
268
+ const SELF_READYING_CAPABILITIES = new Set([
269
+ 'platform-probe',
270
+ 'device-history',
271
+ ]);
272
+ /** Whether the `capability:provider-registered` hook may emit `ready` for a capability. */
260
273
  function shouldEmitProviderRegisteredReady(capability) {
261
- return capability !== 'platform-probe';
274
+ return !SELF_READYING_CAPABILITIES.has(capability);
262
275
  }
263
276
  /**
264
277
  * Is this capability device-scoped?
@@ -2378,6 +2391,17 @@ class AddonRegistryService {
2378
2391
  // (the running instance) and `entry.initialized` untouched so
2379
2392
  // `restartAddon` still shuts the live instance down cleanly.
2380
2393
  const existing = this.addonEntries.get(registered.declaration.id);
2394
+ // Another PACKAGE claiming a running addon's id (a package since folded
2395
+ // into the framework, still on disk) never relabels it: its declaration's
2396
+ // entry would then be resolved against the owner's dir. Same rule as
2397
+ // `AddonLoader.loadDeclaration` at boot.
2398
+ if (existing && existing.packageName !== registered.packageName) {
2399
+ this.logger.warn('loadNewAddons: skipped a package claiming a running addon id', {
2400
+ tags: { addonId: registered.declaration.id },
2401
+ meta: { skippedPackage: registered.packageName, ownerPackage: existing.packageName },
2402
+ });
2403
+ continue;
2404
+ }
2381
2405
  if (existing) {
2382
2406
  existing.packageVersion = registered.packageVersion;
2383
2407
  existing.packageName = registered.packageName;
@@ -3376,10 +3400,6 @@ class AddonRegistryService {
3376
3400
  plannedRunnerIds() {
3377
3401
  return [...this.buildAddonGroupPlan([...this.addonEntries.keys()]).keys()];
3378
3402
  }
3379
- /** The forked addon ids (the ones whose Init the D870 guard gates). */
3380
- forkedAddonIds() {
3381
- return [...this.buildAddonGroupPlan([...this.addonEntries.keys()]).values()].flatMap((roster) => roster.map((r) => r.addonId));
3382
- }
3383
3403
  /**
3384
3404
  * Snapshot of the live runner subprocesses (`$process.list`), reduced to the
3385
3405
  * `{ runnerId, addonIds }` shape the convergence planner needs. Returns `[]`
@@ -22,27 +22,11 @@ const DescriptorSchema = zod_1.z.object({
22
22
  .readonly()
23
23
  .optional(),
24
24
  });
25
- /** Shape of a `settings-store.query` record's `data` for this collection. */
25
+ /** A row as the store reads it: `at` as `lastActive`, plus the descriptor. */
26
26
  const RowDataSchema = zod_1.z.object({
27
27
  lastActive: zod_1.z.number(),
28
28
  descriptor: DescriptorSchema,
29
29
  });
30
- /**
31
- * @durable class=ledger owner=cluster-agent
32
- * write="one row per node the hub has ever seen online, GATED BY A READ-BACK.
33
- * `snapshot()` runs on every 30 s topology pass but commits only when there is no
34
- * existing row, when the descriptor differs field-by-field, or when the stored
35
- * `lastActive` has aged past LAST_ACTIVE_HEARTBEAT_MS (5 min) — an unchanged online
36
- * node therefore writes 288 times a day instead of 2,880, ~864/day for three rows
37
- * (D131). Off that gate: `touch()` on `$node.disconnected` (the offline edge, always
38
- * written), `recordPackages` on a node's own declaration (grow-only merge) and
39
- * `prunePackages` on an explicit undeploy"
40
- * retention="none — nothing ages a row out; the only deletion is the operator's
41
- * `forget(nodeId)` behind the inline Forget-node action. Losing the table makes every
42
- * node that is not in the live Moleculer window vanish from the topology instead of
43
- * rendering OFFLINE, and empties the package roster the addon back-fill converges to."
44
- */
45
- const COLLECTION = 'cluster-node-history';
46
30
  /**
47
31
  * How stale `lastActive` may get on an ONLINE node before an otherwise
48
32
  * unchanged snapshot is written anyway. Five minutes turns the 30 s topology
@@ -81,12 +65,9 @@ function samePackages(a, b) {
81
65
  return (a.length === b.length && a.every((p, i) => p.name === b[i]?.name && p.version === b[i]?.version));
82
66
  }
83
67
  class ClusterNodeHistoryStore {
84
- resolveSettingsStore;
85
68
  logger;
86
- declared = false;
87
69
  resolveKind;
88
- constructor(resolveSettingsStore, logger, resolveKind = () => null) {
89
- this.resolveSettingsStore = resolveSettingsStore;
70
+ constructor(logger, resolveKind = () => null) {
90
71
  this.logger = logger;
91
72
  this.resolveKind = resolveKind;
92
73
  }
@@ -94,38 +75,6 @@ class ClusterNodeHistoryStore {
94
75
  bindRecordsKind(resolver) {
95
76
  this.resolveKind = resolver;
96
77
  }
97
- /**
98
- * Idempotently declare the typed collection. Returns the resolved provider
99
- * or null if the settings-store isn't available yet (early boot / hub
100
- * unreachable) — callers treat null as "skip, best-effort".
101
- */
102
- async ensureDeclared() {
103
- if (this.resolveKind() !== null)
104
- return this.resolveSettingsStore();
105
- const store = this.resolveSettingsStore();
106
- if (!store)
107
- return null;
108
- if (this.declared)
109
- return store;
110
- try {
111
- await store.declareCollection({
112
- collection: COLLECTION,
113
- columns: [
114
- { name: 'id', type: 'TEXT', primaryKey: true, notNull: true },
115
- { name: 'lastActive', type: 'INTEGER', notNull: true },
116
- { name: 'descriptor', type: 'JSON', notNull: true },
117
- ],
118
- });
119
- this.declared = true;
120
- return store;
121
- }
122
- catch (err) {
123
- this.logger.warn('declareCollection failed (best-effort)', {
124
- meta: { error: (0, types_1.errMsg)(err) },
125
- });
126
- return null;
127
- }
128
- }
129
78
  /**
130
79
  * Upsert a node's descriptor + addon roster while it is ONLINE, stamping
131
80
  * `lastActive = Date.now()`. Called on each `listNodes()` refresh for every
@@ -145,19 +94,19 @@ class ClusterNodeHistoryStore {
145
94
  * `$node.disconnected`, and buys 288 writes/day/node instead of 2,880.
146
95
  */
147
96
  async snapshot(descriptor) {
148
- const store = await this.ensureDeclared();
149
- if (!store)
97
+ const kind = this.resolveKind();
98
+ if (kind === null)
150
99
  return;
151
100
  try {
152
101
  // Carry the package roster forward. `listNodes()` does not resolve it
153
102
  // (only the reconcile does), so writing this descriptor blind would erase
154
103
  // the back-fill's intended set on the very next topology poll.
155
- const existing = await this.readStored(descriptor.id);
104
+ const existing = await this.readStored(kind, descriptor.id);
156
105
  const packages = descriptor.packages ?? existing?.descriptor.packages;
157
106
  const next = packages === undefined ? descriptor : { ...descriptor, packages };
158
107
  if (existing !== null && !this.worthWriting(existing, next))
159
108
  return;
160
- await this.writeStored(descriptor.id, Date.now(), next);
109
+ await this.writeStored(kind, descriptor.id, Date.now(), next);
161
110
  }
162
111
  catch (err) {
163
112
  this.logger.warn('snapshot upsert failed (best-effort)', {
@@ -177,47 +126,21 @@ class ClusterNodeHistoryStore {
177
126
  return !descriptorsEqual(existing.descriptor, next);
178
127
  }
179
128
  /** Read one node's row, validated. `null` when absent or malformed. */
180
- async readStored(nodeId) {
181
- const kind = this.resolveKind();
182
- if (kind !== null) {
183
- const row = await kind.get(nodeId);
184
- if (row === null)
185
- return null;
186
- const parsed = RowDataSchema.safeParse({
187
- lastActive: row.at ?? 0,
188
- descriptor: row.data.descriptor,
189
- });
190
- return parsed.success ? parsed.data : null;
191
- }
192
- const store = this.resolveSettingsStore();
193
- if (!store)
129
+ async readStored(kind, nodeId) {
130
+ const row = await kind.get(nodeId);
131
+ if (row === null)
194
132
  return null;
195
- return this.readRowLegacy(store, nodeId);
196
- }
197
- async readRowLegacy(store, nodeId) {
198
- const raw = await store.get({ collection: COLLECTION, key: nodeId });
199
- if (raw === null || raw === undefined)
200
- return null;
201
- const parsed = RowDataSchema.safeParse(raw);
133
+ const parsed = RowDataSchema.safeParse({
134
+ lastActive: row.at ?? 0,
135
+ descriptor: row.data.descriptor,
136
+ });
202
137
  return parsed.success ? parsed.data : null;
203
138
  }
204
- async writeStored(nodeId, lastActive, descriptor) {
205
- const kind = this.resolveKind();
206
- if (kind !== null) {
207
- const body = system_1.ClusterNodeHistoryBodySchema.safeParse({ descriptor });
208
- if (!body.success)
209
- return;
210
- await kind.put({ id: nodeId, at: lastActive, data: body.data });
139
+ async writeStored(kind, nodeId, lastActive, descriptor) {
140
+ const body = system_1.ClusterNodeHistoryBodySchema.safeParse({ descriptor });
141
+ if (!body.success)
211
142
  return;
212
- }
213
- const store = this.resolveSettingsStore();
214
- if (!store)
215
- return;
216
- await store.set({
217
- collection: COLLECTION,
218
- key: nodeId,
219
- value: { lastActive, descriptor },
220
- });
143
+ await kind.put({ id: nodeId, at: lastActive, data: body.data });
221
144
  }
222
145
  /** A placeholder descriptor for a node the topology pass has not seen yet. */
223
146
  static placeholderDescriptor(nodeId) {
@@ -242,11 +165,11 @@ class ClusterNodeHistoryStore {
242
165
  * nothing is delivered rather than something guessed.
243
166
  */
244
167
  async getPackages(nodeId) {
245
- const store = await this.ensureDeclared();
246
- if (!store)
168
+ const kind = this.resolveKind();
169
+ if (kind === null)
247
170
  return [];
248
171
  try {
249
- return (await this.readStored(nodeId))?.descriptor.packages ?? [];
172
+ return (await this.readStored(kind, nodeId))?.descriptor.packages ?? [];
250
173
  }
251
174
  catch (err) {
252
175
  this.logger.warn('getPackages failed (best-effort)', {
@@ -267,11 +190,11 @@ class ClusterNodeHistoryStore {
267
190
  async recordPackages(nodeId, packages) {
268
191
  if (packages.length === 0)
269
192
  return;
270
- const store = await this.ensureDeclared();
271
- if (!store)
193
+ const kind = this.resolveKind();
194
+ if (kind === null)
272
195
  return;
273
196
  try {
274
- const existing = await this.readStored(nodeId);
197
+ const existing = await this.readStored(kind, nodeId);
275
198
  const merged = new Map((existing?.descriptor.packages ?? []).map((p) => [p.name, p]));
276
199
  // The node's current declaration wins on version — it is the authority on
277
200
  // what it is actually running.
@@ -281,7 +204,7 @@ class ClusterNodeHistoryStore {
281
204
  ...(existing?.descriptor ?? ClusterNodeHistoryStore.placeholderDescriptor(nodeId)),
282
205
  packages: [...merged.values()],
283
206
  };
284
- await this.writeStored(nodeId, existing?.lastActive ?? Date.now(), descriptor);
207
+ await this.writeStored(kind, nodeId, existing?.lastActive ?? Date.now(), descriptor);
285
208
  }
286
209
  catch (err) {
287
210
  this.logger.warn('recordPackages failed (best-effort)', {
@@ -298,11 +221,11 @@ class ClusterNodeHistoryStore {
298
221
  async prunePackages(nodeId, names) {
299
222
  if (names.length === 0)
300
223
  return;
301
- const store = await this.ensureDeclared();
302
- if (!store)
224
+ const kind = this.resolveKind();
225
+ if (kind === null)
303
226
  return;
304
227
  try {
305
- const existing = await this.readStored(nodeId);
228
+ const existing = await this.readStored(kind, nodeId);
306
229
  if (existing === null)
307
230
  return;
308
231
  const current = existing.descriptor.packages;
@@ -312,7 +235,7 @@ class ClusterNodeHistoryStore {
312
235
  const remaining = current.filter((p) => !drop.has(p.name));
313
236
  if (remaining.length === current.length)
314
237
  return;
315
- await this.writeStored(nodeId, existing.lastActive, {
238
+ await this.writeStored(kind, nodeId, existing.lastActive, {
316
239
  ...existing.descriptor,
317
240
  packages: remaining,
318
241
  });
@@ -334,27 +257,14 @@ class ClusterNodeHistoryStore {
334
257
  * time. A no-op if the node was never snapshotted (nothing to update).
335
258
  */
336
259
  async touch(nodeId) {
337
- const store = await this.ensureDeclared();
338
- if (!store)
260
+ const kind = this.resolveKind();
261
+ if (kind === null)
339
262
  return;
340
263
  try {
341
- const existing = await this.readStored(nodeId);
264
+ const existing = await this.readStored(kind, nodeId);
342
265
  if (existing === null)
343
266
  return;
344
- const kind = this.resolveKind();
345
- if (kind !== null) {
346
- await kind.put({
347
- id: nodeId,
348
- at: Date.now(),
349
- data: { descriptor: existing.descriptor },
350
- });
351
- return;
352
- }
353
- await store.update({
354
- collection: COLLECTION,
355
- id: nodeId,
356
- data: { lastActive: Date.now() },
357
- });
267
+ await this.writeStored(kind, nodeId, Date.now(), existing.descriptor);
358
268
  }
359
269
  catch (err) {
360
270
  this.logger.warn('touch failed (best-effort)', {
@@ -369,27 +279,21 @@ class ClusterNodeHistoryStore {
369
279
  }
370
280
  /**
371
281
  * Read the full persisted history, validated, or `null` when the store did
372
- * not ANSWER (unavailable, or the query threw). A caller that must not claim
282
+ * not ANSWER (the kind is not bound yet, or the read threw). A caller that must not claim
373
283
  * a node absent from an unanswered read (D315) uses this; `getAll` folds the
374
284
  * `null` into `[]` for the callers that only render what exists. Malformed
375
285
  * rows are dropped (and logged) — the rest of the answer still stands.
376
286
  */
377
287
  async tryGetAll() {
378
- const store = await this.ensureDeclared();
379
- if (!store)
288
+ const kind = this.resolveKind();
289
+ if (kind === null)
380
290
  return null;
381
291
  try {
382
- const kind = this.resolveKind();
383
292
  const result = [];
384
- const rows = kind !== null
385
- ? (await (0, system_1.readAllRows)(kind)).map((row) => ({
386
- id: row.id,
387
- data: { lastActive: row.at ?? 0, descriptor: row.data.descriptor },
388
- }))
389
- : (await store.query({ collection: COLLECTION })).map((row) => ({
390
- id: row.id,
391
- data: row.data,
392
- }));
293
+ const rows = (await (0, system_1.readAllRows)(kind)).map((row) => ({
294
+ id: row.id,
295
+ data: { lastActive: row.at ?? 0, descriptor: row.data.descriptor },
296
+ }));
393
297
  for (const row of rows) {
394
298
  const parsed = RowDataSchema.safeParse(row.data);
395
299
  if (!parsed.success) {
@@ -416,21 +320,15 @@ class ClusterNodeHistoryStore {
416
320
  * 2026-09-01 all three reached the UI as `{"success": true}`.
417
321
  */
418
322
  async forget(nodeId) {
419
- const store = await this.ensureDeclared();
420
- if (!store)
323
+ const kind = this.resolveKind();
324
+ if (kind === null)
421
325
  return 'store-unavailable';
422
326
  try {
423
327
  // Read first: `delete` on this provider is idempotent and cannot tell a
424
328
  // removal from a no-op, and "there was nothing to remove" is the answer
425
329
  // that stops an operator repeating a working action forever.
426
- const existed = (await this.readStored(nodeId)) !== null;
427
- const kind = this.resolveKind();
428
- if (kind !== null) {
429
- await kind.delete(nodeId);
430
- }
431
- else {
432
- await store.delete({ collection: COLLECTION, key: nodeId });
433
- }
330
+ const existed = (await this.readStored(kind, nodeId)) !== null;
331
+ await kind.delete(nodeId);
434
332
  if (!existed) {
435
333
  this.logger.info('cluster node had no history row to forget', { tags: { nodeId } });
436
334
  return 'not-found';
@@ -76,7 +76,6 @@ const toast_wrapper_service_1 = require("./core/notification/toast-wrapper.servi
76
76
  const repl_engine_service_1 = require("./core/repl/repl-engine.service");
77
77
  const server_update_service_1 = require("./core/server-update/server-update.service");
78
78
  const runner_boot_check_1 = require("./core/runner-health/runner-boot-check");
79
- const update_preflight_1 = require("./core/runner-health/update-preflight");
80
79
  const storage_service_1 = require("./core/storage/storage.service");
81
80
  const stream_probe_service_1 = require("./core/streaming/stream-probe.service");
82
81
  const topology_emitter_service_1 = require("./core/topology/topology-emitter.service");
@@ -163,11 +162,11 @@ async function bootManual(opts) {
163
162
  // Same post-construction reason: the cluster-node lifecycle (forget, catalog
164
163
  // removal) must leave a per-node line, and `console.log` is not a log store.
165
164
  agentRegistryService.setLogger(loggingService.createLogger('ClusterNodes'));
166
- // A1: durable, routing-blind offline-node history — persisted through the
167
- // hub-resident `settings-store` cap (resolved lazily; null until it boots).
168
- // Wired post-construction so the service's 3-arg constructor stays intact.
169
- const clusterNodeHistoryStore = new cluster_node_history_store_1.ClusterNodeHistoryStore(() => capabilityService.getRegistry()?.getSingleton('settings-store') ??
170
- null, loggingService.createLogger('cluster-node-history'));
165
+ // A1: durable, routing-blind offline-node history — the `cluster-agent`
166
+ // records kind, bound by the addon registry once the records provider is up
167
+ // (unavailable until then, D902). Wired post-construction so the service's
168
+ // 3-arg constructor stays intact.
169
+ const clusterNodeHistoryStore = new cluster_node_history_store_1.ClusterNodeHistoryStore(loggingService.createLogger('cluster-node-history'));
171
170
  agentRegistryService.setClusterNodeHistoryStore(clusterNodeHistoryStore);
172
171
  addonRegistryService.setClusterNodeHistoryStore(clusterNodeHistoryStore);
173
172
  // ---- Layer 4: need AddonRegistry ---------------------------------------
@@ -287,16 +286,7 @@ async function bootManual(opts) {
287
286
  });
288
287
  },
289
288
  });
290
- const settingsStoreOrNull = () => capabilityService.getRegistry()?.getSingleton('settings-store') ?? null;
291
289
  const serverUpdateService = new server_update_service_1.ServerUpdateService({
292
- updatePreflight: (0, update_preflight_1.buildUpdatePreflight)({
293
- logger: loggingService.createLogger('UpdatePreflight'),
294
- forkedAddonIds: () => addonRegistryService.forkedAddonIds(),
295
- store: () => {
296
- const store = settingsStoreOrNull();
297
- return store === null ? null : (0, update_preflight_1.legacyGuardClientOf)(store);
298
- },
299
- }),
300
290
  runnerBootCheck: () => hubRunnerBootCheck.current(),
301
291
  logger: loggingService.createLogger('ServerUpdate'),
302
292
  restartServer: (requestedBy, update) => addonPackageService.restartServer(requestedBy, update),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.610",
3
+ "version": "1.2.612",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -38,19 +38,19 @@
38
38
  ]
39
39
  },
40
40
  "dependencies": {
41
- "@camstack/addon-admin-ui": "1.2.445",
42
- "@camstack/addon-agent-ui": "1.2.251",
43
- "@camstack/addon-auth": "1.2.156",
44
- "@camstack/addon-decoder-nodeav": "1.2.131",
45
- "@camstack/addon-notifiers": "1.2.609",
46
- "@camstack/addon-pipeline": "1.2.609",
47
- "@camstack/addon-pipeline-orchestrator": "1.2.611",
48
- "@camstack/addon-post-analysis": "1.2.616",
49
- "@camstack/develop": "0.1.21",
50
- "@camstack/sdk": "1.2.141",
51
- "@camstack/system": "1.2.459",
52
- "@camstack/types": "1.2.391",
53
- "@camstack/ui-library": "1.2.308",
41
+ "@camstack/addon-admin-ui": "1.2.448",
42
+ "@camstack/addon-agent-ui": "1.2.254",
43
+ "@camstack/addon-auth": "1.2.159",
44
+ "@camstack/addon-decoder-nodeav": "1.2.134",
45
+ "@camstack/addon-notifiers": "1.2.612",
46
+ "@camstack/addon-pipeline": "1.2.612",
47
+ "@camstack/addon-pipeline-orchestrator": "1.2.614",
48
+ "@camstack/addon-post-analysis": "1.2.619",
49
+ "@camstack/develop": "0.1.24",
50
+ "@camstack/sdk": "1.2.144",
51
+ "@camstack/system": "1.2.462",
52
+ "@camstack/types": "1.2.394",
53
+ "@camstack/ui-library": "1.2.311",
54
54
  "@fastify/compress": "^9.0.0",
55
55
  "@fastify/cookie": "^11.0.2",
56
56
  "@fastify/cors": "^11.2.0",
@@ -1,62 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LEGACY_BLOB_GUARD_SINCE = void 0;
4
- exports.buildUpdatePreflight = buildUpdatePreflight;
5
- exports.legacyGuardClientOf = legacyGuardClientOf;
6
- /**
7
- * The `applyServerUpdate` pre-flight (D877).
8
- *
9
- * Question: would the TARGET version refuse to start any addon on THIS node's
10
- * live data? Today there is one deterministic Init refusal the framework can
11
- * pre-check: the D870 legacy-blob guard (`assertLegacyBlobsMigrated`) - an addon
12
- * whose settings still sit in its per-addon blob tables while the standard store
13
- * holds none.
14
- *
15
- * The store-shape check is deliberately version-independent: the live store is
16
- * what it is, so the only version question is "does the target contain the
17
- * guard?" - answered by {@link LEGACY_BLOB_GUARD_SINCE}. The honest limit: it
18
- * runs inside the version that ships it, so it protects updates FROM this
19
- * version onwards; an update from an older hub never ran it. And it knows only
20
- * the refusals listed here - a new deterministic Init refusal has to be added
21
- * to this module to be pre-checked.
22
- */
23
- const system_1 = require("@camstack/system");
24
- const index_js_1 = require("../../server-root/index.js");
25
- /** The first server version whose addons run `assertLegacyBlobsMigrated` at Init. */
26
- exports.LEGACY_BLOB_GUARD_SINCE = '1.2.585';
27
- function buildUpdatePreflight(deps) {
28
- return async (targetVersion) => {
29
- if ((0, index_js_1.compareSemver)(targetVersion, exports.LEGACY_BLOB_GUARD_SINCE) < 0)
30
- return [];
31
- const store = deps.store();
32
- if (store === null) {
33
- throw new Error('the settings store is not available to pre-check the update');
34
- }
35
- const blockers = [];
36
- for (const addonId of deps.forkedAddonIds()) {
37
- try {
38
- await (0, system_1.assertLegacyBlobsMigrated)(store, addonId, deps.logger);
39
- }
40
- catch (err) {
41
- // A refusal is the finding; any other error is "could not ask" and the
42
- // caller treats a throw as exactly that (it never blocks on it).
43
- if (!(err instanceof system_1.UnmigratedSettingsError))
44
- throw err;
45
- blockers.push({
46
- addonId,
47
- reason: err.message,
48
- remedy: system_1.UNMIGRATED_SETTINGS_REMEDY,
49
- });
50
- }
51
- }
52
- return blockers;
53
- };
54
- }
55
- /** The three store calls the guard makes, over the hub's `settings-store` provider. */
56
- function legacyGuardClientOf(store) {
57
- return {
58
- query: { query: (input) => store.query(input) },
59
- get: { query: (input) => store.get(input) },
60
- count: { query: (input) => store.count(input) },
61
- };
62
- }