@camstack/system 1.2.378 → 1.2.379

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 +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/auth/auth-manager.d.ts +10 -2
  6. package/dist/auth/jwt-signing-key.d.ts +42 -0
  7. package/dist/auth/user-manager.d.ts +19 -19
  8. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  12. package/dist/builtins/alerts/alerts.addon.js +1 -1
  13. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  14. package/dist/builtins/autotrack/index.js +1 -1
  15. package/dist/builtins/autotrack/index.mjs +1 -1
  16. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  17. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  18. package/dist/builtins/calendars/calendars.addon.js +1 -1
  19. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  20. package/dist/builtins/camera-grid/index.js +1 -1
  21. package/dist/builtins/camera-grid/index.mjs +1 -1
  22. package/dist/builtins/composer/composer.addon.js +2 -2
  23. package/dist/builtins/composer/composer.addon.mjs +2 -2
  24. package/dist/builtins/console-logging/index.js +1 -1
  25. package/dist/builtins/console-logging/index.mjs +1 -1
  26. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  27. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  28. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  29. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  30. package/dist/builtins/hub-forwarder/index.js +1 -1
  31. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  32. package/dist/builtins/keychain/index.js +34 -4
  33. package/dist/builtins/keychain/index.mjs +34 -4
  34. package/dist/builtins/keychain/keychain-service.d.ts +14 -2
  35. package/dist/builtins/keychain/system-held-secrets.d.ts +11 -0
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  37. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  38. package/dist/builtins/local-auth/admin-account-boot.d.ts +1 -1
  39. package/dist/builtins/local-auth/auth-config-reader.d.ts +9 -7
  40. package/dist/builtins/local-auth/jwt-signing-key-keeper.d.ts +30 -0
  41. package/dist/builtins/local-auth/local-auth.addon.d.ts +2 -6
  42. package/dist/builtins/local-auth/local-auth.addon.js +127 -221
  43. package/dist/builtins/local-auth/local-auth.addon.mjs +126 -220
  44. package/dist/builtins/local-network/local-network.addon.js +1 -1
  45. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  46. package/dist/builtins/loki-logging/index.js +1 -1
  47. package/dist/builtins/loki-logging/index.mjs +1 -1
  48. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  49. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  50. package/dist/builtins/platform-probe/index.js +1 -1
  51. package/dist/builtins/platform-probe/index.mjs +1 -1
  52. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  54. package/dist/builtins/snapshot/index.js +1 -1
  55. package/dist/builtins/snapshot/index.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  57. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  58. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  59. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  60. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  61. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  62. package/dist/builtins/system-config/cluster-secret-keeper.d.ts +36 -0
  63. package/dist/builtins/system-config/system-config.addon.d.ts +13 -0
  64. package/dist/builtins/system-config/system-config.addon.js +244 -53
  65. package/dist/builtins/system-config/system-config.addon.mjs +244 -54
  66. package/dist/builtins/winston-logging/index.js +1 -1
  67. package/dist/builtins/winston-logging/index.mjs +1 -1
  68. package/dist/{child-cap-dispatch-C9lmrWFT.js → child-cap-dispatch-CZeRUgYF.js} +1 -1
  69. package/dist/{child-cap-dispatch-DpHovtQd.mjs → child-cap-dispatch-CpjohToR.mjs} +1 -1
  70. package/dist/cluster-secret-BDue-uJj.mjs +83 -0
  71. package/dist/cluster-secret-Dg0OOOSr.js +148 -0
  72. package/dist/{composition-sources-CxhGdtdh.js → composition-sources-CHvLMrFI.js} +1 -1
  73. package/dist/{composition-sources-BLBd338N.mjs → composition-sources-DT_uJwI3.mjs} +1 -1
  74. package/dist/{dist-Bvl5JgNt.mjs → dist-B9oFw2jh.mjs} +58 -2
  75. package/dist/{dist-KxsOMQik.js → dist-Cc6KOdY-.js} +69 -1
  76. package/dist/{event-loop-stall-monitor-D8rqSWfW.mjs → event-loop-stall-monitor-anUH2IE1.mjs} +14 -14
  77. package/dist/{event-loop-stall-monitor-CbSmCWkp.js → event-loop-stall-monitor-mk6WGxir.js} +14 -14
  78. package/dist/index.d.ts +4 -2
  79. package/dist/index.js +192 -62
  80. package/dist/index.mjs +171 -55
  81. package/dist/jwt-signing-key-C2rE3wH6.js +174 -0
  82. package/dist/jwt-signing-key-Qhde8r82.mjs +127 -0
  83. package/dist/kernel/config-manager.d.ts +1 -1
  84. package/dist/kernel/config-schema.d.ts +4 -11
  85. package/dist/kernel/index.d.ts +2 -1
  86. package/dist/kernel/moleculer/broker-factory.d.ts +0 -2
  87. package/dist/kernel/moleculer/cluster-secret.d.ts +31 -0
  88. package/dist/kernel/moleculer/hub-service.d.ts +7 -4
  89. package/dist/kernel/moleculer/node-registry.d.ts +1 -1
  90. package/dist/kernel/system-settings-policy.d.ts +7 -9
  91. package/dist/{retired-settings-keys-BAcfvFtm.mjs → retired-settings-keys-DlZ3VV6x.mjs} +1 -1
  92. package/dist/{retired-settings-keys-gTsZfpmP.js → retired-settings-keys-YOOKLA3v.js} +1 -1
  93. package/package.json +1 -1
