@uns-kit/core 3.0.28 → 3.0.31

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 (110) hide show
  1. package/MIGRATIONS.md +118 -0
  2. package/README.md +10 -0
  3. package/dist/config/app-config.d.ts +15 -0
  4. package/dist/config/app-config.d.ts.map +1 -1
  5. package/dist/config/app-config.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/runtime/configuration-group-authorization.d.ts +50 -0
  11. package/dist/runtime/configuration-group-authorization.d.ts.map +1 -0
  12. package/dist/runtime/configuration-group-authorization.js +113 -0
  13. package/dist/runtime/configuration-group-authorization.js.map +1 -0
  14. package/dist/runtime/configuration-group-contract.d.ts +75 -0
  15. package/dist/runtime/configuration-group-contract.d.ts.map +1 -0
  16. package/dist/runtime/configuration-group-contract.js +233 -0
  17. package/dist/runtime/configuration-group-contract.js.map +1 -0
  18. package/dist/runtime/mqtt-output-state-contract.d.ts +27 -0
  19. package/dist/runtime/mqtt-output-state-contract.d.ts.map +1 -0
  20. package/dist/runtime/mqtt-output-state-contract.js +32 -0
  21. package/dist/runtime/mqtt-output-state-contract.js.map +1 -0
  22. package/dist/runtime/mqtt-output-state-contract.json +8 -0
  23. package/dist/runtime/mqtt-output-state-program.d.mts +28 -0
  24. package/dist/runtime/mqtt-output-state-program.mjs +511 -0
  25. package/dist/runtime/process-group-authorization.d.ts +2 -0
  26. package/dist/runtime/process-group-authorization.d.ts.map +1 -1
  27. package/dist/runtime/process-group-authorization.js +34 -12
  28. package/dist/runtime/process-group-authorization.js.map +1 -1
  29. package/dist/runtime/process-group-backup-contract.d.ts +22 -0
  30. package/dist/runtime/process-group-backup-contract.d.ts.map +1 -0
  31. package/dist/runtime/process-group-backup-contract.js +20 -0
  32. package/dist/runtime/process-group-backup-contract.js.map +1 -0
  33. package/dist/runtime/process-group-backup-contract.json +7 -0
  34. package/dist/runtime/process-group-configured-upgrade.d.ts +4 -0
  35. package/dist/runtime/process-group-configured-upgrade.d.ts.map +1 -0
  36. package/dist/runtime/process-group-configured-upgrade.js +107 -0
  37. package/dist/runtime/process-group-configured-upgrade.js.map +1 -0
  38. package/dist/runtime/process-group-generation.d.ts +166 -0
  39. package/dist/runtime/process-group-generation.d.ts.map +1 -0
  40. package/dist/runtime/process-group-generation.js +278 -0
  41. package/dist/runtime/process-group-generation.js.map +1 -0
  42. package/dist/runtime/process-group-inspection.d.ts +2 -0
  43. package/dist/runtime/process-group-inspection.d.ts.map +1 -1
  44. package/dist/runtime/process-group-inspection.js +120 -40
  45. package/dist/runtime/process-group-inspection.js.map +1 -1
  46. package/dist/runtime/process-group-resources.d.ts +1 -0
  47. package/dist/runtime/process-group-resources.d.ts.map +1 -1
  48. package/dist/runtime/process-group-resources.js +8 -0
  49. package/dist/runtime/process-group-resources.js.map +1 -1
  50. package/dist/runtime/process-group-runtime-selection.d.ts +14 -0
  51. package/dist/runtime/process-group-runtime-selection.d.ts.map +1 -0
  52. package/dist/runtime/process-group-runtime-selection.js +29 -0
  53. package/dist/runtime/process-group-runtime-selection.js.map +1 -0
  54. package/dist/runtime/process-group-snapshot.mjs +60 -8
  55. package/dist/runtime/process-group-staging.d.ts +2 -2
  56. package/dist/runtime/process-group-staging.d.ts.map +1 -1
  57. package/dist/runtime/process-group-staging.js +6 -4
  58. package/dist/runtime/process-group-staging.js.map +1 -1
  59. package/dist/runtime/process-group-supervision.d.ts +2 -2
  60. package/dist/runtime/process-group-supervision.d.ts.map +1 -1
  61. package/dist/runtime/process-group-supervision.js +3 -2
  62. package/dist/runtime/process-group-supervision.js.map +1 -1
  63. package/dist/runtime/process-group-upgrade.d.ts +76 -6
  64. package/dist/runtime/process-group-upgrade.d.ts.map +1 -1
  65. package/dist/runtime/process-group-upgrade.js +7 -1
  66. package/dist/runtime/process-group-upgrade.js.map +1 -1
  67. package/dist/uns/uns-interfaces.d.ts +3 -0
  68. package/dist/uns/uns-interfaces.d.ts.map +1 -1
  69. package/dist/uns/uns-interfaces.js.map +1 -1
  70. package/dist/uns/uns-proxy-process.d.ts.map +1 -1
  71. package/dist/uns/uns-proxy-process.js +3 -0
  72. package/dist/uns/uns-proxy-process.js.map +1 -1
  73. package/dist/uns-config/config-schema.d.ts +20 -0
  74. package/dist/uns-config/config-schema.d.ts.map +1 -1
  75. package/dist/uns-config/mqtt-channel.d.ts +1 -1
  76. package/dist/uns-config/mqtt-channel.d.ts.map +1 -1
  77. package/dist/uns-config/mqtt-channel.js +3 -0
  78. package/dist/uns-config/mqtt-channel.js.map +1 -1
  79. package/dist/uns-config/uns-core-schema.d.ts +145 -0
  80. package/dist/uns-config/uns-core-schema.d.ts.map +1 -1
  81. package/dist/uns-config/uns-core-schema.js +6 -1
  82. package/dist/uns-config/uns-core-schema.js.map +1 -1
  83. package/dist/uns-mqtt/mqtt-interfaces.d.ts +5 -0
  84. package/dist/uns-mqtt/mqtt-interfaces.d.ts.map +1 -1
  85. package/dist/uns-mqtt/mqtt-interfaces.js.map +1 -1
  86. package/dist/uns-mqtt/mqtt-proxy.d.ts +3 -0
  87. package/dist/uns-mqtt/mqtt-proxy.d.ts.map +1 -1
  88. package/dist/uns-mqtt/mqtt-proxy.js +26 -1
  89. package/dist/uns-mqtt/mqtt-proxy.js.map +1 -1
  90. package/dist/uns-mqtt/mqtt-worker.d.ts +3 -1
  91. package/dist/uns-mqtt/mqtt-worker.d.ts.map +1 -1
  92. package/dist/uns-mqtt/mqtt-worker.js +45 -8
  93. package/dist/uns-mqtt/mqtt-worker.js.map +1 -1
  94. package/dist/uns-mqtt/publish-queue-contract.d.ts +37 -0
  95. package/dist/uns-mqtt/publish-queue-contract.d.ts.map +1 -0
  96. package/dist/uns-mqtt/publish-queue-contract.js +19 -0
  97. package/dist/uns-mqtt/publish-queue-contract.js.map +1 -0
  98. package/dist/uns-mqtt/publish-queue-store.d.ts +34 -0
  99. package/dist/uns-mqtt/publish-queue-store.d.ts.map +1 -0
  100. package/dist/uns-mqtt/publish-queue-store.js +176 -0
  101. package/dist/uns-mqtt/publish-queue-store.js.map +1 -0
  102. package/dist/uns-mqtt/publish-queue.d.ts +43 -0
  103. package/dist/uns-mqtt/publish-queue.d.ts.map +1 -0
  104. package/dist/uns-mqtt/publish-queue.js +235 -0
  105. package/dist/uns-mqtt/publish-queue.js.map +1 -0
  106. package/dist/uns-mqtt/uns-mqtt-proxy.d.ts +7 -0
  107. package/dist/uns-mqtt/uns-mqtt-proxy.d.ts.map +1 -1
  108. package/dist/uns-mqtt/uns-mqtt-proxy.js +98 -5
  109. package/dist/uns-mqtt/uns-mqtt-proxy.js.map +1 -1
  110. package/package.json +1 -1
