@camstack/system 1.2.424 → 1.2.426

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 (85) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-databases.d.ts +51 -0
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +19 -0
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +268 -17
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +268 -17
  13. package/dist/builtins/backup-orchestrator/manifest-store.d.ts +23 -3
  14. package/dist/builtins/backup-orchestrator/run-queue.d.ts +3 -0
  15. package/dist/builtins/backup-orchestrator/schedule-store.d.ts +7 -1
  16. package/dist/builtins/backup-orchestrator/seed-marker-store.d.ts +34 -0
  17. package/dist/builtins/calendars/calendars.addon.js +1 -1
  18. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  19. package/dist/builtins/camera-grid/index.js +1 -1
  20. package/dist/builtins/camera-grid/index.mjs +1 -1
  21. package/dist/builtins/composer/composer.addon.js +2 -2
  22. package/dist/builtins/composer/composer.addon.mjs +2 -2
  23. package/dist/builtins/console-logging/index.js +1 -1
  24. package/dist/builtins/console-logging/index.mjs +1 -1
  25. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  26. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  27. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  28. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  29. package/dist/builtins/hub-forwarder/index.js +1 -1
  30. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  31. package/dist/builtins/keychain/index.js +3 -3
  32. package/dist/builtins/keychain/index.mjs +3 -3
  33. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  34. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  35. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  36. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  37. package/dist/builtins/local-network/address-book.d.ts +21 -0
  38. package/dist/builtins/local-network/local-network.addon.d.ts +6 -0
  39. package/dist/builtins/local-network/local-network.addon.js +48 -7
  40. package/dist/builtins/local-network/local-network.addon.mjs +48 -7
  41. package/dist/builtins/loki-logging/index.js +1 -1
  42. package/dist/builtins/loki-logging/index.mjs +1 -1
  43. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  44. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  45. package/dist/builtins/platform-probe/index.js +1 -1
  46. package/dist/builtins/platform-probe/index.mjs +1 -1
  47. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  48. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  49. package/dist/builtins/snapshot/index.js +1 -1
  50. package/dist/builtins/snapshot/index.mjs +1 -1
  51. package/dist/builtins/sqlite-storage/database-snapshot-provider.d.ts +18 -0
  52. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  53. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  54. package/dist/builtins/sqlite-storage/index.js +1 -1
  55. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +49 -7
  57. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +49 -7
  58. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  59. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  60. package/dist/builtins/system-backup/system-backup.service.d.ts +48 -15
  61. package/dist/builtins/system-config/system-config.addon.js +2 -2
  62. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  63. package/dist/builtins/winston-logging/index.js +1 -1
  64. package/dist/builtins/winston-logging/index.mjs +1 -1
  65. package/dist/{composition-sources-BWNs-ZeH.js → composition-sources-60tuHQ9N.js} +1 -1
  66. package/dist/{composition-sources-tRG7nKWK.mjs → composition-sources-gxVpghXh.mjs} +1 -1
  67. package/dist/{constants-DHhhVjkt.mjs → constants-B6YTf0v2.mjs} +1 -1
  68. package/dist/{constants-BBWZlnpQ.js → constants-CO5zpJQY.js} +1 -1
  69. package/dist/{dist-B-NmfvVC.mjs → dist-CGz9u2HD.mjs} +869 -800
  70. package/dist/{dist-CiUKtVkX.js → dist-K3WFop9Q.js} +869 -800
  71. package/dist/file-host-worker.js +1 -1
  72. package/dist/file-host-worker.mjs +1 -1
  73. package/dist/index.js +272 -198
  74. package/dist/index.mjs +272 -198
  75. package/dist/{jwt-signing-key-nwRpRgf_.js → jwt-signing-key-BdDy9ZJN.js} +1 -1
  76. package/dist/{jwt-signing-key-CUpweD22.mjs → jwt-signing-key-C_w5ielw.mjs} +1 -1
  77. package/dist/kernel/moleculer/process-service.d.ts +1 -110
  78. package/dist/kernel/moleculer/runner-heap-flags.d.ts +228 -0
  79. package/dist/{restore-archive-DA-aJtg7.js → restore-archive-BJp4Llea.js} +156 -65
  80. package/dist/{restore-archive-CFmPVlWQ.mjs → restore-archive-CZyUM8Qf.mjs} +151 -66
  81. package/dist/{retired-settings-keys-D62kN_hT.js → retired-settings-keys-Dr2AyhsD.js} +1 -1
  82. package/dist/{retired-settings-keys-DCGEI4Vi.mjs → retired-settings-keys-Dut6QWh6.mjs} +1 -1
  83. package/dist/{wal-maintenance-BlqNqaQ3.mjs → wal-maintenance-BZhzoxqA.mjs} +0 -0
  84. package/dist/{wal-maintenance-BzdCQGjH.js → wal-maintenance-Bzm3O2Nj.js} +0 -0
  85. package/package.json +1 -1
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CiUKtVkX.js");
6
+ const require_dist = require("../../dist-K3WFop9Q.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { G as addonPagesCapability, cr as errMsg, lr as BaseAddon, ti as EventCategory } from "../../dist-B-NmfvVC.mjs";
1
+ import { G as addonPagesCapability, cr as errMsg, lr as BaseAddon, ti as EventCategory } from "../../dist-CGz9u2HD.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CiUKtVkX.js");
6
+ const require_dist = require("../../dist-K3WFop9Q.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { cr as errMsg, lr as BaseAddon, q as addonWidgetsCapability, ti as EventCategory } from "../../dist-B-NmfvVC.mjs";
1
+ import { cr as errMsg, lr as BaseAddon, q as addonWidgetsCapability, ti as EventCategory } from "../../dist-CGz9u2HD.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CiUKtVkX.js");
6
+ const require_dist = require("../../dist-K3WFop9Q.js");
7
7
  //#region src/builtins/alerts/device-label.ts