@@ -1,4 +1,126 @@
1
- import { Dr as hydrateSchema, cr as errMsg, lr as BaseAddon } from "../../dist-Bvl5JgNt.mjs";
1
+ import { Kr as transformSecretWrite, Or as hydrateSchema, Ur as secretTreeFromSchema, cr as errMsg, jr as isSecretMarker, lr as BaseAddon } from "../../dist-B9oFw2jh.mjs";
2
+ import { d as setProcessClusterSecretGate, l as hashClusterSecret, n as CLUSTER_SECRET_REF } from "../../cluster-secret-BDue-uJj.mjs";
3
+ //#region src/builtins/system-config/cluster-secret-keeper.ts
4
+ /**
5
+ * Keeps the hub's cluster-secret gate in step with System → Cluster (D797).
6
+ *
7
+ * The field is a `password` of `system-config`: its `system-settings` row
8
+ * (`cluster.clusterSecret`) holds a keychain marker, the value lives in the
9
+ * keychain (owner `system`). The gate the `$hub.registerNode` handler reads:
10
+ *
11
+ * - no marker → `off` — every node may register, exactly as a hub
12
+ * that never had a secret;
13
+ * - marker + value → `on` — nodes present the matching hash;
14
+ * - marker, no answer → `unknown` — refused, retryably, and retried here.
15
+ * A locked keychain or a value lost to a reset NEVER opens the gate.
16
+ */
17
+ /** The field key inside the `cluster` section. */
18
+ var CLUSTER_SECRET_FIELD = "clusterSecret";
19
+ var ClusterSecretKeeper = class {
20
+ deps;
21
+ retryTimer = null;
22
+ unsubscribe = null;
23
+ stopped = false;
24
+ last = null;
25
+ constructor(deps) {
26
+ this.deps = deps;
27
+ }
28
+ async start() {
29
+ this.unsubscribe = this.deps.secrets?.onChange((ref) => {
30
+ if (ref === CLUSTER_SECRET_REF) this.reload();
31
+ }) ?? null;
32
+ await this.reload();
33
+ }
34
+ stop() {
35
+ this.stopped = true;
36
+ if (this.retryTimer !== null) clearTimeout(this.retryTimer);
37
+ this.retryTimer = null;
38
+ this.unsubscribe?.();
39
+ this.unsubscribe = null;
40
+ }
41
+ /** Re-read the row and the keychain; publish the gate. */
42
+ async reload() {
43
+ if (this.stopped) return;
44
+ const gate = await this.compute();
45
+ if (this.stopped) return;
46
+ setProcessClusterSecretGate(gate);
47
+ this.report(gate);
48
+ if (gate.kind === "unknown") this.scheduleRetry();
49
+ }
50
+ async compute() {
51
+ let stored;
52
+ try {
53
+ stored = (await this.deps.readSection())[CLUSTER_SECRET_FIELD];
54
+ } catch (err) {
55
+ return {
56
+ kind: "unknown",
57
+ reason: `settings unreadable: ${errText(err)}`
58
+ };
59
+ }
60
+ if (stored === void 0 || stored === null || stored === "") return { kind: "off" };
61
+ if (!isSecretMarker(stored)) return {
62
+ kind: "unknown",
63
+ reason: "stored inline, not in the keychain — set it again in System → Cluster"
64
+ };
65
+ const secrets = this.deps.secrets;
66
+ if (secrets === null) return {
67
+ kind: "unknown",
68
+ reason: "no keychain door in this process"
69
+ };
70
+ let entry;
71
+ try {
72
+ entry = (await secrets.resolve([CLUSTER_SECRET_REF]))[CLUSTER_SECRET_REF];
73
+ } catch (err) {
74
+ return {
75
+ kind: "unknown",
76
+ reason: errText(err)
77
+ };
78
+ }
79
+ if (entry === void 0) return {
80
+ kind: "unknown",
81
+ reason: "no-answer"
82
+ };
83
+ switch (entry.kind) {
84
+ case "value": return {
85
+ kind: "on",
86
+ hash: hashClusterSecret(entry.value)
87
+ };
88
+ case "unavailable": return {
89
+ kind: "unknown",
90
+ reason: entry.reason
91
+ };
92
+ case "missing": return {
93
+ kind: "unknown",
94
+ reason: "the stored value is gone — set it again in System → Cluster"
95
+ };
96
+ case "forbidden": return {
97
+ kind: "unknown",
98
+ reason: "forbidden"
99
+ };
100
+ }
101
+ }
102
+ report(gate) {
103
+ const changed = this.last !== gate.kind;
104
+ this.last = gate.kind;
105
+ if (gate.kind === "unknown") {
106
+ this.deps.logger.error("Agents cannot register: a cluster secret is set but cannot be read from the keychain. Registrations are refused until it can (no fallback).", { meta: { reason: gate.reason } });
107
+ return;
108
+ }
109
+ if (changed) this.deps.logger.info(gate.kind === "on" ? "Cluster secret set — agents must present it to register" : "No cluster secret set — any node may register");
110
+ }
111
+ scheduleRetry() {
112
+ if (this.stopped || this.retryTimer !== null) return;
113
+ this.retryTimer = setTimeout(() => {
114
+ this.retryTimer = null;
115
+ this.reload();
116
+ }, this.deps.retryMs ?? 3e4);
117
+ this.retryTimer.unref?.();
118
+ }
119
+ };
120
+ function errText(err) {
121
+ return err instanceof Error ? err.message : String(err);
122
+ }
123
+ //#endregion
2
124
  //#region src/builtins/system-config/system-config.addon.ts
