@camstack/system 1.2.21 → 1.2.23

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 (61) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +17 -13
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1319 -1097
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1319 -1097
  12. package/dist/builtins/backup-orchestrator/schedule-store.d.ts +33 -0
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  23. package/dist/builtins/local-network/local-network.addon.d.ts +16 -0
  24. package/dist/builtins/local-network/local-network.addon.js +30 -2
  25. package/dist/builtins/local-network/local-network.addon.mjs +30 -2
  26. package/dist/builtins/loki-logging/index.js +34 -12
  27. package/dist/builtins/loki-logging/index.mjs +34 -12
  28. package/dist/builtins/loki-logging/loki-logging.addon.d.ts +19 -0
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +1 -1
  36. package/dist/builtins/snapshot/index.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +7 -0
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +112 -6
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +112 -6
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +17 -1
  45. package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +5 -0
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-DmQ6thmF.mjs → dist-CIek6F9X.mjs} +536 -76
  51. package/dist/{dist-OQ1yU92D.js → dist-CS0pdC8l.js} +536 -76
  52. package/dist/index.js +58 -8
  53. package/dist/index.mjs +58 -8
  54. package/dist/kernel/capability-registry.d.ts +9 -0
  55. package/dist/kernel/transport/local-child-registry.d.ts +18 -0
  56. package/dist/kernel/transport/parent-unowned-call.d.ts +22 -0
  57. package/dist/logging/log-manager.d.ts +32 -1
  58. package/dist/logging/partitioned-log-buffer.d.ts +15 -3
  59. package/dist/{manifest-python-deps-B30qVAuW.mjs → manifest-python-deps-57PA4rHm.mjs} +15 -6
  60. package/dist/{manifest-python-deps-CnRW2Utv.js → manifest-python-deps-CqDPWqEE.js} +14 -5
  61. package/package.json +3 -2