8
8
  /**
9
9
  * Operator-facing device label for alert messages.
@@ -1,4 +1,4 @@
1
- import { J as alertsCapability, Tr as createEvent, cr as errMsg, lr as BaseAddon, n as AlertSchema, ti as EventCategory } from "../../dist-B-NmfvVC.mjs";
1
+ import { J as alertsCapability, Tr as createEvent, cr as errMsg, lr as BaseAddon, n as AlertSchema, ti as EventCategory } from "../../dist-CGz9u2HD.mjs";
2
2
  //#region src/builtins/alerts/device-label.ts
3
3
  /**
4
4
  * Operator-facing device label for alert messages.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CiUKtVkX.js");
6
+ const require_dist = require("../../dist-K3WFop9Q.js");
7
7
  //#region src/builtins/autotrack/autotrack-config.ts
8
8
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
9
9
  deadZoneFraction: .5,
@@ -1,4 +1,4 @@
1
- import { C as FIRST_LEVEL_MACRO_CLASSES, Pr as isEvent, Z as bareAddonId, cr as errMsg, hn as ptzAutotrackCapability, lr as BaseAddon, ti as EventCategory } from "../../dist-B-NmfvVC.mjs";
1
+ import { C as FIRST_LEVEL_MACRO_CLASSES, Pr as isEvent, Z as bareAddonId, cr as errMsg, hn as ptzAutotrackCapability, lr as BaseAddon, ti as EventCategory } from "../../dist-CGz9u2HD.mjs";
2
2
  //#region src/builtins/autotrack/autotrack-config.ts
3
3
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
4
4
  deadZoneFraction: .5,
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Which database files a backup carries, and when (D852).
3
+ *
4
+ * Settings is the one a decision reads and runs nightly. Events and history are
5
+ * statistical or ledger stores; losing up to a week costs chart gaps, never
6
+ * configuration, so they ride a WEEKLY schedule whose archive is a consistent
7
+ * full set. Pure functions, no I/O: the orchestrator feeds them what it read.
8
+ */
9
+ import type { DataStoreDatabase } from "@camstack/types";
10
+ import type { BackupSchedule } from "./schedule-store.js";
11
+ import type { SeedMarkers } from "./seed-marker-store.js";
12
+ /** Stable id of the seeded weekly schedule. */
13
+ export declare const WEEKLY_SCHEDULE_ID = "weekly-events-history";
14
+ export declare const WEEKLY_SCHEDULE_LABEL = "Weekly — events & history";
15
+ /** Sundays 04:00 — an hour after the nightly settings run. */
16
+ export declare const WEEKLY_SCHEDULE_CRON = "0 4 * * 0";
17
+ export declare const WEEKLY_SCHEDULE_RETENTION = 4;
18
+ /**
19
+ * Events and history ONLY: a run that does not select settings carries no
20
+ * data-root walk either, so the weekly archive is just the two snapshots.
21
+ */
22
+ export declare const WEEKLY_SCHEDULE_DATABASES: readonly DataStoreDatabase[];
23
+ /** What a schedule carries: its own list, else the minimum (D382). */
24
+ export declare function databasesOfSchedule(schedule: Pick<BackupSchedule, "databases">): readonly DataStoreDatabase[];
25
+ /**
26
+ * True when a run asks for a database beyond settings and the owner has none
27
+ * of them open: the run would only repeat the nightly archive. A settings-only
28
+ * run is never nothing-to-carry.
29
+ */
30
+ export declare function hasNothingBeyondSettings(requested: readonly DataStoreDatabase[], owned: readonly DataStoreDatabase[]): boolean;
31
+ /**
32
+ * The weekly schedule to seed, or null. Created only when NO schedule carries
33
+ * `events` (a seed never overwrites, D728 — an operator's own weekly schedule,
34
+ * or an edited seed, is left alone), and only when there is a nightly schedule
35
+ * to take the destinations from: a hub that backs up nowhere gets no weekly
36
+ * archive either.
37
+ */
38
+ export declare function planWeeklySeed(existing: readonly BackupSchedule[]): BackupSchedule | null;
39
+ /** The slice of the schedule store the seed needs. */
40
+ export interface ScheduleSeedStore {
41
+ list(): Promise<readonly BackupSchedule[]>;
42
+ upsert(schedule: BackupSchedule): Promise<void>;
43
+ }
44
+ /**
45
+ * Seed the weekly schedule if {@link planWeeklySeed} says so; returns what it
46
+ * wrote. Exactly once ever: the marker is written after the row, and a marker
47
+ * that exists ends the seed for good — an operator who disabled or deleted the
48
+ * weekly is never second-guessed. A weekly row found with no marker (seeded
49
+ * before the marker existed) is marked, not re-created.
50
+ */
51
+ export declare function seedWeeklySchedule(store: ScheduleSeedStore, markers: SeedMarkers): Promise<BackupSchedule | null>;
@@ -249,6 +249,19 @@ export declare class BackupOrchestratorAddon extends BaseAddon<Record<string, ne
249
249
  */
