@camstack/system 1.2.423 → 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 (97) 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/file-host-client.d.ts +59 -0
  51. package/dist/builtins/sqlite-storage/file-host-handlers.d.ts +23 -0
  52. package/dist/builtins/sqlite-storage/file-host-protocol.d.ts +151 -0
  53. package/dist/builtins/sqlite-storage/file-host-worker.d.ts +1 -0
  54. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  55. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/index.js +21 -20
  57. package/dist/builtins/sqlite-storage/index.mjs +2 -1
  58. package/dist/builtins/sqlite-storage/owned-sqlite-file.d.ts +136 -0
  59. package/dist/builtins/sqlite-storage/placement-errors.d.ts +60 -0
  60. package/dist/builtins/sqlite-storage/placement-router.d.ts +80 -0
  61. package/dist/builtins/sqlite-storage/retired-collections.d.ts +7 -0
  62. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +68 -8
  63. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +23 -26
  64. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  66. package/dist/builtins/sqlite-storage/vector-provider.d.ts +24 -0
  67. package/dist/builtins/sqlite-storage/wal-maintenance.d.ts +17 -0
  68. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +3 -1
  69. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +3 -1
  70. package/dist/builtins/system-backup/system-backup.service.d.ts +48 -15
  71. package/dist/builtins/system-config/system-config.addon.js +2 -2
  72. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  73. package/dist/builtins/winston-logging/index.js +1 -1
  74. package/dist/builtins/winston-logging/index.mjs +1 -1
  75. package/dist/{composition-sources-CgiAeG-2.js → composition-sources-197keifF.js} +1 -1
  76. package/dist/{composition-sources-BXDchWFn.mjs → composition-sources-DmzTSy26.mjs} +1 -1
  77. package/dist/{constants-1_7QtPNo.js → constants-B5Jd8Zch.js} +1 -1
  78. package/dist/{constants-C2dhkIuF.mjs → constants-CkNLi-XW.mjs} +1 -1
  79. package/dist/{dist-DimotS8e.mjs → dist-DMWeqeTF.mjs} +863 -750
  80. package/dist/{dist-VIIxVsBW.js → dist-DnALrQF-.js} +866 -747
  81. package/dist/file-host-worker.js +144 -0
  82. package/dist/file-host-worker.mjs +144 -0
  83. package/dist/index.js +9 -8
  84. package/dist/index.mjs +6 -5
  85. package/dist/{jwt-signing-key-C8nMdNYW.mjs → jwt-signing-key-CCHXAtrs.mjs} +1 -1
  86. package/dist/{jwt-signing-key-C8s9syVg.js → jwt-signing-key-DeqG7LD9.js} +1 -1
  87. package/dist/{restore-archive-DjppfrIB.js → restore-archive-C4UhQOsg.js} +156 -65
  88. package/dist/{restore-archive-bOMZwY0D.mjs → restore-archive-GevpOHw8.mjs} +151 -66
  89. package/dist/{retired-settings-keys-_muTvNyh.js → retired-settings-keys-0FOsnzIE.js} +1 -1
  90. package/dist/{retired-settings-keys-BYnjMBE-.mjs → retired-settings-keys-CdVFl7m2.mjs} +1 -1
  91. package/dist/sqlite-snapshot-worker.js +1 -1
  92. package/dist/sqlite-snapshot-worker.mjs +1 -1
  93. package/dist/wal-checkpoint-worker.js +1 -1
  94. package/dist/wal-checkpoint-worker.mjs +1 -1
  95. package/dist/wal-maintenance-BtfyfuT5.mjs +0 -0
  96. package/dist/wal-maintenance-DJV1rde2.js +0 -0
  97. package/package.json +1 -1
@@ -1,9 +1,141 @@
1
- import { Pn as storageOccupancyCapability, Y as backupCapability, Zr as toNodeId, cr as BaseAddon, ei as EventCategory, en as mayWriteToLocation, zr as nodePin } from "../../dist-DimotS8e.mjs";
2
- import { a as BackupSnapshotRefusedError, c as BackupKeyFormatError, d as generateBackupKeyPair, f as parseBackupPublicKey, i as BackupCancelledError, n as precheckRestoreKey, o as SystemBackupService, p as parseBackupSecretKey, r as prepareRestoreArchive, u as ENCRYPTED_BACKUP_SUFFIX } from "../../restore-archive-bOMZwY0D.mjs";
1
+ import { Br as nodePin, Fn as storageOccupancyCapability, Qr as toNodeId, X as backupCapability, lr as BaseAddon, ti as EventCategory, tn as mayWriteToLocation } from "../../dist-DMWeqeTF.mjs";
2
+ import { a as BackupSnapshotRefusedError, d as ENCRYPTED_BACKUP_SUFFIX, f as generateBackupKeyPair, i as BackupCancelledError, l as BackupKeyFormatError, m as parseBackupSecretKey, n as precheckRestoreKey, o as DEFAULT_BACKUP_DATABASES, p as parseBackupPublicKey, r as prepareRestoreArchive, s as SystemBackupService } from "../../restore-archive-GevpOHw8.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import * as fsp from "node:fs/promises";
5
5
  import * as path$1 from "node:path";
6
6
  import * as fs from "node:fs";
