@camstack/system 1.2.424 → 1.2.425

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 (81) 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 +13 -0
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +248 -17
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +248 -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/local-network.addon.js +1 -1
  38. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  39. package/dist/builtins/loki-logging/index.js +1 -1
  40. package/dist/builtins/loki-logging/index.mjs +1 -1
  41. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  42. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  43. package/dist/builtins/platform-probe/index.js +1 -1
  44. package/dist/builtins/platform-probe/index.mjs +1 -1
  45. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  46. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  47. package/dist/builtins/snapshot/index.js +1 -1
  48. package/dist/builtins/snapshot/index.mjs +1 -1
  49. package/dist/builtins/sqlite-storage/database-snapshot-provider.d.ts +18 -0
  50. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  51. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  52. package/dist/builtins/sqlite-storage/index.js +1 -1
  53. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  54. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +49 -7
  55. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +49 -7
  56. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  57. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  58. package/dist/builtins/system-backup/system-backup.service.d.ts +48 -15
  59. package/dist/builtins/system-config/system-config.addon.js +2 -2
  60. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  61. package/dist/builtins/winston-logging/index.js +1 -1
  62. package/dist/builtins/winston-logging/index.mjs +1 -1
  63. package/dist/{composition-sources-BWNs-ZeH.js → composition-sources-197keifF.js} +1 -1
  64. package/dist/{composition-sources-tRG7nKWK.mjs → composition-sources-DmzTSy26.mjs} +1 -1
  65. package/dist/{constants-BBWZlnpQ.js → constants-B5Jd8Zch.js} +1 -1
  66. package/dist/{constants-DHhhVjkt.mjs → constants-CkNLi-XW.mjs} +1 -1
  67. package/dist/{dist-B-NmfvVC.mjs → dist-DMWeqeTF.mjs} +4341 -4308
  68. package/dist/{dist-CiUKtVkX.js → dist-DnALrQF-.js} +4341 -4308
  69. package/dist/file-host-worker.js +1 -1
  70. package/dist/file-host-worker.mjs +1 -1
  71. package/dist/index.js +5 -5
  72. package/dist/index.mjs +5 -5
  73. package/dist/{jwt-signing-key-CUpweD22.mjs → jwt-signing-key-CCHXAtrs.mjs} +1 -1
  74. package/dist/{jwt-signing-key-nwRpRgf_.js → jwt-signing-key-DeqG7LD9.js} +1 -1
  75. package/dist/{restore-archive-DA-aJtg7.js → restore-archive-C4UhQOsg.js} +156 -65
  76. package/dist/{restore-archive-CFmPVlWQ.mjs → restore-archive-GevpOHw8.mjs} +151 -66
  77. package/dist/{retired-settings-keys-D62kN_hT.js → retired-settings-keys-0FOsnzIE.js} +1 -1
  78. package/dist/{retired-settings-keys-DCGEI4Vi.mjs → retired-settings-keys-CdVFl7m2.mjs} +1 -1
  79. package/dist/{wal-maintenance-BlqNqaQ3.mjs → wal-maintenance-BtfyfuT5.mjs} +0 -0
  80. package/dist/{wal-maintenance-BzdCQGjH.js → wal-maintenance-DJV1rde2.js} +0 -0
  81. 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-DnALrQF-.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-DMWeqeTF.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-DnALrQF-.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-DMWeqeTF.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-DnALrQF-.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-DMWeqeTF.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-DnALrQF-.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-DMWeqeTF.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:
@@ -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-DnALrQF-.js");
7
+ const require_restore_archive = require("../../restore-archive-C4UhQOsg.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 = {
@@ -2385,7 +2557,8 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2385
2557
  ...input?.destinations ? { destinations: input.destinations } : {},
2386
2558
  ...input?.locations ? { locations: input.locations } : {},
2387
2559
  ...input?.label !== void 0 ? { label: input.label } : {},
2388
- ...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {}
2560
+ ...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {},
2561
+ ...input?.databases ? { databases: input.databases } : {}
2389
2562
  };
2390
2563
  const submitted = this.runQueue.submit(request);
2391
2564
  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 +2632,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2459
2632
  signal,
2460
2633
  onArchiveBytes: (bytes) => progress.setStagedBytes(bytes),
