@sentropic/h2a-runtime 0.90.2 → 0.91.0

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 (69) hide show
  1. package/dist/cli-help-groups.d.ts +1 -1
  2. package/dist/cli-help-groups.d.ts.map +1 -1
  3. package/dist/cli-help-groups.js +5 -0
  4. package/dist/cli-help-groups.js.map +1 -1
  5. package/dist/convsync.d.ts +1 -1
  6. package/dist/convsync.d.ts.map +1 -1
  7. package/dist/convsync.js +14 -2
  8. package/dist/convsync.js.map +1 -1
  9. package/dist/identity-cull/cull.d.ts +167 -0
  10. package/dist/identity-cull/cull.d.ts.map +1 -0
  11. package/dist/identity-cull/cull.js +924 -0
  12. package/dist/identity-cull/cull.js.map +1 -0
  13. package/dist/index.d.ts +13 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +158 -129
  16. package/dist/index.js.map +1 -1
  17. package/dist/llm-gateway-runtime/accounts.d.ts +1 -1
  18. package/dist/llm-gateway-runtime/accounts.d.ts.map +1 -1
  19. package/dist/llm-gateway-runtime/accounts.js +12 -4
  20. package/dist/llm-gateway-runtime/accounts.js.map +1 -1
  21. package/dist/llm-gateway-runtime/model-catalog.d.ts.map +1 -1
  22. package/dist/llm-gateway-runtime/model-catalog.js +33 -3
  23. package/dist/llm-gateway-runtime/model-catalog.js.map +1 -1
  24. package/dist/llm-gateway-runtime/proxy-anthropic.d.ts.map +1 -1
  25. package/dist/llm-gateway-runtime/proxy-anthropic.js +27 -9
  26. package/dist/llm-gateway-runtime/proxy-anthropic.js.map +1 -1
  27. package/dist/llm-gateway-runtime/proxy-cloud-code.d.ts +11 -0
  28. package/dist/llm-gateway-runtime/proxy-cloud-code.d.ts.map +1 -0
  29. package/dist/llm-gateway-runtime/proxy-cloud-code.js +215 -0
  30. package/dist/llm-gateway-runtime/proxy-cloud-code.js.map +1 -0
  31. package/dist/llm-gateway-runtime/proxy-openai.d.ts.map +1 -1
  32. package/dist/llm-gateway-runtime/proxy-openai.js +0 -3
  33. package/dist/llm-gateway-runtime/proxy-openai.js.map +1 -1
  34. package/dist/llm-gateway-runtime/sticky.d.ts +15 -6
  35. package/dist/llm-gateway-runtime/sticky.d.ts.map +1 -1
  36. package/dist/llm-gateway-runtime/sticky.js +104 -29
  37. package/dist/llm-gateway-runtime/sticky.js.map +1 -1
  38. package/dist/llm-mesh.d.ts +31 -7
  39. package/dist/llm-mesh.d.ts.map +1 -1
  40. package/dist/llm-mesh.js +104 -73
  41. package/dist/llm-mesh.js.map +1 -1
  42. package/dist/migrate.d.ts.map +1 -1
  43. package/dist/migrate.js +4 -3
  44. package/dist/migrate.js.map +1 -1
  45. package/dist/prompt-delivery.d.ts +9 -0
  46. package/dist/prompt-delivery.d.ts.map +1 -1
  47. package/dist/prompt-delivery.js +30 -3
  48. package/dist/prompt-delivery.js.map +1 -1
  49. package/dist/relaunch.d.ts +74 -10
  50. package/dist/relaunch.d.ts.map +1 -1
  51. package/dist/relaunch.js +120 -18
  52. package/dist/relaunch.js.map +1 -1
  53. package/dist/restore.d.ts +32 -36
  54. package/dist/restore.d.ts.map +1 -1
  55. package/dist/restore.js +125 -77
  56. package/dist/restore.js.map +1 -1
  57. package/dist/session-class.d.ts +12 -0
  58. package/dist/session-class.d.ts.map +1 -1
  59. package/dist/session-class.js +15 -0
  60. package/dist/session-class.js.map +1 -1
  61. package/dist/tmux.d.ts +7 -3
  62. package/dist/tmux.d.ts.map +1 -1
  63. package/dist/tmux.js +23 -6
  64. package/dist/tmux.js.map +1 -1
  65. package/package.json +2 -1
  66. package/dist/llm-gateway-runtime/proxy-gemini.d.ts +0 -82
  67. package/dist/llm-gateway-runtime/proxy-gemini.d.ts.map +0 -1
  68. package/dist/llm-gateway-runtime/proxy-gemini.js +0 -523
  69. package/dist/llm-gateway-runtime/proxy-gemini.js.map +0 -1