7
+ //#region src/builtins/backup-orchestrator/backup-databases.ts
8
+ /** Stable id of the seeded weekly schedule. */
9
+ var WEEKLY_SCHEDULE_ID = "weekly-events-history";
10
+ var WEEKLY_SCHEDULE_LABEL = "Weekly — events & history";
11
+ /** Sundays 04:00 — an hour after the nightly settings run. */
12
+ var WEEKLY_SCHEDULE_CRON = "0 4 * * 0";
13
+ /**
14
+ * Events and history ONLY: a run that does not select settings carries no
15
+ * data-root walk either, so the weekly archive is just the two snapshots.
16
+ */
17
+ var WEEKLY_SCHEDULE_DATABASES = ["events", "history"];
18
+ /** What a schedule carries: its own list, else the minimum (D382). */
19
+ function databasesOfSchedule(schedule) {
20
+ return schedule.databases !== void 0 && schedule.databases.length > 0 ? schedule.databases : DEFAULT_BACKUP_DATABASES;
21
+ }
22
+ /**
23
+ * True when a run asks for a database beyond settings and the owner has none
24
+ * of them open: the run would only repeat the nightly archive. A settings-only
25
+ * run is never nothing-to-carry.
26
+ */
27
+ function hasNothingBeyondSettings(requested, owned) {
28
+ const extra = requested.filter((id) => id !== "settings");
29
+ if (extra.length === 0) return false;
30
+ return !extra.some((id) => owned.includes(id));
31
+ }
32
+ /**
33
+ * The weekly schedule to seed, or null. Created only when NO schedule carries
34
+ * `events` (a seed never overwrites, D728 — an operator's own weekly schedule,
35
+ * or an edited seed, is left alone), and only when there is a nightly schedule
36
+ * to take the destinations from: a hub that backs up nowhere gets no weekly
37
+ * archive either.
38
+ */
39
+ function planWeeklySeed(existing) {
40
+ if (existing.some((s) => s.id === "weekly-events-history")) return null;
41
+ if (existing.some((s) => databasesOfSchedule(s).includes("events"))) return null;
42
+ const destinations = /* @__PURE__ */ new Set();
43
+ for (const s of existing) {
44
+ if (!s.enabled) continue;
45
+ for (const id of s.locationIds) destinations.add(id);
46
+ }
47
+ if (destinations.size === 0) return null;
48
+ return {
49
+ id: WEEKLY_SCHEDULE_ID,
50
+ label: WEEKLY_SCHEDULE_LABEL,
51
+ cron: WEEKLY_SCHEDULE_CRON,
52
+ enabled: true,
53
+ locationIds: [...destinations].toSorted(),
54
+ retentionCount: 4,
55
+ databases: WEEKLY_SCHEDULE_DATABASES
56
+ };
57
+ }
58
+ /**
59
+ * Seed the weekly schedule if {@link planWeeklySeed} says so; returns what it
60
+ * wrote. Exactly once ever: the marker is written after the row, and a marker
61
+ * that exists ends the seed for good — an operator who disabled or deleted the
62
+ * weekly is never second-guessed. A weekly row found with no marker (seeded
63
+ * before the marker existed) is marked, not re-created.
64
+ */
65
+ async function seedWeeklySchedule(store, markers) {
66
+ if (await markers.has("weekly-events-history")) return null;
67
+ const existing = await store.list();
68
+ if (existing.some((s) => s.id === "weekly-events-history")) {
69
+ await markers.mark(WEEKLY_SCHEDULE_ID);
70
+ return null;
71
+ }
72
+ const seed = planWeeklySeed(existing);
73
+ if (seed === null) return null;
74
+ await store.upsert(seed);
75
+ await markers.mark(WEEKLY_SCHEDULE_ID);
76
+ return seed;
77
+ }
78
+ //#endregion
79
+ //#region src/builtins/backup-orchestrator/seed-marker-store.ts
80
+ /**
81
+ * @durable class=config owner=backup-orchestrator
82
+ * write="the orchestrator inserts one row per seed id the first time that
83
+ * seed is created (or found already created); never updated"
84
+ * retention="none — one tiny row per seed ever made; deleting it lets the
85
+ * seed be re-created once more on the next boot"
86
+ */
87
+ var BACKUP_SEED_MARKERS_COLLECTION = "backup_seed_markers";
88
+ var COLUMNS$1 = [{
89
+ name: "seed_id",
90
+ type: "TEXT",
91
+ primaryKey: true
92
+ }, {
93
+ name: "seeded_at",
94
+ type: "INTEGER",
95
+ notNull: true
96
+ }];
97
+ var BackupSeedMarkerStore = class {
98
+ store;
99
+ now;
100
+ declared = false;
101
+ constructor(store, now = Date.now) {
102
+ this.store = store;
103
+ this.now = now;
104
+ }
105
+ async ensureDeclared() {
106
+ if (this.declared) return;
107
+ await this.store.declareCollection({
108
+ collection: BACKUP_SEED_MARKERS_COLLECTION,
109
+ columns: COLUMNS$1
110
+ });
111
+ this.declared = true;
112
+ }
113
+ async has(seedId) {
114
+ await this.ensureDeclared();
115
+ return (await this.store.query({
116
+ collection: BACKUP_SEED_MARKERS_COLLECTION,
117
+ filter: {
118
+ where: { seed_id: seedId },
119
+ limit: 1
120
+ }
121
+ })).length > 0;
122
+ }
123
+ async mark(seedId) {
124
+ await this.ensureDeclared();
125
+ if (await this.has(seedId)) return;
126
+ await this.store.insert({
127
+ collection: BACKUP_SEED_MARKERS_COLLECTION,
128
+ record: {
129
+ id: seedId,
130
+ data: {
131
+ seed_id: seedId,
132
+ seeded_at: this.now()
133
+ }
134
+ }
135
+ });
136
+ }
137
+ };
138
+ //#endregion
7
139
  //#region ../../node_modules/croner/dist/croner.js