@@ -0,0 +1,33 @@
1
+ import { ISettingsBackend, TableSchema } from '@camstack/types';
2
+ /**
3
+ * A backup schedule row.
4
+ *
5
+ * `locationIds` is the fan-out set (destination `backups` locations).
6
+ * `retentionCount` applies to every one of them for THIS schedule's
7
+ * runs. `dataSources` optionally narrows which top-level state
8
+ * locations get archived; empty = the orchestrator's full default set.
9
+ */
10
+ export interface BackupSchedule {
11
+ readonly id: string;
12
+ readonly label: string;
13
+ readonly cron: string;
14
+ readonly enabled: boolean;
15
+ readonly locationIds: readonly string[];
16
+ readonly retentionCount: number;
17
+ readonly dataSources?: readonly string[];
18
+ /** ms-epoch of the last successful run. */
19
+ readonly lastRunAt?: number;
20
+ }
21
+ export declare const BACKUP_SCHEDULES_SCHEMA: TableSchema;
22
+ export declare class BackupScheduleService {
23
+ private readonly backend;
24
+ private tableEnsured;
25
+ constructor(backend: ISettingsBackend);
26
+ initialize(): Promise<void>;
27
+ private ensureTable;
28
+ list(): Promise<readonly BackupSchedule[]>;
29
+ get(id: string): Promise<BackupSchedule | null>;
30
+ /** Replace-or-insert (delete + insert — mirrors the policy store). */
31
+ upsert(s: BackupSchedule): Promise<void>;
32
+ delete(id: string): Promise<void>;
33
+ }
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.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 { at as BaseAddon, z as logDestinationCapability } from "../../dist-DmQ6thmF.mjs";
1
+ import { at as BaseAddon, z as logDestinationCapability } from "../../dist-CIek6F9X.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 = {
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  let _camstack_types_node = require("@camstack/types/node");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { At as sleep, C as deviceStatusCapability, Ct as isDeviceConfigCap, D as enumerateSchemaFields, E as enumerateItemArrayFields, G as parseStreamParamsFormPatch, O as evaluateLinkExpression, S as deviceStateCapability, U as normalizeUnit, Y as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as BaseAddon, b as buildStreamParamsConfigSchema, dt as DeviceType, it as errMsg, j as getByPath, jt as EventCategory, l as STREAM_PROFILE_META, lt as DeviceFeature, mt as WELL_KNOWN_TAB_MAP, o as DeviceStatusSchema, rt as validateExpressionSource, t as ALL_CAPABILITY_DEFINITIONS, tt as toExpressionValue, ut as DeviceRole, x as deviceManagerCapability } from "../../dist-DmQ6thmF.mjs";
1
+ import { At as sleep, C as deviceStatusCapability, Ct as isDeviceConfigCap, D as enumerateSchemaFields, E as enumerateItemArrayFields, G as parseStreamParamsFormPatch, O as evaluateLinkExpression, S as deviceStateCapability, U as normalizeUnit, Y as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as BaseAddon, b as buildStreamParamsConfigSchema, dt as DeviceType, it as errMsg, j as getByPath, jt as EventCategory, l as STREAM_PROFILE_META, lt as DeviceFeature, mt as WELL_KNOWN_TAB_MAP, o as DeviceStatusSchema, rt as validateExpressionSource, t as ALL_CAPABILITY_DEFINITIONS, tt as toExpressionValue, ut as DeviceRole, x as deviceManagerCapability } from "../../dist-CIek6F9X.mjs";
2
2
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  //#region src/builtins/doorbell/trigger-engine.ts
8
8
  /**
9
9
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -1,4 +1,4 @@
1
- import { at as BaseAddon, dt as DeviceType, it as errMsg, jt as EventCategory, vt as createEvent, w as doorbellCapability } from "../../dist-DmQ6thmF.mjs";
1
+ import { at as BaseAddon, dt as DeviceType, it as errMsg, jt as EventCategory, vt as createEvent, w as doorbellCapability } from "../../dist-CIek6F9X.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { at as BaseAddon, z as logDestinationCapability } from "../../dist-DmQ6thmF.mjs";
1
+ import { at as BaseAddon, z as logDestinationCapability } from "../../dist-CIek6F9X.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,5 +1,5 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { at as BaseAddon, f as UserRecordSchema, nt as userManagementCapability, r as ApiKeyRecordSchema, u as ScopedTokenSchema, v as authProviderCapability } from "../../dist-DmQ6thmF.mjs";
2
+ import { at as BaseAddon, f as UserRecordSchema, nt as userManagementCapability, r as ApiKeyRecordSchema, u as ScopedTokenSchema, v as authProviderCapability } from "../../dist-CIek6F9X.mjs";
3
3
  import * as crypto$1 from "node:crypto";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import nodeCrypto from "crypto";
@@ -20,6 +20,14 @@ interface LocalNetworkConfig {
20
20
  * with LAN/Wi-Fi addresses) from "operator explicitly cleared the
21
21
  * allowlist" (true + empty → respect operator's choice). */
22
22
  readonly bootSeeded: boolean;
23
+ /**
24
+ * The endpoint outbound artifacts (notification attachment links, deep
25
+ * links) must be addressed by. Empty = AUTO: the first public candidate,
26
+ * never loopback. Stored VERBATIM rather than as an index into the candidate
27
+ * list — interfaces come and go, and an index would silently re-point at a
28
+ * different address after a reboot.
29
+ */
30
+ readonly notificationBaseUrl: string;
23
31
  }
