@camstack/system 1.2.444 → 1.2.446

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.
Files changed (144) hide show
  1. package/dist/addon-runner.js +40 -31
  2. package/dist/addon-runner.mjs +41 -32
  3. package/dist/{agent-https-DlpTMDqA.js → agent-https-5DYAOJMO.js} +810 -12
  4. package/dist/{agent-https-U0d4uvn2.mjs → agent-https-BWUEf6ev.mjs} +697 -13
  5. package/dist/auth/admin-token-manager.d.ts +9 -3
  6. package/dist/auth/api-key-manager.d.ts +9 -3
  7. package/dist/auth/auth-audit-log.d.ts +9 -3
  8. package/dist/auth/device-credential-manager.d.ts +5 -1
  9. package/dist/auth/external-identity-manager.d.ts +8 -21
  10. package/dist/auth/login-session-manager.d.ts +11 -3
  11. package/dist/auth/scoped-token-manager.d.ts +8 -52
  12. package/dist/auth/totp-manager.d.ts +11 -3
  13. package/dist/auth/user-manager.d.ts +10 -17
  14. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  15. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  16. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  17. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  18. package/dist/builtins/alerts/alert-store.d.ts +32 -0
  19. package/dist/builtins/alerts/alerts.addon.d.ts +9 -2
  20. package/dist/builtins/alerts/alerts.addon.js +373 -154
  21. package/dist/builtins/alerts/alerts.addon.mjs +372 -154
  22. package/dist/builtins/alerts/critical-push.d.ts +4 -0
  23. package/dist/builtins/alerts/emit-alert-policy.d.ts +28 -0
  24. package/dist/builtins/alerts/record-kinds.d.ts +15 -0
  25. package/dist/builtins/autotrack/index.js +1 -1
  26. package/dist/builtins/autotrack/index.mjs +1 -1
  27. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +1 -0
  28. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +134 -193
  29. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +134 -193
  30. package/dist/builtins/backup-orchestrator/destination-policy.d.ts +4 -2
  31. package/dist/builtins/backup-orchestrator/encryption-key-store.d.ts +4 -2
  32. package/dist/builtins/backup-orchestrator/record-kinds.d.ts +58 -0
  33. package/dist/builtins/backup-orchestrator/schedule-store.d.ts +4 -2
  34. package/dist/builtins/backup-orchestrator/seed-marker-store.d.ts +4 -2
  35. package/dist/builtins/calendars/calendar-prefs-store.d.ts +13 -5
  36. package/dist/builtins/calendars/calendars.addon.d.ts +2 -2
  37. package/dist/builtins/calendars/calendars.addon.js +67 -64
  38. package/dist/builtins/calendars/calendars.addon.mjs +59 -64
  39. package/dist/builtins/calendars/record-kinds.d.ts +18 -0
  40. package/dist/builtins/camera-grid/index.js +1 -1
  41. package/dist/builtins/camera-grid/index.mjs +1 -1
  42. package/dist/builtins/cluster-agent/record-kinds.d.ts +36 -0
  43. package/dist/builtins/composer/composer.addon.js +2 -2
  44. package/dist/builtins/composer/composer.addon.mjs +2 -2
  45. package/dist/builtins/console-logging/index.js +1 -1
  46. package/dist/builtins/console-logging/index.mjs +1 -1
  47. package/dist/builtins/core-blocks/block-settings-store.d.ts +8 -4
  48. package/dist/builtins/core-blocks/core-block-store.d.ts +8 -16
  49. package/dist/builtins/core-blocks/core-blocks.addon.js +85 -112
  50. package/dist/builtins/core-blocks/core-blocks.addon.mjs +86 -113
  51. package/dist/builtins/core-blocks/index.mjs +1 -1
  52. package/dist/builtins/core-blocks/record-kinds.d.ts +26 -0
  53. package/dist/builtins/device-manager/device-manager.addon.js +111 -187
  54. package/dist/builtins/device-manager/device-manager.addon.mjs +110 -187
  55. package/dist/builtins/device-manager/device-tombstone-store.d.ts +9 -48
  56. package/dist/builtins/device-manager/record-kinds.d.ts +14 -0
  57. package/dist/builtins/hub-forwarder/index.js +1 -1
  58. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  59. package/dist/builtins/keychain/index.js +152 -22
  60. package/dist/builtins/keychain/index.mjs +152 -22
  61. package/dist/builtins/keychain/keychain-service.d.ts +3 -2
  62. package/dist/builtins/keychain/keychain-store.d.ts +13 -2
  63. package/dist/builtins/keychain/record-kinds.d.ts +16 -0
  64. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  65. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  66. package/dist/builtins/local-auth/auth-schema.d.ts +3 -1
  67. package/dist/builtins/local-auth/local-auth.addon.js +2903 -2123
  68. package/dist/builtins/local-auth/local-auth.addon.mjs +2904 -2124
  69. package/dist/builtins/local-auth/oauth-session-manager.d.ts +8 -48
  70. package/dist/builtins/local-auth/record-kinds.d.ts +141 -0
  71. package/dist/builtins/local-network/local-network.addon.js +2 -2
  72. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  73. package/dist/builtins/loki-logging/index.js +1 -1
  74. package/dist/builtins/loki-logging/index.mjs +1 -1
  75. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  76. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  77. package/dist/builtins/platform-probe/index.js +1 -1
  78. package/dist/builtins/platform-probe/index.mjs +1 -1
  79. package/dist/builtins/record-entries/index.js +1 -1
  80. package/dist/builtins/record-entries/index.mjs +1 -1
  81. package/dist/builtins/records/legacy-table-row-count.d.ts +5 -0
  82. package/dist/builtins/records/phase6-declare-gate.d.ts +28 -0
  83. package/dist/builtins/records-isolated.d.ts +10 -0
  84. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  85. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  86. package/dist/builtins/snapshot/index.js +1 -1
  87. package/dist/builtins/snapshot/index.mjs +1 -1
  88. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  89. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  90. package/dist/builtins/sqlite-storage/index.js +1 -1
  91. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  92. package/dist/builtins/sqlite-storage/integration-record-kinds.d.ts +30 -0
  93. package/dist/builtins/sqlite-storage/integration-registry.d.ts +10 -16
  94. package/dist/builtins/sqlite-storage/records/count-settings-array-legacy.d.ts +5 -0
  95. package/dist/builtins/sqlite-storage/records/count-settings-brokers-legacy.d.ts +6 -0
  96. package/dist/builtins/sqlite-storage/records/records-engine-types.d.ts +5 -0
  97. package/dist/builtins/sqlite-storage/records/records-engine.d.ts +4 -0
  98. package/dist/builtins/sqlite-storage/records/records-provider.d.ts +5 -1
  99. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +17 -0
  100. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +111 -3
  101. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +111 -3
  102. package/dist/builtins/sqlite-storage/storage-admin-provider.d.ts +9 -1
  103. package/dist/builtins/storage-orchestrator/drain-state-store.d.ts +5 -2
  104. package/dist/builtins/storage-orchestrator/location-store.d.ts +7 -2
  105. package/dist/builtins/storage-orchestrator/record-kinds.d.ts +38 -0
  106. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +2 -0
  107. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +196 -5
  108. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +195 -6
  109. package/dist/builtins/system-config/record-kinds.d.ts +32 -0
  110. package/dist/builtins/system-config/system-config.addon.js +53 -618
  111. package/dist/builtins/system-config/system-config.addon.mjs +29 -577
  112. package/dist/builtins/winston-logging/index.js +1 -1
  113. package/dist/builtins/winston-logging/index.mjs +1 -1
  114. package/dist/{composition-sources-C3rKncIF.mjs → composition-sources-BmC5jAUv.mjs} +1 -1
  115. package/dist/{composition-sources-NAPZddic.js → composition-sources-BwIIltnZ.js} +1 -1
  116. package/dist/{constants-BKX2QnIg.mjs → constants-6XHGmEUF.mjs} +1 -1
  117. package/dist/{constants-BXiNEIeV.js → constants-DA_tjzIk.js} +1 -1
  118. package/dist/{device-ca-jxjSL8Ov.mjs → device-ca-Bh_ClxMO.mjs} +1 -1
  119. package/dist/{device-ca-DT9X2S6d.js → device-ca-CnbrgfmW.js} +1 -1
  120. package/dist/{dist-D1vECb_6.js → dist-BkNfEJKa.js} +521 -64
  121. package/dist/{dist-DERK-gP5.mjs → dist-CkZIrsAP.mjs} +462 -65
  122. package/dist/file-host-worker.js +1 -1
  123. package/dist/file-host-worker.mjs +1 -1
  124. package/dist/{hub-mdns-ciao-CRaC97V0.mjs → hub-mdns-ciao-CoyIUWHC.mjs} +2 -2
  125. package/dist/{hub-mdns-ciao-Cah9GCIg.js → hub-mdns-ciao-m5LIr4oA.js} +2 -2
  126. package/dist/index.d.ts +7 -0
  127. package/dist/index.js +3095 -2821
  128. package/dist/index.mjs +3067 -2810
  129. package/dist/kernel/index.d.ts +2 -0
  130. package/dist/kernel/moleculer/event-bus.d.ts +3 -1
  131. package/dist/kernel/moleculer/mesh-mtls/mesh-certificate-registry.d.ts +5 -1
  132. package/dist/kernel/moleculer/remote-logger.d.ts +2 -2
  133. package/dist/kernel/transport/cap-route-resolver.d.ts +9 -38
  134. package/dist/phase6-declare-gate-BDWZuB2n.mjs +93 -0
  135. package/dist/phase6-declare-gate-Bl7KocEs.js +98 -0
  136. package/dist/read-all-rows-CVoO1ONM.js +29 -0
  137. package/dist/read-all-rows-TaPqdWNF.mjs +25 -0
  138. package/dist/records/read-all-rows.d.ts +6 -0
  139. package/dist/{restore-archive-Dy-qKTdF.js → restore-archive-B57xI5Ue.js} +353 -5
  140. package/dist/{restore-archive-daKyE24F.mjs → restore-archive-CMr4V7Fi.mjs} +339 -9
  141. package/dist/tls/agent-https-names.d.ts +9 -13
  142. package/dist/{wal-maintenance-zRc24mOC.mjs → wal-maintenance-Bqm4zrNA.mjs} +0 -0
  143. package/dist/{wal-maintenance-dAv4IdzC.js → wal-maintenance-CuG1DUca.js} +0 -0
  144. package/package.json +1 -1