8
140
  function T(s) {
9
141
  return Date.UTC(s.y, s.m - 1, s.d, s.h, s.i, s.s);
@@ -1269,7 +1401,8 @@ function normalizeManifest(raw) {
1269
1401
  sizeBytes: Number(cand["sizeBytes"]),
1270
1402
  ...typeof cand["label"] === "string" ? { label: cand["label"] } : {},
1271
1403
  manifest,
1272
- ...encryptionOf(cand["encryption"])
1404
+ ...encryptionOf(cand["encryption"]),
1405
+ ...databasesOf(cand["databases"])
1273
1406
  };
1274
1407
  archives.push(entry);
1275
1408
  }
@@ -1282,6 +1415,15 @@ var EMPTY_MANIFEST = {
1282
1415
  version: 1,
1283
1416
  archives: []
1284
1417
  };
1418
+ function databasesOf(raw) {
1419
+ if (!Array.isArray(raw)) return {};
1420
+ const known = [];
1421
+ const unknown = [];
1422
+ for (const item of raw) if (item === "settings" || item === "events" || item === "history") known.push(item);
1423
+ else if (typeof item === "string") unknown.push(item);
1424
+ if (known.length > 0) return { databases: known };
1425
+ return unknown.length > 0 ? { unrecognisedDatabases: unknown } : {};
1426
+ }
1285
1427
  function encryptionOf(raw) {
1286
1428
  if (typeof raw !== "object" || raw === null) return {};
1287
1429
  const scheme = Reflect.get(raw, "scheme");
@@ -1305,7 +1447,8 @@ function outlineOfManifest(manifest) {
1305
1447
  locations: [...manifest.locations],
1306
1448
  entries: [],
1307
1449
  totalBytes: manifest.totalBytes,
1308
- totalFiles: manifest.totalFiles
1450
+ totalFiles: manifest.totalFiles,
1451
+ ...manifest.omittedDatabases !== void 0 ? { omittedDatabases: [...manifest.omittedDatabases] } : {}
1309
1452
  };
1310
1453
  }
1311
1454
  /**
@@ -1321,9 +1464,15 @@ function buildArchiveEntry(input) {
1321
1464
  sizeBytes: input.sizeBytes,
1322
1465
  ...input.label !== void 0 ? { label: input.label } : {},
1323
1466
  manifest: input.encryption !== void 0 ? outlineOfManifest(input.manifest) : input.manifest,
1324
- ...input.encryption !== void 0 ? { encryption: input.encryption } : {}
1467
+ ...input.encryption !== void 0 ? { encryption: input.encryption } : {},
1468
+ ...input.databases !== void 0 ? { databases: [...input.databases] } : {}
1325
1469
  };
1326
1470
  }
1471
+ /** The key under which archives of one cadence prune each other. */
1472
+ function retentionClassOf(entry) {
1473
+ if (entry.databases === void 0 && entry.unrecognisedDatabases !== void 0) return `?${[...entry.unrecognisedDatabases].toSorted().join("+")}`;
1474
+ return [...entry.databases ?? DEFAULT_BACKUP_DATABASES].toSorted().join("+");
1475
+ }
1327
1476
  /**
1328
1477
  * Pure retention split: given an archive list and the operator's
1329
1478
  * `retentionCount`, return `{ keep, drop }` where `keep` is the most-
@@ -1332,20 +1481,27 @@ function buildArchiveEntry(input) {
1332
1481
  * `retentionCount <= 0` is treated as "no pruning" — defensive against
1333
1482
  * a config value that would otherwise wipe every archive on the next
1334
1483
  * write.
1484
+ *
1485
+ * `cadence` (see {@link retentionClassOf}) restricts the count to archives of
1486
+ * the run that was just written: nightly archives (settings) and weekly ones
1487
+ * (settings + events + history) are retained separately.
1335
1488
  */