@@ -0,0 +1,924 @@
1
+ /**
2
+ * Read-only DEF identity-cull analysis for SPEC #156.
3
+ *
4
+ * This module intentionally has no active-store mutation path. It does,
5
+ * however, implement and verify the canonical binding-fence + held-descriptor
6
+ * CAS needed by a future, separately owner-authorized executor.
7
+ */
8
+ import { createHash, randomUUID } from "node:crypto";
9
+ import { closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, unlinkSync, writeFileSync, } from "node:fs";
10
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
11
+ const PIN_ACTORS = [
12
+ "claude:h2a:e3c21fe83da3",
13
+ "claude:h2a:c18853e319ea",
14
+ "claude:h2a:87db03b72762",
15
+ "e3c21fe8-3da3-4cf3-a837-6fdbadda8d95",
16
+ "c18853e3-19ea-410f-bba8-88f69d97d9b5",
17
+ "87db03b7-2762-43fb-8977-8dd67f625c82",
18
+ ];
19
+ const LIVE_DEF_CONTROLS = [
20
+ "claude:h2a:16f6e26295a3",
21
+ "claude:h2a:c3d1621ed118",
22
+ "claude:h2a:8b329a6c9c31",
23
+ ];
24
+ const IDENTITY_BINDING_FENCE_PROTOCOL = "identity-binding-fence-v1";
25
+ /**
26
+ * #160 only stages the §4-§6 proof packet. Per SPEC #156 §7.1 and §8, the
27
+ * executor—not staging—owns the final compare-to-rename critical section.
28
+ * A real staging-to-executor handoff transfers the held fence and descriptor;
29
+ * staging must never release and then reacquire that fence around a rename.
30
+ */
31
+ const EXECUTOR_FENCE_HANDOFF_CONTRACT = {
32
+ specReference: "SPEC #156 §7.1 and §8",
33
+ executorScope: "The executor holds the canonical fence exclusively from the final held-descriptor compare through descriptor-relative rename and read-back.",
34
+ transferRule: "A staging-to-executor handoff transmits the held fence and descriptor; staging must not release and reacquire them around the rename.",
35
+ };
36
+ function sha256(bytes) {
37
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
38
+ }
39
+ function isRecord(value) {
40
+ return value !== null && typeof value === "object" && !Array.isArray(value);
41
+ }
42
+ function requiredString(record, key) {
43
+ const value = record[key];
44
+ return typeof value === "string" && value.length > 0 ? value : undefined;
45
+ }
46
+ function parseBindingLine(bytes, lineNumber) {
47
+ const fingerprint = sha256(bytes);
48
+ const exactLineBase64 = bytes.toString("base64");
49
+ let text = bytes.toString("utf8");
50
+ if (text.endsWith("\n"))
51
+ text = text.slice(0, -1);
52
+ if (text.endsWith("\r"))
53
+ text = text.slice(0, -1);
54
+ if (text.length === 0) {
55
+ return { kind: "malformed", lineNumber, rowFingerprint: fingerprint, exactLineBase64, parseError: "EMPTY_JSONL_ROW" };
56
+ }
57
+ try {
58
+ const parsed = JSON.parse(text);
59
+ if (!isRecord(parsed))
60
+ throw new Error("JSON_VALUE_NOT_OBJECT");
61
+ const host = requiredString(parsed, "host");
62
+ const providerSessionId = requiredString(parsed, "providerSessionId");
63
+ const workspaceId = requiredString(parsed, "workspaceId");
64
+ const instance = requiredString(parsed, "instance");
65
+ const agentUuid = requiredString(parsed, "agentUuid");
66
+ const at = requiredString(parsed, "at");
67
+ if (!host || !providerSessionId || !workspaceId || !instance || !agentUuid || !at) {
68
+ throw new Error("BINDING_SCHEMA_INVALID");
69
+ }
70
+ return {
71
+ kind: "valid",
72
+ lineNumber,
73
+ rowFingerprint: fingerprint,
74
+ exactLineBase64,
75
+ binding: { host, providerSessionId, workspaceId, instance, agentUuid, at },
76
+ };
77
+ }
78
+ catch (error) {
79
+ return {
80
+ kind: "malformed",
81
+ lineNumber,
82
+ rowFingerprint: fingerprint,
83
+ exactLineBase64,
84
+ parseError: error instanceof Error ? error.message : "JSON_PARSE_FAILED",
85
+ };
86
+ }
87
+ }
88
+ function parseExactRows(bytes) {
89
+ const rows = [];
90
+ let start = 0;
91
+ let lineNumber = 1;
92
+ for (let offset = 0; offset < bytes.length; offset += 1) {
93
+ if (bytes[offset] !== 0x0a)
94
+ continue;
95
+ rows.push(parseBindingLine(bytes.subarray(start, offset + 1), lineNumber));
96
+ start = offset + 1;
97
+ lineNumber += 1;
98
+ }
99
+ if (start < bytes.length)
100
+ rows.push(parseBindingLine(bytes.subarray(start), lineNumber));
101
+ return rows;
102
+ }
103
+ function assertRegularNonSymlink(path, description) {
104
+ const stat = lstatSync(path);
105
+ if (stat.isSymbolicLink() || !stat.isFile()) {
106
+ throw new Error(`${description} must be a regular non-symlink file: ${path}`);
107
+ }
108
+ }
109
+ function captureBindings(root) {
110
+ const canonicalRoot = realpathSync(root);
111
+ const identityDir = join(canonicalRoot, "identity");
112
+ const identityStat = lstatSync(identityDir);
113
+ if (identityStat.isSymbolicLink() || !identityStat.isDirectory()) {
114
+ throw new Error(`DEF identity directory must be a real directory: ${identityDir}`);
115
+ }
116
+ const bindingsPath = join(identityDir, "bindings.jsonl");
117
+ assertRegularNonSymlink(bindingsPath, "DEF bindings");
118
+ const fd = openSync(bindingsPath, "r");
119
+ try {
120
+ const before = fstatSync(fd);
121
+ const bytes = Buffer.alloc(before.size);
122
+ let read = 0;
123
+ while (read < bytes.length) {
124
+ const count = readSync(fd, bytes, read, bytes.length - read, read);
125
+ if (count === 0)
126
+ throw new Error("DEF bindings changed while being captured");
127
+ read += count;
128
+ }
129
+ const after = fstatSync(fd);
130
+ if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size) {
131
+ throw new Error("DEF bindings descriptor changed while being captured");
132
+ }
133
+ return {
134
+ canonicalPath: realpathSync(bindingsPath),
135
+ device: Number(before.dev),
136
+ inode: Number(before.ino),
137
+ size: before.size,
138
+ lineCount: parseExactRows(bytes).length,
139
+ sha256: sha256(bytes),
140
+ bytes,
141
+ };
142
+ }
143
+ finally {
144
+ closeSync(fd);
145
+ }
146
+ }
147
+ function isInside(path, root) {
148
+ const result = relative(root, path);
149
+ return result === "" || (!result.startsWith("..") && !isAbsolute(result));
150
+ }
151
+ function descriptorPath(fd, name) {
152
+ if (name !== undefined && (name.length === 0 || name.includes(sep) || name === "." || name === "..")) {
153
+ throw new Error(`unsafe descriptor-relative packet name: ${name}`);
154
+ }
155
+ return name === undefined ? `/proc/self/fd/${fd}` : `/proc/self/fd/${fd}/${name}`;
156
+ }
157
+ function openNoFollowDirectory(path) {
158
+ const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
159
+ try {
160
+ if (!fstatSync(fd).isDirectory())
161
+ throw new Error(`packet path component is not a directory: ${path}`);
162
+ return fd;
163
+ }
164
+ catch (error) {
165
+ closeSync(fd);
166
+ throw error;
167
+ }
168
+ }
169
+ function assertDirectoryOutsideProtectedRoots(fd, defRoot, pinRoot) {
170
+ const canonicalPath = realpathSync(descriptorPath(fd));
171
+ if (isInside(canonicalPath, defRoot) || isInside(canonicalPath, pinRoot)) {
172
+ throw new Error("packet output must be outside both DEF and PIN roots");
173
+ }
174
+ return canonicalPath;
175
+ }
176
+ function capturedDirectoryDevice(path, description) {
177
+ const fd = openNoFollowDirectory(path);
178
+ try {
179
+ const stat = fstatSync(fd);
180
+ if (!stat.isDirectory())
181
+ throw new Error(`${description} must be a directory: ${path}`);
182
+ return Number(stat.dev);
183
+ }
184
+ finally {
185
+ closeSync(fd);
186
+ }
187
+ }
188
+ function assertDistinctPacketDevice(packetDevice, defDevice, pinDevice) {
189
+ if (packetDevice === defDevice || packetDevice === pinDevice) {
190
+ throw new Error("packet output must be on a filesystem device distinct from both DEF and PIN");
191
+ }
192
+ }
193
+ /**
194
+ * A directory descriptor follows its inode across rename. Its device is
195
+ * therefore a structural boundary: a packet directory on a device distinct
196
+ * from DEF and PIN cannot be renamed into either protected tree (EXDEV).
197
+ * Re-fstat the held descriptor before mutations so a visible mount/device
198
+ * change fails closed; never re-resolve a pathname as a containment check.
199
+ */
200
+ function assertPacketWriteContainment(directory, kind, name, phase) {
201
+ const attempt = { kind, name, packetRoot: directory.packetRoot, phase };
202
+ directory.beforePacketWrite?.(attempt);
203
+ const device = Number(fstatSync(directory.fd).dev);
204
+ if (device !== directory.packetDevice) {
205
+ throw new Error("packet write aborted: held packet directory device changed");
206
+ }
207
+ assertDistinctPacketDevice(device, directory.defDevice, directory.pinDevice);
208
+ directory.afterPacketWriteConfinement?.(attempt);
209
+ }
210
+ /**
211
+ * Create and pin a fresh packet directory without trusting the supplied path
212
+ * after traversal begins. Every component is lstat'd and opened with
213
+ * O_NOFOLLOW relative to the descriptor for its already-pinned parent; packet
214
+ * members are subsequently opened relative to the returned descriptor only.
215
+ */
216
+ function openPacketOutputDirectory(outputDir, defRoot, pinRoot) {
217
+ const absoluteOutput = resolve(outputDir);
218
+ const components = relative(sep, absoluteOutput).split(sep).filter(Boolean);
219
+ if (components.length === 0)
220
+ throw new Error("packet output directory must be a new non-root directory");
221
+ const defDevice = capturedDirectoryDevice(defRoot, "DEF root");
222
+ const pinDevice = capturedDirectoryDevice(pinRoot, "PIN root");
223
+ let currentFd = openNoFollowDirectory(sep);
224
+ try {
225
+ for (const [index, component] of components.entries()) {
226
+ const componentPath = descriptorPath(currentFd, component);
227
+ const isFinalComponent = index === components.length - 1;
228
+ let createdFinalComponent = false;
229
+ let stat;
230
+ try {
231
+ stat = lstatSync(componentPath);
232
+ }
233
+ catch (error) {
234
+ const code = error.code;
235
+ if (code !== "ENOENT" || !isFinalComponent) {
236
+ throw new Error(`packet output path has a missing or inaccessible ancestor: ${absoluteOutput}`);
237
+ }
238
+ assertDirectoryOutsideProtectedRoots(currentFd, defRoot, pinRoot);
239
+ assertDistinctPacketDevice(Number(fstatSync(currentFd).dev), defDevice, pinDevice);
240
+ mkdirSync(componentPath, { recursive: false, mode: 0o700 });
241
+ createdFinalComponent = true;
242
+ stat = lstatSync(componentPath);
243
+ }
244
+ if (stat.isSymbolicLink()) {
245
+ throw new Error(`packet output path refuses symlink component: ${component}`);
246
+ }
247
+ if (!stat.isDirectory()) {
248
+ throw new Error(`packet output path component is not a directory: ${component}`);
249
+ }
250
+ if (isFinalComponent && !createdFinalComponent) {
251
+ throw new Error(`packet output directory already exists: ${absoluteOutput}`);
252
+ }
253
+ const nextFd = openNoFollowDirectory(componentPath);
254
+ closeSync(currentFd);
255
+ currentFd = nextFd;
256
+ }
257
+ const canonicalPath = assertDirectoryOutsideProtectedRoots(currentFd, defRoot, pinRoot);
258
+ const packetDevice = Number(fstatSync(currentFd).dev);
259
+ assertDistinctPacketDevice(packetDevice, defDevice, pinDevice);
260
+ return {
261
+ fd: currentFd,
262
+ packetRoot: canonicalPath,
263
+ defRoot,
264
+ pinRoot,
265
+ packetDevice,
266
+ defDevice,
267
+ pinDevice,
268
+ };
269
+ }
270
+ catch (error) {
271
+ closeSync(currentFd);
272
+ throw error;
273
+ }
274
+ }
275
+ function createPacketDirectory(parent, name) {
276
+ assertPacketWriteContainment(parent, "directory", name, "before-create");
277
+ const path = descriptorPath(parent.fd, name);
278
+ mkdirSync(path, { recursive: false, mode: 0o700 });
279
+ const fd = openNoFollowDirectory(path);
280
+ try {
281
+ const device = Number(fstatSync(fd).dev);
282
+ if (device !== parent.packetDevice) {
283
+ throw new Error("packet directory creation aborted: held packet directory device changed");
284
+ }
285
+ assertDistinctPacketDevice(device, parent.defDevice, parent.pinDevice);
286
+ return {
287
+ fd,
288
+ packetRoot: parent.packetRoot,
289
+ defRoot: parent.defRoot,
290
+ pinRoot: parent.pinRoot,
291
+ packetDevice: parent.packetDevice,
292
+ defDevice: parent.defDevice,
293
+ pinDevice: parent.pinDevice,
294
+ ...(parent.beforePacketWrite === undefined ? {} : { beforePacketWrite: parent.beforePacketWrite }),
295
+ ...(parent.afterPacketWriteConfinement === undefined
296
+ ? {}
297
+ : { afterPacketWriteConfinement: parent.afterPacketWriteConfinement }),
298
+ };
299
+ }
300
+ catch (error) {
301
+ closeSync(fd);
302
+ throw error;
303
+ }
304
+ }
305
+ function writePacketFile(directory, name, bytes, mode = 0o600) {
306
+ // Opening with O_CREAT is itself a mutation; enforce structural confinement first.
307
+ assertPacketWriteContainment(directory, "file", name, "before-create");
308
+ const fd = openSync(descriptorPath(directory.fd, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, mode);
309
+ try {
310
+ // Re-fstat detects a visible mount/device change before bytes are written.
311
+ assertPacketWriteContainment(directory, "file", name, "before-write");
312
+ writeFileSync(fd, bytes);
313
+ fsyncSync(fd);
314
+ }
315
+ finally {
316
+ closeSync(fd);
317
+ }
318
+ }
319
+ function readPacketFile(directory, name) {
320
+ const fd = openSync(descriptorPath(directory.fd, name), constants.O_RDONLY | constants.O_NOFOLLOW);
321
+ try {
322
+ const stat = fstatSync(fd);
323
+ if (!stat.isFile())
324
+ throw new Error(`packet member is not a regular file: ${name}`);
325
+ return readFileSync(fd);
326
+ }
327
+ finally {
328
+ closeSync(fd);
329
+ }
330
+ }
331
+ function readOptionalPinBindings(pinRoot) {
332
+ try {
333
+ const canonicalPin = realpathSync(pinRoot);
334
+ const candidates = [
335
+ join(canonicalPin, "identity", "bindings.jsonl"),
336
+ join(canonicalPin, ".h2a", "identity", "bindings.jsonl"),
337
+ ];
338
+ const path = candidates.find((candidate) => existsSync(candidate));
339
+ if (!path)
340
+ return { reason: "PIN_BINDINGS_UNAVAILABLE" };
341
+ assertRegularNonSymlink(path, "PIN bindings");
342
+ const root = path.endsWith(join(".h2a", "identity", "bindings.jsonl"))
343
+ ? join(canonicalPin, ".h2a")
344
+ : canonicalPin;
345
+ return { snapshot: captureBindings(root), sourcePath: path };
346
+ }
347
+ catch (error) {
348
+ return { reason: error instanceof Error ? `PIN_BINDINGS_ERROR:${error.message}` : "PIN_BINDINGS_ERROR" };
349
+ }
350
+ }
351
+ class UnionFind {
352
+ parent;
353
+ constructor(size) {
354
+ this.parent = Array.from({ length: size }, (_value, index) => index);
355
+ }
356
+ find(index) {
357
+ const parent = this.parent[index];
358
+ if (parent === undefined)
359
+ throw new Error(`invalid union-find index ${index}`);
360
+ if (parent === index)
361
+ return index;
362
+ const root = this.find(parent);
363
+ this.parent[index] = root;
364
+ return root;
365
+ }
366
+ union(left, right) {
367
+ const leftRoot = this.find(left);
368
+ const rightRoot = this.find(right);
369
+ if (leftRoot !== rightRoot)
370
+ this.parent[rightRoot] = leftRoot;
371
+ }
372
+ }
373
+ function componentize(rows) {
374
+ const unionFind = new UnionFind(rows.length);
375
+ const edges = [];
376
+ const byInstance = new Map();
377
+ const byUuid = new Map();
378
+ const byProviderSession = new Map();
379
+ const joinOn = (kind, key, index, map) => {
380
+ const first = map.get(key);
381
+ if (first === undefined) {
382
+ map.set(key, index);
383
+ return;
384
+ }
385
+ unionFind.union(first, index);
386
+ edges.push({ kind, leftLine: rows[first].lineNumber, rightLine: rows[index].lineNumber });
387
+ };
388
+ for (const [index, row] of rows.entries()) {
389
+ if (row.kind !== "valid")
390
+ continue;
391
+ joinOn("instance", row.binding.instance, index, byInstance);
392
+ joinOn("agentUuid", row.binding.agentUuid, index, byUuid);
393
+ joinOn("providerSession", `${row.binding.host}\u0000${row.binding.providerSessionId}`, index, byProviderSession);
394
+ }
395
+ const grouped = new Map();
396
+ for (const [index, row] of rows.entries()) {
397
+ const root = unionFind.find(index);
398
+ const members = grouped.get(root);
399
+ if (members)
400
+ members.push(row);
401
+ else
402
+ grouped.set(root, [row]);
403
+ }
404
+ return [...grouped.values()]
405
+ .map((members) => {
406
+ const lineNumbers = new Set(members.map((row) => row.lineNumber));
407
+ const memberEdges = edges.filter((edge) => lineNumbers.has(edge.leftLine) && lineNumbers.has(edge.rightLine));
408
+ const conflicts = [];
409
+ const uuidByInstance = new Map();
410
+ const instanceByUuid = new Map();
411
+ for (const row of members) {
412
+ if (row.kind === "malformed") {
413
+ conflicts.push(`MALFORMED_ROW:${row.lineNumber}`);
414
+ continue;
415
+ }
416
+ const uuids = uuidByInstance.get(row.binding.instance) ?? new Set();
417
+ uuids.add(row.binding.agentUuid);
418
+ uuidByInstance.set(row.binding.instance, uuids);
419
+ const instances = instanceByUuid.get(row.binding.agentUuid) ?? new Set();
420
+ instances.add(row.binding.instance);
421
+ instanceByUuid.set(row.binding.agentUuid, instances);
422
+ }
423
+ for (const [instance, uuids] of uuidByInstance) {
424
+ if (uuids.size > 1)
425
+ conflicts.push(`CONFLICTING_INSTANCE_UUID:${instance}`);
426
+ }
427
+ for (const [uuid, instances] of instanceByUuid) {
428
+ if (instances.size > 1)
429
+ conflicts.push(`CONFLICTING_UUID_INSTANCE:${uuid}`);
430
+ }
431
+ const orderedRows = [...members].sort((left, right) => left.lineNumber - right.lineNumber);
432
+ return {
433
+ id: `component:${sha256(orderedRows.map((row) => row.rowFingerprint).join("\n")).slice(7, 31)}`,
434
+ rows: orderedRows,
435
+ edges: memberEdges,
436
+ conflicts: [...new Set(conflicts)].sort(),
437
+ };
438
+ })
439
+ .sort((left, right) => left.rows[0].lineNumber - right.rows[0].lineNumber);
440
+ }
441
+ function componentValues(component) {
442
+ const values = new Set();
443
+ for (const row of component.rows) {
444
+ if (row.kind !== "valid")
445
+ continue;
446
+ values.add(row.binding.instance);
447
+ values.add(row.binding.agentUuid);
448
+ }
449
+ return values;
450
+ }
451
+ function gateLife(component, evidence) {
452
+ const values = componentValues(component);
453
+ const life = evidence.life;
454
+ const live = life?.liveReferences?.some((reference) => values.has(reference)) ?? false;
455
+ if (live)
456
+ return { verdict: "FAIL", reason: "L_LIVE_EVIDENCE" };
457
+ if (!life?.complete)
458
+ return { verdict: "UNKNOWN", reason: "L_EVIDENCE_COVERAGE_MISSING" };
459
+ if (!life.noLife || !life.twoObservationsAgree || !life.resumeSafe) {
460
+ return { verdict: "UNKNOWN", reason: "L_POSITIVE_PROOF_MISSING" };
461
+ }
462
+ return { verdict: "PASS", reason: "L_POSITIVE_NO_LIFE_PROOF" };
463
+ }
464
+ function gateOwnership(component, evidence) {
465
+ const values = componentValues(component);
466
+ const ownership = evidence.ownership;
467
+ const owned = ownership?.ownedReferences?.some((reference) => values.has(reference)) ?? false;
468
+ if (owned)
469
+ return { verdict: "FAIL", reason: "O_OWNED_WORK_REFERENCE" };
470
+ if (!ownership?.complete)
471
+ return { verdict: "UNKNOWN", reason: "O_EVIDENCE_COVERAGE_MISSING" };
472
+ if (!ownership.noOwnedWork)
473
+ return { verdict: "UNKNOWN", reason: "O_POSITIVE_NO_WORK_PROOF_MISSING" };
474
+ return { verdict: "PASS", reason: "O_POSITIVE_NO_OWNED_WORK_PROOF" };
475
+ }
476
+ function ownerOrHumanReference(value) {
477
+ const lower = value.toLowerCase();
478
+ return lower.includes("focus:local-human") || lower.includes("owner") || lower.includes("human") || lower.includes("principal") || lower.includes("antoinefa");
479
+ }
480
+ function gateProtected(component, protectedValues, ownerAllowlist, pinAvailable, evidence) {
481
+ const values = componentValues(component);
482
+ if ([...values].some((value) => protectedValues.has(value))) {
483
+ return { verdict: "FAIL", reason: "P_PROTECTED_IDENTITY" };
484
+ }
485
+ if ([...values].some(ownerOrHumanReference))
486
+ return { verdict: "FAIL", reason: "P_OWNER_HUMAN_AMBIGUITY" };
487
+ if (ownerAllowlist.length === 0)
488
+ return { verdict: "UNKNOWN", reason: "P_OWNER_ALLOWLIST_UNRATIFIED" };
489
+ if (!pinAvailable)
490
+ return { verdict: "UNKNOWN", reason: "P_PIN_SNAPSHOT_UNAVAILABLE" };
491
+ if (!evidence.protectedSetComplete)
492
+ return { verdict: "UNKNOWN", reason: "P_PROTECTED_SOURCE_COVERAGE_MISSING" };
493
+ return { verdict: "PASS", reason: "P_POSITIVE_NOT_PROTECTED" };
494
+ }
495
+ function gateChurn(component) {
496
+ // S_R is intentionally not accepted by this implementation. A future root
497
+ // fix must introduce owner-ratified, independently verified continuity edges
498
+ // before a cull candidate can ever obtain C=PASS.
499
+ void component;
500
+ return { verdict: "UNKNOWN", reason: "C_MIGRATION_MAP_MISSING" };
501
+ }
502
+ function json(value) {
503
+ return `${JSON.stringify(value, null, 2)}\n`;
504
+ }
505
+ function writeJson(directory, name, value) {
506
+ writePacketFile(directory, name, json(value));
507
+ }
508
+ function writeJsonLines(directory, name, records) {
509
+ writePacketFile(directory, name, records.map((record) => JSON.stringify(record)).join(records.length > 0 ? "\n" : "") + (records.length > 0 ? "\n" : ""));
510
+ }
511
+ function decisionRecord(component, gates) {
512
+ const decision = Object.values(gates).every((gate) => gate.verdict === "PASS") ? "WOULD_CULL" : "KEEP";
513
+ const keepReasons = Object.values(gates)
514
+ .filter((gate) => gate.verdict !== "PASS")
515
+ .map((gate) => gate.reason);
516
+ return {
517
+ componentId: component.id,
518
+ rows: component.rows.map((row) => row.kind === "valid"
519
+ ? { lineNumber: row.lineNumber, rowFingerprint: row.rowFingerprint, exactLineBase64: row.exactLineBase64, binding: row.binding }
520
+ : { lineNumber: row.lineNumber, rowFingerprint: row.rowFingerprint, exactLineBase64: row.exactLineBase64, parseError: row.parseError }),
521
+ edges: component.edges,
522
+ conflicts: component.conflicts,
523
+ gates,
524
+ evidenceRefs: ["evidence/def-bindings.jsonl", "coverage.json", "dependencies.json"],
525
+ decision,
526
+ keepReasons,
527
+ };
528
+ }
529
+ function makeLookupReplay(rows) {
530
+ const lastWins = new Map();
531
+ for (const row of rows) {
532
+ if (row.kind !== "valid")
533
+ continue;
534
+ lastWins.set(`${row.binding.host}\u0000${row.binding.providerSessionId}`, row);
535
+ }
536
+ return {
537
+ semantics: "last-wins(host,providerSessionId)",
538
+ cullSetEmpty: true,
539
+ keys: [...lastWins.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([key, row]) => ({
540
+ key,
541
+ before: { lineNumber: row.lineNumber, rowFingerprint: row.rowFingerprint, instance: row.binding.instance },
542
+ after: { lineNumber: row.lineNumber, rowFingerprint: row.rowFingerprint, instance: row.binding.instance },
543
+ predecessorExposed: false,
544
+ legacyKeyRetired: false,
545
+ })),
546
+ };
547
+ }
548
+ function increment(histogram, reason) {
549
+ histogram[reason] = (histogram[reason] ?? 0) + 1;
550
+ }
551
+ /**
552
+ * Produce the analysis packet. This is intentionally the only runnable path
553
+ * exposed by the CLI: it reads DEF/PIN and writes a new packet outside both.
554
+ */
555
+ export function runIdentityCullDryRun(input) {
556
+ const canonicalDefRoot = realpathSync(input.defRoot);
557
+ const canonicalPinRoot = realpathSync(input.pinRoot);
558
+ if (isInside(canonicalDefRoot, canonicalPinRoot) || isInside(canonicalPinRoot, canonicalDefRoot)) {
559
+ throw new Error("DEF and PIN roots must be distinct non-overlapping canonical paths");
560
+ }
561
+ const snapshot = captureBindings(canonicalDefRoot);
562
+ const rows = parseExactRows(snapshot.bytes);
563
+ const components = componentize(rows);
564
+ const pin = readOptionalPinBindings(canonicalPinRoot);
565
+ const pinRows = "snapshot" in pin ? parseExactRows(pin.snapshot.bytes) : [];
566
+ const ownerAllowlist = [...(input.ownerAllowlist ?? [])].sort();
567
+ const protectedValues = new Set([...PIN_ACTORS, ...LIVE_DEF_CONTROLS, ...ownerAllowlist]);
568
+ for (const row of pinRows) {
569
+ if (row.kind !== "valid")
570
+ continue;
571
+ protectedValues.add(row.binding.instance);
572
+ protectedValues.add(row.binding.agentUuid);
573
+ }
574
+ const evidence = input.evidence ?? {};
575
+ const records = components.map((component) => decisionRecord(component, {
576
+ L: gateLife(component, evidence),
577
+ O: gateOwnership(component, evidence),
578
+ P: gateProtected(component, protectedValues, ownerAllowlist, "snapshot" in pin, evidence),
579
+ C: gateChurn(component),
580
+ }));
581
+ const wouldCull = records.filter((record) => record.decision === "WOULD_CULL");
582
+ // An implementation error must never widen a cull set. C is intentionally
583
+ // UNKNOWN in this release, but retain this assertion as a hard invariant.
584
+ if (wouldCull.length > 0)
585
+ throw new Error("refusing: CULL set is not authorized in this release");
586
+ const keep = records.filter((record) => record.decision === "KEEP");
587
+ if (records.length !== components.length || keep.length !== components.length) {
588
+ throw new Error("packet component reconciliation failed");
589
+ }
590
+ const keepReasonHistogram = {};
591
+ for (const record of keep) {
592
+ for (const reason of record.keepReasons)
593
+ increment(keepReasonHistogram, reason);
594
+ }
595
+ const packet = openPacketOutputDirectory(input.outputDir, canonicalDefRoot, canonicalPinRoot);
596
+ const packetWithWriteObserver = {
597
+ ...packet,
598
+ ...(input.beforePacketWrite === undefined ? {} : { beforePacketWrite: input.beforePacketWrite }),
599
+ ...(input.afterPacketWriteConfinement === undefined
600
+ ? {}
601
+ : { afterPacketWriteConfinement: input.afterPacketWriteConfinement }),
602
+ };
603
+ let evidenceDir;
604
+ try {
605
+ const evidence = createPacketDirectory(packetWithWriteObserver, "evidence");
606
+ evidenceDir = evidence;
607
+ writePacketFile(evidence, "def-bindings.jsonl", snapshot.bytes, 0o400);
608
+ if ("snapshot" in pin)
609
+ writePacketFile(evidence, "pin-bindings.jsonl", pin.snapshot.bytes, 0o400);
610
+ const now = (input.now ?? (() => new Date()))().toISOString();
611
+ const coverage = {
612
+ version: 1,
613
+ sources: [
614
+ { id: "def-bindings", status: "COMPLETE", parser: "strict-jsonl-v1", sha256: snapshot.sha256 },
615
+ { id: "pin-bindings", status: "snapshot" in pin ? "COMPLETE" : "MISSING", reason: "snapshot" in pin ? undefined : pin.reason },
616
+ { id: "owner-allowlist", status: ownerAllowlist.length > 0 ? "PRESENT_BUT_UNRATIFIED" : "MISSING" },
617
+ { id: "fleet-presence-process-mcp-resume", status: "MISSING" },
618
+ { id: "owned-work-and-attribution", status: "MISSING" },
619
+ { id: "attested-defect-window", status: "MISSING" },
620
+ { id: "S_R-root-fix-migration-map", status: "MISSING" },
621
+ ],
622
+ };
623
+ const dependencies = {
624
+ version: 1,
625
+ gates: {
626
+ L: ["fleet-presence-process-mcp-resume", "S_R-root-fix-migration-map"],
627
+ O: ["owned-work-and-attribution"],
628
+ P: ["pin-bindings", "owner-allowlist"],
629
+ C: ["attested-defect-window", "S_R-root-fix-migration-map"],
630
+ },
631
+ edges: ["instance", "agentUuid", "host+providerSessionId"],
632
+ expresslyNotEdges: ["name", "workspaceId"],
633
+ };
634
+ const runManifest = {
635
+ version: 1,
636
+ runId: randomUUID(),
637
+ capturedAt: now,
638
+ operator: input.operator ?? process.env.USER ?? "unknown",
639
+ mode: "dry-run",
640
+ def: { root: canonicalDefRoot, bindings: { path: snapshot.canonicalPath, device: snapshot.device, inode: snapshot.inode, size: snapshot.size, lineCount: snapshot.lineCount, sha256: snapshot.sha256 } },
641
+ pin: "snapshot" in pin
642
+ ? { root: canonicalPinRoot, bindings: { path: pin.sourcePath, sha256: pin.snapshot.sha256 } }
643
+ : { root: canonicalPinRoot, unavailable: pin.reason },
644
+ S_R_present: false,
645
+ executionHardGated: true,
646
+ fencePrimitiveProvided: true,
647
+ checkActExecutedByStaging: false,
648
+ "checkActIsExecutorScopePer_§7_1": true,
649
+ handoffContract: EXECUTOR_FENCE_HANDOFF_CONTRACT,
650
+ structuralWriterFence: {
651
+ protocol: IDENTITY_BINDING_FENCE_PROTOCOL,
652
+ canonicalLockPath: join(canonicalDefRoot, "identity", ".lock"),
653
+ epochRequired: true,
654
+ heldDescriptorCas: "fstat+full-read+fstat size/hash primitive for the executor's immediately-pre-rename check",
655
+ singleWriterVerification: "required before executor continuation; bypass inventory aborts",
656
+ },
657
+ packetWriteConfinement: {
658
+ strategy: "no-cross-device",
659
+ packetDevice: packet.packetDevice,
660
+ defDevice: packet.defDevice,
661
+ pinDevice: packet.pinDevice,
662
+ structuralGuarantee: "packet-root st_dev differs from DEF and PIN, so a packet directory or member cannot be renamed into either root",
663
+ mountChangeDetection: "before each create and byte write, fstat the held packet directory and abort if st_dev differs from its captured value",
664
+ residual: "Node exposes no openat2 RESOLVE_BENEATH binding; this guarantee covers rename containment while the captured device identities hold, and fstat can only detect mount changes that alter the held descriptor st_dev",
665
+ },
666
+ writeSet: ["packet output only"],
667
+ };
668
+ const positiveControls = {
669
+ version: 1,
670
+ pinActors: PIN_ACTORS.map((identity) => ({ identity, presentInPin: pinRows.some((row) => row.kind === "valid" && (row.binding.instance === identity || row.binding.agentUuid === identity)), absentFromWouldCull: true })),
671
+ liveDefActors: LIVE_DEF_CONTROLS.map((identity) => ({ identity, presentInDef: rows.some((row) => row.kind === "valid" && row.binding.instance === identity), absentFromWouldCull: true })),
672
+ ownerAllowlist: { count: ownerAllowlist.length, absentFromWouldCull: true, status: ownerAllowlist.length > 0 ? "UNRATIFIED" : "MISSING" },
673
+ quietSingleConversationMirror: { requiredResult: "KEEP", observedReason: "C_MIGRATION_MAP_MISSING" },
674
+ concurrentSameNameWorkspace: { joinRule: "name/workspaceId are not component edges", observedResult: "KEEP" },
675
+ fallbackAndOutsideWindow: { requiredResult: "KEEP", observedResult: "KEEP" },
676
+ };
677
+ const lookupReplay = makeLookupReplay(rows);
678
+ const summary = {
679
+ version: 1,
680
+ componentCount: components.length,
681
+ componentReconciliation: { decisions: records.length, wouldCull: wouldCull.length, keep: keep.length, reconciled: records.length === wouldCull.length + keep.length },
682
+ cullSetSize: wouldCull.length,
683
+ gateCounts: ["L", "O", "P", "C"].reduce((counts, name) => {
684
+ counts[name] = { PASS: 0, FAIL: 0, UNKNOWN: 0 };
685
+ for (const record of records) {
686
+ const gates = record.gates;
687
+ const gate = gates[name];
688
+ counts[name][gate.verdict] += 1;
689
+ }
690
+ return counts;
691
+ }, {}),
692
+ keepReasonHistogram,
693
+ S_R_present: false,
694
+ executionHardGated: true,
695
+ };
696
+ writeJson(packetWithWriteObserver, "run-manifest.json", runManifest);
697
+ writeJson(packetWithWriteObserver, "coverage.json", coverage);
698
+ writeJson(packetWithWriteObserver, "dependencies.json", dependencies);
699
+ writeJson(evidence, "acquisition.json", { defBindings: { path: snapshot.canonicalPath, sha256: snapshot.sha256, size: snapshot.size }, pinStatus: "snapshot" in pin ? "COMPLETE" : pin.reason });
700
+ writeJsonLines(packetWithWriteObserver, "decisions.jsonl", records);
701
+ writeJsonLines(packetWithWriteObserver, "would-cull.jsonl", wouldCull);
702
+ writeJsonLines(packetWithWriteObserver, "keep.jsonl", keep);
703
+ writeJson(packetWithWriteObserver, "lookup-replay.json", lookupReplay);
704
+ writeJson(packetWithWriteObserver, "positive-controls.json", positiveControls);
705
+ writeJson(packetWithWriteObserver, "summary.json", summary);
706
+ writePacketFile(packetWithWriteObserver, "report.txt", `DEF identity cull dry-run\ncomponents: ${components.length}\ncull-set: ${wouldCull.length}\nKEEP reasons: ${JSON.stringify(keepReasonHistogram)}\n`);
707
+ const artifactPaths = [
708
+ "run-manifest.json", "coverage.json", "dependencies.json", "evidence/def-bindings.jsonl", "evidence/acquisition.json", "decisions.jsonl", "would-cull.jsonl", "keep.jsonl", "lookup-replay.json", "positive-controls.json", "summary.json", "report.txt",
709
+ ...("snapshot" in pin ? ["evidence/pin-bindings.jsonl"] : []),
710
+ ];
711
+ writeJson(packetWithWriteObserver, "member-hashes.json", {
712
+ version: 1,
713
+ members: artifactPaths.map((path) => {
714
+ const [directory, name] = path.startsWith("evidence/")
715
+ ? [evidence, path.slice("evidence/".length)]
716
+ : [packet, path];
717
+ return { path, sha256: sha256(readPacketFile(directory, name)) };
718
+ }),
719
+ });
720
+ return {
721
+ packetDir: packet.packetRoot,
722
+ componentCount: components.length,
723
+ cullSetSize: wouldCull.length,
724
+ keepReasonHistogram,
725
+ summaryPath: join(packet.packetRoot, "summary.json"),
726
+ snapshot: { canonicalPath: snapshot.canonicalPath, size: snapshot.size, sha256: snapshot.sha256 },
727
+ };
728
+ }
729
+ finally {
730
+ if (evidenceDir)
731
+ closeSync(evidenceDir.fd);
732
+ closeSync(packetWithWriteObserver.fd);
733
+ }
734
+ }
735
+ /**
736
+ * The hard execution gate. It deliberately has no write side effects, even if
737
+ * supplied fixture values say that every prerequisite is true: this release has
738
+ * no owner GO and no descriptor-relative Node rename primitive.
739
+ */
740
+ export function refuseCullExecution(input = {}) {
741
+ const refusalCodes = [];
742
+ if (!input.ownerAuthorizationValid)
743
+ refusalCodes.push("OWNER_AUTHORIZATION_MISSING_OR_INVALID");
744
+ if (!input.migrationMapRatified)
745
+ refusalCodes.push("S_R_MISSING_OR_UNRATIFIED");
746
+ if (!input.verifiedNonEmptyCullSet)
747
+ refusalCodes.push("VERIFIED_NONEMPTY_CULL_SET_MISSING");
748
+ if (!input.approvedPacketMatches)
749
+ refusalCodes.push("APPROVED_PACKET_MISMATCH");
750
+ if (!input.rootFixDeployedAndObserved)
751
+ refusalCodes.push("ROOT_FIX_NOT_DEPLOYED_AND_OBSERVED");
752
+ if (!input.singleWriterInvariant)
753
+ refusalCodes.push("SINGLE_WRITER_INVARIANT_UNVERIFIED");
754
+ if (!input.osWriteConfinement)
755
+ refusalCodes.push("OS_WRITE_CONFINEMENT_UNVERIFIED");
756
+ if (!input.descriptorRelativeRenameAvailable)
757
+ refusalCodes.push("DESCRIPTOR_RELATIVE_RENAME_UNAVAILABLE");
758
+ refusalCodes.push("EXECUTION_DISABLED_PENDING_SEPARATE_OWNER_GO");
759
+ return { executed: false, refusalCodes };
760
+ }
761
+ /**
762
+ * Verify rather than construct the root-fix precondition. The only accepted
763
+ * binding writer is the deployed `reclaimOrMint` transaction, and every
764
+ * inventory entry which writes bindings must require this lock + epoch.
765
+ */
766
+ export function verifySingleWriterPrecondition(inventory) {
767
+ if (!inventory.complete)
768
+ return { valid: false, reason: "WRITER_INVENTORY_INCOMPLETE" };
769
+ const writers = inventory.paths.filter((path) => path.writesBindings);
770
+ if (writers.some((path) => path.canBypassFence || !path.requiresCanonicalFence || !path.fenceEpochRequired)) {
771
+ return { valid: false, reason: "BINDING_WRITE_BYPASS_PRESENT" };
772
+ }
773
+ if (writers.length !== 1
774
+ || writers[0].id !== "reclaimOrMint"
775
+ || writers[0].fenceProtocol !== IDENTITY_BINDING_FENCE_PROTOCOL) {
776
+ return { valid: false, reason: "CANONICAL_WRITER_PATH_INVALID" };
777
+ }
778
+ return { valid: true };
779
+ }
780
+ function compareHeldDescriptor(fd, expectedSize, expectedSha256) {
781
+ const before = fstatSync(fd);
782
+ if (before.size !== expectedSize)
783
+ return { state: "ABORTED", reason: "HELD_DESCRIPTOR_SIZE_MISMATCH" };
784
+ const bytes = Buffer.alloc(before.size);
785
+ let read = 0;
786
+ while (read < bytes.length) {
787
+ const count = readSync(fd, bytes, read, bytes.length - read, read);
788
+ if (count === 0)
789
+ return { state: "ABORTED", reason: "HELD_DESCRIPTOR_HASH_MISMATCH" };
790
+ read += count;
791
+ }
792
+ const after = fstatSync(fd);
793
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== expectedSize) {
794
+ return { state: "ABORTED", reason: "HELD_DESCRIPTOR_SIZE_MISMATCH" };
795
+ }
796
+ return sha256(bytes) === expectedSha256
797
+ ? { state: "READY" }
798
+ : { state: "ABORTED", reason: "HELD_DESCRIPTOR_HASH_MISMATCH" };
799
+ }
800
+ function fencePayload(fence) {
801
+ return JSON.stringify({
802
+ protocol: IDENTITY_BINDING_FENCE_PROTOCOL,
803
+ fenceEpoch: fence.epoch,
804
+ pid: process.pid,
805
+ acquiredAt: new Date().toISOString(),
806
+ });
807
+ }
808
+ function fenceIsHeld(fence) {
809
+ try {
810
+ const stat = lstatSync(fence.lockPath);
811
+ if (stat.isSymbolicLink() || !stat.isFile())
812
+ return false;
813
+ const payload = JSON.parse(readFileSync(fence.lockPath, "utf8"));
814
+ return isRecord(payload)
815
+ && payload.protocol === IDENTITY_BINDING_FENCE_PROTOCOL
816
+ && payload.fenceEpoch === fence.epoch
817
+ && payload.pid === process.pid;
818
+ }
819
+ catch {
820
+ return false;
821
+ }
822
+ }
823
+ function acquireCanonicalBindingFence(lockPath) {
824
+ const fence = { lockPath, epoch: randomUUID() };
825
+ let fd;
826
+ try {
827
+ fd = openSync(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
828
+ }
829
+ catch (error) {
830
+ if (error.code === "EEXIST")
831
+ return undefined;
832
+ throw error;
833
+ }
834
+ try {
835
+ writeFileSync(fd, fencePayload(fence));
836
+ fsyncSync(fd);
837
+ }
838
+ catch (error) {
839
+ closeSync(fd);
840
+ try {
841
+ unlinkSync(lockPath);
842
+ }
843
+ catch {
844
+ // A failure to clean up an owned fence is safer than proceeding.
845
+ }
846
+ throw error;
847
+ }
848
+ closeSync(fd);
849
+ return fence;
850
+ }
851
+ function releaseCanonicalBindingFence(fence) {
852
+ if (!fenceIsHeld(fence))
853
+ throw new Error("canonical binding fence ownership was lost");
854
+ unlinkSync(fence.lockPath);
855
+ }
856
+ /**
857
+ * Exercises the authoritative len/hash primitive over one held descriptor.
858
+ * This staging-only verification releases its test fence before it returns and
859
+ * never authorizes a caller to rename. Per SPEC #156 §7.1 and §8, an executor
860
+ * must instead retain its own transmitted fence and descriptor from its final
861
+ * compare through descriptor-relative rename and read-back.
862
+ */
863
+ export function verifyHeldDescriptorCas(input) {
864
+ if (!verifySingleWriterPrecondition(input.writerInventory).valid) {
865
+ return { state: "ABORTED", reason: "SINGLE_WRITER_PRECONDITION_FAILED" };
866
+ }
867
+ const canonicalBindingPath = realpathSync(input.bindingPath);
868
+ const fence = acquireCanonicalBindingFence(join(dirname(canonicalBindingPath), ".lock"));
869
+ if (!fence)
870
+ return { state: "ABORTED", reason: "CANONICAL_BINDING_FENCE_UNAVAILABLE" };
871
+ try {
872
+ if (!fenceIsHeld(fence))
873
+ return { state: "ABORTED", reason: "CANONICAL_BINDING_FENCE_LOST" };
874
+ const fd = openSync(canonicalBindingPath, constants.O_RDONLY | constants.O_NOFOLLOW);
875
+ try {
876
+ if (!fstatSync(fd).isFile())
877
+ return { state: "ABORTED", reason: "HELD_DESCRIPTOR_HASH_MISMATCH" };
878
+ const preflight = compareHeldDescriptor(fd, input.expectedSize, input.expectedSha256);
879
+ if (preflight.state === "ABORTED")
880
+ return preflight;
881
+ // This seam runs while the canonical fence is held. A direct append
882
+ // here models a bypass; the immediately-pre-rename held-fd CAS below
883
+ // must refuse it before any executor could rename.
884
+ input.afterPreflight?.();
885
+ if (!fenceIsHeld(fence))
886
+ return { state: "ABORTED", reason: "CANONICAL_BINDING_FENCE_LOST" };
887
+ const final = compareHeldDescriptor(fd, input.expectedSize, input.expectedSha256);
888
+ if (final.state === "ABORTED")
889
+ return final;
890
+ if (!fenceIsHeld(fence))
891
+ return { state: "ABORTED", reason: "CANONICAL_BINDING_FENCE_LOST" };
892
+ // Never call rename: execution remains hard-gated for this release. This
893
+ // staging verification releases its fence at return; an executor must not
894
+ // use it as a released-then-reacquired rename precondition.
895
+ void input.rename;
896
+ return { state: "STAGING_VERIFIED" };
897
+ }
898
+ finally {
899
+ closeSync(fd);
900
+ }
901
+ }
902
+ finally {
903
+ releaseCanonicalBindingFence(fence);
904
+ }
905
+ }
906
+ function isValidTail(tail) {
907
+ if (tail.length === 0)
908
+ return true;
909
+ if (tail[tail.length - 1] !== 0x0a)
910
+ return false;
911
+ return parseExactRows(tail).every((row) => row.kind === "valid");
912
+ }
913
+ /** Plan, but do not write, B_R || N_pre || N_post restoration bytes. */
914
+ export function planLosslessRestoration(input) {
915
+ if (input.nPre.length > 0 && input.nPost.length > 0 && !input.tailOrderProven) {
916
+ return { state: "REFUSED", reason: "TAIL_ORDER_AMBIGUOUS" };
917
+ }
918
+ if (!isValidTail(input.nPre) || !isValidTail(input.nPost)) {
919
+ return { state: "REFUSED", reason: "TAIL_INVALID_JSONL" };
920
+ }
921
+ const restored = Buffer.concat([input.original, input.nPre, input.nPost]);
922
+ return { state: "READY", restored, restoredSha256: sha256(restored) };
923
+ }
924
+ //# sourceMappingURL=cull.js.map