2461
2634
  owner: this.databaseOwner(),
2635
+ ...input.databases ? { databases: input.databases } : {},
2462
2636
  ...encryptTo !== null ? { encryptTo: encryptTo.publicKey } : {}
2463
2637
  });
2464
2638
  } catch (err) {
@@ -2491,6 +2665,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2491
2665
  sizeBytes: result.sizeBytes,
2492
2666
  archiveManifest: result.manifest,
2493
2667
  retentionCount: input.retentionCount ?? policy.retentionCount,
2668
+ databases: input.databases ?? require_restore_archive.DEFAULT_BACKUP_DATABASES,
2494
2669
  createdAt,
2495
2670
  label: formatArchiveLabel(dest.id, createdAt, input.label),
2496
2671
  ...result.encryption !== void 0 ? { encryption: result.encryption } : {},
@@ -2562,9 +2737,9 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2562
2737
  };
2563
2738
  return {
2564
2739
  listDatabases: async () => {
2565
- return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases.map((d) => d.livePath);
2740
+ return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases;
2566
2741
  },
2567
- writeSnapshot: async () => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({}, pin())
2742
+ writeSnapshot: async (databaseId) => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({ databaseId }, pin())
2568
2743
  };
2569
2744
  }
2570
2745
  /**
@@ -2616,10 +2791,11 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2616
2791
  sizeBytes: input.sizeBytes,
2617
2792
  label: input.label,
2618
2793
  manifest: input.archiveManifest,
2619
- ...input.encryption !== void 0 ? { encryption: input.encryption } : {}
2794
+ ...input.encryption !== void 0 ? { encryption: input.encryption } : {},
2795
+ databases: input.databases
2620
2796
  });
2621
2797
  await this.manifestLock.run(input.locationId, async () => {
2622
- const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount);
2798
+ const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount, retentionClassOf(entry));
2623
2799
  for (const stale of drop) try {
2624
2800
  await this.ctx.api.storage.delete.mutate({
2625
2801
  location: input.locationId,
@@ -2673,7 +2849,8 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
2673
2849
  sizeBytes: a.sizeBytes,
2674
2850
  ...a.label !== void 0 ? { label: a.label } : {},
2675
2851
  locations: [...a.manifest.locations],
2676
- ...a.encryption !== void 0 ? { encryption: a.encryption } : {}
2852
+ ...a.encryption !== void 0 ? { encryption: a.encryption } : {},
2853
+ ...a.databases !== void 0 ? { databases: [...a.databases] } : {}
2677
2854
  }));
2678
2855
  }
2679
2856
  /**
@@ -3040,6 +3217,55 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3040
3217
  }
3041
3218
  }
3042
3219
  /**
3220
+ * A schedule that asks for events/history while the owner has neither open
3221
+ * would only repeat the nightly archive (D852: the weekly schedule has
3222
+ * nothing to carry until those files exist). Said on the record, no archive
3223
+ * built, no outcome stamped. An owner that cannot answer never skips: the
3224
+ * run goes ahead and fails — or succeeds — in the open.
3225
+ */
3226
+ async nothingToCarry(schedule) {
3227
+ const requested = databasesOfSchedule(schedule);
3228
+ if (!requested.some((id) => id !== "settings")) return false;
3229
+ let owned;
3230
+ try {
3231
+ owned = (await this.databaseOwner().listDatabases()).map((d) => d.databaseId);
3232
+ } catch (err) {
3233
+ this.ctx.logger.warn("Scheduled backup: the database owner did not answer — running anyway", { meta: {
3234
+ scheduleId: schedule.id,
3235
+ label: schedule.label,
3236
+ error: err instanceof Error ? err.message : String(err)
3237
+ } });
3238
+ return false;
3239
+ }
3240
+ if (!hasNothingBeyondSettings(requested, owned)) return false;
3241
+ this.ctx.logger.info("Scheduled backup skipped — no database it carries exists yet", { meta: {
3242
+ scheduleId: schedule.id,
3243
+ label: schedule.label,
3244
+ requested: [...requested],
3245
+ owned
3246
+ } });
3247
+ return true;
3248
+ }
3249
+ /**
3250
+ * Seed the weekly "events & history" schedule (D852) — once, and never over
3251
+ * anything the operator has: see `planWeeklySeed`.
3252
+ */
3253
+ async seedWeekly() {
3254
+ if (!this.schedules) return;
3255
+ try {
3256
+ const store = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
3257
+ if (!store) return;
3258
+ const seed = await seedWeeklySchedule(this.schedules, new BackupSeedMarkerStore(store));
3259
+ if (seed === null) return;
3260
+ this.ctx.logger.info("backup: seeded the weekly events & history schedule", { meta: {
3261
+ scheduleId: seed.id,
3262
+ destinationIds: [...seed.locationIds]
3263
+ } });
3264
+ } catch (err) {
3265
+ this.ctx.logger.warn("backup: the weekly schedule could not be seeded", { meta: { error: err instanceof Error ? err.message : String(err) } });
3266
+ }
3267
+ }
3268
+ /**
3043
3269
  * Fire one schedule and RECORD what happened to it, naming every
3044
3270
  * destination. Three outcomes, three different persisted states —
3045
3271
  * because "fired" and "backed up" were the same field until D384:
@@ -3050,13 +3276,15 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3050
3276
  */