@@ -0,0 +1,511 @@
1
+ // SDK-owned data-only runtime/snapshot/restore contract. Never imports an application.
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import { createRequire } from "node:module";
5
+ import path from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+
8
+ const require = createRequire(import.meta.url),
9
+ sha = (x) => crypto.createHash("sha256").update(x).digest("hex");
10
+ const guardName = ".mqtt-output-root.sqlite",
11
+ markerName = ".mqtt-output-restore.json",
12
+ stageName = ".mqtt-output-restore-stage";
13
+ const safeName = /^[a-z][a-z0-9-]{0,99}$/,
14
+ hex = /^[a-f0-9]{64}$/,
15
+ uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
16
+ const exactKeys = (value, keys) =>
17
+ value &&
18
+ typeof value === "object" &&
19
+ !Array.isArray(value) &&
20
+ Object.keys(value).length === keys.length &&
21
+ keys.every((k) => Object.hasOwn(value, k));
22
+ const fail = () => {
23
+ throw Error("MQTT output state validation failed; preserve storage and review the operation");
24
+ };
25
+ const sqlite = () => require("node:sqlite");
26
+ function hashFile(file) {
27
+ const hash = crypto.createHash("sha256"),
28
+ buffer = Buffer.allocUnsafe(65536),
29
+ fd = fs.openSync(file, "r");
30
+ try {
31
+ let n;
32
+ while ((n = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, n));
33
+ return hash.digest("hex");
34
+ } finally {
35
+ fs.closeSync(fd);
36
+ }
37
+ }
38
+ export function outputStateLimits(value) {
39
+ const expected = {
40
+ schemaVersion: 1,
41
+ maximumQueues: 32,
42
+ maximumRecordsPerQueue: 100000,
43
+ maximumManifestBytes: 262144,
44
+ maximumSnapshotBytes: 536870912,
45
+ maximumCapsuleBytes: 1073741824,
46
+ };
47
+ if (!value || Object.keys(value).length !== Object.keys(expected).length || Object.entries(expected).some(([k, v]) => value[k] !== v)) fail();
48
+ return Object.freeze({ ...expected });
49
+ }
50
+ function check(p, directory = false, optional = false) {
51
+ let s;
52
+ try {
53
+ s = fs.lstatSync(p);
54
+ } catch (e) {
55
+ if (optional && e.code === "ENOENT") return;
56
+ throw e;
57
+ }
58
+ if (
59
+ s.isSymbolicLink() ||
60
+ s.uid !== process.getuid?.() ||
61
+ s.mode & 0o077 ||
62
+ (!directory && (!s.isFile() || s.nlink !== 1)) ||
63
+ (directory && !s.isDirectory())
64
+ )
65
+ fail();
66
+ return s;
67
+ }
68
+ function noLinks(p) {
69
+ for (let q = p; q !== "/"; q = path.dirname(q)) {
70
+ try {
71
+ if (fs.lstatSync(q).isSymbolicLink()) fail();
72
+ } catch (e) {
73
+ if (e.code !== "ENOENT") throw e;
74
+ }
75
+ }
76
+ }
77
+ function privateJson(p) {
78
+ const s = check(p);
79
+ if (s.size > 262144) fail();
80
+ return JSON.parse(fs.readFileSync(p, "utf8"));
81
+ }
82
+ function atomicJson(p, value) {
83
+ const temp = p + ".writing-" + crypto.randomUUID();
84
+ let fd;
85
+ try {
86
+ fd = fs.openSync(temp, "wx", 0o600);
87
+ fs.writeFileSync(fd, JSON.stringify(value) + "\n");
88
+ fs.fsyncSync(fd);
89
+ fs.closeSync(fd);
90
+ fd = undefined;
91
+ fs.renameSync(temp, p);
92
+ sync(path.dirname(p));
93
+ } finally {
94
+ if (fd !== undefined) fs.closeSync(fd);
95
+ if (fs.existsSync(temp)) fs.unlinkSync(temp);
96
+ }
97
+ }
98
+ function sync(directory) {
99
+ const fd = fs.openSync(directory, "r");
100
+ try {
101
+ fs.fsyncSync(fd);
102
+ } finally {
103
+ fs.closeSync(fd);
104
+ }
105
+ }
106
+ export function assertOutputStateRoot(root) {
107
+ if (process.platform !== "linux" || typeof root !== "string" || root !== path.resolve(root) || !root.startsWith("/var/lib/")) fail();
108
+ noLinks(root);
109
+ check(root, true);
110
+ const mount = fs
111
+ .readFileSync("/proc/self/mountinfo", "utf8")
112
+ .split("\n")
113
+ .map((l) => l.split(" "))
114
+ .find((f) => f[4] === root);
115
+ if (!mount || !["ext4", "xfs", "btrfs", "zfs"].includes(mount[mount.indexOf("-") + 1])) fail();
116
+ }
117
+ export function outputStateRelative(root, directory) {
118
+ if (directory !== path.resolve(directory)) fail();
119
+ const rel = path.relative(root, directory).split(path.sep);
120
+ if (rel.length !== 2 || rel.some((x) => !safeName.test(x))) fail();
121
+ return rel.join("/");
122
+ }
123
+ function rootLease(root, exclusive = false) {
124
+ assertOutputStateRoot(root);
125
+ const file = path.join(root, guardName);
126
+ try {
127
+ fs.closeSync(fs.openSync(file, "wx", 0o600));
128
+ } catch (e) {
129
+ if (e.code !== "EEXIST") throw e;
130
+ }
131
+ check(file);
132
+ check(file + "-journal", false, true);
133
+ const { DatabaseSync } = sqlite(),
134
+ db = new DatabaseSync(file);
135
+ try {
136
+ db.exec("PRAGMA busy_timeout=3000; PRAGMA synchronous=FULL");
137
+ if (db.prepare("PRAGMA journal_mode").get().journal_mode !== "delete") fail();
138
+ // Concurrent first owners serialize initialization, then retain shared read leases.
139
+ if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='metadata'").get()) {
140
+ db.exec("PRAGMA busy_timeout=3000; BEGIN IMMEDIATE");
141
+ if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='metadata'").get()) {
142
+ if (db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().length) fail();
143
+ db.exec("CREATE TABLE metadata(key TEXT PRIMARY KEY,value TEXT NOT NULL)");
144
+ db.prepare("INSERT INTO metadata VALUES(?,?)").run("schema", "mqtt-output-root-1");
145
+ db.prepare("INSERT INTO metadata VALUES(?,?)").run("id", crypto.randomUUID());
146
+ db.exec("COMMIT");
147
+ sync(root);
148
+ } else db.exec("ROLLBACK");
149
+ }
150
+ db.exec(exclusive ? "BEGIN EXCLUSIVE" : "BEGIN");
151
+ const meta = Object.fromEntries(
152
+ db
153
+ .prepare("SELECT key,value FROM metadata")
154
+ .all()
155
+ .map((r) => [r.key, r.value]),
156
+ );
157
+ if (!exactKeys(meta, ["schema", "id"]) || meta.schema !== "mqtt-output-root-1" || !uuid.test(meta.id)) fail();
158
+ return { db, rootId: meta.id, close: () => db.close() };
159
+ } catch (e) {
160
+ db.close();
161
+ throw e;
162
+ }
163
+ }
164
+ export function openManagedOutputRoot(directory, root) {
165
+ if (!root) return undefined;
166
+ assertOutputStateRoot(root);
167
+ const relative = outputStateRelative(root, directory);
168
+ noLinks(directory);
169
+ for (let p = path.dirname(directory); p !== root; p = path.dirname(p)) check(p, true, true);
170
+ const lease = rootLease(root);
171
+ try {
172
+ const marker = path.join(root, markerName);
173
+ if (fs.existsSync(marker)) {
174
+ const m = privateJson(marker);
175
+ if (
176
+ !exactKeys(m, ["schemaVersion", "state", "sourceSha", "rootId"]) ||
177
+ m.schemaVersion !== 1 ||
178
+ m.state !== "complete" ||
179
+ !hex.test(m.sourceSha) ||
180
+ m.rootId !== lease.rootId
181
+ )
182
+ throw Error("MQTT output restoration is incomplete; reconcile the same restore before starting");
183
+ }
184
+ return { rootId: lease.rootId, relative, schema: "2", close: lease.close };
185
+ } catch (e) {
186
+ lease.close();
187
+ throw e;
188
+ }
189
+ }
190
+ export function validateOutputRow(row) {
191
+ if (typeof row.record !== "string" || sha(row.record) !== row.sha256) throw Error("MQTT outbox integrity check failed; pending work preserved");
192
+ const r = JSON.parse(row.record);
193
+ if (
194
+ !exactKeys(r, ["seq", "id", "topic", "message", "options", "bytes"]) ||
195
+ r.seq !== Number(row.seq) ||
196
+ r.id !== row.id ||
197
+ r.bytes !== Number(row.bytes) ||
198
+ !Number.isSafeInteger(r.seq) ||
199
+ r.seq < 1 ||
200
+ typeof r.id !== "string" ||
201
+ !r.id ||
202
+ r.id.length > 200 ||
203
+ typeof r.topic !== "string" ||
204
+ !r.topic ||
205
+ /[\0+#]/.test(r.topic) ||
206
+ Buffer.byteLength(r.topic) > 65535 ||
207
+ typeof r.message !== "string" ||
208
+ !r.options ||
209
+ ![1, 2].includes(r.options.qos) ||
210
+ typeof r.options.retain !== "boolean" ||
211
+ Object.keys(r.options).some((k) => !["qos", "retain"].includes(k)) ||
212
+ r.bytes !== Buffer.byteLength(r.topic) + Buffer.byteLength(r.message) + 256
213
+ )
214
+ fail();
215
+ return r;
216
+ }
217
+ function inspectDatabase(file, binding, limits) {
218
+ check(file);
219
+ const { DatabaseSync } = sqlite(),
220
+ db = new DatabaseSync(file, { readOnly: true });
221
+ try {
222
+ if (db.prepare("PRAGMA quick_check").get().quick_check !== "ok") fail();
223
+ const meta = Object.fromEntries(
224
+ db
225
+ .prepare("SELECT key,value FROM metadata")
226
+ .all()
227
+ .map((r) => [r.key, r.value]),
228
+ );
229
+ if (
230
+ meta.schema !== "2" ||
231
+ meta.rootId !== binding.rootId ||
232
+ meta.relative !== binding.relative ||
233
+ !hex.test(meta.identity) ||
234
+ Object.keys(meta).some((k) => !["schema", "rootId", "relative", "identity"].includes(k))
235
+ )
236
+ fail();
237
+ let records = 0,
238
+ bytes = 0,
239
+ lastSeq = 0;
240
+ for (const row of db.prepare("SELECT seq,id,record,bytes,sha256 FROM pending ORDER BY seq").iterate()) {
241
+ const r = validateOutputRow(row);
242
+ records++;
243
+ bytes += r.bytes;
244
+ lastSeq = r.seq;
245
+ if (records > limits.maximumRecordsPerQueue || bytes > limits.maximumSnapshotBytes) fail();
246
+ }
247
+ return { identity: meta.identity, records, bytes, lastSeq };
248
+ } finally {
249
+ db.close();
250
+ }
251
+ }
252
+ function manifestBytes(directory) {
253
+ const file = path.join(directory, "manifest.json");
254
+ if (check(file).size > 262144) fail();
255
+ return fs.readFileSync(file);
256
+ }
257
+ export function validateOutputSnapshot(directory, suppliedLimits) {
258
+ const limits = outputStateLimits(suppliedLimits);
259
+ noLinks(directory);
260
+ check(directory, true);
261
+ const data = manifestBytes(directory);
262
+ if (data.length > limits.maximumManifestBytes) fail();
263
+ const manifest = JSON.parse(data);
264
+ if (
265
+ !exactKeys(manifest, ["schemaVersion", "kind", "rootId", "capturedAt", "restorePolicy", "queues"]) ||
266
+ manifest.schemaVersion !== 1 ||
267
+ manifest.kind !== "uns-mqtt-output-state-snapshot" ||
268
+ !uuid.test(manifest.rootId) ||
269
+ typeof manifest.capturedAt !== "string" ||
270
+ !Number.isFinite(Date.parse(manifest.capturedAt)) ||
271
+ manifest.restorePolicy !== "offline-source-stop-confirmation-required-no-replay" ||
272
+ !Array.isArray(manifest.queues) ||
273
+ manifest.queues.length > limits.maximumQueues
274
+ )
275
+ fail();
276
+ const expected = new Set(["manifest.json"]),
277
+ directories = new Set();
278
+ let size = 0;
279
+ const seen = new Set();
280
+ for (const q of manifest.queues) {
281
+ if (
282
+ !exactKeys(q, ["path", "file", "size", "sha256", "identity", "records", "bytes", "lastSeq"]) ||
283
+ q.path !== outputStateRelative("/", "/" + q.path) ||
284
+ seen.has(q.path) ||
285
+ q.file !== q.path + "/mqtt-outbox.sqlite" ||
286
+ !hex.test(q.sha256) ||
287
+ !hex.test(q.identity)
288
+ )
289
+ fail();
290
+ seen.add(q.path);
291
+ directories.add(q.path.split("/")[0]);
292
+ directories.add(q.path);
293
+ const file = path.join(directory, q.file);
294
+ noLinks(file);
295
+ const s = check(file);
296
+ size += s.size;
297
+ if (s.size !== q.size || size > limits.maximumSnapshotBytes || hashFile(file) !== q.sha256) fail();
298
+ const actual = inspectDatabase(file, { rootId: manifest.rootId, relative: q.path }, limits);
299
+ if (["identity", "records", "bytes", "lastSeq"].some((k) => actual[k] !== q[k])) fail();
300
+ expected.add(q.file);
301
+ }
302
+ // No undeclared files, symlinks or SQLite sidecars enter a restore.
303
+ function walk(dir, rel = "") {
304
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
305
+ const n = rel ? rel + "/" + e.name : e.name;
306
+ if (e.isSymbolicLink()) fail();
307
+ if (e.isDirectory()) {
308
+ if (!directories.has(n)) fail();
309
+ check(path.join(dir, e.name), true);
310
+ walk(path.join(dir, e.name), n);
311
+ } else if (!expected.has(n)) fail();
312
+ }
313
+ }
314
+ walk(directory);
315
+ return manifest;
316
+ }
317
+ async function capture(root, out, limits) {
318
+ check(out, true);
319
+ if (fs.readdirSync(out).length) fail();
320
+ const lease = rootLease(root);
321
+ const manifest = {
322
+ schemaVersion: 1,
323
+ kind: "uns-mqtt-output-state-snapshot",
324
+ rootId: lease.rootId,
325
+ capturedAt: new Date().toISOString(),
326
+ restorePolicy: "offline-source-stop-confirmation-required-no-replay",
327
+ queues: [],
328
+ };
329
+ let total = 0;
330
+ try {
331
+ const marker = path.join(root, markerName);
332
+ if (fs.existsSync(marker) && privateJson(marker).state !== "complete") fail();
333
+ for (const e of fs.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
334
+ if ([guardName, guardName + "-journal", markerName].includes(e.name)) {
335
+ check(path.join(root, e.name));
336
+ continue;
337
+ }
338
+ if (!e.isDirectory() || !safeName.test(e.name)) fail();
339
+ const ns = path.join(root, e.name);
340
+ check(ns, true);
341
+ for (const id of fs.readdirSync(ns).sort()) {
342
+ if (!safeName.test(id) || manifest.queues.length >= limits.maximumQueues) fail();
343
+ const relative = e.name + "/" + id,
344
+ source = path.join(ns, id);
345
+ noLinks(source);
346
+ check(source, true);
347
+ const allowed = new Set([
348
+ "mqtt-outbox.sqlite",
349
+ "mqtt-outbox.sqlite-wal",
350
+ "mqtt-outbox.sqlite-shm",
351
+ "mqtt-outbox-owner.sqlite",
352
+ "mqtt-outbox-owner.sqlite-journal",
353
+ ]);
354
+ for (const f of fs.readdirSync(source)) {
355
+ if (!allowed.has(f)) fail();
356
+ const s = check(path.join(source, f));
357
+ if (s.size > limits.maximumSnapshotBytes) fail();
358
+ }
359
+ const main = path.join(source, "mqtt-outbox.sqlite"),
360
+ before = check(main);
361
+ const targetDir = path.join(out, relative);
362
+ fs.mkdirSync(targetDir, { recursive: true, mode: 0o700 });
363
+ const dest = path.join(targetDir, "mqtt-outbox.sqlite");
364
+ const { DatabaseSync, backup } = sqlite(),
365
+ db = new DatabaseSync(main, { readOnly: true });
366
+ try {
367
+ await backup(db, dest, {
368
+ rate: 128,
369
+ progress: ({ totalPages }) => {
370
+ if (totalPages * 4096 > limits.maximumSnapshotBytes) fail();
371
+ },
372
+ });
373
+ } finally {
374
+ db.close();
375
+ }
376
+ fs.chmodSync(dest, 0o600);
377
+ const frozen = new (sqlite().DatabaseSync)(dest);
378
+ try {
379
+ frozen.exec("PRAGMA journal_mode=DELETE");
380
+ } finally {
381
+ frozen.close();
382
+ }
383
+ const after = check(main);
384
+ if (before.ino !== after.ino || before.dev !== after.dev) fail();
385
+ const s = check(dest);
386
+ total += s.size;
387
+ if (total > limits.maximumSnapshotBytes) fail();
388
+ const data = inspectDatabase(dest, { rootId: lease.rootId, relative }, limits);
389
+ manifest.queues.push({ path: relative, file: relative + "/mqtt-outbox.sqlite", size: s.size, sha256: hashFile(dest), ...data });
390
+ }
391
+ }
392
+ const encoded = JSON.stringify(manifest) + "\n";
393
+ if (Buffer.byteLength(encoded) > limits.maximumManifestBytes) fail();
394
+ fs.writeFileSync(path.join(out, "manifest.json"), encoded, { mode: 0o600 });
395
+ sync(out);
396
+ return manifest;
397
+ } finally {
398
+ lease.close();
399
+ }
400
+ }
401
+ async function restore(source, root, limits, sourceStopped) {
402
+ if (sourceStopped !== "source-stopped") throw Error("Explicit confirmation of the stopped source is required");
403
+ const manifest = validateOutputSnapshot(source, limits),
404
+ sourceSha = sha(manifestBytes(source));
405
+ assertOutputStateRoot(root);
406
+ // Empty or same-operation target only. An exclusive root lease excludes live SDK writers.
407
+ for (const name of fs.readdirSync(root))
408
+ if (![guardName, guardName + "-journal"].includes(name) && !fs.existsSync(path.join(root, markerName)))
409
+ throw Error("MQTT restore target is not empty; existing state is preserved");
410
+ const lease = rootLease(root, true),
411
+ marker = path.join(root, markerName),
412
+ stage = path.join(root, stageName);
413
+ try {
414
+ let prior;
415
+ if (fs.existsSync(marker)) {
416
+ prior = privateJson(marker);
417
+ if (
418
+ !exactKeys(prior, ["schemaVersion", "state", "sourceSha", "rootId"]) ||
419
+ prior.schemaVersion !== 1 ||
420
+ prior.sourceSha !== sourceSha ||
421
+ !["applying", "complete"].includes(prior.state)
422
+ )
423
+ fail();
424
+ }
425
+ const allowed = new Set([guardName, guardName + "-journal", markerName, stageName, ...manifest.queues.map((q) => q.path.split("/")[0])]);
426
+ for (const e of fs.readdirSync(root)) {
427
+ if (!allowed.has(e) || (!prior && ![guardName, guardName + "-journal"].includes(e)))
428
+ throw Error("MQTT restore target is not empty; existing state is preserved");
429
+ }
430
+ // A resumed restore never adopts undeclared children inside a known namespace.
431
+ for (const ns of new Set(manifest.queues.map((q) => q.path.split("/")[0]))) {
432
+ const dir = path.join(root, ns);
433
+ if (fs.existsSync(dir)) {
434
+ noLinks(dir);
435
+ check(dir, true);
436
+ for (const id of fs.readdirSync(dir)) if (!manifest.queues.some((q) => q.path === ns + "/" + id)) fail();
437
+ }
438
+ }
439
+ if (!prior) {
440
+ atomicJson(marker, { schemaVersion: 1, state: "applying", sourceSha, rootId: manifest.rootId });
441
+ fs.mkdirSync(stage, { mode: 0o700 });
442
+ } else if (prior.rootId !== manifest.rootId) fail();
443
+ lease.db.prepare("UPDATE metadata SET value=? WHERE key='id'").run(manifest.rootId);
444
+ for (const q of manifest.queues) {
445
+ const target = path.join(root, q.path),
446
+ file = path.join(target, "mqtt-outbox.sqlite");
447
+ noLinks(target);
448
+ if (fs.existsSync(target)) {
449
+ check(target, true);
450
+ if (fs.readdirSync(target).some((x) => x !== "mqtt-outbox.sqlite") || hashFile(file) !== q.sha256) fail();
451
+ continue;
452
+ }
453
+ if (prior?.state === "complete") fail();
454
+ const staged = path.join(stage, q.path);
455
+ fs.mkdirSync(staged, { recursive: true, mode: 0o700 });
456
+ const copy = path.join(staged, "mqtt-outbox.sqlite");
457
+ if (fs.existsSync(copy)) {
458
+ check(copy);
459
+ if (hashFile(copy) !== q.sha256) fail();
460
+ } else {
461
+ fs.copyFileSync(path.join(source, q.file), copy, fs.constants.COPYFILE_EXCL);
462
+ fs.chmodSync(copy, 0o600);
463
+ const fd = fs.openSync(copy, "r");
464
+ fs.fsyncSync(fd);
465
+ fs.closeSync(fd);
466
+ }
467
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
468
+ fs.renameSync(staged, target);
469
+ sync(path.dirname(target));
470
+ }
471
+ // Every restored file is rechecked before the root is declared usable.
472
+ for (const q of manifest.queues) if (hashFile(path.join(root, q.file)) !== q.sha256) fail();
473
+ lease.db.exec("COMMIT");
474
+ if (fs.existsSync(stage)) {
475
+ for (const ns of fs.readdirSync(stage)) {
476
+ const dir = path.join(stage, ns);
477
+ check(dir, true);
478
+ if (fs.readdirSync(dir).length) fail();
479
+ fs.rmdirSync(dir);
480
+ }
481
+ fs.rmdirSync(stage);
482
+ }
483
+ atomicJson(marker, { schemaVersion: 1, state: "complete", sourceSha, rootId: manifest.rootId });
484
+ return {
485
+ schemaVersion: 1,
486
+ state: "complete",
487
+ sourceSha,
488
+ queues: manifest.queues.length,
489
+ records: manifest.queues.reduce((n, q) => n + q.records, 0),
490
+ };
491
+ } finally {
492
+ lease.close();
493
+ }
494
+ }
495
+ export async function runOutputStateProgram(args) {
496
+ const [action, root, out, fixed, confirmation] = args,
497
+ limits = outputStateLimits(JSON.parse(fixed));
498
+ if (action === "capture") return capture(root, out, limits);
499
+ if (action === "validate") return validateOutputSnapshot(root, limits);
500
+ if (action === "restore") return restore(root, out, limits, confirmation);
501
+ fail();
502
+ }
503
+ export function outputStateMain(args = process.argv.slice(2)) {
504
+ return runOutputStateProgram(args)
505
+ .then((r) => process.stdout.write(JSON.stringify(r) + "\n"))
506
+ .catch(() => {
507
+ process.stderr.write("MQTT output state operation refused; preserve source and target for review\n");
508
+ process.exitCode = 1;
509
+ });
510
+ }
511
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await outputStateMain();
@@ -52,6 +52,8 @@ interface GenericAuthorization {
52
52
  readonly groupId: string;
53
53
  readonly groupProcessName: string;
54
54
  readonly namespace: string;
55
+ readonly generationId?: string;
56
+ readonly configurationGroupSha256?: string;
55
57
  }