@@ -1,4 +1,6 @@
1
- import { $i as EventCategory, Fr as BaseAddon, Pr as errMsg, ct as alertsCapability, i as AlertSchema, yi as createEvent } from "../../dist-DERK-gP5.mjs";
1
+ import { Ci as createEvent, Di as defineRecordKinds, Lr as errMsg, Rr as BaseAddon, ft as alertsCapability, i as AlertSchema, la as EventCategory } from "../../dist-CkZIrsAP.mjs";
2
+ import { t as require_read_all_rows } from "../../read-all-rows-TaPqdWNF.mjs";
3
+ import { t as resolvePhase6UseRecordsPathForHub } from "../../phase6-declare-gate-BDWZuB2n.mjs";
2
4
  //#region src/builtins/alerts/critical-push.ts
3
5
  /** Alert categories that are critical system findings. */
4
6
  var CRITICAL_CATEGORIES = new Set([
@@ -6,6 +8,9 @@ var CRITICAL_CATEGORIES = new Set([
6
8
  "addon.runner-refused",
7
9
  "system.runner-check"
8
10
  ]);
11
+ function isCriticalPush(alert) {
12
+ return alert.severity === "critical" || CRITICAL_CATEGORIES.has(alert.category);
13
+ }
9
14
  function isPushState(value) {
10
15
  if (typeof value !== "object" || value === null) return false;
11
16
  const rec = value;
@@ -103,6 +108,59 @@ async function deliverDigest(input) {
103
108
  return out;
104
109
  }
105
110
  //#endregion
111
+ //#region src/builtins/alerts/emit-alert-policy.ts
112
+ function episodeKeyFromMetadata(metadata) {
113
+ const key = metadata?.["episodeKey"];
114
+ return typeof key === "string" ? key : void 0;
115
+ }
116
+ /** Completed episodes start fresh — prior push must not suppress the next digest. */
117
+ function shouldDropPriorPush(existingStatus) {
118
+ return existingStatus === "completed";
119
+ }
120
+ function alertWithRetainedPush(toStore, existingPush, existingStatus) {
121
+ if (existingPush === void 0 || shouldDropPriorPush(existingStatus)) return toStore;
122
+ return {
123
+ ...toStore,
124
+ metadata: {
125
+ ...toStore.metadata,
126
+ push: existingPush
127
+ }
128
+ };
129
+ }
130
+ /**
131
+ * Re-emit of the same alert id: preserve read when the episode is unchanged
132
+ * and still active. A completed episode starts again. Queue a critical push
133
+ * only for active, unread critical findings.
134
+ */
135
+ function mergeEmitAlert(input) {
136
+ const incomingKey = episodeKeyFromMetadata(input.incoming.metadata);
137
+ const existingKey = input.existing?.episodeKey;
138
+ if (input.existing?.read === true) {
139
+ const sameEpisode = incomingKey === void 0 || incomingKey === existingKey;
140
+ const priorCompleted = input.existing.status === "completed";
141
+ if (sameEpisode && !priorCompleted) return {
142
+ alert: {
143
+ ...input.incoming,
144
+ read: true
145
+ },
146
+ queuePush: false
147
+ };
148
+ }
149
+ const queuePush = isCriticalPush(input.incoming) && input.incoming.status === "active" && input.incoming.read === false;
150
+ return {
151
+ alert: input.incoming,
152
+ queuePush
153
+ };
154
+ }
155
+ function criticalPushPayload(alert) {
156
+ const key = episodeKeyFromMetadata(alert.metadata) ?? alert.id;
157
+ const pushLine = alert.metadata?.["pushLine"];
158
+ return {
159
+ key,
160
+ line: typeof pushLine === "string" ? pushLine : alert.title
161
+ };
162
+ }
163
+ //#endregion
106
164
  //#region src/builtins/alerts/device-label.ts
107
165
  /**
108
166
  * Operator-facing device label for alert messages.
@@ -277,6 +335,210 @@ function runnerPushLine(record) {
277
335
  return record.state === "refused" ? `${where}: refused to start. ${record.remedy ?? "See the Addons page."}` : `${where}: stopped after repeated crashes. Restart it from the Addons page.`;
278
336
  }
279
337
  //#endregion
338
+ //#region src/builtins/alerts/record-kinds.ts
339
+ var import_read_all_rows = require_read_all_rows();
340
+ /**
341
+ * Record kinds owned by alert-center (records plan phase 4).
342
+ */
343
+ /** Legacy collection / adoptsFrom target (canonical KV before phase 4). */
344
+ var ALERTS_COLLECTION = "alerts";
345
+ var _alertCenterRecordKinds = defineRecordKinds({ alerts: {
346
+ schema: AlertSchema.omit({ id: true }),
347
+ class: "audit",
348
+ writeTrigger: "EventBus categories the alert center tracks, operator mark read/dismiss, and model-download progress merges",
349
+ device: "none",
350
+ at: "none",
351
+ indexes: [
352
+ {
353
+ name: "read",
354
+ fields: ["read"]
355
+ },
356
+ {
357
+ name: "created_at",
358
+ fields: ["createdAt"]
359
+ },
360
+ {
361
+ name: "updated_at",
362
+ fields: ["updatedAt"]
363
+ }
364
+ ],
365
+ retention: {
366
+ owner: "alert-center",
367
+ reason: "maxAlerts cap and retentionDays enforced on write (enforceMaxAlerts / enforceRetention); read alerts preferred when trimming overflow"
368
+ },
369
+ adoptsFrom: ALERTS_COLLECTION
370
+ } });
371
+ var alertCenterRecordKinds = _alertCenterRecordKinds;
372
+ /** Narrow kind for `records.kind()` — `IsolatedRecordKinds` string index is `| undefined`. */
373
+ var alertsRecordKind = _alertCenterRecordKinds.alerts;
374
+ //#endregion
375
+ //#region src/builtins/alerts/alert-store.ts
376
+ function toRecord(row) {
377
+ return {
378
+ id: row.id,
379
+ data: row.data
380
+ };
381
+ }
382
+ function toRecordFromLegacy(row) {
383
+ return {
384
+ id: row.id,
385
+ data: row.data
386
+ };
387
+ }
388
+ function isRecord(value) {
389
+ return typeof value === "object" && value !== null && !Array.isArray(value);
390
+ }
391
+ function legacyGetData(value) {
392
+ if (!isRecord(value)) return null;
393
+ if ("data" in value) {
394
+ const data = value.data;
395
+ return isRecord(data) ? data : null;
396
+ }
397
+ return value;
398
+ }
399
+ var AlertStore = class AlertStore {
400
+ kind;
401
+ legacyStore;
402
+ constructor(kind, legacyStore) {
403
+ this.kind = kind;
404
+ this.legacyStore = legacyStore;
405
+ }
406
+ static of(records) {
407
+ return new AlertStore(records.kind(alertsRecordKind), null);
408
+ }
409
+ static legacy(store) {
410
+ return new AlertStore(null, store);
411
+ }
412
+ async legacyRows() {
413
+ const store = this.legacyStore;
414
+ if (store === null) return [];
415
+ return (await store.query.query({
416
+ collection: ALERTS_COLLECTION,
417
+ filter: {}
418
+ })).map((row) => toRecordFromLegacy({
419
+ id: row.id,
420
+ data: row.data
421
+ }));
422
+ }
423
+ async list(filter) {
424
+ let out;
425
+ const kind = this.kind;
426
+ if (kind !== null) out = (await (0, import_read_all_rows.readAllRows)(kind, { order: "id" })).map(toRecord);
427
+ else out = [...await this.legacyRows()];
428
+ if (filter?.unreadOnly === true) out = out.filter((r) => r.data["read"] === false);
429
+ out.sort((a, b) => Number(b.data["updatedAt"] ?? 0) - Number(a.data["updatedAt"] ?? 0));
430
+ const limit = filter?.limit ?? 100;
431
+ return out.slice(0, limit);
432
+ }
433
+ async countUnread() {
434
+ return (await this.list({
435
+ unreadOnly: true,
436
+ limit: 1e4
437
+ })).length;
438
+ }
439
+ async countAll() {
440
+ const kind = this.kind;
441
+ if (kind !== null) return (await (0, import_read_all_rows.readAllRows)(kind)).length;
442
+ return (await this.legacyRows()).length;
443
+ }
444
+ async getData(id) {
445
+ const kind = this.kind;
446
+ if (kind !== null) {
447
+ const row = await kind.get(id);
448
+ if (row === null) return null;
449
+ return row.data;
450
+ }
451
+ const store = this.legacyStore;
452
+ if (store === null) return null;
453
+ return legacyGetData(await store.get.query({
454
+ collection: ALERTS_COLLECTION,
455
+ key: id
456
+ }));
457
+ }
458
+ async mergeUpdate(id, patch) {
459
+ const existing = await this.getData(id);
460
+ if (existing === null) return;
461
+ await this.putRaw(id, {
462
+ ...existing,
463
+ ...patch
464
+ });
465
+ }
466
+ async persist(alert) {
467
+ const { id, ...body } = alert;
468
+ const data = alertsRecordKind.schema.parse(body);
469
+ const kind = this.kind;
470
+ if (kind !== null) {
471
+ await kind.put({
472
+ id,
473
+ data
474
+ });
475
+ return;
476
+ }
477
+ await this.putRaw(id, {
478
+ ...body,
479
+ id
480
+ });
481
+ }
482
+ async putRaw(id, data) {
483
+ const { id: _drop, ...body } = AlertSchema.parse({
484
+ id,
485
+ ...data
486
+ });
487
+ const kind = this.kind;
488
+ if (kind !== null) {
489
+ await kind.put({
490
+ id,
491
+ data: body
492
+ });
493
+ return;
494
+ }
495
+ const store = this.legacyStore;
496
+ if (store === null) return;
497
+ await store.set.mutate({
498
+ collection: ALERTS_COLLECTION,
499
+ key: id,
500
+ value: {
501
+ id,
502
+ ...body
503
+ }
504
+ });
505
+ }
506
+ async delete(id) {
507
+ const kind = this.kind;
508
+ if (kind !== null) {
509
+ await kind.delete(id);
510
+ return;
511
+ }
512
+ const store = this.legacyStore;
513
+ if (store === null) return;
514
+ await store.delete.mutate({
515
+ collection: ALERTS_COLLECTION,
516
+ key: id
517
+ });
518
+ }
519
+ async listAllByCreatedAtAsc() {
520
+ const kind = this.kind;
521
+ let out;
522
+ if (kind !== null) out = (await (0, import_read_all_rows.readAllRows)(kind)).map(toRecord);
523
+ else out = [...await this.legacyRows()];
524
+ out.sort((a, b) => Number(a.data["createdAt"] ?? 0) - Number(b.data["createdAt"] ?? 0));
525
+ return out;
526
+ }
527
+ async listRecent(limit) {
528
+ const kind = this.kind;
529
+ let out;
530
+ if (kind !== null) out = (await (0, import_read_all_rows.readAllRows)(kind, { order: "id" })).map(toRecord);
531
+ else out = [...await this.legacyRows()];
532
+ out.sort((a, b) => Number(b.data["updatedAt"] ?? 0) - Number(a.data["updatedAt"] ?? 0));
533
+ return out.slice(0, limit);
534
+ }
535
+ async findWhereDataField(field, value) {
536
+ const kind = this.kind;
537
+ if (kind !== null) return (await (0, import_read_all_rows.readAllRows)(kind)).filter((r) => r.data[field] === value).map(toRecord);
538
+ return (await this.legacyRows()).filter((r) => r.data[field] === value);
539
+ }
540
+ };
541
+ //#endregion
280
542
  //#region src/builtins/alerts/alerts.addon.ts
281
543
  var CATEGORY_MAPPINGS = {
282
544
  "addon.crashed": {
@@ -449,13 +711,18 @@ var DEFAULT_CONFIG = {
449
711
  maxAlerts: 500,
450
712
  retentionDays: 30,
451
713
  criticalPushEnabled: true,
452
- criticalPushTargetIds: ""
714
+ criticalPushTargetIds: []
453
715
  };
716
+ /** Accept the live `string[]` shape and the pre-multiselect CSV string. */
717
+ function normalizeCriticalPushTargetIds(raw) {
718
+ if (Array.isArray(raw)) return raw.filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim());
719
+ if (typeof raw === "string" && raw.trim().length > 0) return raw.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
720
+ return [];
721
+ }
454
722
  /** One burst of critical findings (a whole boot of refused runners) is ONE digest push. */
455
723
  var PUSH_COALESCE_MS = 2e3;
456
724
  /** Addons whose (re)start is a reason to retry pending critical pushes. */
457
725
  var PUSH_RECONCILE_ADDONS = new Set(["notifiers", "notification-center"]);
458
- var ALERTS_COLLECTION = "alerts";
459
726
  var EVENT_SOURCE = {
460
727
  type: "addon",
461
728
  id: "alert-center"
@@ -476,6 +743,7 @@ function toPrunable(record) {
476
743
  };
477
744
  }
478
745
  var AlertCenterAddon = class extends BaseAddon {
746
+ alertStore = null;
479
747
  unsubscribers = [];
480
748
  /** How long a burst of critical findings is gathered into one digest push. */
481
749
  pushCoalesceMs;
@@ -490,6 +758,11 @@ var AlertCenterAddon = class extends BaseAddon {
490
758
  */
491
759
  rateLimitCounters = /* @__PURE__ */ new Map();
492
760
  async onInitialize() {
761
+ const records = this.ctx.records;
762
+ const store = this.ctx.api?.settingsStore;
763
+ const useRecords = records !== void 0 && store !== void 0 && await resolvePhase6UseRecordsPathForHub(this.ctx, "alert-center", alertCenterRecordKinds, ["alerts"]);
764
+ this.alertStore = useRecords && records !== void 0 ? AlertStore.of(records) : store === void 0 ? null : AlertStore.legacy(store);
765
+ if (this.alertStore === null) throw new Error("alert-center needs ctx.records or settings-store on the hub");
493
766
  const alertsProvider = {
494
767
  emit: (alert) => this.emitAlert(alert),
495
768
  update: (input) => this.updateAlert(input.alertId, input.patch),
@@ -509,6 +782,7 @@ var AlertCenterAddon = class extends BaseAddon {
509
782
  this.unsubscribers = unsubs;
510
783
  this.reconcilePendingPushes();
511
784
  this.dismissLegacyStreamCards(void 0);
785
+ this.migrateCriticalPushTargetIds();
512
786
  this.ctx.logger.info("Initialized", { meta: { trackedCategoriesCount: TRACKED_CATEGORIES.length } });
513
787
  return [{
514
788
  capability: alertsCapability,
@@ -556,43 +830,28 @@ var AlertCenterAddon = class extends BaseAddon {
556
830
  type: "boolean",
557
831
  key: "criticalPushEnabled",
558
832
  label: "Push critical system alerts",
559
- description: "A runner that failed or refused to start, or a node whose runners are degraded, is pushed straight to the notifier targets - even when notification-center is down.",
833
+ description: "A runner that failed or refused to start, or a node whose runners are degraded, is pushed straight to the notifier targets — even when Notification Center is down. This path does not use notification rules.",
560
834
  default: true
561
835
  }),
562
836
  this.field({
563
- type: "text",
837
+ type: "notification-target-multiselect",
564
838
  key: "criticalPushTargetIds",
565
839
  label: "Critical push targets",
566
- description: "Comma-separated notifier target ids. Empty = every enabled shared (not personal) target.",
567
- default: ""
840
+ description: "Notifier targets that receive critical system alerts. Empty = every enabled shared (not personal) target.",
841
+ default: []
568
842
  })
569
843
  ]
570
844
  }] });
571
845
  }
572
846
  async listAlerts(filter) {
573
- const backend = this.ctx.api.settingsStore;
574
- if (!backend) return [];
575
- const whereClause = {};
576
- if (filter?.unreadOnly) whereClause.read = false;
577
- return (await backend.query.query({
578
- collection: ALERTS_COLLECTION,
579
- filter: {
580
- where: Object.keys(whereClause).length > 0 ? whereClause : void 0,
581
- orderBy: {
582
- field: "updatedAt",
583
- direction: "desc"
584
- },
585
- limit: filter?.limit ?? 100
586
- }
587
- })).map(parseAlert);
847
+ const store = this.alertStore;
848
+ if (store === null) return [];
849
+ return (await store.list(filter)).map(parseAlert);
588
850
  }
589
851
  async getUnreadCount() {
590
- const backend = this.ctx.api.settingsStore;
591
- if (!backend) return 0;
592
- return backend.count.query({
593
- collection: ALERTS_COLLECTION,
594
- filter: { where: { read: false } }
595
- });
852
+ const store = this.alertStore;
853
+ if (store === null) return 0;
854
+ return store.countUnread();
596
855
  }
597
856
  async markRead(alertId) {
598
857
  await this.mergeUpdate(alertId, {
@@ -601,11 +860,11 @@ var AlertCenterAddon = class extends BaseAddon {
601
860
  });
602
861
  }
603
862
  async markAllRead() {
604
- const backend = this.ctx.api.settingsStore;
605
- if (!backend) return;
606
- const unread = await backend.query.query({
607
- collection: ALERTS_COLLECTION,
608
- filter: { where: { read: false } }
863
+ const store = this.alertStore;
864
+ if (store === null) return;
865
+ const unread = await store.list({
866
+ unreadOnly: true,
867
+ limit: 1e4
609
868
  });
610
869
  const now = Date.now();
611
870
  for (const record of unread) await this.mergeUpdate(record.id, {
@@ -702,14 +961,14 @@ var AlertCenterAddon = class extends BaseAddon {
702
961
  }
703
962
  }
704
963
  async readRunnerRecord(alertId) {
705
- const backend = this.ctx.api.settingsStore;
706
- if (!backend) return null;
707
- const raw = await backend.get.query({
708
- collection: ALERTS_COLLECTION,
709
- key: alertId
964
+ const store = this.alertStore;
965
+ if (store === null) return null;
966
+ const raw = await store.getData(alertId);
967
+ if (raw === null) return null;
968
+ const alert = AlertSchema.safeParse({
969
+ id: alertId,
970
+ ...raw
710
971
  });
711
- if (raw === null || raw === void 0 || typeof raw !== "object") return null;
712
- const alert = AlertSchema.safeParse(raw);
713
972
  if (!alert.success || alert.data.status === "dismissed") return null;
714
973
  return parseRunnerRecord(alert.data.metadata?.["runner"]);
715
974
  }
@@ -837,20 +1096,20 @@ var AlertCenterAddon = class extends BaseAddon {
837
1096
  });
838
1097
  }
839
1098
  async readAlertRaw(alertId) {
840
- const backend = this.ctx.api.settingsStore;
841
- if (!backend) return null;
842
- const raw = await backend.get.query({
843
- collection: ALERTS_COLLECTION,
844
- key: alertId
845
- });
846
- if (raw === null || raw === void 0 || typeof raw !== "object") return null;
1099
+ const store = this.alertStore;
1100
+ if (store === null) return null;
1101
+ const raw = await store.getData(alertId);
1102
+ if (raw === null) return null;
847
1103
  const rec = Object.fromEntries(Object.entries(raw));
848
1104
  const meta = rec["metadata"];
849
1105
  const push = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta["push"] : void 0;
1106
+ const metadata = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta : void 0;
850
1107
  return {
851
1108
  ...typeof rec["createdAt"] === "number" ? { createdAt: rec["createdAt"] } : {},
852
1109
  ...typeof rec["read"] === "boolean" ? { read: rec["read"] } : {},
853
- ...isPushState(push) ? { push } : {}
1110
+ ...typeof rec["status"] === "string" ? { status: rec["status"] } : {},
1111
+ ...isPushState(push) ? { push } : {},
1112
+ ...episodeKeyFromMetadata(metadata) !== void 0 ? { episodeKey: episodeKeyFromMetadata(metadata) } : {}
854
1113
  };
855
1114
  }
856
1115
  /** Pushes run one at a time so a burst can never send the same state twice. */
@@ -861,9 +1120,19 @@ var AlertCenterAddon = class extends BaseAddon {
861
1120
  pushPolicy() {
862
1121
  return {
863
1122
  enabled: this.config.criticalPushEnabled,
864
- targetIds: this.config.criticalPushTargetIds.split(",").map((id) => id.trim()).filter((id) => id.length > 0)
1123
+ targetIds: normalizeCriticalPushTargetIds(this.config.criticalPushTargetIds)
865
1124
  };
866
1125
  }
1126
+ /** Rewrite a legacy CSV `criticalPushTargetIds` string into `string[]`. */
1127
+ async migrateCriticalPushTargetIds() {
1128
+ const raw = this.config.criticalPushTargetIds;
1129
+ if (typeof raw !== "string") return;
1130
+ try {
1131
+ await this.updateGlobalSettings({ criticalPushTargetIds: normalizeCriticalPushTargetIds(raw) });
1132
+ } catch (err) {
1133
+ this.ctx.logger.warn("criticalPushTargetIds CSV migration failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
1134
+ }
1135
+ }
867
1136
  pushPorts() {
868
1137
  const api = this.ctx.api;
869
1138
  return {
@@ -940,27 +1209,16 @@ var AlertCenterAddon = class extends BaseAddon {
940
1209
  return this.pushChain;
941
1210
  }
942
1211
  async storePushState(alertId, push) {
943
- const backend = this.ctx.api.settingsStore;
944
- if (!backend) return;
945
- const raw = await backend.get.query({
946
- collection: ALERTS_COLLECTION,
947
- key: alertId
948
- });
949
- if (raw === null || raw === void 0 || typeof raw !== "object") return;
950
- const existing = Object.fromEntries(Object.entries(raw));
951
- const meta = existing["metadata"];
1212
+ const store = this.alertStore;
1213
+ if (store === null) return;
1214
+ const raw = await store.getData(alertId);
1215
+ if (raw === null) return;
1216
+ const meta = raw["metadata"];
952
1217
  const metadata = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta : {};
953
- await backend.set.mutate({
954
- collection: ALERTS_COLLECTION,
955
- key: alertId,
956
- value: {
957
- ...existing,
958
- metadata: {
959
- ...metadata,
960
- push
961
- }
962
- }
963
- });
1218
+ await store.mergeUpdate(alertId, { metadata: {
1219
+ ...metadata,
1220
+ push
1221
+ } });
964
1222
  }
965
1223
  /**
966
1224
  * Retry every critical alert whose push is still pending - at boot and when
@@ -968,26 +1226,17 @@ var AlertCenterAddon = class extends BaseAddon {
968
1226
  * delivered is skipped by its key, so reconciling never sends twice.
969
1227
  */
970
1228
  async reconcilePendingPushes() {
971
- const backend = this.ctx.api.settingsStore;
972
- if (!backend) return;
1229
+ const store = this.alertStore;
1230
+ if (store === null) return;
973
1231
  try {
974
- const rows = await backend.query.query({
975
- collection: ALERTS_COLLECTION,
976
- filter: {
977
- orderBy: {
978
- field: "updatedAt",
979
- direction: "desc"
980
- },
981
- limit: 500
982
- }
983
- });
1232
+ const rows = await store.listRecent(500);
984
1233
  for (const row of rows) {
985
1234
  const parsed = AlertSchema.safeParse({
986
1235
  id: row.id,
987
1236
  ...row.data
988
1237
  });
989
1238
  if (!parsed.success || parsed.data.status !== "active") continue;
990
- if (!CRITICAL_CATEGORIES.has(parsed.data.category)) continue;
1239
+ if (!isCriticalPush(parsed.data)) continue;
991
1240
  const push = parsed.data.metadata?.["push"];
992
1241
  if (!isPushState(push) || push.state !== "pending") continue;
993
1242
  this.pushQueue.set(parsed.data.id, {
@@ -1007,12 +1256,9 @@ var AlertCenterAddon = class extends BaseAddon {
1007
1256
  const modelId = data.modelId ?? "unknown";
1008
1257
  const progress = data.progress ?? 0;
1009
1258
  const stableId = `model-download:${modelId}`;
1010
- const backend = this.ctx.api.settingsStore;
1011
- if (!backend) return;
1012
- if ((await backend.query.query({
1013
- collection: ALERTS_COLLECTION,
1014
- filter: { where: { id: stableId } }
1015
- })).length > 0) {
1259
+ const store = this.alertStore;
1260
+ if (store === null) return;
1261
+ if (await store.getData(stableId) !== null) {
1016
1262
  const now = Date.now();
1017
1263
  if (progress >= 100) {
1018
1264
  await this.mergeUpdate(stableId, {
@@ -1336,8 +1582,29 @@ var AlertCenterAddon = class extends BaseAddon {
1336
1582
  }
1337
1583
  }
1338
1584
  async emitAlert(alert) {
1339
- await this.persistAlert(alert);
1340
- this.emitAlertCreatedEvent(alert);
1585
+ const existingRaw = await this.readAlertRaw(alert.id);
1586
+ const { alert: toStore, queuePush } = mergeEmitAlert({
1587
+ existing: existingRaw === null ? null : {
1588
+ read: existingRaw.read,
1589
+ episodeKey: existingRaw.episodeKey,
1590
+ status: existingRaw.status
1591
+ },
1592
+ incoming: alert
1593
+ });
1594
+ const stored = alertWithRetainedPush(toStore, existingRaw?.push, existingRaw?.status);
1595
+ const isNew = existingRaw === null;
1596
+ await this.persistAlert(stored);
1597
+ if (isNew) this.emitAlertCreatedEvent(stored);
1598
+ else this.emitAlertUpdatedEvent(alert.id, stored);
1599
+ if (queuePush) {
1600
+ const { key, line } = criticalPushPayload(stored);
1601
+ this.queuePush({
1602
+ alertId: stored.id,
1603
+ key,
1604
+ title: stored.title,
1605
+ line
1606
+ });
1607
+ }
1341
1608
  await this.enforceMaxAlerts();
1342
1609
  await this.enforceRetention();
1343
1610
  }
@@ -1353,45 +1620,14 @@ var AlertCenterAddon = class extends BaseAddon {
1353
1620
  * so we must read the existing record, merge the patch, and write back.
1354
1621
  */
1355
1622
  async mergeUpdate(id, patch) {
1356
- const backend = this.ctx.api.settingsStore;
1357
- if (!backend) return;
1358
- const raw = await backend.get.query({
1359
- collection: ALERTS_COLLECTION,
1360
- key: id
1361
- });
1362
- if (raw === null || raw === void 0 || typeof raw !== "object") return;
1363
- const existing = Object.fromEntries(Object.entries(raw));
1364
- await backend.set.mutate({
1365
- collection: ALERTS_COLLECTION,
1366
- key: id,
1367
- value: {
1368
- ...existing,
1369
- ...patch
1370
- }
1371
- });
1623
+ const store = this.alertStore;
1624
+ if (store === null) return;
1625
+ await store.mergeUpdate(id, patch);
1372
1626
  }
1373
1627
  async persistAlert(alert) {
1374
- const backend = this.ctx.api.settingsStore;
1375
- if (!backend) return;
1376
- const data = {
1377
- id: alert.id,
1378
- category: alert.category,
1379
- severity: alert.severity,
1380
- title: alert.title,
1381
- message: alert.message,
1382
- status: alert.status,
1383
- read: alert.read,
1384
- createdAt: alert.createdAt,
1385
- updatedAt: alert.updatedAt,
1386
- ...alert.progress !== void 0 ? { progress: alert.progress } : {},
1387
- ...alert.source ? { source: alert.source } : {},
1388
- ...alert.metadata ? { metadata: alert.metadata } : {}
1389
- };
1390
- await backend.set.mutate({
1391
- collection: ALERTS_COLLECTION,
1392
- key: alert.id,
1393
- value: data
1394
- });
1628
+ const store = this.alertStore;
1629
+ if (store === null) return;
1630
+ await store.persist(alert);
1395
1631
  }
1396
1632
  emitAlertCreatedEvent(alert) {
1397
1633
  this.ctx.eventBus?.emit(createEvent("alert.created", EVENT_SOURCE, {
@@ -1411,36 +1647,18 @@ var AlertCenterAddon = class extends BaseAddon {
1411
1647
  }));
1412
1648
  }
1413
1649
  async enforceMaxAlerts() {
1414
- const backend = this.ctx.api.settingsStore;
1415
- if (!backend) return;
1416
- if (await backend.count.query({ collection: ALERTS_COLLECTION }) <= this.config.maxAlerts) return;
1417
- const all = await backend.query.query({
1418
- collection: ALERTS_COLLECTION,
1419
- filter: { orderBy: {
1420
- field: "createdAt",
1421
- direction: "asc"
1422
- } }
1423
- });
1424
- for (const id of selectOverflow(all.map(toPrunable), this.config.maxAlerts)) await backend.delete.mutate({
1425
- collection: ALERTS_COLLECTION,
1426
- key: id
1427
- });
1650
+ const store = this.alertStore;
1651
+ if (store === null) return;
1652
+ if (await store.countAll() <= this.config.maxAlerts) return;
1653
+ const all = await store.listAllByCreatedAtAsc();
1654
+ for (const id of selectOverflow(all.map(toPrunable), this.config.maxAlerts)) await store.delete(id);
1428
1655
  }
1429
1656
  async enforceRetention() {
1430
- const backend = this.ctx.api.settingsStore;
1431
- if (!backend) return;
1657
+ const store = this.alertStore;
1658
+ if (store === null) return;
1432
1659
  const cutoff = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1e3;
1433
- const all = await backend.query.query({
1434
- collection: ALERTS_COLLECTION,
1435
- filter: { orderBy: {
1436
- field: "createdAt",
1437
- direction: "asc"
1438
- } }
1439
- });
1440
- for (const id of selectExpired(all.map(toPrunable), cutoff)) await backend.delete.mutate({
1441
- collection: ALERTS_COLLECTION,
1442
- key: id
1443
- });
1660
+ const all = await store.listAllByCreatedAtAsc();
1661
+ for (const id of selectExpired(all.map(toPrunable), cutoff)) await store.delete(id);
1444
1662
  }
1445
1663
  };
1446
1664
  //#endregion
@@ -59,6 +59,10 @@ export interface PushPolicy {
59
59
  }
60
60
  /** Alert categories that are critical system findings. */
61
61
  export declare const CRITICAL_CATEGORIES: ReadonlySet<string>;
62
+ export declare function isCriticalPush(alert: {
63
+ readonly severity: string;
64
+ readonly category: string;
65
+ }): boolean;
62
66
  export declare function isPushState(value: unknown): value is PushState;
63
67
  export declare function selectPushTargets(targets: readonly PushTarget[], policy: PushPolicy): readonly PushTarget[];
64
68
  /** One critical finding waiting for its push. */