3
125
  /**
4
126
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -12,6 +134,9 @@ import { Dr as hydrateSchema, cr as errMsg, lr as BaseAddon } from "../../dist-B
12
134
  * Sections exposed — this list is the whole of it:
13
135
  * - server (port, host, dataPath — READ-ONLY, loaded from config.yaml at bootstrap)
14
136
  * - auth (tokenExpiry)
137
+ * - cluster (clusterSecret — a `password` field: the value goes to the
138
+ * keychain, owner `system`; the row keeps a marker. Also drawn
139
+ * on System → Cluster. D797)
15
140
  *
16
141
  * Sections NOT exposed (each owned by the addon that implements them):
17
142
  * - logging → `winston-logging` addon (level + retention)
@@ -40,75 +165,112 @@ import { Dr as hydrateSchema, cr as errMsg, lr as BaseAddon } from "../../dist-B
40
165
  /** Section key → user-facing section title. */
41
166
  var SECTION_TITLES = {
42
167
  server: "Server",
43
- auth: "Authentication"
168
+ auth: "Authentication",
169
+ cluster: "Cluster"
44
170
  };
171
+ /** The keychain owner of the hub's own secrets (D797). */
172
+ var SYSTEM_SECRET_OWNER = "system";
45
173
  /** Keys that map 1:1 to a given yml section — used by `updateGlobalSettings`
46
174
  * to figure out which section owns each key in the incoming patch. */
47
175
  var KEY_TO_SECTION = {
48
176
  port: "server",
49
177
  host: "server",
50
178
  dataPath: "server",
51
- tokenExpiry: "auth"
179
+ tokenExpiry: "auth",
180
+ [CLUSTER_SECRET_FIELD]: "cluster"
52
181
  };