56
58
  export type ProcessGroupAuthorization = legacy.HvAuthorization | GenericAuthorization;
57
59
  export type HvAuthorization = ProcessGroupAuthorization;
@@ -1 +1 @@
1
- {"version":3,"file":"process-group-authorization.d.ts","sourceRoot":"","sources":["../../src/runtime/process-group-authorization.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,MAAM,MAAM,+CAA+C,CAAC;AAexE,OAAO,EAAE,8BAA8B,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEpH,eAAO,MAAM,4BAA4B,4CAAsC,CAAC;AAahF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiB1C,CAAC;AACH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAC9F,eAAO,MAAM,0BAA0B;;;;;;;;;;;;kBAarC,CAAC;AACH,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AACD,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC,eAAe,GAAG,oBAAoB,CAAC;AACtF,MAAM,MAAM,eAAe,GAAG,yBAAyB,CAAC;AACxD,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,qBAAqB,CAAC;AACjE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;kBAA+B,CAAC;AAClE,eAAO,MAAM,kBAAkB,8BAA4B,CAAC;AAsC5D;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,GAAG,CAAC,MAAM,CAAC,CAgFzD;AACD,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAuGD,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gCAAgC,GAAG,yBAAyB,CAEjH;AACD,oFAAoF;AACpF,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,gCAAgC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAElH;AACD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,gCAAgC,GAAG,MAAM,CAI/F;AACD,eAAO,MAAM,kBAAkB,qCAA+B,CAAC;AAC/D,wBAAgB,sCAAsC,CACpD,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,cAAc,EAAE,MAAM,EACtB,UAAU,UAAQ,6BAUnB;AACD,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,yBAAyB,GAAG,SAAS,EAC5C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,IAAI,CAON;AACD,eAAO,MAAM,4BAA4B,+CAAyC,CAAC;AACnF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,yBAAyB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBjG;AACD,eAAO,MAAM,kBAAkB,qCAA+B,CAAC;AAC/D,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,yBAAyB,EAChC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,IAAI,CAsBN;AACD,eAAO,MAAM,oBAAoB,uCAAiC,CAAC"}
1
+ {"version":3,"file":"process-group-authorization.d.ts","sourceRoot":"","sources":["../../src/runtime/process-group-authorization.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,MAAM,MAAM,+CAA+C,CAAC;AAiBxE,OAAO,EAAE,8BAA8B,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEpH,eAAO,MAAM,4BAA4B,4CAAsC,CAAC;AAahF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiB1C,CAAC;AACH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAC9F,eAAO,MAAM,0BAA0B;;;;;;;;;;;;kBAarC,CAAC;AACH,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAC5C;AACD,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC,eAAe,GAAG,oBAAoB,CAAC;AACtF,MAAM,MAAM,eAAe,GAAG,yBAAyB,CAAC;AACxD,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,qBAAqB,CAAC;AACjE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;kBAA+B,CAAC;AAClE,eAAO,MAAM,kBAAkB,8BAA4B,CAAC;AAiD5D;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,GAAG,CAAC,MAAM,CAAC,CAiFzD;AACD,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA4GD,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gCAAgC,GAAG,yBAAyB,CAEjH;AACD,oFAAoF;AACpF,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,gCAAgC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAElH;AACD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,gCAAgC,GAAG,MAAM,CAI/F;AACD,eAAO,MAAM,kBAAkB,qCAA+B,CAAC;AAC/D,wBAAgB,sCAAsC,CACpD,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,cAAc,EAAE,MAAM,EACtB,UAAU,UAAQ,6BAUnB;AACD,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,yBAAyB,GAAG,SAAS,EAC5C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,IAAI,CAON;AACD,eAAO,MAAM,4BAA4B,+CAAyC,CAAC;AACnF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,yBAAyB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBjG;AACD,eAAO,MAAM,kBAAkB,qCAA+B,CAAC;AAC/D,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,yBAAyB,EAChC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,IAAI,CAwBN;AACD,eAAO,MAAM,oBAAoB,uCAAiC,CAAC"}
@@ -2,8 +2,9 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { z } from "zod";
4
4
  import * as legacy from "./compatibility/energenti-authorization-v1.js";
5
+ import { configurationGroupAuthorizationSchema, configurationGroupPolicyIdentity, verifyConfigurationGroupPolicy } from "./configuration-group-authorization.js";
5
6
  import { normalizeProcessGroupConfiguration, processGroupDescriptor, resolvedProcessGroupChannels, } from "./process-group-contract.js";
6
- import { configurationAuthorizationHash, groupArtifactHash, groupChannelsHash, readProcessGroupResource as read, sha256 as sha, } from "./process-group-resources.js";
7
+ import { configurationAuthorizationHash, groupArtifactHash, groupChannelsHash, readProcessGroupResource as read, sha256 as sha, assertPrivateProcessGroupDirectory, } from "./process-group-resources.js";
7
8
  export { configurationAuthorizationHash, groupArtifactHash, groupChannelsHash } from "./process-group-resources.js";
8
9
  export const hasProcessGroupAuthorization = legacy.hasProcessGroupAuthorization;
9
10
  const digest = z.string().regex(/^[a-f0-9]{64}$/), id = z.string().regex(/^[a-z][a-z0-9-]{0,99}$/);
@@ -52,14 +53,7 @@ export const processGroupManifestSchema = z.strictObject({
52
53
  export const hvAuthorizationSchema = legacy.hvAuthorizationSchema;
53
54
  export const productionGroupIds = legacy.productionGroupIds;
54
55
  const issued = new WeakSet();
55
- function privateDirectory(dir) {
56
- for (let p = dir; p !== path.dirname(p); p = path.dirname(p))
57
- if (fs.lstatSync(p).isSymbolicLink())
58
- throw Error("Linked policy directory");
59
- const s = fs.lstatSync(dir);
60
- if (!s.isDirectory() || s.uid !== process.getuid?.() || (s.mode & 0o077) !== 0)
61
- throw Error("Insecure policy directory");
62
- }
56
+ const privateDirectory = assertPrivateProcessGroupDirectory;
63
57
  function registryRoot(root, dir) {
64
58
  if (!path.isAbsolute(root) || path.resolve(root) !== root || path.resolve(dir) !== dir)
65
59
  throw Error("Invalid policy registry");
@@ -74,6 +68,24 @@ function registryRoot(root, dir) {
74
68
  return relative.split("/")[0];
75
69
  }
76
70
  function selectedFile(pkg, raw, root, explicit) {
71
+ const configured = configurationGroupPolicyIdentity(pkg, raw);
72
+ if (configured) {
73
+ if (explicit)
74
+ return explicit;
75
+ const directory = process.env["UNS_PROCESS_GROUP_AUTHORIZATION_DIRECTORY"];
76
+ if (directory) {
77
+ const selected = registryRoot(root, directory);
78
+ if (selected !== "*" && selected !== configured.namespace)
79
+ throw Error("Foreign runtime registry");
80
+ const registry = selected === "*" ? path.join(root, configured.namespace, "authorizations") : directory;
81
+ privateDirectory(registry);
82
+ return path.join(registry, configured.generationId + ".json");
83
+ }
84
+ const single = process.env["UNS_PROCESS_GROUP_AUTHORIZATION_FILE"];
85
+ if (!single)
86
+ throw Error("No selected policy");
87
+ return single;
88
+ }
77
89
  const descriptor = processGroupDescriptor(pkg);
78
90
  if (!descriptor)
79
91
  throw Error("Missing runtime contract");
@@ -175,10 +187,11 @@ export function configuredGroupProcessNames() {
175
187
  const result = new Set();
176
188
  for (const file of files) {
177
189
  const raw = JSON.parse(read(file, true).toString());
178
- const p = raw.schemaVersion === 3
190
+ const p = raw.schemaVersion === 4 ? configurationGroupAuthorizationSchema.parse(raw) : raw.schemaVersion === 3
179
191
  ? processGroupAuthorizationSchema.parse(raw)
180
192
  : z.union([legacy.hvAuthorizationSchema, legacy.processGroupAuthorizationSchema]).parse(raw);
181
- const { namespace, group } = p.schemaVersion === 3 ? { namespace: p.namespace, group: p.groupId } : legacy.legacyPolicyIdentity(p);
193
+ const { namespace, group } = p.schemaVersion === 4 ? { namespace: p.namespace, group: p.generationId }
194
+ : p.schemaVersion === 3 ? { namespace: p.namespace, group: p.groupId } : legacy.legacyPolicyIdentity(p);
182
195
  if (p.stateRoot !== root ||
183
196
  file !== path.join(root, namespace, "authorizations", group + ".json") ||
184
197
  p.directory !== path.join(root, namespace, "admission", group))
@@ -200,6 +213,12 @@ function verifyRuntimePolicy(context, candidate) {
200
213
  if (!root || !context.controllerName)
201
214
  throw Error();
202
215
  const file = selectedFile(context.packageRoot, context.rawConfiguration, root, context.file), bytes = candidate ?? read(file, true), unparsed = JSON.parse(bytes.toString());
216
+ if (unparsed.schemaVersion === 4) {
217
+ const result = verifyConfigurationGroupPolicy(context, bytes, root, file);
218
+ if (!candidate)
219
+ issued.add(result);
220
+ return result;
221
+ }
203
222
  if (unparsed.schemaVersion !== 3) {
204
223
  if (candidate)
205
224
  throw Error("Only generic policies can be staged");
@@ -331,7 +350,7 @@ export function assertProcessGroupStateMount(value, root) {
331
350
  return;
332
351
  }
333
352
  assertProcessGroupJournalAuthorization(value, value.directory, value.journalId ?? undefined);
334
- if (process.platform !== "linux" || !("namespace" in value) || value.directory !== path.join(root, value.namespace, "admission", value.groupId))
353
+ if (process.platform !== "linux" || !("namespace" in value) || value.directory !== path.join(root, value.namespace, "admission", value.generationId ?? value.groupId))
335
354
  throw Error("Process group requires its reviewed Linux state mount");
336
355
  const mount = fs
337
356
  .readFileSync("/proc/self/mountinfo", "utf8")
@@ -361,6 +380,9 @@ export function assertProcessGroupTargetLaunch(value, dir, operationId, launchId
361
380
  !z.string().uuid().safeParse(r.lastLaunch?.requestId).success ||
362
381
  !digest.safeParse(r.configurationChecksum).success)
363
382
  throw Error();
383
+ if ("configurationGroupSha256" in value && value.configurationGroupSha256 !== undefined &&
384
+ (r.configurationGroupSha256 !== value.configurationGroupSha256 || r.generationId !== value.generationId))
385
+ throw Error();
364
386
  }
365
387
  catch {
366
388
  throw Error("Target requires its exact reviewed group reservation and launch identity");