@scotthuang/agent-knock-knock 0.12.5 → 0.12.7

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.
@@ -180,8 +180,174 @@ export function captureCodexRolloutAcceptanceAnchor(options) {
180
180
  fs.closeSync(opened.fd);
181
181
  }
182
182
  }
183
+ export function captureCodexCandidateSetRolloutAcceptanceAnchor({ inventory: inventoryValue, now = new Date() }) {
184
+ const inventory = validateCodexOpenRootInventoryForAcceptance(inventoryValue);
185
+ const candidateRollouts = inventory.roots.map((identity) => {
186
+ const rollout = normalizedRolloutIdentity(identity.rollout);
187
+ const opened = openExactRollout(rollout);
188
+ try {
189
+ const before = opened.stat;
190
+ assertPrivateRegularFile(before);
191
+ if (before.size > 0 && !fileEndsWithNewline(opened.fd, before.size)) {
192
+ throw new Error("Codex candidate rollout did not end at a complete JSONL record before terminal submission");
193
+ }
194
+ const after = fs.fstatSync(opened.fd);
195
+ if (!sameStableFile(before, after)) {
196
+ throw new Error("Codex candidate rollout changed while its terminal submission anchor was captured");
197
+ }
198
+ return {
199
+ native_thread_id: exactNativeThreadId(identity.sessionId),
200
+ rollout,
201
+ offset_bytes: before.size
202
+ };
203
+ }
204
+ finally {
205
+ fs.closeSync(opened.fd);
206
+ }
207
+ });
208
+ const base = {
209
+ schema: "agent-knock-knock/codex-rollout-acceptance-anchor",
210
+ version: 3,
211
+ process_uuid: inventory.processUuid,
212
+ process_birth: inventory.processBirth,
213
+ captured_at: now.toISOString(),
214
+ mode: "candidate_set",
215
+ native_thread_binding: "post_submission",
216
+ file_existed: false,
217
+ offset_bytes: 0,
218
+ zero_file_baseline: candidateRollouts.length === 0,
219
+ inventory_pid: inventory.pid,
220
+ ...(inventory.cwd ? { inventory_cwd: inventory.cwd } : {}),
221
+ inventory_fingerprint: inventory.inventoryFingerprint,
222
+ candidate_rollouts: candidateRollouts
223
+ };
224
+ return {
225
+ ...base,
226
+ anchor_fingerprint: fingerprint(base)
227
+ };
228
+ }
229
+ export function detectCodexCandidateSetRolloutAcceptance({ anchor: anchorValue, currentInventory: inventoryValue, requestHash: requestHashValue }) {
230
+ const anchor = validateCodexRolloutAcceptanceAnchor(anchorValue);
231
+ if (anchor.version !== 3) {
232
+ throw new Error("Codex candidate-set acceptance requires a version 3 anchor");
233
+ }
234
+ const inventory = validateCodexOpenRootInventoryForAcceptance(inventoryValue);
235
+ const requestHash = sha256Value(requestHashValue, "terminal request hash");
236
+ if (inventory.processUuid !== anchor.process_uuid ||
237
+ inventory.processBirth !== anchor.process_birth ||
238
+ inventory.pid !== anchor.inventory_pid ||
239
+ inventory.cwd !== anchor.inventory_cwd) {
240
+ return {
241
+ status: "uncertain",
242
+ code: "candidate_inventory_changed",
243
+ reason: "Codex process incarnation changed during candidate-set acceptance polling",
244
+ inspected_candidates: 0,
245
+ exact_matches: 0
246
+ };
247
+ }
248
+ const anchoredByThread = new Map(anchor.candidate_rollouts.map((candidate) => [
249
+ candidate.native_thread_id,
250
+ candidate
251
+ ]));
252
+ const currentByThread = new Map(inventory.roots.map((identity) => [identity.sessionId, identity]));
253
+ for (const identity of inventory.roots) {
254
+ const anchored = anchoredByThread.get(identity.sessionId);
255
+ if (anchored &&
256
+ !sameRolloutIdentity(anchored.rollout, identity.rollout)) {
257
+ return {
258
+ status: "uncertain",
259
+ code: "candidate_inventory_changed",
260
+ reason: `Codex candidate ${identity.sessionId} changed rollout identity after capture`,
261
+ inspected_candidates: 0,
262
+ exact_matches: 0
263
+ };
264
+ }
265
+ }
266
+ for (const anchored of anchor.candidate_rollouts) {
267
+ if (!currentByThread.has(anchored.native_thread_id)) {
268
+ return {
269
+ status: "uncertain",
270
+ code: "candidate_inventory_changed",
271
+ reason: `Codex candidate ${anchored.native_thread_id} is no longer open in the exact process inventory`,
272
+ inspected_candidates: 0,
273
+ exact_matches: 0
274
+ };
275
+ }
276
+ }
277
+ const candidates = inventory.roots.map((identity) => {
278
+ const anchored = anchoredByThread.get(identity.sessionId);
279
+ return {
280
+ identity,
281
+ offsetBytes: anchored?.offset_bytes ?? 0,
282
+ requireFreshHeader: anchored === undefined
283
+ };
284
+ });
285
+ const matches = [];
286
+ let incompleteCandidates = 0;
287
+ for (const candidate of candidates) {
288
+ let scan;
289
+ try {
290
+ scan = scanCodexRolloutAcceptance({
291
+ rollout: candidate.identity.rollout,
292
+ nativeThreadId: candidate.identity.sessionId,
293
+ processUuid: anchor.process_uuid,
294
+ processBirth: anchor.process_birth,
295
+ requestHash,
296
+ anchorFingerprint: anchor.anchor_fingerprint,
297
+ offsetBytes: candidate.offsetBytes,
298
+ requireFreshHeader: candidate.requireFreshHeader,
299
+ capturedAt: anchor.captured_at
300
+ });
301
+ }
302
+ catch (error) {
303
+ return {
304
+ status: "uncertain",
305
+ code: "candidate_scan_invalid",
306
+ reason: error instanceof Error ? error.message : String(error),
307
+ inspected_candidates: candidates.indexOf(candidate) + 1,
308
+ exact_matches: matches.length
309
+ };
310
+ }
311
+ if (scan.status === "incomplete") {
312
+ incompleteCandidates += 1;
313
+ }
314
+ else if (scan.status === "accepted") {
315
+ matches.push({
316
+ identity: candidate.identity,
317
+ evidence: scan.evidence
318
+ });
319
+ }
320
+ }
321
+ if (matches.length > 1) {
322
+ return {
323
+ status: "uncertain",
324
+ code: "multiple_exact_request_acceptances",
325
+ reason: "multiple Codex root rollouts durably accepted the exact terminal request",
326
+ inspected_candidates: candidates.length,
327
+ exact_matches: matches.length
328
+ };
329
+ }
330
+ if (matches.length === 0 || incompleteCandidates > 0) {
331
+ return {
332
+ status: "pending",
333
+ inspected_candidates: candidates.length,
334
+ exact_matches: matches.length,
335
+ ...(incompleteCandidates > 0
336
+ ? { incomplete_candidates: incompleteCandidates }
337
+ : {})
338
+ };
339
+ }
340
+ return {
341
+ status: "accepted",
342
+ identity: matches[0].identity,
343
+ evidence: matches[0].evidence
344
+ };
345
+ }
183
346
  export function detectCodexRolloutAcceptance(options) {
184
347
  const anchor = validateCodexRolloutAcceptanceAnchor(options.anchor);
348
+ if (anchor.version === 3) {
349
+ throw new Error("Codex candidate-set acceptance requires the inventory-aware detector");
350
+ }
185
351
  const requestHash = sha256Value(options.requestHash, "terminal request hash");
186
352
  const current = options.currentIdentity;
187
353
  const currentNativeThreadId = exactNativeThreadId(current.sessionId);
@@ -201,66 +367,18 @@ export function detectCodexRolloutAcceptance(options) {
201
367
  !sameRolloutIdentity(anchor.rollout, rollout)) {
202
368
  throw new Error("Codex rollout identity changed during acceptance polling");
203
369
  }
204
- const opened = openExactRollout(rollout);
205
- try {
206
- const before = opened.stat;
207
- assertPrivateRegularFile(before);
208
- if (before.size < anchor.offset_bytes) {
209
- throw new Error("Codex rollout was truncated after terminal submission");
210
- }
211
- const bytesToRead = before.size - anchor.offset_bytes;
212
- if (bytesToRead === 0) {
213
- return undefined;
214
- }
215
- if (bytesToRead > CODEX_ACCEPTANCE_MAX_BYTES) {
216
- throw new Error("Codex rollout acceptance suffix exceeded the bounded read limit");
217
- }
218
- if (!fileEndsWithNewline(opened.fd, before.size)) {
219
- return undefined;
220
- }
221
- const buffer = Buffer.allocUnsafe(bytesToRead);
222
- const bytesRead = fs.readSync(opened.fd, buffer, 0, bytesToRead, anchor.offset_bytes);
223
- if (bytesRead !== bytesToRead) {
224
- return undefined;
225
- }
226
- const after = fs.fstatSync(opened.fd);
227
- if (!sameStableFile(before, after)) {
228
- return undefined;
229
- }
230
- const suffix = buffer.toString("utf8");
231
- if (anchor.version === 2) {
232
- assertVirginRolloutHeader({
233
- text: suffix,
234
- nativeThreadId: expectedNativeThreadId,
235
- capturedAt: anchor.captured_at
236
- });
237
- }
238
- const accepted = acceptedCodexTurnFromSuffix(suffix, requestHash);
239
- if (!accepted) {
240
- return undefined;
241
- }
242
- const evidenceBase = {
243
- source: "codex_rollout",
244
- kind: "native_user_turn",
245
- nativeThreadId: expectedNativeThreadId,
246
- requestHash,
247
- acceptanceId: accepted.turnId,
248
- acceptedAt: accepted.userTimestamp ?? accepted.startedAt,
249
- anchorFingerprint: anchor.anchor_fingerprint,
250
- metadata: {
251
- turn_id: accepted.turnId,
252
- anchor_offset_bytes: anchor.offset_bytes,
253
- observed_end_offset_bytes: before.size
254
- }
255
- };
256
- return {
257
- ...evidenceBase,
258
- evidenceFingerprint: fingerprint(evidenceBase)
259
- };
260
- }
261
- finally {
262
- fs.closeSync(opened.fd);
263
- }
370
+ const scan = scanCodexRolloutAcceptance({
371
+ rollout,
372
+ nativeThreadId: expectedNativeThreadId,
373
+ processUuid: anchor.process_uuid,
374
+ processBirth: anchor.process_birth,
375
+ requestHash,
376
+ anchorFingerprint: anchor.anchor_fingerprint,
377
+ offsetBytes: anchor.offset_bytes,
378
+ requireFreshHeader: anchor.version === 2,
379
+ capturedAt: anchor.captured_at
380
+ });
381
+ return scan.status === "accepted" ? scan.evidence : undefined;
264
382
  }