182
+ /** The section id of the cluster form — System → Cluster renders only this one. */
183
+ var SYSTEM_CONFIG_CLUSTER_SECTION_ID = "system-config-cluster";
53
184
  var SystemConfigAddon = class extends BaseAddon {
54
185
  id = "system-config";
186
+ clusterSecretKeeper = null;
55
187
  constructor() {
56
188
  super({});
57
189
  }
58
190
  async onInitialize() {
191
+ this.clusterSecretKeeper = new ClusterSecretKeeper({
192
+ secrets: this.systemSecrets(),
193
+ readSection: async () => await this.ctx.settings?.getSection("cluster") ?? {},
194
+ logger: this.ctx.logger
195
+ });
196
+ await this.clusterSecretKeeper.start();
59
197
  this.ctx.logger.info("Initialized — exposes yml-backed sections via getGlobalSettings");
60
198
  }
199
+ async onShutdown() {
200
+ this.clusterSecretKeeper?.stop();
201
+ this.clusterSecretKeeper = null;
202
+ }
203
+ /** The keychain handle for owner `system`; `null` where there is no door. */
204
+ systemSecrets() {
205
+ const forOwner = this.ctx.secrets?.forOwner ?? null;
206
+ return forOwner === null ? null : forOwner(SYSTEM_SECRET_OWNER);
207
+ }
61
208
  buildGlobalSchema() {
62
- return { sections: [{
63
- id: "system-config-server",
64
- title: SECTION_TITLES["server"],
65
- description: "Core server connection settings — read-only, loaded from config.yaml at bootstrap.",
66
- columns: 2,
67
- fields: [
68
- {
69
- type: "info",
70
- key: "server-restart-note",
71
- label: "Restart required",
72
- content: "Server settings are read-only. Change them in config.yaml and restart.",
73
- variant: "warning"
74
- },
75
- {
209
+ return { sections: [
210
+ {
211
+ id: "system-config-server",
212
+ title: SECTION_TITLES["server"],
213
+ description: "Core server connection settings — read-only, loaded from config.yaml at bootstrap.",
214
+ columns: 2,
215
+ fields: [
216
+ {
217
+ type: "info",
218
+ key: "server-restart-note",
219
+ label: "Restart required",
220
+ content: "Server settings are read-only. Change them in config.yaml and restart.",
221
+ variant: "warning"
222
+ },
223
+ {
224
+ type: "text",
225
+ key: "port",
226
+ label: "Port",
227
+ description: "Listening port",
228
+ disabled: true
229
+ },
230
+ {
231
+ type: "text",
232
+ key: "host",
233
+ label: "Host",
234
+ description: "Bind address",
235
+ disabled: true
236
+ },
237
+ {
238
+ type: "text",
239
+ key: "dataPath",
240
+ label: "Data Path",
241
+ description: "Root data directory",
242
+ disabled: true,
243
+ span: 2
244
+ }
245
+ ]
246
+ },
247
+ {
248
+ id: "system-config-auth",
249
+ title: SECTION_TITLES["auth"],
250
+ description: "Token and session settings.",
251
+ columns: 1,
252
+ fields: [{
76
253
  type: "text",
77
- key: "port",
78
- label: "Port",
79
- description: "Listening port",
80
- disabled: true
81
- },
82
- {
83
- type: "text",
84
- key: "host",
85
- label: "Host",
86
- description: "Bind address",
87
- disabled: true
88
- },
89
- {
90
- type: "text",
91
- key: "dataPath",
92
- label: "Data Path",
93
- description: "Root data directory",
94
- disabled: true,
95
- span: 2
96
- }
97
- ]
98
- }, {
99
- id: "system-config-auth",
100
- title: SECTION_TITLES["auth"],
101
- description: "Token and session settings.",
102
- columns: 1,
103
- fields: [{
104
- type: "text",
105
- key: "tokenExpiry",
106
- label: "Token Expiry",
107
- description: "Lifetime of a session token, as a duration string (e.g. 24h, 7d, 30d). A bare number is rejected — it would be read as milliseconds. Applies to tokens issued from the next login or silent refresh; tokens already handed out keep the lifetime they were signed with.",
108
- placeholder: "30d",
109
- default: "30d"
110
- }]
111
- }] };
254
+ key: "tokenExpiry",
255
+ label: "Token Expiry",
256
+ description: "Lifetime of a session token, as a duration string (e.g. 24h, 7d, 30d). A bare number is rejected — it would be read as milliseconds. Applies to tokens issued from the next login or silent refresh; tokens already handed out keep the lifetime they were signed with.",
257
+ placeholder: "30d",
258
+ default: "30d"
259
+ }]
260
+ },
261
+ {
262
+ id: SYSTEM_CONFIG_CLUSTER_SECTION_ID,
263
+ title: SECTION_TITLES["cluster"],
264
+ description: "Agents joining this hub. Optional: with no cluster secret, any node that reaches the hub may register.",
265
+ columns: 1,
266
+ fields: [{
267
+ type: "password",
268
+ key: CLUSTER_SECRET_FIELD,
269
+ label: "Cluster secret",
270
+ description: "When set, an agent must present it to register. Stored in the keychain. To pair an agent: Reveal and copy it here, then enter it in the agent at first run (its status page or the desktop setup window). Changing it disconnects agents that still hold the old one at their next registration."
271
+ }]
272
+ }
273
+ ] };
112
274
  }
113
275
  async getGlobalSettings() {
114
276
  const schema = this.buildGlobalSchema();
@@ -125,7 +287,7 @@ var SystemConfigAddon = class extends BaseAddon {
125
287
  } });