250
250
  private onScheduleTick;
251
251
  /**
252
+ * A schedule that asks for events/history while the owner has neither open
253
+ * would only repeat the nightly archive (D852: the weekly schedule has
254
+ * nothing to carry until those files exist). Said on the record, no archive
255
+ * built, no outcome stamped. An owner that cannot answer never skips: the
256
+ * run goes ahead and fails — or succeeds — in the open.
257
+ */
258
+ private nothingToCarry;
259
+ /**
260
+ * Seed the weekly "events & history" schedule (D852) — once, and never over
261
+ * anything the operator has: see `planWeeklySeed`.
262
+ */
263
+ private seedWeekly;
264
+ /**
252
265
  * Fire one schedule and RECORD what happened to it, naming every
253
266
  * destination. Three outcomes, three different persisted states —
254
267
  * because "fired" and "backed up" were the same field until D384:
@@ -258,6 +271,12 @@ export declare class BackupOrchestratorAddon extends BaseAddon<Record<string, ne
258
271
  * - the stamp could not be written → logged, named, not swallowed.
259
272
  */
260
273
  private runScheduledBackup;
274
+ /**
275
+ * The database files the owner has open right now, for the selection UI. An
276
+ * owner that cannot answer is `answered: false` — never an empty list, which
277
+ * the UI would read as "none exists yet".
278
+ */
279
+ private listAvailableDatabases;
261
280
  private listSchedules;
262
281
  private upsertSchedule;
