@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
@@ -3,7 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-D1vECb_6.js");
6
+ const require_dist = require("../../dist-BkNfEJKa.js");
7
+ const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
8
+ const require_phase6_declare_gate = require("../../phase6-declare-gate-Bl7KocEs.js");
7
9
  //#region src/builtins/alerts/critical-push.ts
8
10
  /** Alert categories that are critical system findings. */
9
11
  var CRITICAL_CATEGORIES = new Set([
@@ -11,6 +13,9 @@ var CRITICAL_CATEGORIES = new Set([
11
13
  "addon.runner-refused",
12
14
  "system.runner-check"
13
15
  ]);
16
+ function isCriticalPush(alert) {
17
+ return alert.severity === "critical" || CRITICAL_CATEGORIES.has(alert.category);
18
+ }
14
19
  function isPushState(value) {
15
20
  if (typeof value !== "object" || value === null) return false;
16
21
  const rec = value;
@@ -108,6 +113,59 @@ async function deliverDigest(input) {
108
113
  return out;
109
114
  }
110
115
  //#endregion
116
+ //#region src/builtins/alerts/emit-alert-policy.ts
117
+ function episodeKeyFromMetadata(metadata) {
118
+ const key = metadata?.["episodeKey"];
119
+ return typeof key === "string" ? key : void 0;
120
+ }
121
+ /** Completed episodes start fresh — prior push must not suppress the next digest. */
122
+ function shouldDropPriorPush(existingStatus) {
123
+ return existingStatus === "completed";
124
+ }
125
+ function alertWithRetainedPush(toStore, existingPush, existingStatus) {
126
+ if (existingPush === void 0 || shouldDropPriorPush(existingStatus)) return toStore;
127
+ return {
128
+ ...toStore,
129
+ metadata: {
130
+ ...toStore.metadata,
131
+ push: existingPush
132
+ }
133
+ };
134
+ }
135
+ /**
136
+ * Re-emit of the same alert id: preserve read when the episode is unchanged
137
+ * and still active. A completed episode starts again. Queue a critical push
138
+ * only for active, unread critical findings.
139
+ */
140
+ function mergeEmitAlert(input) {
141
+ const incomingKey = episodeKeyFromMetadata(input.incoming.metadata);
142
+ const existingKey = input.existing?.episodeKey;
143
+ if (input.existing?.read === true) {
144
+ const sameEpisode = incomingKey === void 0 || incomingKey === existingKey;
145
+ const priorCompleted = input.existing.status === "completed";
146
+ if (sameEpisode && !priorCompleted) return {
147
+ alert: {
148
+ ...input.incoming,
149
+ read: true
150
+ },
151
+ queuePush: false
152
+ };
153
+ }
154
+ const queuePush = isCriticalPush(input.incoming) && input.incoming.status === "active" && input.incoming.read === false;
155
+ return {
156
+ alert: input.incoming,
157
+ queuePush
158
+ };
159
+ }
160
+ function criticalPushPayload(alert) {
161
+ const key = episodeKeyFromMetadata(alert.metadata) ?? alert.id;
162
+ const pushLine = alert.metadata?.["pushLine"];
163
+ return {
164
+ key,
165
+ line: typeof pushLine === "string" ? pushLine : alert.title
166
+ };
167
+ }
168
+ //#endregion
111
169
  //#region src/builtins/alerts/device-label.ts
112
170
  /**
113
171
  * Operator-facing device label for alert messages.
@@ -282,6 +340,211 @@ function runnerPushLine(record) {
282
340
  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.`;
283
341
  }
284
342
  //#endregion
343
+ //#region src/builtins/alerts/record-kinds.ts
344
+ var import_read_all_rows = require_read_all_rows$1.require_read_all_rows();
345
+ /**
346
+ * Record kinds owned by alert-center (records plan phase 4).
347
+ */
348
+ /** Legacy collection / adoptsFrom target (canonical KV before phase 4). */
349
+ var ALERTS_COLLECTION = "alerts";
350
+ var AlertBodySchema = require_dist.AlertSchema.omit({ id: true });
351
+ var _alertCenterRecordKinds = require_dist.defineRecordKinds({ alerts: {
352
+ schema: AlertBodySchema,
353
+ class: "audit",
354
+ writeTrigger: "EventBus categories the alert center tracks, operator mark read/dismiss, and model-download progress merges",
355
+ device: "none",
356
+ at: "none",
357
+ indexes: [
358
+ {
359
+ name: "read",
360
+ fields: ["read"]
361
+ },
362
+ {
363
+ name: "created_at",
364
+ fields: ["createdAt"]
365
+ },
366
+ {
367
+ name: "updated_at",
368
+ fields: ["updatedAt"]
369
+ }
370
+ ],
371
+ retention: {
372
+ owner: "alert-center",
373
+ reason: "maxAlerts cap and retentionDays enforced on write (enforceMaxAlerts / enforceRetention); read alerts preferred when trimming overflow"
374
+ },
375
+ adoptsFrom: ALERTS_COLLECTION
376
+ } });
377
+ var alertCenterRecordKinds = _alertCenterRecordKinds;
378
+ /** Narrow kind for `records.kind()` — `IsolatedRecordKinds` string index is `| undefined`. */
379
+ var alertsRecordKind = _alertCenterRecordKinds.alerts;
380
+ //#endregion
381
+ //#region src/builtins/alerts/alert-store.ts
382
+ function toRecord(row) {
383
+ return {
384
+ id: row.id,
385
+ data: row.data
386
+ };
387
+ }
388
+ function toRecordFromLegacy(row) {
389
+ return {
390
+ id: row.id,
391
+ data: row.data
392
+ };
393
+ }
394
+ function isRecord(value) {
395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
396
+ }
397
+ function legacyGetData(value) {
398
+ if (!isRecord(value)) return null;
399
+ if ("data" in value) {
400
+ const data = value.data;
401
+ return isRecord(data) ? data : null;
402
+ }
403
+ return value;
404
+ }
405
+ var AlertStore = class AlertStore {
406
+ kind;
407
+ legacyStore;
408
+ constructor(kind, legacyStore) {
409
+ this.kind = kind;
410
+ this.legacyStore = legacyStore;
411
+ }
412
+ static of(records) {
413
+ return new AlertStore(records.kind(alertsRecordKind), null);
414
+ }
415
+ static legacy(store) {
416
+ return new AlertStore(null, store);
417
+ }
418
+ async legacyRows() {
419
+ const store = this.legacyStore;
420
+ if (store === null) return [];
421
+ return (await store.query.query({
422
+ collection: ALERTS_COLLECTION,
423
+ filter: {}
424
+ })).map((row) => toRecordFromLegacy({
425
+ id: row.id,
426
+ data: row.data
427
+ }));
428
+ }
429
+ async list(filter) {
430
+ let out;
431
+ const kind = this.kind;
432
+ if (kind !== null) out = (await (0, import_read_all_rows.readAllRows)(kind, { order: "id" })).map(toRecord);
433
+ else out = [...await this.legacyRows()];
434
+ if (filter?.unreadOnly === true) out = out.filter((r) => r.data["read"] === false);
435
+ out.sort((a, b) => Number(b.data["updatedAt"] ?? 0) - Number(a.data["updatedAt"] ?? 0));
436
+ const limit = filter?.limit ?? 100;
437
+ return out.slice(0, limit);
438
+ }
439
+ async countUnread() {
440
+ return (await this.list({
441
+ unreadOnly: true,
442
+ limit: 1e4
443
+ })).length;
444
+ }
445
+ async countAll() {
446
+ const kind = this.kind;
447
+ if (kind !== null) return (await (0, import_read_all_rows.readAllRows)(kind)).length;
448
+ return (await this.legacyRows()).length;
449
+ }
450
+ async getData(id) {
451
+ const kind = this.kind;
452
+ if (kind !== null) {
453
+ const row = await kind.get(id);
454
+ if (row === null) return null;
455
+ return row.data;
456
+ }
457
+ const store = this.legacyStore;
458
+ if (store === null) return null;
459
+ return legacyGetData(await store.get.query({
460
+ collection: ALERTS_COLLECTION,
461
+ key: id
462
+ }));
463
+ }
464
+ async mergeUpdate(id, patch) {
465
+ const existing = await this.getData(id);
466
+ if (existing === null) return;
467
+ await this.putRaw(id, {
468
+ ...existing,
469
+ ...patch
470
+ });
471
+ }
472
+ async persist(alert) {
473
+ const { id, ...body } = alert;
474
+ const data = alertsRecordKind.schema.parse(body);
475
+ const kind = this.kind;
476
+ if (kind !== null) {
477
+ await kind.put({
478
+ id,
479
+ data
480
+ });
481
+ return;
482
+ }
483
+ await this.putRaw(id, {
484
+ ...body,
485
+ id
486
+ });
487
+ }
488
+ async putRaw(id, data) {
489
+ const { id: _drop, ...body } = require_dist.AlertSchema.parse({
490
+ id,
491
+ ...data
492
+ });
493
+ const kind = this.kind;
494
+ if (kind !== null) {
495
+ await kind.put({
496
+ id,
497
+ data: body
498
+ });
499
+ return;
500
+ }
501
+ const store = this.legacyStore;
502
+ if (store === null) return;
503
+ await store.set.mutate({
504
+ collection: ALERTS_COLLECTION,
505
+ key: id,
506
+ value: {
507
+ id,
508
+ ...body
509
+ }
510
+ });
511
+ }
512
+ async delete(id) {
513
+ const kind = this.kind;
514
+ if (kind !== null) {
515
+ await kind.delete(id);
516
+ return;
517
+ }
518
+ const store = this.legacyStore;
519
+ if (store === null) return;
520
+ await store.delete.mutate({
521
+ collection: ALERTS_COLLECTION,
522
+ key: id
523
+ });
524
+ }
525
+ async listAllByCreatedAtAsc() {
526
+ const kind = this.kind;
527
+ let out;
528
+ if (kind !== null) out = (await (0, import_read_all_rows.readAllRows)(kind)).map(toRecord);
529
+ else out = [...await this.legacyRows()];
530
+ out.sort((a, b) => Number(a.data["createdAt"] ?? 0) - Number(b.data["createdAt"] ?? 0));
531
+ return out;
532
+ }
533
+ async listRecent(limit) {
534
+ const kind = this.kind;
535
+ let out;
536
+ if (kind !== null) out = (await (0, import_read_all_rows.readAllRows)(kind, { order: "id" })).map(toRecord);
537
+ else out = [...await this.legacyRows()];
538
+ out.sort((a, b) => Number(b.data["updatedAt"] ?? 0) - Number(a.data["updatedAt"] ?? 0));
539
+ return out.slice(0, limit);
540
+ }
541
+ async findWhereDataField(field, value) {
542
+ const kind = this.kind;
543
+ if (kind !== null) return (await (0, import_read_all_rows.readAllRows)(kind)).filter((r) => r.data[field] === value).map(toRecord);
544
+ return (await this.legacyRows()).filter((r) => r.data[field] === value);
545
+ }
546
+ };
547
+ //#endregion
285
548
  //#region src/builtins/alerts/alerts.addon.ts
286
549
  var CATEGORY_MAPPINGS = {
287
550
  "addon.crashed": {
@@ -454,13 +717,18 @@ var DEFAULT_CONFIG = {
454
717
  maxAlerts: 500,
455
718
  retentionDays: 30,
456
719
  criticalPushEnabled: true,
457
- criticalPushTargetIds: ""
720
+ criticalPushTargetIds: []
458
721
  };
722
+ /** Accept the live `string[]` shape and the pre-multiselect CSV string. */
723
+ function normalizeCriticalPushTargetIds(raw) {
724
+ if (Array.isArray(raw)) return raw.filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim());
725
+ if (typeof raw === "string" && raw.trim().length > 0) return raw.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
726
+ return [];
727
+ }
459
728
  /** One burst of critical findings (a whole boot of refused runners) is ONE digest push. */
460
729
  var PUSH_COALESCE_MS = 2e3;
461
730
  /** Addons whose (re)start is a reason to retry pending critical pushes. */
462
731
  var PUSH_RECONCILE_ADDONS = new Set(["notifiers", "notification-center"]);
463
- var ALERTS_COLLECTION = "alerts";
464
732
  var EVENT_SOURCE = {
465
733
  type: "addon",
466
734
  id: "alert-center"
@@ -481,6 +749,7 @@ function toPrunable(record) {
481
749
  };
482
750
  }
483
751
  var AlertCenterAddon = class extends require_dist.BaseAddon {
752
+ alertStore = null;
484
753
  unsubscribers = [];
485
754
  /** How long a burst of critical findings is gathered into one digest push. */
486
755
  pushCoalesceMs;
@@ -495,6 +764,11 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
495
764
  */
496
765
  rateLimitCounters = /* @__PURE__ */ new Map();
497
766
  async onInitialize() {
767
+ const records = this.ctx.records;
768
+ const store = this.ctx.api?.settingsStore;
769
+ const useRecords = records !== void 0 && store !== void 0 && await require_phase6_declare_gate.resolvePhase6UseRecordsPathForHub(this.ctx, "alert-center", alertCenterRecordKinds, ["alerts"]);
770
+ this.alertStore = useRecords && records !== void 0 ? AlertStore.of(records) : store === void 0 ? null : AlertStore.legacy(store);
771
+ if (this.alertStore === null) throw new Error("alert-center needs ctx.records or settings-store on the hub");
498
772
  const alertsProvider = {
499
773
  emit: (alert) => this.emitAlert(alert),
500
774
  update: (input) => this.updateAlert(input.alertId, input.patch),
@@ -514,6 +788,7 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
514
788
  this.unsubscribers = unsubs;
515
789
  this.reconcilePendingPushes();
516
790
  this.dismissLegacyStreamCards(void 0);
791
+ this.migrateCriticalPushTargetIds();
517
792
  this.ctx.logger.info("Initialized", { meta: { trackedCategoriesCount: TRACKED_CATEGORIES.length } });
518
793
  return [{
519
794
  capability: require_dist.alertsCapability,
@@ -561,43 +836,28 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
561
836
  type: "boolean",
562
837
  key: "criticalPushEnabled",
563
838
  label: "Push critical system alerts",
564
- 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.",
839
+ 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.",
565
840
  default: true
566
841
  }),
567
842
  this.field({
568
- type: "text",
843
+ type: "notification-target-multiselect",
569
844
  key: "criticalPushTargetIds",
570
845
  label: "Critical push targets",
571
- description: "Comma-separated notifier target ids. Empty = every enabled shared (not personal) target.",
572
- default: ""
846
+ description: "Notifier targets that receive critical system alerts. Empty = every enabled shared (not personal) target.",
847
+ default: []
573
848
  })
574
849
  ]
575
850
  }] });
576
851
  }
577
852
  async listAlerts(filter) {
578
- const backend = this.ctx.api.settingsStore;
579
- if (!backend) return [];
580
- const whereClause = {};
581
- if (filter?.unreadOnly) whereClause.read = false;
582
- return (await backend.query.query({
583
- collection: ALERTS_COLLECTION,
584
- filter: {
585
- where: Object.keys(whereClause).length > 0 ? whereClause : void 0,
586
- orderBy: {
587
- field: "updatedAt",
588
- direction: "desc"
589
- },
590
- limit: filter?.limit ?? 100
591
- }
592
- })).map(parseAlert);
853
+ const store = this.alertStore;
854
+ if (store === null) return [];
855
+ return (await store.list(filter)).map(parseAlert);
593
856
  }
594
857
  async getUnreadCount() {
595
- const backend = this.ctx.api.settingsStore;
596
- if (!backend) return 0;
597
- return backend.count.query({
598
- collection: ALERTS_COLLECTION,
599
- filter: { where: { read: false } }
600
- });
858
+ const store = this.alertStore;
859
+ if (store === null) return 0;
860
+ return store.countUnread();
601
861
  }
602
862
  async markRead(alertId) {
603
863
  await this.mergeUpdate(alertId, {
@@ -606,11 +866,11 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
606
866
  });
607
867
  }
608
868
  async markAllRead() {
609
- const backend = this.ctx.api.settingsStore;
610
- if (!backend) return;
611
- const unread = await backend.query.query({
612
- collection: ALERTS_COLLECTION,
613
- filter: { where: { read: false } }
869
+ const store = this.alertStore;
870
+ if (store === null) return;
871
+ const unread = await store.list({
872
+ unreadOnly: true,
873
+ limit: 1e4
614
874
  });
615
875
  const now = Date.now();
616
876
  for (const record of unread) await this.mergeUpdate(record.id, {
@@ -707,14 +967,14 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
707
967
  }
708
968
  }
709
969
  async readRunnerRecord(alertId) {
710
- const backend = this.ctx.api.settingsStore;
711
- if (!backend) return null;
712
- const raw = await backend.get.query({
713
- collection: ALERTS_COLLECTION,
714
- key: alertId
970
+ const store = this.alertStore;
971
+ if (store === null) return null;
972
+ const raw = await store.getData(alertId);
973
+ if (raw === null) return null;
974
+ const alert = require_dist.AlertSchema.safeParse({
975
+ id: alertId,
976
+ ...raw
715
977
  });
716
- if (raw === null || raw === void 0 || typeof raw !== "object") return null;
717
- const alert = require_dist.AlertSchema.safeParse(raw);
718
978
  if (!alert.success || alert.data.status === "dismissed") return null;
719
979
  return parseRunnerRecord(alert.data.metadata?.["runner"]);
720
980
  }
@@ -842,20 +1102,20 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
842
1102
  });
843
1103
  }
844
1104
  async readAlertRaw(alertId) {
845
- const backend = this.ctx.api.settingsStore;
846
- if (!backend) return null;
847
- const raw = await backend.get.query({
848
- collection: ALERTS_COLLECTION,
849
- key: alertId
850
- });
851
- if (raw === null || raw === void 0 || typeof raw !== "object") return null;
1105
+ const store = this.alertStore;
1106
+ if (store === null) return null;
1107
+ const raw = await store.getData(alertId);
1108
+ if (raw === null) return null;
852
1109
  const rec = Object.fromEntries(Object.entries(raw));
853
1110
  const meta = rec["metadata"];
854
1111
  const push = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta["push"] : void 0;
1112
+ const metadata = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta : void 0;
855
1113
  return {
856
1114
  ...typeof rec["createdAt"] === "number" ? { createdAt: rec["createdAt"] } : {},
857
1115
  ...typeof rec["read"] === "boolean" ? { read: rec["read"] } : {},
858
- ...isPushState(push) ? { push } : {}
1116
+ ...typeof rec["status"] === "string" ? { status: rec["status"] } : {},
1117
+ ...isPushState(push) ? { push } : {},
1118
+ ...episodeKeyFromMetadata(metadata) !== void 0 ? { episodeKey: episodeKeyFromMetadata(metadata) } : {}
859
1119
  };
860
1120
  }
861
1121
  /** Pushes run one at a time so a burst can never send the same state twice. */
@@ -866,9 +1126,19 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
866
1126
  pushPolicy() {
867
1127
  return {
868
1128
  enabled: this.config.criticalPushEnabled,
869
- targetIds: this.config.criticalPushTargetIds.split(",").map((id) => id.trim()).filter((id) => id.length > 0)
1129
+ targetIds: normalizeCriticalPushTargetIds(this.config.criticalPushTargetIds)
870
1130
  };
871
1131
  }
1132
+ /** Rewrite a legacy CSV `criticalPushTargetIds` string into `string[]`. */
1133
+ async migrateCriticalPushTargetIds() {
1134
+ const raw = this.config.criticalPushTargetIds;
1135
+ if (typeof raw !== "string") return;
1136
+ try {
1137
+ await this.updateGlobalSettings({ criticalPushTargetIds: normalizeCriticalPushTargetIds(raw) });
1138
+ } catch (err) {
1139
+ this.ctx.logger.warn("criticalPushTargetIds CSV migration failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
1140
+ }
1141
+ }
872
1142
  pushPorts() {
873
1143
  const api = this.ctx.api;
874
1144
  return {
@@ -945,27 +1215,16 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
945
1215
  return this.pushChain;
946
1216
  }
947
1217
  async storePushState(alertId, push) {
948
- const backend = this.ctx.api.settingsStore;
949
- if (!backend) return;
950
- const raw = await backend.get.query({
951
- collection: ALERTS_COLLECTION,
952
- key: alertId
953
- });
954
- if (raw === null || raw === void 0 || typeof raw !== "object") return;
955
- const existing = Object.fromEntries(Object.entries(raw));
956
- const meta = existing["metadata"];
1218
+ const store = this.alertStore;
1219
+ if (store === null) return;
1220
+ const raw = await store.getData(alertId);
1221
+ if (raw === null) return;
1222
+ const meta = raw["metadata"];
957
1223
  const metadata = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta : {};
958
- await backend.set.mutate({
959
- collection: ALERTS_COLLECTION,
960
- key: alertId,
961
- value: {
962
- ...existing,
963
- metadata: {
964
- ...metadata,
965
- push
966
- }
967
- }
968
- });
1224
+ await store.mergeUpdate(alertId, { metadata: {
1225
+ ...metadata,
1226
+ push
1227
+ } });
969
1228
  }
970
1229
  /**
971
1230
  * Retry every critical alert whose push is still pending - at boot and when
@@ -973,26 +1232,17 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
973
1232
  * delivered is skipped by its key, so reconciling never sends twice.
974
1233
  */
975
1234
  async reconcilePendingPushes() {
976
- const backend = this.ctx.api.settingsStore;
977
- if (!backend) return;
1235
+ const store = this.alertStore;
1236
+ if (store === null) return;
978
1237
  try {
979
- const rows = await backend.query.query({
980
- collection: ALERTS_COLLECTION,
981
- filter: {
982
- orderBy: {
983
- field: "updatedAt",
984
- direction: "desc"
985
- },
986
- limit: 500
987
- }
988
- });
1238
+ const rows = await store.listRecent(500);
989
1239
  for (const row of rows) {
990
1240
  const parsed = require_dist.AlertSchema.safeParse({
991
1241
  id: row.id,
992
1242
  ...row.data
993
1243
  });
994
1244
  if (!parsed.success || parsed.data.status !== "active") continue;
995
- if (!CRITICAL_CATEGORIES.has(parsed.data.category)) continue;
1245
+ if (!isCriticalPush(parsed.data)) continue;
996
1246
  const push = parsed.data.metadata?.["push"];
997
1247
  if (!isPushState(push) || push.state !== "pending") continue;
998
1248
  this.pushQueue.set(parsed.data.id, {
@@ -1012,12 +1262,9 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
1012
1262
  const modelId = data.modelId ?? "unknown";
1013
1263
  const progress = data.progress ?? 0;
1014
1264
  const stableId = `model-download:${modelId}`;
1015
- const backend = this.ctx.api.settingsStore;
1016
- if (!backend) return;
1017
- if ((await backend.query.query({
1018
- collection: ALERTS_COLLECTION,
1019
- filter: { where: { id: stableId } }
1020
- })).length > 0) {
1265
+ const store = this.alertStore;
1266
+ if (store === null) return;
1267
+ if (await store.getData(stableId) !== null) {
1021
1268
  const now = Date.now();
1022
1269
  if (progress >= 100) {
1023
1270
  await this.mergeUpdate(stableId, {
@@ -1341,8 +1588,29 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
1341
1588
  }
1342
1589
  }
1343
1590
  async emitAlert(alert) {
1344
- await this.persistAlert(alert);
1345
- this.emitAlertCreatedEvent(alert);
1591
+ const existingRaw = await this.readAlertRaw(alert.id);
1592
+ const { alert: toStore, queuePush } = mergeEmitAlert({
1593
+ existing: existingRaw === null ? null : {
1594
+ read: existingRaw.read,
1595
+ episodeKey: existingRaw.episodeKey,
1596
+ status: existingRaw.status
1597
+ },
1598
+ incoming: alert
1599
+ });
1600
+ const stored = alertWithRetainedPush(toStore, existingRaw?.push, existingRaw?.status);
1601
+ const isNew = existingRaw === null;
1602
+ await this.persistAlert(stored);
1603
+ if (isNew) this.emitAlertCreatedEvent(stored);
1604
+ else this.emitAlertUpdatedEvent(alert.id, stored);
1605
+ if (queuePush) {
1606
+ const { key, line } = criticalPushPayload(stored);
1607
+ this.queuePush({
1608
+ alertId: stored.id,
1609
+ key,
1610
+ title: stored.title,
1611
+ line
1612
+ });
1613
+ }
1346
1614
  await this.enforceMaxAlerts();
1347
1615
  await this.enforceRetention();
1348
1616
  }
@@ -1358,45 +1626,14 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
1358
1626
  * so we must read the existing record, merge the patch, and write back.
1359
1627
  */
1360
1628
  async mergeUpdate(id, patch) {
1361
- const backend = this.ctx.api.settingsStore;
1362
- if (!backend) return;
1363
- const raw = await backend.get.query({
1364
- collection: ALERTS_COLLECTION,
1365
- key: id
1366
- });
1367
- if (raw === null || raw === void 0 || typeof raw !== "object") return;
1368
- const existing = Object.fromEntries(Object.entries(raw));
1369
- await backend.set.mutate({
1370
- collection: ALERTS_COLLECTION,
1371
- key: id,
1372
- value: {
1373
- ...existing,
1374
- ...patch
1375
- }
1376
- });
1629
+ const store = this.alertStore;
1630
+ if (store === null) return;
1631
+ await store.mergeUpdate(id, patch);
1377
1632
  }
1378
1633
  async persistAlert(alert) {
1379
- const backend = this.ctx.api.settingsStore;
1380
- if (!backend) return;
1381
- const data = {
1382
- id: alert.id,
1383
- category: alert.category,
1384
- severity: alert.severity,
1385
- title: alert.title,
1386
- message: alert.message,
1387
- status: alert.status,
1388
- read: alert.read,
1389
- createdAt: alert.createdAt,
1390
- updatedAt: alert.updatedAt,
1391
- ...alert.progress !== void 0 ? { progress: alert.progress } : {},
1392
- ...alert.source ? { source: alert.source } : {},
1393
- ...alert.metadata ? { metadata: alert.metadata } : {}
1394
- };
1395
- await backend.set.mutate({
1396
- collection: ALERTS_COLLECTION,
1397
- key: alert.id,
1398
- value: data
1399
- });
1634
+ const store = this.alertStore;
1635
+ if (store === null) return;
1636
+ await store.persist(alert);
1400
1637
  }
1401
1638
  emitAlertCreatedEvent(alert) {
1402
1639
  this.ctx.eventBus?.emit(require_dist.createEvent("alert.created", EVENT_SOURCE, {
@@ -1416,36 +1653,18 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
1416
1653
  }));
1417
1654
  }
1418
1655
  async enforceMaxAlerts() {
1419
- const backend = this.ctx.api.settingsStore;
1420
- if (!backend) return;
1421
- if (await backend.count.query({ collection: ALERTS_COLLECTION }) <= this.config.maxAlerts) return;
1422
- const all = await backend.query.query({
1423
- collection: ALERTS_COLLECTION,
1424
- filter: { orderBy: {
1425
- field: "createdAt",
1426
- direction: "asc"
1427
- } }
1428
- });
1429
- for (const id of selectOverflow(all.map(toPrunable), this.config.maxAlerts)) await backend.delete.mutate({
1430
- collection: ALERTS_COLLECTION,
1431
- key: id
1432
- });
1656
+ const store = this.alertStore;
1657
+ if (store === null) return;
1658
+ if (await store.countAll() <= this.config.maxAlerts) return;
1659
+ const all = await store.listAllByCreatedAtAsc();
1660
+ for (const id of selectOverflow(all.map(toPrunable), this.config.maxAlerts)) await store.delete(id);
1433
1661
  }
1434
1662
  async enforceRetention() {
1435
- const backend = this.ctx.api.settingsStore;
1436
- if (!backend) return;
1663
+ const store = this.alertStore;
1664
+ if (store === null) return;
1437
1665
  const cutoff = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1e3;
1438
- const all = await backend.query.query({
1439
- collection: ALERTS_COLLECTION,
1440
- filter: { orderBy: {
1441
- field: "createdAt",
1442
- direction: "asc"
1443
- } }
1444
- });
1445
- for (const id of selectExpired(all.map(toPrunable), cutoff)) await backend.delete.mutate({
1446
- collection: ALERTS_COLLECTION,
1447
- key: id
1448
- });
1666
+ const all = await store.listAllByCreatedAtAsc();
1667
+ for (const id of selectExpired(all.map(toPrunable), cutoff)) await store.delete(id);
1449
1668
  }
1450
1669
  };
1451
1670
  //#endregion