1336
- function applyRetention(archives, retentionCount) {
1489
+ function applyRetention(archives, retentionCount, cadence) {
1337
1490
  if (retentionCount <= 0) return {
1338
1491
  keep: archives,
1339
1492
  drop: []
1340
1493
  };
1341
1494
  const sorted = [...archives].toSorted((a, b) => b.createdAt - a.createdAt);
1342
- if (sorted.length <= retentionCount) return {
1495
+ const inCadence = cadence === void 0 ? sorted : sorted.filter((a) => retentionClassOf(a) === cadence);
1496
+ if (inCadence.length <= retentionCount) return {
1343
1497
  keep: sorted,
1344
1498
  drop: []
1345
1499
  };
1500
+ const drop = inCadence.slice(retentionCount);
1501
+ const dropped = new Set(drop);
1346
1502
  return {
1347
- keep: sorted.slice(0, retentionCount),
1348
- drop: sorted.slice(retentionCount)
1503
+ keep: sorted.filter((a) => !dropped.has(a)),
1504
+ drop
1349
1505
  };
1350
1506
  }
1351
1507
  /**
@@ -1629,7 +1785,8 @@ function requestSignature(request) {
1629
1785
  destinations: [...request.destinations ?? []].toSorted(),
1630
1786
  locations: [...request.locations ?? []].toSorted(),
1631
1787
  label: request.label ?? null,
1632
- retentionCount: request.retentionCount ?? null
1788
+ retentionCount: request.retentionCount ?? null,
1789
+ databases: [...request.databases ?? []].toSorted()
1633
1790
  });
1634
1791
  }
1635
1792
  //#endregion
@@ -1688,6 +1845,10 @@ var BACKUP_SCHEDULES_COLUMNS = [
1688
1845
  name: "data_sources",
1689
1846
  type: "JSON"
1690
1847
  },
1848
+ {
1849
+ name: "databases",
1850
+ type: "JSON"
1851
+ },
1691
1852
  {
1692
1853
  name: "last_run_at",
1693
1854
  type: "INTEGER"
@@ -1777,6 +1938,7 @@ function mapScheduleToRow(s) {
1777
1938
  location_ids: [...s.locationIds],
1778
1939
  retention_count: s.retentionCount,
1779
1940
  data_sources: s.dataSources ? [...s.dataSources] : null,
1941
+ databases: s.databases ? [...s.databases] : null,
1780
1942
  last_run_at: s.lastAttemptAt ?? null,
1781
1943
  last_success_at: s.lastSuccessAt ?? null,
1782
1944
  last_error: s.lastError ?? null
@@ -1793,6 +1955,12 @@ function parseStringArray(raw) {
1793
1955
  if (!Array.isArray(value)) return [];
1794
1956
  return value.filter((v) => typeof v === "string");
1795
1957
  }
1958
+ /** Known database ids only: an unknown name in a stored row is dropped, not carried. */
1959
+ function parseDatabaseIds(raw) {
1960
+ const out = [];
1961
+ for (const id of parseStringArray(raw)) if (id === "settings" || id === "events" || id === "history") out.push(id);
1962
+ return out;
1963
+ }
1796
1964
  function tryParseJson(raw) {
1797
1965
  if (raw.length === 0) return null;
1798
1966
  try {
@@ -1806,6 +1974,8 @@ function mapRowToSchedule(row) {
1806
1974
  if (!id) return null;
1807
1975
  const dataSourcesRaw = row["data_sources"];
1808
1976
  const dataSources = dataSourcesRaw === null || dataSourcesRaw === void 0 ? void 0 : parseStringArray(dataSourcesRaw);
1977
+ const databasesRaw = row["databases"];
1978
+ const databases = databasesRaw === null || databasesRaw === void 0 ? void 0 : parseDatabaseIds(databasesRaw);
1809
1979
  const lastRunRaw = row["last_run_at"];
1810
1980
  const lastAttemptAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
1811
1981
  const lastSuccessRaw = row["last_success_at"];
@@ -1820,6 +1990,7 @@ function mapRowToSchedule(row) {
1820
1990
  locationIds: parseStringArray(row["location_ids"]),
1821
1991
  retentionCount: Number(row["retention_count"] ?? 7),
1822
1992
  ...dataSources !== void 0 ? { dataSources } : {},
1993
+ ...databases !== void 0 && databases.length > 0 ? { databases } : {},
1823
1994
  ...lastAttemptAt !== void 0 ? { lastAttemptAt } : {},
1824
1995
  ...lastSuccessAt !== void 0 ? { lastSuccessAt } : {},
1825
1996
  ...lastError !== void 0 ? { lastError } : {}
@@ -2072,6 +2243,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
2072
2243
  this.ctx.logger.error("backup-orchestrator: schedule table init failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
2073
2244
  this.schedules = null;
2074
2245
  }
2246
+ await this.seedWeekly();
2075
2247
  const settingsStore = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
2076
2248
  this.encryptionKeys = settingsStore ? new BackupEncryptionKeyStore(settingsStore) : null;
2077
2249
  const provider = {
@@ -2377,7 +2549,8 @@ var BackupOrchestratorAddon = class extends BaseAddon {
2377
2549
  ...input?.destinations ? { destinations: input.destinations } : {},
2378
2550
  ...input?.locations ? { locations: input.locations } : {},
2379
2551
  ...input?.label !== void 0 ? { label: input.label } : {},
2380
- ...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {}
2552
+ ...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {},
2553
+ ...input?.databases ? { databases: input.databases } : {}
2381
2554
  };
2382
2555
  const submitted = this.runQueue.submit(request);
2383
2556
  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 } });
@@ -2451,6 +2624,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
2451
2624
  signal,
2452
2625
  onArchiveBytes: (bytes) => progress.setStagedBytes(bytes),
2453
2626
  owner: this.databaseOwner(),
2627
+ ...input.databases ? { databases: input.databases } : {},
2454
2628
  ...encryptTo !== null ? { encryptTo: encryptTo.publicKey } : {}
2455
2629
  });
2456
2630
  } catch (err) {
@@ -2483,6 +2657,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
2483
2657
  sizeBytes: result.sizeBytes,
2484
2658
  archiveManifest: result.manifest,
2485
2659
  retentionCount: input.retentionCount ?? policy.retentionCount,
2660
+ databases: input.databases ?? DEFAULT_BACKUP_DATABASES,
2486
2661
  createdAt,
2487
2662
  label: formatArchiveLabel(dest.id, createdAt, input.label),
2488
2663
  ...result.encryption !== void 0 ? { encryption: result.encryption } : {},
@@ -2554,9 +2729,9 @@ var BackupOrchestratorAddon = class extends BaseAddon {
2554
2729
  };
2555
2730
  return {
2556
2731
  listDatabases: async () => {
2557
- return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases.map((d) => d.livePath);
2732
+ return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases;
2558
2733
  },
2559
- writeSnapshot: async () => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({}, pin())
2734
+ writeSnapshot: async (databaseId) => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({ databaseId }, pin())
2560
2735
  };
2561
2736
  }
2562
2737
  /**
@@ -2608,10 +2783,11 @@ var BackupOrchestratorAddon = class extends BaseAddon {
2608
2783
  sizeBytes: input.sizeBytes,
2609
2784
  label: input.label,
2610
2785
  manifest: input.archiveManifest,
2611
- ...input.encryption !== void 0 ? { encryption: input.encryption } : {}
2786
+ ...input.encryption !== void 0 ? { encryption: input.encryption } : {},
2787
+ databases: input.databases
2612
2788
  });
2613
2789
  await this.manifestLock.run(input.locationId, async () => {
2614
- const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount);
2790
+ const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount, retentionClassOf(entry));
2615
2791
  for (const stale of drop) try {
2616
2792
  await this.ctx.api.storage.delete.mutate({
2617
2793
  location: input.locationId,
@@ -2665,7 +2841,8 @@ var BackupOrchestratorAddon = class extends BaseAddon {
2665
2841
  sizeBytes: a.sizeBytes,
2666
2842
  ...a.label !== void 0 ? { label: a.label } : {},
2667
2843
  locations: [...a.manifest.locations],
2668
- ...a.encryption !== void 0 ? { encryption: a.encryption } : {}
2844
+ ...a.encryption !== void 0 ? { encryption: a.encryption } : {},
2845
+ ...a.databases !== void 0 ? { databases: [...a.databases] } : {}
2669
2846
  }));
2670
2847
  }
2671
2848
  /**
@@ -3032,6 +3209,55 @@ var BackupOrchestratorAddon = class extends BaseAddon {
3032
3209
  }
3033
3210
  }
3034
3211
  /**
3212
+ * A schedule that asks for events/history while the owner has neither open
3213
+ * would only repeat the nightly archive (D852: the weekly schedule has
3214
+ * nothing to carry until those files exist). Said on the record, no archive
3215
+ * built, no outcome stamped. An owner that cannot answer never skips: the
3216
+ * run goes ahead and fails — or succeeds — in the open.
3217
+ */
3218
+ async nothingToCarry(schedule) {
3219
+ const requested = databasesOfSchedule(schedule);
3220
+ if (!requested.some((id) => id !== "settings")) return false;
3221
+ let owned;
3222
+ try {
3223
+ owned = (await this.databaseOwner().listDatabases()).map((d) => d.databaseId);
3224
+ } catch (err) {
3225
+ this.ctx.logger.warn("Scheduled backup: the database owner did not answer — running anyway", { meta: {
3226
+ scheduleId: schedule.id,
3227
+ label: schedule.label,
3228
+ error: err instanceof Error ? err.message : String(err)
3229
+ } });
3230
+ return false;
3231
+ }
3232
+ if (!hasNothingBeyondSettings(requested, owned)) return false;
3233
+ this.ctx.logger.info("Scheduled backup skipped — no database it carries exists yet", { meta: {
3234
+ scheduleId: schedule.id,
3235
+ label: schedule.label,
3236
+ requested: [...requested],
3237
+ owned
3238
+ } });
3239
+ return true;
3240
+ }
3241
+ /**
3242
+ * Seed the weekly "events & history" schedule (D852) — once, and never over
3243
+ * anything the operator has: see `planWeeklySeed`.
3244
+ */
3245
+ async seedWeekly() {
3246
+ if (!this.schedules) return;
3247
+ try {
3248
+ const store = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
3249
+ if (!store) return;
3250
+ const seed = await seedWeeklySchedule(this.schedules, new BackupSeedMarkerStore(store));
3251
+ if (seed === null) return;
3252
+ this.ctx.logger.info("backup: seeded the weekly events & history schedule", { meta: {
3253
+ scheduleId: seed.id,
3254
+ destinationIds: [...seed.locationIds]
3255
+ } });
3256
+ } catch (err) {
3257
+ this.ctx.logger.warn("backup: the weekly schedule could not be seeded", { meta: { error: err instanceof Error ? err.message : String(err) } });
3258
+ }
3259
+ }
3260
+ /**
3035
3261
  * Fire one schedule and RECORD what happened to it, naming every
3036
3262
  * destination. Three outcomes, three different persisted states —
3037
3263
  * because "fired" and "backed up" were the same field until D384:
@@ -3042,13 +3268,15 @@ var BackupOrchestratorAddon = class extends BaseAddon {
3042
3268
  */
3043
3269
  async runScheduledBackup(schedule, firedAt) {
3044
3270
  const destinationIds = [...schedule.locationIds];
3271
+ if (await this.nothingToCarry(schedule)) return;
3045
3272
  let failure;
3046
3273
  try {
3047
3274
  const result = await this.triggerBackup({
3048
3275
  destinations: destinationIds,
3049
3276
  ...schedule.dataSources && schedule.dataSources.length > 0 ? { locations: [...schedule.dataSources] } : {},
3050
3277
  label: schedule.label,
3051
- retentionCount: schedule.retentionCount
3278
+ retentionCount: schedule.retentionCount,
3279
+ ...schedule.databases ? { databases: [...schedule.databases] } : {}
3052
3280
  });
3053
3281
  if (result.entries.length === 0 && !result.cancelled) failure = `no destination accepted the archive (${destinationIds.join(", ")})`;
3054
3282
  } catch (err) {
@@ -3097,6 +3325,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
3097
3325
  locationIds: [...s.locationIds],
3098
3326
  retentionCount: s.retentionCount,
3099
3327
  ...s.dataSources !== void 0 ? { dataSources: [...s.dataSources] } : {},
3328
+ ...s.databases !== void 0 ? { databases: [...s.databases] } : {},
3100
3329
  ...s.lastAttemptAt !== void 0 ? { lastAttemptAt: s.lastAttemptAt } : {},
3101
3330
  ...s.lastSuccessAt !== void 0 ? { lastSuccessAt: s.lastSuccessAt } : {},
3102
3331
  ...s.lastError !== void 0 ? { lastError: s.lastError } : {},
@@ -3118,6 +3347,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
3118
3347
  locationIds: [...input.locationIds],
3119
3348
  retentionCount: input.retentionCount,
3120
3349
  ...input.dataSources && input.dataSources.length > 0 ? { dataSources: [...input.dataSources] } : {},
3350
+ ...input.databases && input.databases.length > 0 ? { databases: [...input.databases] } : {},
3121
3351
  ...existing?.lastAttemptAt !== void 0 ? { lastAttemptAt: existing.lastAttemptAt } : {},
3122
3352
  ...existing?.lastSuccessAt !== void 0 ? { lastSuccessAt: existing.lastSuccessAt } : {},
3123
3353
  ...existing?.lastError !== void 0 ? { lastError: existing.lastError } : {}
@@ -3136,6 +3366,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
3136
3366
  locationIds: [...next.locationIds],
3137
3367
  retentionCount: next.retentionCount,
3138
3368
  ...next.dataSources !== void 0 ? { dataSources: [...next.dataSources] } : {},
3369
+ ...next.databases !== void 0 ? { databases: [...next.databases] } : {},
3139
3370
  ...next.lastAttemptAt !== void 0 ? { lastAttemptAt: next.lastAttemptAt } : {},
3140
3371
  ...next.lastSuccessAt !== void 0 ? { lastSuccessAt: next.lastSuccessAt } : {},
3141
3372
  ...next.lastError !== void 0 ? { lastError: next.lastError } : {},
@@ -17,8 +17,8 @@
17
17
  * snapshot of the embedded `ArchiveManifest` so `getEntries` can
18
18
  * serve the contents preview without re-reading the tarball.
19
19
  */
20
- import type { AddonApi } from "@camstack/types";
21
- import type { ArchiveManifest } from "../system-backup/system-backup.service.js";
20
+ import type { AddonApi, DataStoreDatabase } from "@camstack/types";
21
+ import { type ArchiveManifest } from "../system-backup/system-backup.service.js";
22
22
  /** File path (relative to the location root) where the index lives. */
23
23
  export declare const MANIFESTS_FILENAME = "manifests.json";
24
24
  export interface ManifestArchiveEntry {
@@ -37,6 +37,19 @@ export interface ManifestArchiveEntry {
37
37
  readonly manifest: ArchiveManifest;
38
38
  /** Present when the archive is encrypted to a backup key. */
39
39
  readonly encryption?: ManifestArchiveEncryption;
40
+ /**
41
+ * The databases the run ASKED to carry — its retention class: archives of one
42
+ * cadence prune each other, never another cadence's (a weekly archive that
43
+ * holds the events ledgers must not be evicted by seven nightly ones).
44
+ * Absent on an entry written before several files were owned: `['settings']`.
45
+ */
46
+ readonly databases?: readonly DataStoreDatabase[];
47
+ /**
48
+ * Ids this build does not know, kept ONLY when no known id remains: such an
49
+ * entry is its own retention class and is never counted or pruned by another
50
+ * cadence (a newer build's database must not collapse to `settings`).
51
+ */
52
+ readonly unrecognisedDatabases?: readonly string[];
40
53
  }
41
54
  export interface ManifestArchiveEncryption {
42
55
  readonly scheme: string;
@@ -87,7 +100,10 @@ export declare function buildArchiveEntry(input: {
87
100
  label?: string;
88
101
  manifest: ArchiveManifest;
89
102
  encryption?: ManifestArchiveEncryption;
103
+ databases?: readonly DataStoreDatabase[];
90
104
  }): ManifestArchiveEntry;
105
+ /** The key under which archives of one cadence prune each other. */
106
+ export declare function retentionClassOf(entry: Pick<ManifestArchiveEntry, "databases" | "unrecognisedDatabases">): string;
91
107
  /**
92
108
  * Pure retention split: given an archive list and the operator's
93
109
  * `retentionCount`, return `{ keep, drop }` where `keep` is the most-
@@ -96,8 +112,12 @@ export declare function buildArchiveEntry(input: {
96
112
  * `retentionCount <= 0` is treated as "no pruning" — defensive against
97
113
  * a config value that would otherwise wipe every archive on the next
98
114
  * write.
115
+ *
116
+ * `cadence` (see {@link retentionClassOf}) restricts the count to archives of
117
+ * the run that was just written: nightly archives (settings) and weekly ones
118
+ * (settings + events + history) are retained separately.
99
119
  */
100
- export declare function applyRetention(archives: readonly ManifestArchiveEntry[], retentionCount: number): {
120
+ export declare function applyRetention(archives: readonly ManifestArchiveEntry[], retentionCount: number, cadence?: string): {
101
121
  readonly keep: readonly ManifestArchiveEntry[];
102
122
  readonly drop: readonly ManifestArchiveEntry[];
103
123
  };
@@ -1,3 +1,4 @@
1
+ import type { DataStoreDatabase } from "@camstack/types";
1
2
  export type BackupRunState = "queued" | "running" | "succeeded" | "failed" | "cancelled";
2
3
  export type BackupRunPhase = "queued" | "building" | "uploading" | "done";
3
4
  /** What a trigger asks for — the queue keys joining on this. */
@@ -6,6 +7,8 @@ export interface BackupRunRequest {
6
7
  readonly locations?: readonly string[];
7
8
  readonly label?: string;
8
9
  readonly retentionCount?: number;
10
+ /** Database files to carry; absent = settings only. */
11
+ readonly databases?: readonly DataStoreDatabase[];
9
12
  }
10
13
  /** Immutable snapshot of one run — the wire shape of `backup.listRuns`. */
11
14
  export interface BackupRunSnapshot {
@@ -14,7 +14,7 @@
14
14
  * the surface serialises and parses them, so this file passes and
15
15
  * receives real arrays.
16
16
  */
17
- import type { CollectionColumn, ISettingsStoreProvider } from "@camstack/types";
17
+ import type { CollectionColumn, DataStoreDatabase, ISettingsStoreProvider } from "@camstack/types";
18
18
  /**
19
19
  * A backup schedule row.
20
20
  *
@@ -32,6 +32,12 @@ export interface BackupSchedule {
32
32
  readonly retentionCount: number;
33
33
  readonly dataSources?: readonly string[];
34
34
  /**
35
+ * Which database files this schedule carries. Absent (a `NULL` column) =
36
+ * `['settings']`: an unstated ask is the minimum (D382). Settings runs
37
+ * nightly, events and history weekly (D852).
38
+ */
39
+ readonly databases?: readonly DataStoreDatabase[];
40
+ /**
35
41
  * ms-epoch of the last tick that FIRED this schedule. Stamped BEFORE the
36
42
  * archive runs — it is the dedupe anchor and the cron's "after" cursor —
37
43
  * so it means "attempted", never "succeeded". It was documented as the
@@ -0,0 +1,34 @@
1
+ /**
2
+ * "This seed has run, once, ever" (D728, D852).
3
+ *
4
+ * A seed that decides from the data alone re-creates what the operator deleted
5
+ * on the next boot. The marker is the orchestrator's own row in the settings
6
+ * store it already owns (D62: no new authority), one per seed id, written
7
+ * AFTER the seeded row landed. A read that fails THROWS: "could not read the
8
+ * marker" must never look like "never seeded".
9
+ */
10
+ import type { ISettingsStoreProvider } from "@camstack/types";
11
+ /**
12
+ * @durable class=config owner=backup-orchestrator
13
+ * write="the orchestrator inserts one row per seed id the first time that
14
+ * seed is created (or found already created); never updated"
15
+ * retention="none — one tiny row per seed ever made; deleting it lets the
16
+ * seed be re-created once more on the next boot"
17
+ */
18
+ export declare const BACKUP_SEED_MARKERS_COLLECTION = "backup_seed_markers";
19
+ type Store = Pick<ISettingsStoreProvider, "declareCollection" | "query" | "insert">;
20
+ /** The slice of the marker store a seed needs. */
21
+ export interface SeedMarkers {
22
+ has(seedId: string): Promise<boolean>;
23
+ mark(seedId: string): Promise<void>;
24
+ }
25
+ export declare class BackupSeedMarkerStore implements SeedMarkers {
26
+ private readonly store;
27
+ private readonly now;
28
+ private declared;
29
+ constructor(store: Store, now?: () => number);
30
+ private ensureDeclared;
31
+ has(seedId: string): Promise<boolean>;
32
+ mark(seedId: string): Promise<void>;
33
+ }
34
+ export {};
@@ -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-VIIxVsBW.js");
6
+ const require_dist = require("../../dist-DnALrQF-.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let zod = require("zod");
9
9
  //#region src/builtins/calendars/calendar-color.ts
@@ -1,4 +1,4 @@
1
- import { cr as BaseAddon, et as calendarsCapability, p as CalendarPreferencesSchema } from "../../dist-DimotS8e.mjs";
1
+ import { lr as BaseAddon, p as CalendarPreferencesSchema, tt as calendarsCapability } from "../../dist-DMWeqeTF.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/builtins/calendars/calendar-color.ts
@@ -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-VIIxVsBW.js");
6
+ const require_dist = require("../../dist-DnALrQF-.js");
7
7
  let zod = require("zod");
8
8
  let _camstack_types_node = require("@camstack/types/node");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { In as streamCatalogCapability, N as MOTION_CLOSE_AFTER_MS, b as DeclaredDevices, cr as BaseAddon, ei as EventCategory, i as BaseDevice, in as motionCapability, ir as DeviceFeature, jn as snapshotCapability, jr as hydrateSchema, mt as declarationOwnerNodeId, or as DeviceType, qn as buildFfmpegArgs, sr as errMsg, tn as memoizePinnedFfmpeg, tt as cameraGridLayoutCapability, wr as createEvent, y as DERIVED_CAMERA_SILENCED_CAP_NAMES } from "../../dist-DimotS8e.mjs";
1
+ import { Jn as buildFfmpegArgs, Ln as streamCatalogCapability, Mn as snapshotCapability, Mr as hydrateSchema, P as MOTION_CLOSE_AFTER_MS, Tr as createEvent, an as motionCapability, ar as DeviceFeature, b as DERIVED_CAMERA_SILENCED_CAP_NAMES, cr as errMsg, ht as declarationOwnerNodeId, i as BaseDevice, lr as BaseAddon, nn as memoizePinnedFfmpeg, nt as cameraGridLayoutCapability, sr as DeviceType, ti as EventCategory, x as DeclaredDevices } from "../../dist-DMWeqeTF.mjs";
2
2
  import { z } from "zod";
3
3
  import { FfmpegProcess } from "@camstack/types/node";
4
4
  import { join } from "node:path";
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-VIIxVsBW.js");
7
- const require_composition_sources = require("../../composition-sources-CgiAeG-2.js");
6
+ const require_dist = require("../../dist-DnALrQF-.js");
7
+ const require_composition_sources = require("../../composition-sources-197keifF.js");
8
8
  let zod = require("zod");
9
9
  //#region src/builtins/shared/device-state-changed.ts
10
10
  /** Structural narrowing helper for untrusted bus payloads. */
@@ -1,5 +1,5 @@
1
- import { $n as compositionSliceKey, Et as evaluateCompositionField, Jn as cloneStatefulExpressionMemory, Kn as COMPOSER_ADDON_ID, Q as buildCapabilityLookup, Qn as compositionItemFieldPath, Xn as composedDeviceStableId, Yn as composedDeviceRef, Zn as compositionFeatureEnabled, b as DeclaredDevices, cr as BaseAddon, dn as planComposition, ei as EventCategory, er as compositionSourceKey, i as BaseDevice, it as compositionFieldKey, kt as expressionContainsBuiltinCall, l as COMPOSED_ITEM_FIXED_FIELDS, nr as disarmRoseMemory, or as DeviceType, rr as toExpressionValue, rt as composerCapability, sr as errMsg, t as ALL_CAPABILITY_DEFINITIONS, tr as createStatefulExpressionMemory, wr as createEvent } from "../../dist-DimotS8e.mjs";
2
- import { n as normalizedNativeOwner } from "../../composition-sources-BXDchWFn.mjs";
1
+ import { $ as buildCapabilityLookup, $n as compositionItemFieldPath, At as expressionContainsBuiltinCall, Dt as evaluateCompositionField, Qn as compositionFeatureEnabled, Tr as createEvent, Xn as composedDeviceRef, Yn as cloneStatefulExpressionMemory, Zn as composedDeviceStableId, at as compositionFieldKey, cr as errMsg, er as compositionSliceKey, fn as planComposition, i as BaseDevice, ir as toExpressionValue, it as composerCapability, l as COMPOSED_ITEM_FIXED_FIELDS, lr as BaseAddon, nr as createStatefulExpressionMemory, qn as COMPOSER_ADDON_ID, rr as disarmRoseMemory, sr as DeviceType, t as ALL_CAPABILITY_DEFINITIONS, ti as EventCategory, tr as compositionSourceKey, x as DeclaredDevices } from "../../dist-DMWeqeTF.mjs";
2
+ import { n as normalizedNativeOwner } from "../../composition-sources-DmzTSy26.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/builtins/shared/device-state-changed.ts
5
5
  /** Structural narrowing helper for untrusted bus payloads. */
@@ -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-VIIxVsBW.js");
6
+ const require_dist = require("../../dist-DnALrQF-.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { Xt as logDestinationCapability, cr as BaseAddon } from "../../dist-DimotS8e.mjs";
1
+ import { Zt as logDestinationCapability, lr as BaseAddon } from "../../dist-DMWeqeTF.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {