@camstack/system 1.2.470 → 1.2.472

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 (93) hide show
  1. package/dist/addon-runner.js +248 -19
  2. package/dist/addon-runner.mjs +249 -20
  3. package/dist/addon-utils.mjs +1 -1
  4. package/dist/{agent-https-DmNQyAl7.mjs → agent-https-JhvY1axF.mjs} +1 -1
  5. package/dist/{agent-https-D_oo3PNv.js → agent-https-bnyFRK7Y.js} +1 -1
  6. package/dist/auth/user-manager.d.ts +2 -2
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +4 -4
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +5 -5
  11. package/dist/builtins/alerts/alerts.addon.js +1 -1
  12. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  13. package/dist/builtins/autotrack/index.js +1 -1
  14. package/dist/builtins/autotrack/index.mjs +1 -1
  15. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  16. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +5 -5
  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-history/device-history.addon.js +1 -1
  28. package/dist/builtins/device-history/device-history.addon.mjs +1 -1
  29. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  30. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  31. package/dist/builtins/hub-forwarder/index.js +1 -1
  32. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  33. package/dist/builtins/keychain/index.js +3 -3
  34. package/dist/builtins/keychain/index.mjs +3 -3
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.d.ts +9 -25
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +240 -39
  37. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +237 -38
  38. package/dist/builtins/liveness-monitor/liveness-reporter.d.ts +28 -0
  39. package/dist/builtins/liveness-monitor/storage-root-access.d.ts +53 -0
  40. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  41. package/dist/builtins/local-auth/local-auth.addon.mjs +5 -5
  42. package/dist/builtins/local-network/local-network.addon.js +2 -2
  43. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  44. package/dist/builtins/loki-logging/index.js +1 -1
  45. package/dist/builtins/loki-logging/index.mjs +1 -1
  46. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  47. package/dist/builtins/native-metrics/native-metrics.addon.mjs +10 -10
  48. package/dist/builtins/platform-probe/coral-accelerators.d.ts +14 -4
  49. package/dist/builtins/platform-probe/index.js +9 -6
  50. package/dist/builtins/platform-probe/index.mjs +15 -12
  51. package/dist/builtins/record-entries/index.js +1 -1
  52. package/dist/builtins/record-entries/index.mjs +1 -1
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  55. package/dist/builtins/snapshot/index.js +1 -1
  56. package/dist/builtins/snapshot/index.mjs +1 -1
  57. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  58. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +20 -20
  59. package/dist/builtins/sqlite-storage/index.js +1 -1
  60. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  61. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  62. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  63. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  64. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  65. package/dist/builtins/system-config/system-config.addon.js +3 -3
  66. package/dist/builtins/system-config/system-config.addon.mjs +115 -115
  67. package/dist/builtins/winston-logging/index.js +1 -1
  68. package/dist/builtins/winston-logging/index.mjs +1 -1
  69. package/dist/{composition-sources-B0tqvf0t.mjs → composition-sources-BY6rFuAE.mjs} +1 -1
  70. package/dist/{composition-sources-CRJffEiB.js → composition-sources-D3h-tNoJ.js} +1 -1
  71. package/dist/{constants-WF1AqoKF.js → constants-CqbO6e9i.js} +1 -1
  72. package/dist/{constants--A505iuO.mjs → constants-zBiEodEM.mjs} +1 -1
  73. package/dist/{coral-accelerators-Cri32b7q.js → coral-accelerators-BSVsBVzt.js} +46 -11
  74. package/dist/{coral-accelerators-Dl-GnMHn.mjs → coral-accelerators-DBlgWB8k.mjs} +52 -17
  75. package/dist/{custom-action-registry-BGYDjTqY.mjs → custom-action-registry-DZSCLBuT.mjs} +37 -37
  76. package/dist/{device-ca-DYmToMp8.mjs → device-ca-BWTWWBcc.mjs} +1 -1
  77. package/dist/{device-ca-R4MZZtc0.js → device-ca-CfiIg3a9.js} +1 -1
  78. package/dist/{dist-X1r44BdF.mjs → dist-CdFkkmXW.mjs} +6845 -1651
  79. package/dist/{dist-DRL3rEfN.js → dist-DX5Fu3mV.js} +6845 -1651
  80. package/dist/file-host-worker.js +1 -1
  81. package/dist/file-host-worker.mjs +1 -1
  82. package/dist/{hub-mdns-ciao-DUoTS6jN.js → hub-mdns-ciao-C174UDzO.js} +2 -2
  83. package/dist/{hub-mdns-ciao-CZV5Hion.mjs → hub-mdns-ciao-CYp1Gipm.mjs} +2 -2
  84. package/dist/index.js +235 -17
  85. package/dist/index.mjs +453 -235
  86. package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +24 -4
  87. package/dist/kernel/hwaccel/vaapi-init-probe.d.ts +29 -0
  88. package/dist/kernel/moleculer/runner-process-group.d.ts +4 -0
  89. package/dist/{restore-archive-Df6PsOHP.js → restore-archive-B2jIFfu_.js} +1 -1
  90. package/dist/{restore-archive-X0ciGMmO.mjs → restore-archive-BX0gaKtG.mjs} +94 -94
  91. package/dist/{wal-maintenance-DYvzJiuK.js → wal-maintenance-Dxqd5igw.js} +0 -0
  92. package/dist/{wal-maintenance-D6J_MmZ8.mjs → wal-maintenance-KHzJXlF2.mjs} +0 -0
  93. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DRL3rEfN.js");
3
+ const require_dist = require("../../dist-DX5Fu3mV.js");
4
4
  const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
5
- const require_composition_sources = require("../../composition-sources-CRJffEiB.js");
5
+ const require_composition_sources = require("../../composition-sources-D3h-tNoJ.js");
6
6
  const require_prefix_range = require("../../prefix-range-D_KueXbQ.js");
7
7
  const require_prefix_walk = require("../../prefix-walk-Dv6MLZlM.js");
8
8
  let zod = require("zod");
@@ -1,6 +1,6 @@
1
- import { $r as errMsg, Bt as coreBlockComposition, Ht as coreBlockSourceKind, Ki as defineRecordKinds, Kn as normalizeCoreBlockWrite, Kr as compositionSourceKey, Mr as validateComposition, N as DeclaredDevices, Ot as buildCapabilityLookup, Qi as hydrateSchema, Qn as planComposition, Rt as compositionSourceRefs, Ut as coreBlocksCapability, Vi as collectSecretMarkerRefs, Vr as composedDeviceRef, Vt as coreBlockIdFromAddonId, Zt as describeComposableCaps, _a as resolveSecretMarkers, aa as isSecretRefOwnedBy, ar as replacedFieldKeys, ba as secretTreeFromSchema, cr as resolveCoreBlockEntry, ei as BaseAddon, f as COMPOSER_RUNNER_ID, lr as resolveCoreBlockFiles, m as CORE_BLOCK_ADDON_PREFIX, p as CORE_BLOCKS_ADDON_ID, r as ALL_CAPABILITY_DEFINITIONS, ra as isSecretMarker, v as CoreBlockSchema, wa as transformSecretWrite, zt as coreBlockAddonId } from "../../dist-X1r44BdF.mjs";
1
+ import { $r as errMsg, Bt as coreBlockComposition, Ht as coreBlockSourceKind, Ki as defineRecordKinds, Kn as normalizeCoreBlockWrite, Kr as compositionSourceKey, Mr as validateComposition, N as DeclaredDevices, Ot as buildCapabilityLookup, Qi as hydrateSchema, Qn as planComposition, Rt as compositionSourceRefs, Ut as coreBlocksCapability, Vi as collectSecretMarkerRefs, Vr as composedDeviceRef, Vt as coreBlockIdFromAddonId, Zt as describeComposableCaps, _a as resolveSecretMarkers, aa as isSecretRefOwnedBy, ar as replacedFieldKeys, ba as secretTreeFromSchema, cr as resolveCoreBlockEntry, ei as BaseAddon, f as COMPOSER_RUNNER_ID, lr as resolveCoreBlockFiles, m as CORE_BLOCK_ADDON_PREFIX, p as CORE_BLOCKS_ADDON_ID, r as ALL_CAPABILITY_DEFINITIONS, ra as isSecretMarker, v as CoreBlockSchema, wa as transformSecretWrite, zt as coreBlockAddonId } from "../../dist-CdFkkmXW.mjs";
2
2
  import { t as require_read_all_rows } from "../../read-all-rows-TaPqdWNF.mjs";
3
- import { t as describeCompositionSources } from "../../composition-sources-B0tqvf0t.mjs";
3
+ import { t as describeCompositionSources } from "../../composition-sources-BY6rFuAE.mjs";
4
4
  import { t as prefixRange } from "../../prefix-range-Dl0znowN.mjs";
5
5
  import { n as unwrapStoredValue, t as loadPrefixed } from "../../prefix-walk-DAOBSyH1.mjs";
6
6
  import { createRequire } from "node:module";
@@ -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-DRL3rEfN.js");
6
+ const require_dist = require("../../dist-DX5Fu3mV.js");
7
7
  //#region src/builtins/device-history/time/bucket-time.ts
8
8
  /**
9
9
  * Bucket arithmetic for the three rollup tiers (D850, plan §4/§5.5).
@@ -1,4 +1,4 @@
1
- import { B as HISTORY_ITEM_CATALOG, Ct as armRemovalReconcile, D as DEVICE_HISTORY_ENERGY_KEY, Da as EventCategory, Ji as emitReadiness, Qr as DeviceType, Zi as historyItemFieldPath, ei as BaseAddon, en as deviceHistoryCapability, li as HISTORY_DEFAULT_MIN_INTERVAL_MS, nn as deviceRemovalHookCapability, z as HISTORY_CATALOG } from "../../dist-X1r44BdF.mjs";
1
+ import { B as HISTORY_ITEM_CATALOG, Ct as armRemovalReconcile, D as DEVICE_HISTORY_ENERGY_KEY, Da as EventCategory, Ji as emitReadiness, Qr as DeviceType, Zi as historyItemFieldPath, ei as BaseAddon, en as deviceHistoryCapability, li as HISTORY_DEFAULT_MIN_INTERVAL_MS, nn as deviceRemovalHookCapability, z as HISTORY_CATALOG } from "../../dist-CdFkkmXW.mjs";
2
2
  //#region src/builtins/device-history/time/bucket-time.ts
3
3
  /**
4
4
  * Bucket arithmetic for the three rollup tiers (D850, plan §4/§5.5).
@@ -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-DRL3rEfN.js");
6
+ const require_dist = require("../../dist-DX5Fu3mV.js");
7
7
  const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
8
8
  const require_prefix_range = require("../../prefix-range-D_KueXbQ.js");
9
9
  const require_prefix_walk = require("../../prefix-walk-Dv6MLZlM.js");
@@ -1,4 +1,4 @@
1
- import { $i as isDeviceConfigCap, $r as errMsg, $t as deviceExtensionCapability, Ca as toSecretFieldView, Da as EventCategory, Di as SECRET_CLEAR, F as DeviceTombstoneSchema, Hi as collectZodSecretPaths, In as locationIconKey, Ki as defineRecordKinds, L as FieldClaimModeSchema, Mi as WELL_KNOWN_TAB_MAP, Mn as legacyAutoLinkIds, P as DeviceStatusSchema, Pt as canonicalIconKey, Qr as DeviceType, R as FieldClaimSchema, Ta as userError, Wt as createDeviceExtensionProvider, Xn as parseStreamParamsFormPatch, Xr as DeviceFeature, Y as MINUTES_PER_DAY, Yt as defineExtensionSlotProvider, Zr as DeviceRole, _a as resolveSecretMarkers, br as siteZoneKey, ei as BaseAddon, gn as formatMinutes, ia as isSecretMarkerLike, in as deviceStatusCapability, jt as buildStreamParamsConfigSchema, l as CAP_NAMES_WITH_STATUS, mr as runtimeStatePolicyFor, na as isSecretClear, on as enumerateItemArrayFields, ot as STREAM_PROFILE_META, qn as normalizeUnit, r as ALL_CAPABILITY_DEFINITIONS, ra as isSecretMarker, rn as deviceStateCapability, s as CAMERA_SWITCH_ORDER, sn as enumerateSchemaFields, ta as isKeychainStoredField, tn as deviceManagerCapability, un as expandContainer, w as DAY_NAMES, wa as transformSecretWrite, xa as sleep$1, ya as secretTreeFromPaths } from "../../dist-X1r44BdF.mjs";
1
+ import { $i as isDeviceConfigCap, $r as errMsg, $t as deviceExtensionCapability, Ca as toSecretFieldView, Da as EventCategory, Di as SECRET_CLEAR, F as DeviceTombstoneSchema, Hi as collectZodSecretPaths, In as locationIconKey, Ki as defineRecordKinds, L as FieldClaimModeSchema, Mi as WELL_KNOWN_TAB_MAP, Mn as legacyAutoLinkIds, P as DeviceStatusSchema, Pt as canonicalIconKey, Qr as DeviceType, R as FieldClaimSchema, Ta as userError, Wt as createDeviceExtensionProvider, Xn as parseStreamParamsFormPatch, Xr as DeviceFeature, Y as MINUTES_PER_DAY, Yt as defineExtensionSlotProvider, Zr as DeviceRole, _a as resolveSecretMarkers, br as siteZoneKey, ei as BaseAddon, gn as formatMinutes, ia as isSecretMarkerLike, in as deviceStatusCapability, jt as buildStreamParamsConfigSchema, l as CAP_NAMES_WITH_STATUS, mr as runtimeStatePolicyFor, na as isSecretClear, on as enumerateItemArrayFields, ot as STREAM_PROFILE_META, qn as normalizeUnit, r as ALL_CAPABILITY_DEFINITIONS, ra as isSecretMarker, rn as deviceStateCapability, s as CAMERA_SWITCH_ORDER, sn as enumerateSchemaFields, ta as isKeychainStoredField, tn as deviceManagerCapability, un as expandContainer, w as DAY_NAMES, wa as transformSecretWrite, xa as sleep$1, ya as secretTreeFromPaths } from "../../dist-CdFkkmXW.mjs";
2
2
  import { t as require_read_all_rows } from "../../read-all-rows-TaPqdWNF.mjs";
3
3
  import { t as prefixRange } from "../../prefix-range-Dl0znowN.mjs";
4
4
  import { t as loadPrefixed } from "../../prefix-walk-DAOBSyH1.mjs";
@@ -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-DRL3rEfN.js");
6
+ const require_dist = require("../../dist-DX5Fu3mV.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 { Ln as logDestinationCapability, ei as BaseAddon } from "../../dist-X1r44BdF.mjs";
1
+ import { Ln as logDestinationCapability, ei as BaseAddon } from "../../dist-CdFkkmXW.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,10 +3,10 @@ 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-DRL3rEfN.js");
7
- const require_device_ca = require("../../device-ca-R4MZZtc0.js");
6
+ const require_dist = require("../../dist-DX5Fu3mV.js");
7
+ const require_device_ca = require("../../device-ca-CfiIg3a9.js");
8
8
  const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
9
- const require_constants = require("../../constants-WF1AqoKF.js");
9
+ const require_constants = require("../../constants-CqbO6e9i.js");
10
10
  const require_platform_key_source = require("../../platform-key-source-Di7HrlGR.js");
11
11
  let node_crypto = require("node:crypto");
12
12
  let _camstack_types_node = require("@camstack/types/node");
@@ -1,7 +1,7 @@
1
- import { An as keychainAdminCapability, Da as EventCategory, G as KeychainResetRecordSchema, Ki as defineRecordKinds, U as KEYCHAIN_RESET_TRIGGER_ACTOR, W as KeychainAuditEntrySchema, Xi as formatSecretScope, ei as BaseAddon, jn as keychainCapability, pa as parseSecretRef, ua as normalizeSecretOwner } from "../../dist-X1r44BdF.mjs";
2
- import { d as JWT_SIGNING_KEY_REF, n as DEVICE_CA_KEY_REF, p as SYSTEM_SECRET_OWNER } from "../../device-ca-DYmToMp8.mjs";
1
+ import { An as keychainAdminCapability, Da as EventCategory, G as KeychainResetRecordSchema, Ki as defineRecordKinds, U as KEYCHAIN_RESET_TRIGGER_ACTOR, W as KeychainAuditEntrySchema, Xi as formatSecretScope, ei as BaseAddon, jn as keychainCapability, pa as parseSecretRef, ua as normalizeSecretOwner } from "../../dist-CdFkkmXW.mjs";
2
+ import { d as JWT_SIGNING_KEY_REF, n as DEVICE_CA_KEY_REF, p as SYSTEM_SECRET_OWNER } from "../../device-ca-BWTWWBcc.mjs";
3
3
  import { t as require_read_all_rows } from "../../read-all-rows-TaPqdWNF.mjs";
4
- import { a as MESH_CA_KEY_REF } from "../../constants--A505iuO.mjs";
4
+ import { a as MESH_CA_KEY_REF } from "../../constants-zBiEodEM.mjs";
5
5
  import { n as getPlatformKeychainKey } from "../../platform-key-source-BWlq0jjn.mjs";
6
6
  import { createCipheriv, createDecipheriv, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
7
7
  import { restrictDirToOwnerSync, restrictFileToOwnerSync, writeOwnerOnlyFileSync } from "@camstack/types/node";
@@ -1,24 +1,3 @@
1
- /**
2
- * liveness-monitor — the node states, out loud, when it has stopped working.
3
- *
4
- * On 2026-08-03 the hub ran for four hours with zero devices, zero published
5
- * streams and zero recordings. Three separate defects caused it, each silent at
6
- * the point of failure, and the operator found out by opening the viewer. Every
7
- * one of those defects has since been fixed and given a log line — but a log
8
- * line per known bug does not cover the next one. This addon covers the class:
9
- * it asserts what must be true of a working node and reports when it is not.
10
- *
11
- * It emits `SystemLivenessFailed` / `SystemLivenessRecovered` on the event bus
12
- * with a STABLE finding id, exactly like the kernel's addon-health monitor, so
13
- * AlertCenter raises one persistent alert per fault and dismisses it on
14
- * recovery instead of producing a stream of them. The same lines go to the log
15
- * at ERROR, which is what makes them alertable in Loki.
16
- *
17
- * Deliberately hub-only and deliberately read-only: it observes through
18
- * `ctx.api` and changes nothing. A monitor that can act is a monitor that can
19
- * cause the outage it was meant to report — this repo has already stopped a
20
- * hub that way once (the remedial-action watcher, 2026-07-30).
21
- */
22
1
  import { BaseAddon, type ProviderRegistration } from "@camstack/types";