263
282
  private deleteSchedule;
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CiUKtVkX.js");
7
- const require_restore_archive = require("../../restore-archive-DA-aJtg7.js");
6
+ const require_dist = require("../../dist-K3WFop9Q.js");
7
+ const require_restore_archive = require("../../restore-archive-BJp4Llea.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_fs_promises = require("node:fs/promises");
10
10
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -12,6 +12,138 @@ let node_path = require("node:path");
12
12
  node_path = require_chunk.__toESM(node_path);
13
13
  let node_fs = require("node:fs");
14
14
  node_fs = require_chunk.__toESM(node_fs);
15
+ //#region src/builtins/backup-orchestrator/backup-databases.ts
16
+ /** Stable id of the seeded weekly schedule. */
17
+ var WEEKLY_SCHEDULE_ID = "weekly-events-history";
18
+ var WEEKLY_SCHEDULE_LABEL = "Weekly — events & history";
19
+ /** Sundays 04:00 — an hour after the nightly settings run. */
20
+ var WEEKLY_SCHEDULE_CRON = "0 4 * * 0";
21
+ /**
22
+ * Events and history ONLY: a run that does not select settings carries no
23
+ * data-root walk either, so the weekly archive is just the two snapshots.
24
+ */
25
+ var WEEKLY_SCHEDULE_DATABASES = ["events", "history"];
26
+ /** What a schedule carries: its own list, else the minimum (D382). */
27
+ function databasesOfSchedule(schedule) {
28
+ return schedule.databases !== void 0 && schedule.databases.length > 0 ? schedule.databases : require_restore_archive.DEFAULT_BACKUP_DATABASES;
29
+ }
30
+ /**
31
+ * True when a run asks for a database beyond settings and the owner has none
32
+ * of them open: the run would only repeat the nightly archive. A settings-only
33
+ * run is never nothing-to-carry.
34
+ */
35
+ function hasNothingBeyondSettings(requested, owned) {
36
+ const extra = requested.filter((id) => id !== "settings");
37
+ if (extra.length === 0) return false;
38
+ return !extra.some((id) => owned.includes(id));
39
+ }
40
+ /**
41
+ * The weekly schedule to seed, or null. Created only when NO schedule carries
42
+ * `events` (a seed never overwrites, D728 — an operator's own weekly schedule,
43
+ * or an edited seed, is left alone), and only when there is a nightly schedule
44
+ * to take the destinations from: a hub that backs up nowhere gets no weekly
45
+ * archive either.
46
+ */
47
+ function planWeeklySeed(existing) {
48
+ if (existing.some((s) => s.id === "weekly-events-history")) return null;
49
+ if (existing.some((s) => databasesOfSchedule(s).includes("events"))) return null;
50
+ const destinations = /* @__PURE__ */ new Set();
51
+ for (const s of existing) {
52
+ if (!s.enabled) continue;
53
+ for (const id of s.locationIds) destinations.add(id);
54
+ }
55
+ if (destinations.size === 0) return null;
56
+ return {
57
+ id: WEEKLY_SCHEDULE_ID,
58
+ label: WEEKLY_SCHEDULE_LABEL,
59
+ cron: WEEKLY_SCHEDULE_CRON,
60
+ enabled: true,
61
+ locationIds: [...destinations].toSorted(),
62
+ retentionCount: 4,
63
+ databases: WEEKLY_SCHEDULE_DATABASES
64
+ };
65
+ }
66
+ /**
67
+ * Seed the weekly schedule if {@link planWeeklySeed} says so; returns what it
68
+ * wrote. Exactly once ever: the marker is written after the row, and a marker
69
+ * that exists ends the seed for good — an operator who disabled or deleted the
70
+ * weekly is never second-guessed. A weekly row found with no marker (seeded
71
+ * before the marker existed) is marked, not re-created.
72
+ */
73
+ async function seedWeeklySchedule(store, markers) {
74
+ if (await markers.has("weekly-events-history")) return null;
75
+ const existing = await store.list();
76
+ if (existing.some((s) => s.id === "weekly-events-history")) {
77
+ await markers.mark(WEEKLY_SCHEDULE_ID);
78
+ return null;
79
+ }
80
+ const seed = planWeeklySeed(existing);
81
+ if (seed === null) return null;
82
+ await store.upsert(seed);
83
+ await markers.mark(WEEKLY_SCHEDULE_ID);
84
+ return seed;
85
+ }
86
+ //#endregion
87
+ //#region src/builtins/backup-orchestrator/seed-marker-store.ts
88
+ /**
89
+ * @durable class=config owner=backup-orchestrator
90
+ * write="the orchestrator inserts one row per seed id the first time that
91
+ * seed is created (or found already created); never updated"
92
+ * retention="none — one tiny row per seed ever made; deleting it lets the
93
+ * seed be re-created once more on the next boot"
94
+ */
95
+ var BACKUP_SEED_MARKERS_COLLECTION = "backup_seed_markers";
96
+ var COLUMNS$1 = [{
97
+ name: "seed_id",
98
+ type: "TEXT",
99
+ primaryKey: true
100
+ }, {
101
+ name: "seeded_at",
102
+ type: "INTEGER",
103
+ notNull: true
104
+ }];
105
+ var BackupSeedMarkerStore = class {
106
+ store;
107
+ now;
108
+ declared = false;
109
+ constructor(store, now = Date.now) {
110
+ this.store = store;
111
+ this.now = now;
112
+ }
113
+ async ensureDeclared() {
114
+ if (this.declared) return;
115
+ await this.store.declareCollection({
116
+ collection: BACKUP_SEED_MARKERS_COLLECTION,
117
+ columns: COLUMNS$1
118
+ });
119
+ this.declared = true;
120
+ }
121
+ async has(seedId) {
122
+ await this.ensureDeclared();
123
+ return (await this.store.query({
124
+ collection: BACKUP_SEED_MARKERS_COLLECTION,
125
+ filter: {
126
+ where: { seed_id: seedId },
127
+ limit: 1
128
+ }
129
+ })).length > 0;
130
+ }
131
+ async mark(seedId) {
132
+ await this.ensureDeclared();
133
+ if (await this.has(seedId)) return;
134
+ await this.store.insert({
135
+ collection: BACKUP_SEED_MARKERS_COLLECTION,
136
+ record: {
137
+ id: seedId,
138
+ data: {
139
+ seed_id: seedId,
140
+ seeded_at: this.now()
141
+ }
142
+ }
143
+ });
144
+ }
145
+ };
146
+ //#endregion
15
147
  //#region ../../node_modules/croner/dist/croner.js
16
148
  function T(s) {
17
149
  return Date.UTC(s.y, s.m - 1, s.d, s.h, s.i, s.s);
@@ -1277,7 +1409,8 @@ function normalizeManifest(raw) {
1277
1409
  sizeBytes: Number(cand["sizeBytes"]),
1278
1410
  ...typeof cand["label"] === "string" ? { label: cand["label"] } : {},
1279
1411
  manifest,
1280
- ...encryptionOf(cand["encryption"])
1412
+ ...encryptionOf(cand["encryption"]),
1413
+ ...databasesOf(cand["databases"])
1281
1414
  };
1282
1415
  archives.push(entry);
1283
1416
  }
@@ -1290,6 +1423,15 @@ var EMPTY_MANIFEST = {
1290
1423
  version: 1,
1291
1424
  archives: []
1292
1425
  };
1426
+ function databasesOf(raw) {
1427
+ if (!Array.isArray(raw)) return {};
1428
+ const known = [];
1429
+ const unknown = [];
1430
+ for (const item of raw) if (item === "settings" || item === "events" || item === "history") known.push(item);
1431
+ else if (typeof item === "string") unknown.push(item);
1432
+ if (known.length > 0) return { databases: known };
1433
+ return unknown.length > 0 ? { unrecognisedDatabases: unknown } : {};
1434
+ }
1293
1435
  function encryptionOf(raw) {
1294
1436
  if (typeof raw !== "object" || raw === null) return {};
1295
1437
  const scheme = Reflect.get(raw, "scheme");
@@ -1313,7 +1455,8 @@ function outlineOfManifest(manifest) {
1313
1455
  locations: [...manifest.locations],
1314
1456
  entries: [],
1315
1457
  totalBytes: manifest.totalBytes,
1316
- totalFiles: manifest.totalFiles
1458
+ totalFiles: manifest.totalFiles,
1459
+ ...manifest.omittedDatabases !== void 0 ? { omittedDatabases: [...manifest.omittedDatabases] } : {}
1317
1460
  };
1318
1461
  }
1319
1462
  /**
@@ -1329,9 +1472,15 @@ function buildArchiveEntry(input) {
1329
1472
  sizeBytes: input.sizeBytes,
1330
1473
  ...input.label !== void 0 ? { label: input.label } : {},
1331
1474
  manifest: input.encryption !== void 0 ? outlineOfManifest(input.manifest) : input.manifest,
1332
- ...input.encryption !== void 0 ? { encryption: input.encryption } : {}
1475
+ ...input.encryption !== void 0 ? { encryption: input.encryption } : {},
1476
+ ...input.databases !== void 0 ? { databases: [...input.databases] } : {}
1333
1477
  };
1334
1478
  }
1479
+ /** The key under which archives of one cadence prune each other. */
1480
+ function retentionClassOf(entry) {
1481
+ if (entry.databases === void 0 && entry.unrecognisedDatabases !== void 0) return `?${[...entry.unrecognisedDatabases].toSorted().join("+")}`;
1482
+ return [...entry.databases ?? require_restore_archive.DEFAULT_BACKUP_DATABASES].toSorted().join("+");
1483
+ }
1335
1484
  /**
1336
1485
  * Pure retention split: given an archive list and the operator's
1337
1486
  * `retentionCount`, return `{ keep, drop }` where `keep` is the most-
@@ -1340,20 +1489,27 @@ function buildArchiveEntry(input) {
1340
1489
  * `retentionCount <= 0` is treated as "no pruning" — defensive against
1341
1490
  * a config value that would otherwise wipe every archive on the next
1342
1491
  * write.
1492
+ *
1493
+ * `cadence` (see {@link retentionClassOf}) restricts the count to archives of
1494
+ * the run that was just written: nightly archives (settings) and weekly ones
1495
+ * (settings + events + history) are retained separately.
1343
1496
  */
1344
- function applyRetention(archives, retentionCount) {
1497
+ function applyRetention(archives, retentionCount, cadence) {
1345
1498
  if (retentionCount <= 0) return {
1346
1499
  keep: archives,
1347
1500
  drop: []
1348
1501
  };
1349
1502
  const sorted = [...archives].toSorted((a, b) => b.createdAt - a.createdAt);
1350
- if (sorted.length <= retentionCount) return {
1503
+ const inCadence = cadence === void 0 ? sorted : sorted.filter((a) => retentionClassOf(a) === cadence);
1504
+ if (inCadence.length <= retentionCount) return {
1351
1505
  keep: sorted,
1352
1506
  drop: []
1353
1507
  };
1508
+ const drop = inCadence.slice(retentionCount);
1509
+ const dropped = new Set(drop);
1354
1510
  return {
1355
- keep: sorted.slice(0, retentionCount),
1356
- drop: sorted.slice(retentionCount)
1511
+ keep: sorted.filter((a) => !dropped.has(a)),
1512
+ drop
1357
1513
  };
1358
1514
  }
1359
1515
  /**
@@ -1637,7 +1793,8 @@ function requestSignature(request) {
1637
1793
  destinations: [...request.destinations ?? []].toSorted(),
1638
1794
  locations: [...request.locations ?? []].toSorted(),
1639
1795
  label: request.label ?? null,
1640
- retentionCount: request.retentionCount ?? null
1796
+ retentionCount: request.retentionCount ?? null,
1797
+ databases: [...request.databases ?? []].toSorted()
1641
1798
  });
1642
1799
  }
1643
1800
  //#endregion
@@ -1696,6 +1853,10 @@ var BACKUP_SCHEDULES_COLUMNS = [
1696
1853
  name: "data_sources",
1697
1854
  type: "JSON"
1698
1855
  },
1856
+ {
1857
+ name: "databases",
1858
+ type: "JSON"
1859
+ },
1699
1860
  {
1700
1861
  name: "last_run_at",
1701
1862
  type: "INTEGER"
@@ -1785,6 +1946,7 @@ function mapScheduleToRow(s) {
1785
1946
  location_ids: [...s.locationIds],
1786
1947
  retention_count: s.retentionCount,
1787
1948
  data_sources: s.dataSources ? [...s.dataSources] : null,
1949
+ databases: s.databases ? [...s.databases] : null,
1788
1950
  last_run_at: s.lastAttemptAt ?? null,
1789
1951
  last_success_at: s.lastSuccessAt ?? null,
1790
1952
  last_error: s.lastError ?? null
@@ -1801,6 +1963,12 @@ function parseStringArray(raw) {
1801
1963
  if (!Array.isArray(value)) return [];
1802
1964
  return value.filter((v) => typeof v === "string");
1803
1965
  }
1966
+ /** Known database ids only: an unknown name in a stored row is dropped, not carried. */
1967
+ function parseDatabaseIds(raw) {
1968
+ const out = [];
1969
+ for (const id of parseStringArray(raw)) if (id === "settings" || id === "events" || id === "history") out.push(id);
1970
+ return out;
1971
+ }
1804
1972
  function tryParseJson(raw) {
1805
1973
  if (raw.length === 0) return null;
1806
1974
  try {
@@ -1814,6 +1982,8 @@ function mapRowToSchedule(row) {
1814
1982
  if (!id) return null;
1815
1983
  const dataSourcesRaw = row["data_sources"];
1816
1984
  const dataSources = dataSourcesRaw === null || dataSourcesRaw === void 0 ? void 0 : parseStringArray(dataSourcesRaw);
1985
+ const databasesRaw = row["databases"];
1986
+ const databases = databasesRaw === null || databasesRaw === void 0 ? void 0 : parseDatabaseIds(databasesRaw);
1817
1987
  const lastRunRaw = row["last_run_at"];
1818
1988
  const lastAttemptAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
1819
1989
  const lastSuccessRaw = row["last_success_at"];
@@ -1828,6 +1998,7 @@ function mapRowToSchedule(row) {
1828
1998
  locationIds: parseStringArray(row["location_ids"]),
1829
1999
  retentionCount: Number(row["retention_count"] ?? 7),
1830
2000
  ...dataSources !== void 0 ? { dataSources } : {},
2001
+ ...databases !== void 0 && databases.length > 0 ? { databases } : {},
1831
2002
  ...lastAttemptAt !== void 0 ? { lastAttemptAt } : {},
1832
2003
  ...lastSuccessAt !== void 0 ? { lastSuccessAt } : {},
1833
2004
  ...lastError !== void 0 ? { lastError } : {}
@@ -2080,6 +2251,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2080
2251
  this.ctx.logger.error("backup-orchestrator: schedule table init failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
2081
2252
  this.schedules = null;
2082
2253
  }
2254
+ await this.seedWeekly();
2083
2255
  const settingsStore = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
2084
2256
  this.encryptionKeys = settingsStore ? new BackupEncryptionKeyStore(settingsStore) : null;
2085
2257
  const provider = {
@@ -2100,6 +2272,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2100
2272
  upsertDestinationPolicy: (input) => this.upsertDestinationPolicy(input),
2101
2273
  previewSchedule: (input) => this.previewSchedule(input),
2102
2274
  listSchedules: () => this.listSchedules(),
2275
+ listAvailableDatabases: () => this.listAvailableDatabases(),
2103
2276
  upsertSchedule: (input) => this.upsertSchedule(input),
2104
2277
  deleteSchedule: (input) => this.deleteSchedule(input)
2105
2278
  };
@@ -2385,7 +2558,8 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2385
2558
  ...input?.destinations ? { destinations: input.destinations } : {},
2386
2559
  ...input?.locations ? { locations: input.locations } : {},
2387
2560
  ...input?.label !== void 0 ? { label: input.label } : {},
2388
- ...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {}
2561
+ ...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {},
2562
+ ...input?.databases ? { databases: input.databases } : {}
2389
2563
  };
2390
2564
  const submitted = this.runQueue.submit(request);
2391
2565
  if (submitted.queued) this.ctx.logger.info(submitted.joined ? "backup trigger: identical run already queued — joined it" : "backup trigger: a run is in flight — enqueued behind it", { meta: { runId: submitted.runId } });
@@ -2459,6 +2633,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2459
2633
  signal,
2460
2634
  onArchiveBytes: (bytes) => progress.setStagedBytes(bytes),
2461
2635
  owner: this.databaseOwner(),
2636
+ ...input.databases ? { databases: input.databases } : {},
2462
2637
  ...encryptTo !== null ? { encryptTo: encryptTo.publicKey } : {}
2463
2638
  });
2464
2639
  } catch (err) {
@@ -2491,6 +2666,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2491
2666
  sizeBytes: result.sizeBytes,
2492
2667
  archiveManifest: result.manifest,
2493
2668
  retentionCount: input.retentionCount ?? policy.retentionCount,
2669
+ databases: input.databases ?? require_restore_archive.DEFAULT_BACKUP_DATABASES,
2494
2670
  createdAt,
2495
2671
  label: formatArchiveLabel(dest.id, createdAt, input.label),
2496
2672
  ...result.encryption !== void 0 ? { encryption: result.encryption } : {},
@@ -2562,9 +2738,9 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2562
2738
  };
2563
2739
  return {
2564
2740
  listDatabases: async () => {
2565
- return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases.map((d) => d.livePath);
2741
+ return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases;
2566
2742
  },
2567
- writeSnapshot: async () => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({}, pin())
2743
+ writeSnapshot: async (databaseId) => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({ databaseId }, pin())
2568
2744
  };
2569
2745
  }
2570
2746
  /**
@@ -2616,10 +2792,11 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2616
2792
  sizeBytes: input.sizeBytes,
2617
2793
  label: input.label,
2618
2794
  manifest: input.archiveManifest,
2619
- ...input.encryption !== void 0 ? { encryption: input.encryption } : {}
2795
+ ...input.encryption !== void 0 ? { encryption: input.encryption } : {},
2796
+ databases: input.databases
2620
2797
  });
2621
2798
  await this.manifestLock.run(input.locationId, async () => {
2622
- const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount);
2799
+ const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount, retentionClassOf(entry));
2623
2800
  for (const stale of drop) try {
2624
2801
  await this.ctx.api.storage.delete.mutate({
2625
2802
  location: input.locationId,
@@ -2673,7 +2850,8 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2673
2850
  sizeBytes: a.sizeBytes,
2674
2851
  ...a.label !== void 0 ? { label: a.label } : {},
2675
2852
  locations: [...a.manifest.locations],
2676
- ...a.encryption !== void 0 ? { encryption: a.encryption } : {}
2853
+ ...a.encryption !== void 0 ? { encryption: a.encryption } : {},
2854
+ ...a.databases !== void 0 ? { databases: [...a.databases] } : {}
2677
2855
  }));
2678
2856
  }
2679
2857
  /**
@@ -3040,6 +3218,55 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3040
3218
  }
3041
3219
  }
3042
3220
  /**
3221
+ * A schedule that asks for events/history while the owner has neither open
3222
+ * would only repeat the nightly archive (D852: the weekly schedule has
3223
+ * nothing to carry until those files exist). Said on the record, no archive
3224
+ * built, no outcome stamped. An owner that cannot answer never skips: the
3225
+ * run goes ahead and fails — or succeeds — in the open.
3226
+ */
3227
+ async nothingToCarry(schedule) {
3228
+ const requested = databasesOfSchedule(schedule);
3229
+ if (!requested.some((id) => id !== "settings")) return false;
3230
+ let owned;
3231
+ try {
3232
+ owned = (await this.databaseOwner().listDatabases()).map((d) => d.databaseId);
3233
+ } catch (err) {
3234
+ this.ctx.logger.warn("Scheduled backup: the database owner did not answer — running anyway", { meta: {
3235
+ scheduleId: schedule.id,
3236
+ label: schedule.label,
3237
+ error: err instanceof Error ? err.message : String(err)
3238
+ } });
3239
+ return false;
3240
+ }
3241
+ if (!hasNothingBeyondSettings(requested, owned)) return false;
3242
+ this.ctx.logger.info("Scheduled backup skipped — no database it carries exists yet", { meta: {
3243
+ scheduleId: schedule.id,
3244
+ label: schedule.label,
3245
+ requested: [...requested],
3246
+ owned
3247
+ } });
3248
+ return true;
3249
+ }
3250
+ /**
3251
+ * Seed the weekly "events & history" schedule (D852) — once, and never over
3252
+ * anything the operator has: see `planWeeklySeed`.
3253
+ */
3254
+ async seedWeekly() {
3255
+ if (!this.schedules) return;
3256
+ try {
3257
+ const store = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
3258
+ if (!store) return;
3259
+ const seed = await seedWeeklySchedule(this.schedules, new BackupSeedMarkerStore(store));
3260
+ if (seed === null) return;
3261
+ this.ctx.logger.info("backup: seeded the weekly events & history schedule", { meta: {
3262
+ scheduleId: seed.id,
3263
+ destinationIds: [...seed.locationIds]
3264
+ } });
3265
+ } catch (err) {
3266
+ this.ctx.logger.warn("backup: the weekly schedule could not be seeded", { meta: { error: err instanceof Error ? err.message : String(err) } });
3267
+ }
3268
+ }
3269
+ /**
3043
3270
  * Fire one schedule and RECORD what happened to it, naming every
3044
3271
  * destination. Three outcomes, three different persisted states —
3045
3272
  * because "fired" and "backed up" were the same field until D384:
@@ -3050,13 +3277,15 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3050
3277
  */
3051
3278
  async runScheduledBackup(schedule, firedAt) {
3052
3279
  const destinationIds = [...schedule.locationIds];
3280
+ if (await this.nothingToCarry(schedule)) return;
3053
3281
  let failure;
3054
3282
  try {
3055
3283
  const result = await this.triggerBackup({
3056
3284
  destinations: destinationIds,
3057
3285
  ...schedule.dataSources && schedule.dataSources.length > 0 ? { locations: [...schedule.dataSources] } : {},
3058
3286
  label: schedule.label,
3059
- retentionCount: schedule.retentionCount
3287
+ retentionCount: schedule.retentionCount,
3288
+ ...schedule.databases ? { databases: [...schedule.databases] } : {}
3060
3289
  });
3061
3290
  if (result.entries.length === 0 && !result.cancelled) failure = `no destination accepted the archive (${destinationIds.join(", ")})`;
3062
3291
  } catch (err) {
@@ -3089,6 +3318,25 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3089
3318
  } });
3090
3319
  }