3051
3277
  async runScheduledBackup(schedule, firedAt) {
3052
3278
  const destinationIds = [...schedule.locationIds];
3279
+ if (await this.nothingToCarry(schedule)) return;
3053
3280
  let failure;
3054
3281
  try {
3055
3282
  const result = await this.triggerBackup({
3056
3283
  destinations: destinationIds,
3057
3284
  ...schedule.dataSources && schedule.dataSources.length > 0 ? { locations: [...schedule.dataSources] } : {},
3058
3285
  label: schedule.label,
3059
- retentionCount: schedule.retentionCount
3286
+ retentionCount: schedule.retentionCount,
3287
+ ...schedule.databases ? { databases: [...schedule.databases] } : {}
3060
3288
  });
3061
3289
  if (result.entries.length === 0 && !result.cancelled) failure = `no destination accepted the archive (${destinationIds.join(", ")})`;
3062
3290
  } catch (err) {
@@ -3105,6 +3333,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3105
3333
  locationIds: [...s.locationIds],
3106
3334
  retentionCount: s.retentionCount,
3107
3335
  ...s.dataSources !== void 0 ? { dataSources: [...s.dataSources] } : {},
3336
+ ...s.databases !== void 0 ? { databases: [...s.databases] } : {},
3108
3337
  ...s.lastAttemptAt !== void 0 ? { lastAttemptAt: s.lastAttemptAt } : {},
3109
3338
  ...s.lastSuccessAt !== void 0 ? { lastSuccessAt: s.lastSuccessAt } : {},
3110
3339
  ...s.lastError !== void 0 ? { lastError: s.lastError } : {},
@@ -3126,6 +3355,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3126
3355
  locationIds: [...input.locationIds],
3127
3356
  retentionCount: input.retentionCount,
3128
3357
  ...input.dataSources && input.dataSources.length > 0 ? { dataSources: [...input.dataSources] } : {},
3358
+ ...input.databases && input.databases.length > 0 ? { databases: [...input.databases] } : {},
3129
3359
  ...existing?.lastAttemptAt !== void 0 ? { lastAttemptAt: existing.lastAttemptAt } : {},
3130
3360
  ...existing?.lastSuccessAt !== void 0 ? { lastSuccessAt: existing.lastSuccessAt } : {},
3131
3361
  ...existing?.lastError !== void 0 ? { lastError: existing.lastError } : {}
@@ -3144,6 +3374,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
3144
3374
  locationIds: [...next.locationIds],
3145
3375
  retentionCount: next.retentionCount,
3146
3376
  ...next.dataSources !== void 0 ? { dataSources: [...next.dataSources] } : {},
3377
+ ...next.databases !== void 0 ? { databases: [...next.databases] } : {},
3147
3378
  ...next.lastAttemptAt !== void 0 ? { lastAttemptAt: next.lastAttemptAt } : {},
3148
3379
  ...next.lastSuccessAt !== void 0 ? { lastSuccessAt: next.lastSuccessAt } : {},
3149
3380
  ...next.lastError !== void 0 ? { lastError: next.lastError } : {},