265
383
  /**
266
384
  * Scans the exact rollout and native turn proven by terminal acceptance.
@@ -300,8 +418,14 @@ export function detectCodexBoundRolloutCompletion(options) {
300
418
  catch (error) {
301
419
  return codexCompletionFailure("invalid_acceptance_evidence", error, initialDiagnostics);
302
420
  }
421
+ const acceptedCandidate = anchor.version === 3
422
+ ? anchor.candidate_rollouts.find((candidate) => candidate.native_thread_id === expectedNativeThreadId)
423
+ : undefined;
424
+ const scanStartOffset = acceptedCandidate?.offset_bytes ??
425
+ anchor.offset_bytes;
303
426
  const baseDiagnostics = {
304
427
  ...initialDiagnostics,
428
+ scan_start_offset_bytes: scanStartOffset,
305
429
  native_thread_id: expectedNativeThreadId
306
430
  };
307
431
  let acceptanceId;
@@ -339,7 +463,7 @@ export function detectCodexBoundRolloutCompletion(options) {
339
463
  return codexCompletionFailure("invalid_acceptance_evidence", error, diagnostics);
340
464
  }
341
465
  if (evidenceAnchorOffset !== undefined &&
342
- evidenceAnchorOffset !== anchor.offset_bytes) {
466
+ evidenceAnchorOffset !== scanStartOffset) {
343
467
  return codexCompletionFailure("acceptance_anchor_mismatch", new Error("Codex acceptance evidence has a different byte offset"), diagnostics);
344
468
  }
345
469
  const current = options.currentIdentity;
@@ -369,7 +493,10 @@ export function detectCodexBoundRolloutCompletion(options) {
369
493
  ...diagnostics,
370
494
  rollout_identity_fingerprint: fingerprint(rollout)
371
495
  };
372
- if (anchor.rollout && !sameRolloutIdentity(anchor.rollout, rollout)) {
496
+ const anchoredRollout = anchor.version === 3
497
+ ? acceptedCandidate?.rollout
498
+ : anchor.rollout;
499
+ if (anchoredRollout && !sameRolloutIdentity(anchoredRollout, rollout)) {
373
500
  return codexCompletionFailure("rollout_identity_mismatch", new Error("Codex rollout identity changed after terminal acceptance"), rolloutDiagnostics);
374
501
  }
375
502
  let opened;
@@ -397,14 +524,14 @@ export function detectCodexBoundRolloutCompletion(options) {
397
524
  catch (error) {
398
525
  return codexCompletionFailure("invalid_acceptance_evidence", error, rolloutDiagnostics);
399
526
  }
400
- if (before.size < anchor.offset_bytes ||
527
+ if (before.size < scanStartOffset ||
401
528
  (evidenceEndOffset !== undefined && before.size < evidenceEndOffset)) {
402
529
  return codexCompletionFailure("rollout_truncated", new Error("Codex rollout was truncated after native acceptance"), {
403
530
  ...rolloutDiagnostics,
404
531
  observed_end_offset_bytes: before.size
405
532
  });
406
533
  }
407
- const bytesToRead = before.size - anchor.offset_bytes;
534
+ const bytesToRead = before.size - scanStartOffset;
408
535
  const observedDiagnostics = {
409
536
  ...rolloutDiagnostics,
410
537
  observed_end_offset_bytes: before.size
@@ -419,7 +546,7 @@ export function detectCodexBoundRolloutCompletion(options) {
419
546
  return codexCompletionPending("partial_rollout_record", observedDiagnostics, "the exact Codex rollout ends with a partial JSONL record");
420
547
  }
421
548
  const buffer = Buffer.allocUnsafe(bytesToRead);
422
- const bytesRead = fs.readSync(opened.fd, buffer, 0, bytesToRead, anchor.offset_bytes);
549
+ const bytesRead = fs.readSync(opened.fd, buffer, 0, bytesToRead, scanStartOffset);
423
550
  const after = fs.fstatSync(opened.fd);
424
551
  if (bytesRead !== bytesToRead || !sameStableFile(before, after)) {
425
552
  return codexCompletionPending("rollout_changed_during_scan", observedDiagnostics, "the exact Codex rollout changed while completion was scanned");
@@ -449,7 +576,7 @@ export function detectCodexBoundRolloutCompletion(options) {
449
576
  native_thread_id: expectedNativeThreadId,
450
577
  anchor_fingerprint: anchor.anchor_fingerprint,
451
578
  rollout_identity_fingerprint: rolloutDiagnostics.rollout_identity_fingerprint,
452
- scan_start_offset_bytes: anchor.offset_bytes,
579
+ scan_start_offset_bytes: scanStartOffset,
453
580
  observed_end_offset_bytes: before.size,
454
581
  scanned_records: scan.scannedRecords,
455
582
  observed_task_complete_records: scan.observedTaskCompleteRecords
@@ -661,6 +788,78 @@ function acceptedCodexTurnFromSuffix(text, requestHash) {
661
788
  }
662
789
  return matches[0];
663
790
  }
791
+ function scanCodexRolloutAcceptance({ rollout: rolloutValue, nativeThreadId: nativeThreadIdValue, processUuid: processUuidValue, processBirth: processBirthValue, requestHash, anchorFingerprint, offsetBytes, requireFreshHeader, capturedAt }) {
792
+ const rollout = normalizedRolloutIdentity(rolloutValue);
793
+ const nativeThreadId = exactNativeThreadId(nativeThreadIdValue);
794
+ requiredString(processUuidValue, "Codex process UUID");
795
+ requiredString(processBirthValue, "Codex process birth");
796
+ if (!Number.isSafeInteger(offsetBytes) || offsetBytes < 0) {
797
+ throw new Error("Codex rollout acceptance byte offset is invalid");
798
+ }
799
+ const opened = openExactRollout(rollout);
800
+ try {
801
+ const before = opened.stat;
802
+ assertPrivateRegularFile(before);
803
+ if (before.size < offsetBytes) {
804
+ throw new Error("Codex rollout was truncated after terminal submission");
805
+ }
806
+ const bytesToRead = before.size - offsetBytes;
807
+ if (bytesToRead === 0) {
808
+ return { status: "pending" };
809
+ }
810
+ if (bytesToRead > CODEX_ACCEPTANCE_MAX_BYTES) {
811
+ throw new Error("Codex rollout acceptance suffix exceeded the bounded read limit");
812
+ }
813
+ if (!fileEndsWithNewline(opened.fd, before.size)) {
814
+ return { status: "incomplete" };
815
+ }
816
+ const buffer = Buffer.allocUnsafe(bytesToRead);
817
+ const bytesRead = fs.readSync(opened.fd, buffer, 0, bytesToRead, offsetBytes);
818
+ if (bytesRead !== bytesToRead) {
819
+ return { status: "incomplete" };
820
+ }
821
+ const after = fs.fstatSync(opened.fd);
822
+ if (!sameStableFile(before, after)) {
823
+ return { status: "incomplete" };
824
+ }
825
+ const suffix = buffer.toString("utf8");
826
+ if (requireFreshHeader) {
827
+ assertVirginRolloutHeader({
828
+ text: suffix,
829
+ nativeThreadId,
830
+ capturedAt
831
+ });
832
+ }
833
+ const accepted = acceptedCodexTurnFromSuffix(suffix, requestHash);
834
+ if (!accepted) {
835
+ return { status: "pending" };
836
+ }
837
+ const evidenceBase = {
838
+ source: "codex_rollout",
839
+ kind: "native_user_turn",
840
+ nativeThreadId,
841
+ requestHash,
842
+ acceptanceId: accepted.turnId,
843
+ acceptedAt: accepted.userTimestamp ?? accepted.startedAt,
844
+ anchorFingerprint,
845
+ metadata: {
846
+ turn_id: accepted.turnId,
847
+ anchor_offset_bytes: offsetBytes,
848
+ observed_end_offset_bytes: before.size
849
+ }
850
+ };
851
+ return {
852
+ status: "accepted",
853
+ evidence: {
854
+ ...evidenceBase,
855
+ evidenceFingerprint: fingerprint(evidenceBase)
856
+ }
857
+ };
858
+ }
859
+ finally {
860
+ fs.closeSync(opened.fd);
861
+ }
862
+ }
664
863
  function exactCodexUserResponseText(content) {
665
864
  if (!Array.isArray(content) || content.length !== 1) {
666
865
  return undefined;
@@ -699,11 +898,11 @@ function assertVirginRolloutHeader(options) {
699
898
  throw new Error("virgin Codex rollout predates its terminal submission anchor");
700
899
  }
701
900
  }
702
- function validateCodexRolloutAcceptanceAnchor(value) {
901
+ export function validateCodexRolloutAcceptanceAnchor(value) {
703
902
  if (!isRecord(value) ||
704
903
  value.schema !== "agent-knock-knock/codex-rollout-acceptance-anchor" ||
705
- (value.version !== 1 && value.version !== 2) ||
706
- !["existing", "pre_materialization"].includes(String(value.mode)) ||
904
+ (value.version !== 1 && value.version !== 2 && value.version !== 3) ||
905
+ !["existing", "pre_materialization", "candidate_set"].includes(String(value.mode)) ||
707
906
  !Number.isSafeInteger(value.offset_bytes) ||
708
907
  value.offset_bytes < 0 ||
709
908
  typeof value.file_existed !== "boolean") {
@@ -715,11 +914,27 @@ function validateCodexRolloutAcceptanceAnchor(value) {
715
914
  throw new Error("bound Codex acceptance anchor has deferred binding state");
716
915
  }
717
916
  }
718
- else if (value.mode !== "pre_materialization" ||
917
+ else if (value.version === 2 && (value.mode !== "pre_materialization" ||
719
918
  value.native_thread_binding !== "post_submission" ||
720
- "native_thread_id" in value) {
919
+ "native_thread_id" in value)) {
721
920
  throw new Error("virgin Codex acceptance anchor is inconsistent");
722
921
  }
922
+ else if (value.version === 3 && (value.mode !== "candidate_set" ||
923
+ value.native_thread_binding !== "post_submission" ||
924
+ "native_thread_id" in value ||
925
+ value.file_existed !== false ||
926
+ value.offset_bytes !== 0 ||
927
+ typeof value.zero_file_baseline !== "boolean" ||
928
+ !Number.isSafeInteger(value.inventory_pid) ||
929
+ value.inventory_pid <= 1 ||
930
+ (value.inventory_cwd !== undefined &&
931
+ !path.isAbsolute(value.inventory_cwd)) ||
932
+ !/^[0-9a-f]{64}$/u.test(String(value.inventory_fingerprint)) ||
933
+ !Array.isArray(value.candidate_rollouts) ||
934
+ value.candidate_rollouts.length > 128 ||
935
+ value.zero_file_baseline !== (value.candidate_rollouts.length === 0))) {
936
+ throw new Error("candidate-set Codex acceptance anchor is inconsistent");
937
+ }
723
938
  requiredString(value.process_uuid, "Codex process UUID");
724
939
  requiredString(value.process_birth, "Codex process birth");
725
940
  if (!validTimestamp(value.captured_at)) {
@@ -730,6 +945,26 @@ function validateCodexRolloutAcceptanceAnchor(value) {
730
945
  if (fingerprint(base) !== value.anchor_fingerprint) {
731
946
  throw new Error("Codex rollout acceptance anchor fingerprint does not match");
732
947
  }
948
+ if (value.version === 3) {
949
+ const seenThreads = new Set();
950
+ const seenFiles = new Set();
951
+ for (const candidate of value.candidate_rollouts) {
952
+ if (!isRecord(candidate)) {
953
+ throw new Error("Codex candidate-set rollout entry is invalid");
954
+ }
955
+ const nativeThreadId = exactNativeThreadId(candidate.native_thread_id);
956
+ const rollout = normalizedRolloutIdentity(candidate.rollout);
957
+ if (!Number.isSafeInteger(candidate.offset_bytes) ||
958
+ candidate.offset_bytes < 0 ||
959
+ seenThreads.has(nativeThreadId) ||
960
+ seenFiles.has(`${rollout.device}:${rollout.inode}`)) {
961
+ throw new Error("Codex candidate-set rollout entries are ambiguous");
962
+ }
963
+ seenThreads.add(nativeThreadId);
964
+ seenFiles.add(`${rollout.device}:${rollout.inode}`);
965
+ }
966
+ return value;
967
+ }
733
968
  const rollout = "rollout" in value ? value.rollout : undefined;
734
969
  if (value.file_existed !== (value.mode === "existing") ||
735
970
  value.file_existed !== Boolean(rollout)) {
@@ -748,6 +983,61 @@ function validateCodexRolloutAcceptanceAnchor(value) {
748
983
  }
749
984
  return value;
750
985
  }
986
+ function validateCodexOpenRootInventoryForAcceptance(value) {
987
+ if (!isRecord(value) ||
988
+ value.schema !==
989
+ "agent-knock-knock/codex-open-root-rollout-inventory" ||
990
+ value.version !== 1 ||
991
+ !["verified_absent", "resolved", "unbound"].includes(String(value.status)) ||
992
+ !Number.isSafeInteger(value.pid) ||
993
+ value.pid <= 1 ||
994
+ !Array.isArray(value.roots) ||
995
+ value.roots.length > 128 ||
996
+ !/^[0-9a-f]{64}$/u.test(String(value.inventoryFingerprint))) {
997
+ throw new Error("Codex open-root rollout inventory is invalid");
998
+ }
999
+ requiredString(value.processUuid, "Codex process UUID");
1000
+ requiredString(value.processBirth, "Codex process birth");
1001
+ if (value.processUuid !==
1002
+ `codex-pid:${value.pid}:birth:${value.processBirth}`) {
1003
+ throw new Error("Codex open-root rollout inventory process UUID is inconsistent");
1004
+ }
1005
+ if (value.cwd !== undefined && !path.isAbsolute(value.cwd)) {
1006
+ throw new Error("Codex open-root rollout inventory cwd is not absolute");
1007
+ }
1008
+ if ((value.status === "verified_absent" && value.roots.length !== 0) ||
1009
+ (value.status === "resolved" && value.roots.length !== 1) ||
1010
+ (value.status === "unbound" &&
1011
+ (value.roots.length < 2 ||
1012
+ value.reason !== "multiple_open_root_rollouts"))) {
1013
+ throw new Error("Codex open-root rollout inventory status is inconsistent");
1014
+ }
1015
+ const seenThreads = new Set();
1016
+ const seenFiles = new Set();
1017
+ for (const identity of value.roots) {
1018
+ if (!isRecord(identity)) {
1019
+ throw new Error("Codex open-root rollout inventory identity is invalid");
1020
+ }
1021
+ const nativeThreadId = exactNativeThreadId(identity.sessionId);
1022
+ if (identity.processUuid !== value.processUuid ||
1023
+ identity.processBirth !== value.processBirth ||
1024
+ identity.evidence !== "codex_open_root_rollout") {
1025
+ throw new Error("Codex open-root rollout inventory has mixed process authority");
1026
+ }
1027
+ const rollout = normalizedRolloutIdentity(identity.rollout);
1028
+ if (seenThreads.has(nativeThreadId) ||
1029
+ seenFiles.has(`${rollout.device}:${rollout.inode}`)) {
1030
+ throw new Error("Codex open-root rollout inventory is ambiguous");
1031
+ }
1032
+ seenThreads.add(nativeThreadId);
1033
+ seenFiles.add(`${rollout.device}:${rollout.inode}`);
1034
+ }
1035
+ const { status: _status, inventoryFingerprint, reason: _reason, ...authority } = value;
1036
+ if (fingerprint(authority) !== inventoryFingerprint) {
1037
+ throw new Error("Codex open-root rollout inventory fingerprint does not match");
1038
+ }
1039
+ return value;
1040
+ }
751
1041
  function openExactRollout(rollout) {
752
1042
  if (!path.isAbsolute(rollout.path)) {
753
1043
  throw new Error("Codex rollout path is not absolute");