126
288
  throw err;
127
289
  }
128
- return hydrateSchema(schema, merged);
290
+ return hydrateSchema(schema, merged, { inlineSecretsAsViews: true });
129
291
  }
130
292
  async updateGlobalSettings(patch) {
131
293
  if (!this.ctx.settings) throw new Error("system-config: ctx.settings not available — cannot write sections");
@@ -148,6 +310,10 @@ var SystemConfigAddon = class extends BaseAddon {
148
310
  bucket[key] = value;
149
311
  }
150
312
  for (const [section, sectionPatch] of perSection) {
313
+ if (section === "cluster") {
314
+ await this.writeClusterSection(sectionPatch);
315
+ continue;
316
+ }
151
317
  await this.ctx.settings.setSection(section, sectionPatch);
152
318
  this.ctx.logger.info("Wrote keys to section", { meta: {
153
319
  section,
@@ -155,6 +321,30 @@ var SystemConfigAddon = class extends BaseAddon {
155
321
  } });
156
322
  }
157
323
  }
324
+ /**
325
+ * The cluster secret's save path: the value goes to the keychain (owner
326
+ * `system`), the row gets the marker; `''` / a view keeps, `$secretClear`
327
+ * clears. A value the keychain cannot store is REFUSED — never written
328
+ * inline (D787, P5).
329
+ */
330
+ async writeClusterSection(patch) {
331
+ const settings = this.ctx.settings;
332
+ const secrets = this.systemSecrets();
333
+ if (secrets === null) throw new Error("system-config: no keychain door — the cluster secret cannot be stored");
334
+ const stored = await settings.getSection("cluster");
335
+ const result = await transformSecretWrite({
336
+ tree: secretTreeFromSchema(this.buildGlobalSchema()),
337
+ secrets,
338
+ patch,
339
+ stored,
340
+ operatorWrite: true
341
+ });
342
+ if (Object.keys(result.patch).length > 0) await settings.setSection("cluster", result.patch);
343
+ const { orphaned } = await result.afterWrite();
344
+ if (orphaned.length > 0) this.ctx.logger.warn("cluster secret cleared, but its old keychain value could not be deleted", { meta: { refs: orphaned } });
345
+ this.ctx.logger.info("Cluster secret updated");
346
+ await this.clusterSecretKeeper?.reload();
347
+ }
158
348
  };
159
349
  //#endregion
160
- export { SystemConfigAddon, SystemConfigAddon as default };
350
+ export { SYSTEM_CONFIG_CLUSTER_SECTION_ID, SystemConfigAddon, SystemConfigAddon as default };
@@ -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-KxsOMQik.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { Zt as logDestinationCapability, lr as BaseAddon } from "../../dist-Bvl5JgNt.mjs";
1
+ import { Zt as logDestinationCapability, lr as BaseAddon } from "../../dist-B9oFw2jh.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_dist = require("./dist-KxsOMQik.js");
2
+ const require_dist = require("./dist-Cc6KOdY-.js");
3
3
  const require_proc_stat = require("./proc-stat-DL-WUOhl.js");
4
4
  require("./hub-cap-forward-COCmYaup.js");
5
5
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { Br as scrubKnownSecrets, Gr as EventCategory$1, Sr as createEvent, en as maskUrlCredentials } from "./dist-Bvl5JgNt.mjs";
1
+ import { Cr as createEvent, Vr as scrubKnownSecrets, en as maskUrlCredentials, qr as EventCategory$1 } from "./dist-B9oFw2jh.mjs";
2
2
  import { a as parseProcStat, i as nodeProcReader } from "./proc-stat-CbC55Kqi.mjs";
3
3
  import "./hub-cap-forward-CH6_VWVO.mjs";
4
4
  import * as crypto$1 from "node:crypto";