3091
3320
  }
3321
+ /**
3322
+ * The database files the owner has open right now, for the selection UI. An
3323
+ * owner that cannot answer is `answered: false` — never an empty list, which
3324
+ * the UI would read as "none exists yet".
3325
+ */
3326
+ async listAvailableDatabases() {
3327
+ try {
3328
+ return {
3329
+ answered: true,
3330
+ databases: (await this.databaseOwner().listDatabases()).map((d) => d.databaseId)
3331
+ };
3332
+ } catch (err) {
3333
+ this.ctx.logger.warn("Backup database list: the database owner did not answer", { meta: { error: err instanceof Error ? err.message : String(err) } });
3334
+ return {
3335
+ answered: false,
3336
+ databases: []
3337
+ };
3338
+ }
3339
+ }
3092
3340
  async listSchedules() {
3093
3341
  if (!this.schedules) return [];
3094
3342
  return (await this.schedules.list()).map((s) => {
@@ -3105,6 +3353,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3105
3353
  locationIds: [...s.locationIds],
3106
3354
  retentionCount: s.retentionCount,
3107
3355
  ...s.dataSources !== void 0 ? { dataSources: [...s.dataSources] } : {},
3356
+ ...s.databases !== void 0 ? { databases: [...s.databases] } : {},
3108
3357
  ...s.lastAttemptAt !== void 0 ? { lastAttemptAt: s.lastAttemptAt } : {},
3109
3358
  ...s.lastSuccessAt !== void 0 ? { lastSuccessAt: s.lastSuccessAt } : {},
3110
3359
  ...s.lastError !== void 0 ? { lastError: s.lastError } : {},
@@ -3126,6 +3375,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3126
3375
  locationIds: [...input.locationIds],
3127
3376
  retentionCount: input.retentionCount,
3128
3377
  ...input.dataSources && input.dataSources.length > 0 ? { dataSources: [...input.dataSources] } : {},
3378
+ ...input.databases && input.databases.length > 0 ? { databases: [...input.databases] } : {},
3129
3379
  ...existing?.lastAttemptAt !== void 0 ? { lastAttemptAt: existing.lastAttemptAt } : {},
3130
3380
  ...existing?.lastSuccessAt !== void 0 ? { lastSuccessAt: existing.lastSuccessAt } : {},
3131
3381
  ...existing?.lastError !== void 0 ? { lastError: existing.lastError } : {}
@@ -3144,6 +3394,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3144
3394
  locationIds: [...next.locationIds],
3145
3395
  retentionCount: next.retentionCount,
3146
3396
  ...next.dataSources !== void 0 ? { dataSources: [...next.dataSources] } : {},
3397
+ ...next.databases !== void 0 ? { databases: [...next.databases] } : {},
3147
3398
  ...next.lastAttemptAt !== void 0 ? { lastAttemptAt: next.lastAttemptAt } : {},
3148
3399
  ...next.lastSuccessAt !== void 0 ? { lastSuccessAt: next.lastSuccessAt } : {},
3149
3400
  ...next.lastError !== void 0 ? { lastError: next.lastError } : {},