24
32
  export declare class LocalNetworkAddon extends BaseAddon<LocalNetworkConfig> {
25
33
  private pollTimer;
@@ -35,6 +43,14 @@ export declare class LocalNetworkAddon extends BaseAddon<LocalNetworkConfig> {
35
43
  */
36
44
  private meshEndpoint;
37
45
  constructor();
46
+ /**
47
+ * What AUTO currently resolves to for outbound artifact links: the first
48
+ * PUBLIC candidate, never loopback and never a LAN address — a link is
49
+ * opened on someone's phone, off this network, possibly hours later.
50
+ * `null` when nothing public is reachable (the caller then ships bytes only
51
+ * rather than a link that 404s).
52
+ */
53
+ private resolveNotificationEndpoint;
38
54
  protected onInitialize(): Promise<ProviderRegistration[]>;
39
55
  protected onShutdown(): Promise<void>;
40
56
  /**
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-OQ1yU92D.js");
3
+ const require_dist = require("../../dist-CS0pdC8l.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -44,9 +44,25 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
44
44
  constructor() {
45
45
  super({
46
46
  allowedAddresses: [],
47
- bootSeeded: false
47
+ bootSeeded: false,
48
+ notificationBaseUrl: ""
48
49
  });
49
50
  }
51
+ /**
52
+ * What AUTO currently resolves to for outbound artifact links: the first
53
+ * PUBLIC candidate, never loopback and never a LAN address — a link is
54
+ * opened on someone's phone, off this network, possibly hours later.
55
+ * `null` when nothing public is reachable (the caller then ships bytes only
56
+ * rather than a link that 404s).
57
+ */
58
+ resolveNotificationEndpoint() {
59
+ const explicit = this.config.notificationBaseUrl;
60
+ if (explicit !== "") return explicit;
61
+ if (this.publicHostname !== "") return `https://${this.publicHostname}`;
62
+ const mesh = this.meshEndpoint;
63
+ if (mesh !== null && mesh.host !== "") return `https://${mesh.host}:${mesh.port}`;
64
+ return null;
65
+ }
50
66
  async onInitialize() {
51
67
  if (!this.config.bootSeeded) {
52
68
  const seed = autoSeedAllowlist(this.enumerate());
@@ -71,6 +87,18 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
71
87
  await this.reconcileMeshEndpoint();
72
88
  return { endpoints: buildEndpoints(interfaces, input.port, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint) };
73
89
  },
90
+ getNotificationEndpoint: async () => ({
91
+ baseUrl: this.config.notificationBaseUrl === "" ? null : this.config.notificationBaseUrl,
92
+ resolved: this.resolveNotificationEndpoint()
93
+ }),
94
+ setNotificationEndpoint: async ({ baseUrl }) => {
95
+ await this.updateGlobalSettings({ notificationBaseUrl: baseUrl ?? "" });
96
+ this.ctx.logger.info("local-network: notification endpoint set", { meta: { baseUrl: baseUrl ?? "(auto)" } });
97
+ return {
98
+ baseUrl: baseUrl === "" ? null : baseUrl,
99
+ resolved: this.resolveNotificationEndpoint()
100
+ };
101
+ },
74
102
  getAllowedAddresses: async () => ({ addresses: this.config.allowedAddresses }),
75
103
  resetAllowlistToBestMatch: async () => {
76
104
  const seed = autoSeedAllowlist(this.enumerate());
@@ -1,4 +1,4 @@
1
- import { R as localNetworkCapability, at as BaseAddon, jt as EventCategory } from "../../dist-DmQ6thmF.mjs";
1
+ import { R as localNetworkCapability, at as BaseAddon, jt as EventCategory } from "../../dist-CIek6F9X.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -41,9 +41,25 @@ var LocalNetworkAddon = class extends BaseAddon {
41
41
  constructor() {
42
42
  super({
43
43
  allowedAddresses: [],
44
- bootSeeded: false
44
+ bootSeeded: false,
45
+ notificationBaseUrl: ""
45
46
  });
46
47
  }
48
+ /**
49
+ * What AUTO currently resolves to for outbound artifact links: the first
50
+ * PUBLIC candidate, never loopback and never a LAN address — a link is
51
+ * opened on someone's phone, off this network, possibly hours later.
52
+ * `null` when nothing public is reachable (the caller then ships bytes only
53
+ * rather than a link that 404s).
54
+ */
55
+ resolveNotificationEndpoint() {
56
+ const explicit = this.config.notificationBaseUrl;
57
+ if (explicit !== "") return explicit;
58
+ if (this.publicHostname !== "") return `https://${this.publicHostname}`;
59
+ const mesh = this.meshEndpoint;
60
+ if (mesh !== null && mesh.host !== "") return `https://${mesh.host}:${mesh.port}`;
61
+ return null;
62
+ }
47
63
  async onInitialize() {
48
64
  if (!this.config.bootSeeded) {
49
65
  const seed = autoSeedAllowlist(this.enumerate());
@@ -68,6 +84,18 @@ var LocalNetworkAddon = class extends BaseAddon {
68
84
  await this.reconcileMeshEndpoint();
69
85
  return { endpoints: buildEndpoints(interfaces, input.port, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint) };
70
86
  },
87
+ getNotificationEndpoint: async () => ({
88
+ baseUrl: this.config.notificationBaseUrl === "" ? null : this.config.notificationBaseUrl,
89
+ resolved: this.resolveNotificationEndpoint()
90
+ }),
91
+ setNotificationEndpoint: async ({ baseUrl }) => {
92
+ await this.updateGlobalSettings({ notificationBaseUrl: baseUrl ?? "" });
93
+ this.ctx.logger.info("local-network: notification endpoint set", { meta: { baseUrl: baseUrl ?? "(auto)" } });
94
+ return {
95
+ baseUrl: baseUrl === "" ? null : baseUrl,
96
+ resolved: this.resolveNotificationEndpoint()
97
+ };
98
+ },
71
99
  getAllowedAddresses: async () => ({ addresses: this.config.allowedAddresses }),
72
100
  resetAllowlistToBestMatch: async () => {
73
101
  const seed = autoSeedAllowlist(this.enumerate());
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -295,7 +295,8 @@ var LokiDestination = class {
295
295
  //#endregion
296
296
  //#region src/builtins/loki-logging/loki-logging.addon.ts
297
297
  var DEFAULTS = {
298
- enabled: false,
298
+ enabled: true,
299
+ migratedEnabled: false,
299
300
  url: "",
300
301
  job: "camstack",
301
302
  level: "info",
@@ -315,12 +316,39 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
315
316
  async onInitialize() {
316
317
  this.destination = new LokiDestination();
317
318
  await this.destination.initialize(this.destinationConfig());
318
- this.ctx.logger.info(this.config.enabled && this.config.url.length > 0 ? `Loki logging initialized → ${this.config.url}` : "Loki logging registered but inactive (set a URL and enable it)");
319
+ this.ctx.logger.info(this.config.url.length > 0 ? `Loki logging initialized → ${this.config.url}` : "Loki logging registered but inactive (no URL set)");
320
+ await this.migrateRetiredEnabledFlag();
319
321
  return [{
320
322
  capability: require_dist.logDestinationCapability,
321
323
  provider: this.destination
322
324
  }];
323
325
  }
326
+ /**
327
+ * ONE-SHOT: carry a stored `enabled: false` over to the orchestrator's
328
+ * per-destination toggle, then never look at the flag again.
329
+ *
330
+ * Without this, retiring the private setting would silently RE-ENABLE Loki
331
+ * for every operator who had turned it off — shipping their logs to a host
332
+ * they may no longer control. Best-effort: if the toggle call fails the
333
+ * migration is simply retried next boot (the flag is only cleared on
334
+ * success).
335
+ */
336
+ async migrateRetiredEnabledFlag() {
337
+ if (this.config.migratedEnabled) return;
338
+ try {
339
+ if (!this.config.enabled) {
340
+ await this.ctx.api.addons.setCapabilityProviderEnabled.mutate({
341
+ capName: "log-destination",
342
+ addonId: this.ctx.id,
343
+ enabled: false
344
+ });
345
+ this.ctx.logger.info("Loki logging: the retired `enabled: false` setting was carried over to the per-destination toggle — re-enable it there if you want Loki shipping again.");
346
+ }
347
+ await this.updateGlobalSettings({ migratedEnabled: true });
348
+ } catch (err) {
349
+ this.ctx.logger.warn("Loki logging: enablement migration deferred to next boot", { meta: { error: err instanceof Error ? err.message : String(err) } });
350
+ }
351
+ }
324
352
  async onShutdown() {
325
353
  await this.destination?.shutdown();
326
354
  this.destination = null;
@@ -334,7 +362,7 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
334
362
  */
335
363
  async onConfigChanged() {
336
364
  await this.destination?.reconfigure(this.destinationConfig());
337
- this.ctx.logger.info(this.config.enabled && this.config.url.length > 0 ? `Loki logging reconfigured → ${this.config.url}` : "Loki logging now inactive (disabled or no URL)");
365
+ this.ctx.logger.info(this.config.url.length > 0 ? `Loki logging reconfigured → ${this.config.url}` : "Loki logging now inactive (no URL)");
338
366
  }
339
367
  /**
340
368
  * `node` is a Loki LABEL, so it must be the node's real identity and must not
@@ -350,6 +378,7 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
350
378
  destinationConfig() {
351
379
  return {
352
380
  ...this.config,
381
+ enabled: true,
353
382
  node: this.ctx.kernel.localNodeId ?? "hub"
354
383
  };
355
384
  }
@@ -361,16 +390,9 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
361
390
  return this.schema({ sections: [{
362
391
  id: "loki-logging",
363
392
  title: "Loki",
364
- description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations enabling this does not disable those.",
393
+ description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations. Turn it on or off with the Enable/Disable control on this destination the same one every other destination uses.",
365
394
  columns: 2,
366
395
  fields: [
367
- this.field({
368
- type: "boolean",
369
- key: "enabled",
370
- label: "Enabled",
371
- description: "Off by default. Needs a URL below to do anything.",
372
- default: false
373
- }),
374
396
  this.field({
375
397
  type: "text",
376
398
  key: "url",
@@ -1,4 +1,4 @@
1
- import { at as BaseAddon, z as logDestinationCapability } from "../../dist-DmQ6thmF.mjs";
1
+ import { at as BaseAddon, z as logDestinationCapability } from "../../dist-CIek6F9X.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -290,7 +290,8 @@ var LokiDestination = class {
290
290
  //#endregion
291
291
  //#region src/builtins/loki-logging/loki-logging.addon.ts
292
292
  var DEFAULTS = {
293
- enabled: false,
293
+ enabled: true,
294
+ migratedEnabled: false,
294
295
  url: "",
295
296
  job: "camstack",
296
297
  level: "info",
@@ -310,12 +311,39 @@ var LokiLoggingAddon = class extends BaseAddon {
310
311
  async onInitialize() {
311
312
  this.destination = new LokiDestination();
312
313
  await this.destination.initialize(this.destinationConfig());
313
- this.ctx.logger.info(this.config.enabled && this.config.url.length > 0 ? `Loki logging initialized → ${this.config.url}` : "Loki logging registered but inactive (set a URL and enable it)");
314
+ this.ctx.logger.info(this.config.url.length > 0 ? `Loki logging initialized → ${this.config.url}` : "Loki logging registered but inactive (no URL set)");
315
+ await this.migrateRetiredEnabledFlag();
314
316
  return [{
315
317
  capability: logDestinationCapability,
316
318
  provider: this.destination
317
319
  }];
318
320
  }
321
+ /**
322
+ * ONE-SHOT: carry a stored `enabled: false` over to the orchestrator's
323
+ * per-destination toggle, then never look at the flag again.
324
+ *
325
+ * Without this, retiring the private setting would silently RE-ENABLE Loki
326
+ * for every operator who had turned it off — shipping their logs to a host
327
+ * they may no longer control. Best-effort: if the toggle call fails the
328
+ * migration is simply retried next boot (the flag is only cleared on
329
+ * success).
330
+ */
331
+ async migrateRetiredEnabledFlag() {
332
+ if (this.config.migratedEnabled) return;
333
+ try {
334
+ if (!this.config.enabled) {
335
+ await this.ctx.api.addons.setCapabilityProviderEnabled.mutate({
336
+ capName: "log-destination",
337
+ addonId: this.ctx.id,
338
+ enabled: false
339
+ });
340
+ this.ctx.logger.info("Loki logging: the retired `enabled: false` setting was carried over to the per-destination toggle — re-enable it there if you want Loki shipping again.");
341
+ }
342
+ await this.updateGlobalSettings({ migratedEnabled: true });
343
+ } catch (err) {
344
+ this.ctx.logger.warn("Loki logging: enablement migration deferred to next boot", { meta: { error: err instanceof Error ? err.message : String(err) } });
345
+ }
346
+ }
319
347
  async onShutdown() {
320
348
  await this.destination?.shutdown();
321
349
  this.destination = null;
@@ -329,7 +357,7 @@ var LokiLoggingAddon = class extends BaseAddon {
329
357
  */
330
358
  async onConfigChanged() {
331
359
  await this.destination?.reconfigure(this.destinationConfig());
332
- this.ctx.logger.info(this.config.enabled && this.config.url.length > 0 ? `Loki logging reconfigured → ${this.config.url}` : "Loki logging now inactive (disabled or no URL)");
360
+ this.ctx.logger.info(this.config.url.length > 0 ? `Loki logging reconfigured → ${this.config.url}` : "Loki logging now inactive (no URL)");
333
361
  }
334
362
  /**
335
363
  * `node` is a Loki LABEL, so it must be the node's real identity and must not
@@ -345,6 +373,7 @@ var LokiLoggingAddon = class extends BaseAddon {
345
373
  destinationConfig() {
346
374
  return {
347
375
  ...this.config,
376
+ enabled: true,
348
377
  node: this.ctx.kernel.localNodeId ?? "hub"
349
378
  };
350
379
  }
@@ -356,16 +385,9 @@ var LokiLoggingAddon = class extends BaseAddon {
356
385
  return this.schema({ sections: [{
357
386
  id: "loki-logging",
358
387
  title: "Loki",
359
- description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations enabling this does not disable those.",
388
+ description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations. Turn it on or off with the Enable/Disable control on this destination the same one every other destination uses.",
360
389
  columns: 2,
361
390
  fields: [
362
- this.field({
363
- type: "boolean",
364
- key: "enabled",
365
- label: "Enabled",
366
- description: "Off by default. Needs a URL below to do anything.",
367
- default: false
368
- }),
369
391
  this.field({
370
392
  type: "text",
371
393
  key: "url",
@@ -1,7 +1,15 @@
1
1
  import { LogLevel, ProviderRegistration, BaseAddon } from '@camstack/types';
2
2
  import { LokiDestination } from './loki-destination.js';
3
3
  interface LokiAddonConfig {
4
+ /**
5
+ * RETIRED (2026-07-30). Enablement is the orchestrator's per-destination
6
+ * toggle now — the ONE truth for every log destination. Kept only so a
7
+ * stored `false` can be migrated ONCE into a registry disable: without that,
8
+ * an operator who had Loki off would silently start shipping logs after this
9
+ * update. `migratedEnabled` marks the migration done; the field is then dead.
10
+ */
4
11
  readonly enabled: boolean;
12
+ readonly migratedEnabled: boolean;
5
13
  readonly url: string;
6
14
  readonly job: string;
7
15
  readonly level: LogLevel;
@@ -17,6 +25,17 @@ export declare class LokiLoggingAddon extends BaseAddon<LokiAddonConfig> {
17
25
  private destination;
18
26
  constructor();
19
27
  protected onInitialize(): Promise<ProviderRegistration[]>;
28
+ /**
29
+ * ONE-SHOT: carry a stored `enabled: false` over to the orchestrator's
30
+ * per-destination toggle, then never look at the flag again.
31
+ *
32
+ * Without this, retiring the private setting would silently RE-ENABLE Loki
33
+ * for every operator who had turned it off — shipping their logs to a host
34
+ * they may no longer control. Best-effort: if the toggle call fails the
35
+ * migration is simply retried next boot (the flag is only cleared on
36
+ * success).
37
+ */
38
+ private migrateRetiredEnabledFlag;
20
39
  protected onShutdown(): Promise<void>;
21
40
  /**
22
41
  * React to a settings change (enable/disable, URL, level, batching) without a
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { H as metricsProviderCapability, at as BaseAddon, jt as EventCategory, vt as createEvent } from "../../dist-DmQ6thmF.mjs";
1
+ import { H as metricsProviderCapability, at as BaseAddon, jt as EventCategory, vt as createEvent } from "../../dist-CIek6F9X.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile, execFileSync } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { J as scoreRuntimes, K as platformProbeCapability, T as enumerateInferenceDevices, at as BaseAddon, bt as emitReadiness, it as errMsg, jt as EventCategory } from "../../dist-DmQ6thmF.mjs";
2
+ import { J as scoreRuntimes, K as platformProbeCapability, T as enumerateInferenceDevices, at as BaseAddon, bt as emitReadiness, it as errMsg, jt as EventCategory } from "../../dist-CIek6F9X.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { execFile } from "node:child_process";
5
5
  import { promisify } from "node:util";
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { at as BaseAddon, jt as EventCategory } from "../../dist-DmQ6thmF.mjs";
1
+ import { at as BaseAddon, jt as EventCategory } from "../../dist-CIek6F9X.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  //#region src/builtins/snapshot/snapshot-coalescing.ts
9
9
  /**
@@ -1,4 +1,4 @@
1
- import { Z as snapshotCapability, at as BaseAddon, dt as DeviceType, et as streamQualityLabel, i as BatteryStatusSchema, it as errMsg, lt as DeviceFeature, wt as nodePin } from "../../dist-DmQ6thmF.mjs";
1
+ import { Z as snapshotCapability, at as BaseAddon, dt as DeviceType, et as streamQualityLabel, i as BatteryStatusSchema, it as errMsg, lt as DeviceFeature, wt as nodePin } from "../../dist-CIek6F9X.mjs";
2
2
  import { execFile } from "node:child_process";
3
3
  //#region src/builtins/snapshot/snapshot-coalescing.ts
4
4
  /**
@@ -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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { $ as storageProviderCapability, A as filesystemBrowseCapability, at as BaseAddon } from "../../dist-DmQ6thmF.mjs";
1
+ import { $ as storageProviderCapability, A as filesystemBrowseCapability, at as BaseAddon } from "../../dist-CIek6F9X.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import * as path$1 from "node:path";
4
4
  import { basename, dirname, join, resolve, sep } 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-OQ1yU92D.js");
6
+ const require_dist = require("../../dist-CS0pdC8l.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let better_sqlite3 = require("better-sqlite3");
9
9
  better_sqlite3 = require_chunk.__toESM(better_sqlite3);
@@ -1,4 +1,4 @@
1
- import { Et as parseJsonUnknown, X as settingsStoreCapability, at as BaseAddon, c as RUNTIME_DEFAULTS, ht as asJsonObject, it as errMsg } from "../../dist-DmQ6thmF.mjs";
1
+ import { Et as parseJsonUnknown, X as settingsStoreCapability, at as BaseAddon, c as RUNTIME_DEFAULTS, ht as asJsonObject, it as errMsg } from "../../dist-CIek6F9X.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import Database from "better-sqlite3";
4
4
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
@@ -64,6 +64,13 @@ export declare class StorageOrchestratorAddon extends BaseAddon<StorageOrchestra
64
64
  * the recorder so disk-pressure is a single core concern.
65
65
  */
66
66
  private resolveLocationThresholds;
67
+ /** statfs capacity of a location's basePath, walking up to the nearest
68
+ * existing ancestor. Null for remote-node locations or unstattable paths. */
69
+ private localCapacityOf;
70
+ /** Per-location cap on EVICTABLE bytes (`config.maxUsedGb`): the operator's
71
+ * "recordings may use at most N GB in total" — enforced by the pressure
72
+ * manager alongside the free-percent guard. */
73
+ private resolveLocationMaxUsedBytes;
67
74
  private locationBasePath;
68
75
  /**
69
76
  * A stable key for the PHYSICAL storage device behind a location — the