@@ -0,0 +1,83 @@
1
+ import { br as buildSecretRef } from "./dist-B9oFw2jh.mjs";
2
+ import { createHash, timingSafeEqual } from "node:crypto";
3
+ //#region src/kernel/moleculer/cluster-secret.ts
4
+ /**
5
+ * Cluster-secret registration gate (opt-in). A registering node sends a
6
+ * SHA-256 hash of its cluster secret; the receiver of a `registerNode` RPC
7
+ * verifies it. The raw secret is never transmitted.
8
+ *
9
+ * Where the secret lives (D797) — never in an environment variable:
10
+ * - the HUB: the keychain, owner `system` ({@link CLUSTER_SECRET_REF}), set
11
+ * from System → Cluster (a `password` field of `system-config`, whose
12
+ * `system-settings` row {@link CLUSTER_SECRET_SETTINGS_KEY} holds the
13
+ * marker). Empty = the gate is off, exactly as a hub without a secret
14
+ * always was;
15
+ * - an AGENT: its bootstrap credential, entered at pairing / first run and
16
+ * kept in its own `agent.json` (0600) — or, in the desktop app, in the OS
17
+ * keychain — because it needs it before it can reach the hub.
18
+ */
19
+ /** The keychain ref of the hub's cluster secret: `secret://system/global/clusterSecret`. */
20
+ var CLUSTER_SECRET_REF = buildSecretRef({
21
+ owner: "system",
22
+ scope: { kind: "global" },
23
+ fieldPath: "clusterSecret"
24
+ });
25
+ /** The `system-settings` key whose value is the cluster secret's keychain marker. */
26
+ var CLUSTER_SECRET_SETTINGS_KEY = "cluster.clusterSecret";
27
+ /** Moleculer error `type` marking a registration rejected for a bad cluster secret. */
28
+ var CLUSTER_SECRET_MISMATCH_TYPE = "CLUSTER_SECRET_MISMATCH";
29
+ /** Process exit code a runner uses when its registration is rejected for a bad secret. */
30
+ var CLUSTER_SECRET_REJECTED_EXIT_CODE = 78;
31
+ /** SHA-256 hex digest of a cluster secret. Never transmit or log the raw secret. */
32
+ function hashClusterSecret(secret) {
33
+ return createHash("sha256").update(secret, "utf8").digest("hex");
34
+ }
35
+ /**
36
+ * True if a registration carrying `presentedHash` is allowed against a
37
+ * receiver whose own secret hashes to `expectedHash`.
38
+ * - `expectedHash` undefined → the gate is OFF; everything is allowed.
39
+ * - otherwise `presentedHash` must be present and equal (constant-time).
40
+ */
41
+ function clusterSecretMatches(expectedHash, presentedHash) {
42
+ if (expectedHash === void 0) return true;
43
+ if (presentedHash === void 0 || presentedHash.length !== expectedHash.length) return false;
44
+ return timingSafeEqual(Buffer.from(expectedHash), Buffer.from(presentedHash));
45
+ }
46
+ /** Recognises a cluster-secret rejection, including after a Moleculer RPC round-trip. */
47
+ function isClusterSecretMismatchError(err) {
48
+ if (typeof err !== "object" || err === null || !("type" in err)) return false;
49
+ const { type } = err;
50
+ return type === CLUSTER_SECRET_MISMATCH_TYPE;
51
+ }
52
+ /** Moleculer error `type` of a registration refused because the hub cannot read its own secret yet. */
53
+ var CLUSTER_SECRET_UNAVAILABLE_TYPE = "CLUSTER_SECRET_UNAVAILABLE";
54
+ function checkClusterSecret(gate, presentedHash) {
55
+ switch (gate.kind) {
56
+ case "off": return "allow";
57
+ case "unknown": return "unavailable";
58
+ case "on": return clusterSecretMatches(gate.hash, presentedHash) ? "allow" : "mismatch";
59
+ }
60
+ }
61
+ var GATE_SLOT = Symbol.for("camstack.cluster.secret-gate");
62
+ var NOT_LOADED = {
63
+ kind: "unknown",
64
+ reason: "not-loaded"
65
+ };
66
+ function isGate(value) {
67
+ if (value === null || typeof value !== "object") return false;
68
+ const kind = Reflect.get(value, "kind");
69
+ if (kind === "off") return true;
70
+ if (kind === "on") return typeof Reflect.get(value, "hash") === "string";
71
+ return kind === "unknown" && typeof Reflect.get(value, "reason") === "string";
72
+ }
73
+ /** Register the hub's gate. Called by `system-config`, which owns the field. */
74
+ function setProcessClusterSecretGate(gate) {
75
+ Reflect.set(globalThis, GATE_SLOT, gate);
76
+ }
77
+ /** The hub's gate; `unknown` until `system-config` loaded it. */
78
+ function getProcessClusterSecretGate() {
79
+ const value = Reflect.get(globalThis, GATE_SLOT);
80
+ return isGate(value) ? value : NOT_LOADED;
81
+ }
82
+ //#endregion
83
+ export { CLUSTER_SECRET_UNAVAILABLE_TYPE as a, getProcessClusterSecretGate as c, setProcessClusterSecretGate as d, CLUSTER_SECRET_SETTINGS_KEY as i, hashClusterSecret as l, CLUSTER_SECRET_REF as n, checkClusterSecret as o, CLUSTER_SECRET_REJECTED_EXIT_CODE as r, clusterSecretMatches as s, CLUSTER_SECRET_MISMATCH_TYPE as t, isClusterSecretMismatchError as u };
@@ -0,0 +1,148 @@
1
+ const require_dist = require("./dist-Cc6KOdY-.js");
2
+ let node_crypto = require("node:crypto");
3
+ //#region src/kernel/moleculer/cluster-secret.ts
4
+ /**
5
+ * Cluster-secret registration gate (opt-in). A registering node sends a
6
+ * SHA-256 hash of its cluster secret; the receiver of a `registerNode` RPC
7
+ * verifies it. The raw secret is never transmitted.
8
+ *
9
+ * Where the secret lives (D797) — never in an environment variable:
10
+ * - the HUB: the keychain, owner `system` ({@link CLUSTER_SECRET_REF}), set
11
+ * from System → Cluster (a `password` field of `system-config`, whose
12
+ * `system-settings` row {@link CLUSTER_SECRET_SETTINGS_KEY} holds the
13
+ * marker). Empty = the gate is off, exactly as a hub without a secret
14
+ * always was;
15
+ * - an AGENT: its bootstrap credential, entered at pairing / first run and
16
+ * kept in its own `agent.json` (0600) — or, in the desktop app, in the OS
17
+ * keychain — because it needs it before it can reach the hub.
18
+ */
19
+ /** The keychain ref of the hub's cluster secret: `secret://system/global/clusterSecret`. */
20
+ var CLUSTER_SECRET_REF = require_dist.buildSecretRef({
21
+ owner: "system",
22
+ scope: { kind: "global" },
23
+ fieldPath: "clusterSecret"
24
+ });
25
+ /** The `system-settings` key whose value is the cluster secret's keychain marker. */
26
+ var CLUSTER_SECRET_SETTINGS_KEY = "cluster.clusterSecret";
27
+ /** Moleculer error `type` marking a registration rejected for a bad cluster secret. */
28
+ var CLUSTER_SECRET_MISMATCH_TYPE = "CLUSTER_SECRET_MISMATCH";
29
+ /** Process exit code a runner uses when its registration is rejected for a bad secret. */
30
+ var CLUSTER_SECRET_REJECTED_EXIT_CODE = 78;
31
+ /** SHA-256 hex digest of a cluster secret. Never transmit or log the raw secret. */
32
+ function hashClusterSecret(secret) {
33
+ return (0, node_crypto.createHash)("sha256").update(secret, "utf8").digest("hex");
34
+ }
35
+ /**
36
+ * True if a registration carrying `presentedHash` is allowed against a
37
+ * receiver whose own secret hashes to `expectedHash`.
38
+ * - `expectedHash` undefined → the gate is OFF; everything is allowed.
39
+ * - otherwise `presentedHash` must be present and equal (constant-time).
40
+ */
41
+ function clusterSecretMatches(expectedHash, presentedHash) {
42
+ if (expectedHash === void 0) return true;
43
+ if (presentedHash === void 0 || presentedHash.length !== expectedHash.length) return false;
44
+ return (0, node_crypto.timingSafeEqual)(Buffer.from(expectedHash), Buffer.from(presentedHash));
45
+ }
46
+ /** Recognises a cluster-secret rejection, including after a Moleculer RPC round-trip. */
47
+ function isClusterSecretMismatchError(err) {
48
+ if (typeof err !== "object" || err === null || !("type" in err)) return false;
49
+ const { type } = err;
50
+ return type === CLUSTER_SECRET_MISMATCH_TYPE;
51
+ }
52
+ /** Moleculer error `type` of a registration refused because the hub cannot read its own secret yet. */
53
+ var CLUSTER_SECRET_UNAVAILABLE_TYPE = "CLUSTER_SECRET_UNAVAILABLE";
54
+ function checkClusterSecret(gate, presentedHash) {
55
+ switch (gate.kind) {
56
+ case "off": return "allow";
57
+ case "unknown": return "unavailable";
58
+ case "on": return clusterSecretMatches(gate.hash, presentedHash) ? "allow" : "mismatch";
59
+ }
60
+ }
61
+ var GATE_SLOT = Symbol.for("camstack.cluster.secret-gate");
62
+ var NOT_LOADED = {
63
+ kind: "unknown",
64
+ reason: "not-loaded"
65
+ };
66
+ function isGate(value) {
67
+ if (value === null || typeof value !== "object") return false;
68
+ const kind = Reflect.get(value, "kind");
69
+ if (kind === "off") return true;
70
+ if (kind === "on") return typeof Reflect.get(value, "hash") === "string";
71
+ return kind === "unknown" && typeof Reflect.get(value, "reason") === "string";
72
+ }
73
+ /** Register the hub's gate. Called by `system-config`, which owns the field. */
74
+ function setProcessClusterSecretGate(gate) {
75
+ Reflect.set(globalThis, GATE_SLOT, gate);
76
+ }
77
+ /** The hub's gate; `unknown` until `system-config` loaded it. */
78
+ function getProcessClusterSecretGate() {
79
+ const value = Reflect.get(globalThis, GATE_SLOT);
80
+ return isGate(value) ? value : NOT_LOADED;
81
+ }
82
+ //#endregion
83
+ Object.defineProperty(exports, "CLUSTER_SECRET_MISMATCH_TYPE", {
84
+ enumerable: true,
85
+ get: function() {
86
+ return CLUSTER_SECRET_MISMATCH_TYPE;
87
+ }
88
+ });
89
+ Object.defineProperty(exports, "CLUSTER_SECRET_REF", {
90
+ enumerable: true,
91
+ get: function() {
92
+ return CLUSTER_SECRET_REF;
93
+ }
94
+ });
95
+ Object.defineProperty(exports, "CLUSTER_SECRET_REJECTED_EXIT_CODE", {
96
+ enumerable: true,
97
+ get: function() {
98
+ return CLUSTER_SECRET_REJECTED_EXIT_CODE;
99
+ }
100
+ });
101
+ Object.defineProperty(exports, "CLUSTER_SECRET_SETTINGS_KEY", {
102
+ enumerable: true,
103
+ get: function() {
104
+ return CLUSTER_SECRET_SETTINGS_KEY;
105
+ }
106
+ });
107
+ Object.defineProperty(exports, "CLUSTER_SECRET_UNAVAILABLE_TYPE", {
108
+ enumerable: true,
109
+ get: function() {
110
+ return CLUSTER_SECRET_UNAVAILABLE_TYPE;
111
+ }
112
+ });
113
+ Object.defineProperty(exports, "checkClusterSecret", {
114
+ enumerable: true,
115
+ get: function() {
116
+ return checkClusterSecret;
117
+ }
118
+ });
119
+ Object.defineProperty(exports, "clusterSecretMatches", {
120
+ enumerable: true,
121
+ get: function() {
122
+ return clusterSecretMatches;
123
+ }
124
+ });
125
+ Object.defineProperty(exports, "getProcessClusterSecretGate", {
126
+ enumerable: true,
127
+ get: function() {
128
+ return getProcessClusterSecretGate;
129
+ }
130
+ });
131
+ Object.defineProperty(exports, "hashClusterSecret", {
132
+ enumerable: true,
133
+ get: function() {
134
+ return hashClusterSecret;
135
+ }
136
+ });
137
+ Object.defineProperty(exports, "isClusterSecretMismatchError", {
138
+ enumerable: true,
139
+ get: function() {
140
+ return isClusterSecretMismatchError;
141
+ }
142
+ });
143
+ Object.defineProperty(exports, "setProcessClusterSecretGate", {
144
+ enumerable: true,
145
+ get: function() {
146
+ return setProcessClusterSecretGate;
147
+ }
148
+ });
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-KxsOMQik.js");
1
+ const require_dist = require("./dist-Cc6KOdY-.js");
2
2
  //#region src/builtins/core-blocks/composition-sources.ts
3
3
  /**
4
4
  * Describe what a composition reads and writes for SAVE-time validation
@@ -1,4 +1,4 @@
1
- import { cr as errMsg, tr as compositionSourceKey } from "./dist-Bvl5JgNt.mjs";
1
+ import { cr as errMsg, tr as compositionSourceKey } from "./dist-B9oFw2jh.mjs";
2
2
  //#region src/builtins/core-blocks/composition-sources.ts
3
3
  /**
4
4
  * Describe what a composition reads and writes for SAVE-time validation