23
2
  import { type LivenessFinding } from "./liveness-checks.js";
24
3
  interface LivenessMonitorConfig extends Record<string, unknown> {
@@ -27,8 +6,9 @@ interface LivenessMonitorConfig extends Record<string, unknown> {
27
6
  export declare class LivenessMonitorAddon extends BaseAddon<LivenessMonitorConfig> {
28
7
  private tickTimer;
29
8
  private startedAtMs;
30
- /** Finding ids currently reported, so recovery can be detected and announced. */
31
- private active;
9
+ private reporter;
10
+ /** Roots whose writability could not be read, already said once. */
11
+ private readonly unknownRootsSaid;
32
12
  constructor();
33
13
  protected onInitialize(): Promise<ProviderRegistration[]>;
34
14
  protected onShutdown(): Promise<void>;
@@ -56,7 +36,11 @@ export declare class LivenessMonitorAddon extends BaseAddon<LivenessMonitorConfi
56
36
  * names it, per camera (D393, and CLAUDE.md's logging rule).
57
37
  */
58
38
  private unreadable;
59
- /** Announce new faults and recoveries; say nothing about an unchanged fault. */
60
- private report;
39
+ /**
40
+ * Every storage root this node writes to must be writable BY THIS PROCESS.
41
+ * Reported through the same finding path (one ERROR, one alert, a recovery
42
+ * when it is fixed). Never throws: an unreadable list is said and skipped.
43
+ */
44
+ checkStorageRoots(): Promise<readonly LivenessFinding[]>;
61
45
  }
62
46
  export {};
@@ -1,6 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DRL3rEfN.js");
2
+ const require_chunk = require("../../chunk-Cek0wNdY.js");
3
+ const require_dist = require("../../dist-DX5Fu3mV.js");
4
+ let node_fs_promises = require("node:fs/promises");
5
+ node_fs_promises = require_chunk.__toESM(node_fs_promises);
6
+ let node_path = require("node:path");
7
+ node_path = require_chunk.__toESM(node_path);
8
+ let node_fs = require("node:fs");
4
9
  //#region src/builtins/liveness-monitor/liveness-checks.ts
5
10
  var NO_DEVICES = "liveness:no-devices";
6
11
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -52,6 +57,198 @@ function isLivenessFindingId(id) {
52
57
  return id === NO_DEVICES || id === ALL_OFFLINE || id.startsWith(NO_FOOTAGE_PREFIX);
53
58
  }
54
59
  //#endregion
60
+ //#region src/builtins/liveness-monitor/liveness-reporter.ts
61
+ /**
62
+ * Announce new liveness faults and their recoveries — never an unchanged fault.
63
+ *
64
+ * One ERROR line and one `SystemLivenessFailed` event per NEW finding id (the
65
+ * alert centre turns it into one persistent alert), one INFO line and one
66
+ * `SystemLivenessRecovered` when it is gone. Several families of checks run on
67
+ * different cadences (the device checks wait out the boot grace, the storage
68
+ * check does not), so a recovery is judged only within the family that was
69
+ * just evaluated: a storage check that found nothing must not "recover" a
70
+ * device finding it never looked at.
71
+ */
72
+ /** Event source stamped on every emission — the addon that observed it. */
73
+ var LIVENESS_SOURCE = {
74
+ type: "addon",
75
+ id: "liveness-monitor"
76
+ };
77
+ var LivenessReporter = class {
78
+ deps;
79
+ /** Finding ids currently reported, so recovery can be detected and announced. */
80
+ active = /* @__PURE__ */ new Set();
81
+ constructor(deps) {
82
+ this.deps = deps;
83
+ }
84
+ /**
85
+ * @param findings what the family found this evaluation
86
+ * @param inFamily which ids the family owns — only those can recover here
87
+ */
88
+ report(findings, inFamily) {
89
+ const current = new Set(findings.map((f) => f.id));
90
+ for (const finding of findings) {
91
+ if (this.active.has(finding.id)) continue;
92
+ this.active.add(finding.id);
93
+ this.deps.logger.error(`liveness: ${finding.title} — ${finding.message}`, {
94
+ ...finding.deviceId !== void 0 ? { tags: { deviceId: finding.deviceId } } : {},
95
+ meta: {
96
+ findingId: finding.id,
97
+ severity: finding.severity
98
+ }
99
+ });
100
+ this.deps.emit(require_dist.createEvent(require_dist.EventCategory.SystemLivenessFailed, LIVENESS_SOURCE, {
101
+ findingId: finding.id,
102
+ severity: finding.severity,
103
+ title: finding.title,
104
+ message: finding.message,
105
+ ...finding.deviceId !== void 0 ? { deviceId: finding.deviceId } : {},
106
+ ...finding.deviceName !== void 0 ? { name: finding.deviceName } : {}
107
+ }));
108
+ }
109
+ for (const id of [...this.active]) {
110
+ if (current.has(id) || !inFamily(id)) continue;
111
+ this.active.delete(id);
112
+ this.deps.logger.info(`liveness: recovered — ${id}`, { meta: { findingId: id } });
113
+ this.deps.emit(require_dist.createEvent(require_dist.EventCategory.SystemLivenessRecovered, LIVENESS_SOURCE, { findingId: id }));
114
+ }
115
+ }
116
+ };
117
+ //#endregion
118
+ //#region src/builtins/liveness-monitor/storage-root-access.ts
119
+ /**
120
+ * Can this node WRITE where its storage locations say media goes?
121
+ *
122
+ * 2026-10-23, a fresh install of the dev hub: the bind mounted at
123
+ * `/recordings` was `root:root 0755` while the hub runs as uid 1001. Nothing
124
+ * said so at boot; the first sign was an `EACCES` on `/recordings/artifacts`,
125
+ * and recordings would have failed one write at a time. A root the process
126
+ * cannot write is a fact known the moment the node starts, so it is reported
127
+ * then — once, naming the path, its owner, its mode and the uid — instead of
128
+ * as a stream of per-write errors.
129
+ *
130
+ * Only a location this node may WRITE (`mayWriteToLocation`), served by the
131
+ * local filesystem provider, on this node, with an absolute `basePath`, is
132
+ * judged. A root that does not exist yet is judged by its nearest existing
133
+ * ancestor: that is the directory the node would have to create it in.
134
+ *
135
+ * A probe that cannot answer (a stat that fails for a reason other than
136
+ * permission) is `unknown` and never a finding (D393): an unreadable fact is
137
+ * not an unwritable disk.
138
+ */
139
+ /** The local filesystem storage provider's id (`FilesystemStorageProvider.providerId`). */
140
+ var FILESYSTEM_PROVIDER_ID = "filesystem-storage";
141
+ var UNWRITABLE_PREFIX = "liveness:storage-root-unwritable:";
142
+ /** The hub-local, writable filesystem roots among `locations`, deduplicated by path. */
143
+ function storageRootTargets(locations, localNodeId) {
144
+ const byPath = /* @__PURE__ */ new Map();
145
+ for (const location of locations) {
146
+ if (location.providerId !== FILESYSTEM_PROVIDER_ID) continue;
147
+ if (location.nodeId !== void 0 && location.nodeId !== localNodeId) continue;
148
+ if (!require_dist.mayWriteToLocation(location)) continue;
149
+ const basePath = location.config["basePath"];
150
+ if (typeof basePath !== "string" || !node_path.default.isAbsolute(basePath)) continue;
151
+ const root = node_path.default.resolve(basePath);
152
+ byPath.set(root, [...byPath.get(root) ?? [], location.id]);
153
+ }
154
+ return [...byPath.entries()].map(([root, locationIds]) => ({
155
+ path: root,
156
+ locationIds: locationIds.toSorted()
157
+ })).toSorted((a, b) => a.path.localeCompare(b.path));
158
+ }
159
+ function isMissing(err) {
160
+ return err instanceof Error && "code" in err && (err.code === "ENOENT" || err.code === "ENOTDIR");
161
+ }
162
+ /** Probe one root: the root itself, or the nearest ancestor that exists. */
163
+ async function probeStorageRoot(target, probe) {
164
+ let dir = target.path;
165
+ let stat;
166
+ for (;;) try {
167
+ stat = await probe.stat(dir);
168
+ break;
169
+ } catch (err) {
170
+ const parent = node_path.default.dirname(dir);
171
+ if (!isMissing(err) || parent === dir) return {
172
+ kind: "unknown",
173
+ target,
174
+ reason: `stat ${dir} failed: ${require_dist.errMsg(err)}`
175
+ };
176
+ dir = parent;
177
+ }
178
+ try {
179
+ await probe.canWrite(dir);
180
+ return {
181
+ kind: "writable",
182
+ target
183
+ };
184
+ } catch (err) {
185
+ return {
186
+ kind: "unwritable",
187
+ target,
188
+ blockedAt: dir,
189
+ stat,
190
+ reason: require_dist.errMsg(err)
191
+ };
192
+ }
193
+ }
194
+ function octalMode(mode) {
195
+ return (mode & 4095).toString(8).padStart(4, "0");
196
+ }
197
+ function processIdentity(probe) {
198
+ if (probe.processUid === null) return "this process";
199
+ return `uid ${String(probe.processUid)}` + (probe.processGid === null ? "" : ` (gid ${String(probe.processGid)})`);
200
+ }
201
+ /**
202
+ * One finding per DIRECTORY that refused — several roots under one unwritable
203
+ * mount (`/recordings` holding recordings, event media and artifacts) are one
204
+ * cause and one alert. Stable id = that directory.
205
+ */
206
+ function storageRootFindings(results, probe) {
207
+ const byBlocked = /* @__PURE__ */ new Map();
208
+ for (const result of results) {
209
+ if (result.kind !== "unwritable") continue;
210
+ const entry = byBlocked.get(result.blockedAt) ?? {
211
+ stat: result.stat,
212
+ roots: [],
213
+ locationIds: []
214
+ };
215
+ entry.roots.push(result.target.path);
216
+ entry.locationIds.push(...result.target.locationIds);
217
+ byBlocked.set(result.blockedAt, entry);
218
+ }
219
+ const uidFix = probe.processUid === null ? "<uid>" : String(probe.processUid);
220
+ const gidFix = probe.processGid === null ? "<gid>" : String(probe.processGid);
221
+ return [...byBlocked.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([blockedAt, { stat, roots, locationIds }]) => {
222
+ const missing = roots.filter((r) => r !== blockedAt);
223
+ const below = missing.length > 0 ? ` (${missing.join(", ")} must be created inside it)` : "";
224
+ return {
225
+ id: `${UNWRITABLE_PREFIX}${blockedAt}`,
226
+ severity: "error",
227
+ title: "Storage location not writable",
228
+ message: `${blockedAt} is owned by ${String(stat.uid)}:${String(stat.gid)} with mode ${octalMode(stat.mode)}, and CamStack runs as ${processIdentity(probe)} — nothing can be written there${below}. Locations affected: ${locationIds.toSorted().join(", ")}. Fix the ownership on the host, e.g. chown -R ${uidFix}:${gidFix} <the host directory bound at ${blockedAt}>.`
229
+ };
230
+ });
231
+ }
232
+ function isStorageRootFindingId(id) {
233
+ return id.startsWith(UNWRITABLE_PREFIX);
234
+ }
235
+ /** The real probe: `fs.access(W_OK | X_OK)` as THIS process, and `fs.stat`. */
236
+ function nodeStorageRootProbe(fs) {
237
+ return {
238
+ canWrite: (dir) => fs.access(dir, node_fs.constants.W_OK | node_fs.constants.X_OK),
239
+ stat: async (dir) => {
240
+ const st = await fs.stat(dir);
241
+ return {
242
+ uid: st.uid,
243
+ gid: st.gid,
244
+ mode: st.mode
245
+ };
246
+ },
247
+ processUid: typeof process.getuid === "function" ? process.getuid() : null,
248
+ processGid: typeof process.getgid === "function" ? process.getgid() : null
249
+ };
250
+ }
251
+ //#endregion
55
252
  //#region src/builtins/liveness-monitor/liveness-monitor.addon.ts
56
253
  /**
57
254
  * liveness-monitor — the node states, out loud, when it has stopped working.
@@ -74,11 +271,8 @@ function isLivenessFindingId(id) {
74
271
  * cause the outage it was meant to report — this repo has already stopped a
75
272
  * hub that way once (the remedial-action watcher, 2026-07-30).
76
273
  */
77
- /** Event source stamped on every emission — the addon that observed it. */
78
- var LIVENESS_SOURCE = {
79
- type: "addon",
80
- id: "liveness-monitor"
81
- };
274
+ /** The node this monitor runs on (it is hub-only). */
275
+ var HUB_NODE_ID = "hub";
82
276
  /** How often the invariants are checked. */
83
277
  var TICK_INTERVAL_MS = 5 * 6e4;
84
278
  /**
@@ -101,13 +295,19 @@ var RECORDING_LOOKBACK_MS = 1440 * 6e4;
101
295
  var LivenessMonitorAddon = class extends require_dist.BaseAddon {
102
296
  tickTimer = null;
103
297
  startedAtMs = 0;
104
- /** Finding ids currently reported, so recovery can be detected and announced. */
105
- active = /* @__PURE__ */ new Set();
298
+ reporter = null;
299
+ /** Roots whose writability could not be read, already said once. */
300
+ unknownRootsSaid = /* @__PURE__ */ new Set();
106
301
  constructor() {
107
302
  super({});
108
303
  }
109
304
  async onInitialize() {
110
305
  this.startedAtMs = Date.now();
306
+ this.reporter = new LivenessReporter({
307
+ logger: this.ctx.logger,
308
+ emit: (event) => this.ctx.eventBus.emit(event)
309
+ });
310
+ this.checkStorageRoots();
111
311
  this.tickTimer = setInterval(() => {
112
312
  this.tick();
113
313
  }, TICK_INTERVAL_MS);
@@ -127,11 +327,12 @@ var LivenessMonitorAddon = class extends require_dist.BaseAddon {
127
327
  * without waiting out the interval.
128
328
  */
129
329
  async tick() {
130
- if (Date.now() - this.startedAtMs < BOOT_GRACE_MS) return [];
330
+ const storage = await this.checkStorageRoots();
331
+ if (Date.now() - this.startedAtMs < BOOT_GRACE_MS) return storage;
131
332
  try {
132
333
  const findings = evaluateLiveness(await this.gather());
133
- this.report(findings);
134
- return findings;
334
+ this.reporter?.report(findings, isLivenessFindingId);
335
+ return [...storage, ...findings];
135
336
  } catch (err) {
136
337
  this.ctx.logger.error("liveness-monitor: check failed — node health is UNKNOWN this tick", { meta: { error: require_dist.errMsg(err) } });
137
338
  return [];
@@ -206,33 +407,33 @@ var LivenessMonitorAddon = class extends require_dist.BaseAddon {
206
407
  this.ctx.logger.warn(`liveness: recording availability was unreadable (${window}) — this camera is not judged this tick`, { tags: { deviceId } });
207
408
  return null;
208
409
  }
209
- /** Announce new faults and recoveries; say nothing about an unchanged fault. */
210
- report(findings) {
211
- const current = new Set(findings.map((f) => f.id));
212
- for (const finding of findings) {
213
- if (this.active.has(finding.id)) continue;
214
- this.active.add(finding.id);
215
- this.ctx.logger.error(`liveness: ${finding.title} — ${finding.message}`, {
216
- ...finding.deviceId !== void 0 ? { tags: { deviceId: finding.deviceId } } : {},
217
- meta: {
218
- findingId: finding.id,
219
- severity: finding.severity
220
- }
221
- });
222
- this.ctx.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.SystemLivenessFailed, LIVENESS_SOURCE, {
223
- findingId: finding.id,
224
- severity: finding.severity,
225
- title: finding.title,
226
- message: finding.message,
227
- ...finding.deviceId !== void 0 ? { deviceId: finding.deviceId } : {},
228
- ...finding.deviceName !== void 0 ? { name: finding.deviceName } : {}
229
- }));
230
- }
231
- for (const id of [...this.active]) {
232
- if (current.has(id)) continue;
233
- this.active.delete(id);
234
- this.ctx.logger.info(`liveness: recovered — ${id}`, { meta: { findingId: id } });
235
- this.ctx.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.SystemLivenessRecovered, LIVENESS_SOURCE, { findingId: id }));
410
+ /**
411
+ * Every storage root this node writes to must be writable BY THIS PROCESS.
412
+ * Reported through the same finding path (one ERROR, one alert, a recovery
413
+ * when it is fixed). Never throws: an unreadable list is said and skipped.
414
+ */
415
+ async checkStorageRoots() {
416
+ const reporter = this.reporter;
417
+ if (reporter === null) return [];
418
+ try {
419
+ const locations = await this.ctx.api.storage.listLocations.query({});
420
+ const probe = nodeStorageRootProbe(node_fs_promises);
421
+ const results = await Promise.all(storageRootTargets(locations, HUB_NODE_ID).map((t) => probeStorageRoot(t, probe)));
422
+ for (const result of results) {
423
+ if (result.kind !== "unknown" || this.unknownRootsSaid.has(result.target.path)) continue;
424
+ this.unknownRootsSaid.add(result.target.path);
425
+ this.ctx.logger.warn("liveness: storage root writability could not be read — not judged", { meta: {
426
+ path: result.target.path,
427
+ locationIds: result.target.locationIds,
428
+ reason: result.reason
429
+ } });
430
+ }
431
+ const findings = storageRootFindings(results, probe);
432
+ reporter.report(findings, isStorageRootFindingId);
433
+ return findings;
434
+ } catch (err) {
435
+ this.ctx.logger.warn("liveness: storage locations unreadable — storage roots not checked", { meta: { error: require_dist.errMsg(err) } });
436
+ return [];
236
437
  }
237
438
  }
238
439
  };