harnery 0.45.2 → 0.46.1

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 (227) hide show
  1. package/dist/commander.d.ts +13 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commands/admission.d.ts.map +1 -1
  4. package/dist/commands/admission.js +214 -27
  5. package/dist/commands/agents.d.ts +4 -0
  6. package/dist/commands/agents.d.ts.map +1 -1
  7. package/dist/commands/agents.js +102 -25
  8. package/dist/commands/browse.d.ts +3 -0
  9. package/dist/commands/browse.d.ts.map +1 -1
  10. package/dist/commands/browse.js +172 -8
  11. package/dist/commands/fetch.js +1 -0
  12. package/dist/commands/ledger-v3.d.ts.map +1 -1
  13. package/dist/commands/ledger-v3.js +8 -2
  14. package/dist/commands/presence.d.ts.map +1 -1
  15. package/dist/commands/presence.js +40 -0
  16. package/dist/commands/qa-run.d.ts.map +1 -1
  17. package/dist/commands/qa-run.js +4 -3
  18. package/dist/commands/review-pack.d.ts.map +1 -1
  19. package/dist/commands/review-pack.js +33 -6
  20. package/dist/core/agents/index.d.ts +1 -0
  21. package/dist/core/agents/index.d.ts.map +1 -1
  22. package/dist/core/agents/index.js +1 -0
  23. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  24. package/dist/core/agents/live-authority-v3.js +11 -4
  25. package/dist/core/agents/render/session-context.d.ts +0 -6
  26. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  27. package/dist/core/agents/render/session-context.js +1 -6
  28. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  29. package/dist/core/agents/rules/claim-conflict.js +36 -16
  30. package/dist/core/agents/rules/stop-hook.d.ts +6 -0
  31. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  32. package/dist/core/agents/rules/stop-hook.js +20 -1
  33. package/dist/core/agents/session-events.d.ts.map +1 -1
  34. package/dist/core/agents/session-events.js +12 -4
  35. package/dist/core/agents/state/heartbeat-reader.d.ts +3 -0
  36. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  37. package/dist/core/agents/state/live-coordination-view.d.ts +3 -1
  38. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  39. package/dist/core/agents/state/live-coordination-view.js +6 -2
  40. package/dist/core/agents/state/parent-reference-contract.d.ts +16 -0
  41. package/dist/core/agents/state/parent-reference-contract.d.ts.map +1 -0
  42. package/dist/core/agents/state/parent-reference-contract.js +1 -0
  43. package/dist/core/agents/state/parent-reference.d.ts +18 -0
  44. package/dist/core/agents/state/parent-reference.d.ts.map +1 -0
  45. package/dist/core/agents/state/parent-reference.js +119 -0
  46. package/dist/core/config.d.ts +39 -0
  47. package/dist/core/config.d.ts.map +1 -1
  48. package/dist/core/config.js +63 -0
  49. package/dist/core/diagnostics/path-identity.d.ts +1 -1
  50. package/dist/core/diagnostics/path-identity.d.ts.map +1 -1
  51. package/dist/core/diagnostics/path-identity.js +51 -6
  52. package/dist/core/events/v3/canonical.js +43 -14
  53. package/dist/core/events/v3/index.d.ts +1 -1
  54. package/dist/core/events/v3/index.d.ts.map +1 -1
  55. package/dist/core/events/v3/index.js +1 -1
  56. package/dist/core/events/v3/producers/recorder.d.ts +34 -0
  57. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  58. package/dist/core/events/v3/producers/recorder.js +117 -13
  59. package/dist/core/events/v3/reader.d.ts.map +1 -1
  60. package/dist/core/events/v3/reader.js +53 -13
  61. package/dist/core/events/v3/validate.d.ts.map +1 -1
  62. package/dist/core/events/v3/validate.js +36 -1
  63. package/dist/core/hooks/adapter/native-parent.d.ts +23 -0
  64. package/dist/core/hooks/adapter/native-parent.d.ts.map +1 -0
  65. package/dist/core/hooks/adapter/native-parent.js +91 -0
  66. package/dist/core/hooks/cli.js +103 -5
  67. package/dist/core/hooks/resolve/owner.d.ts +0 -11
  68. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  69. package/dist/core/hooks/resolve/owner.js +0 -23
  70. package/dist/core/hooks/resolve/transcript.d.ts +12 -0
  71. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  72. package/dist/core/hooks/resolve/transcript.js +54 -0
  73. package/dist/core/hooks/shell-admission.d.ts +84 -0
  74. package/dist/core/hooks/shell-admission.d.ts.map +1 -0
  75. package/dist/core/hooks/shell-admission.js +622 -0
  76. package/dist/core/hooks/shell-commit-tree.d.ts +2 -0
  77. package/dist/core/hooks/shell-commit-tree.d.ts.map +1 -0
  78. package/dist/core/hooks/shell-commit-tree.js +104 -0
  79. package/dist/core/inbox/contract.d.ts +12 -0
  80. package/dist/core/inbox/contract.d.ts.map +1 -1
  81. package/dist/core/inbox/contract.js +16 -0
  82. package/dist/core/inbox/peer-messages.d.ts +41 -16
  83. package/dist/core/inbox/peer-messages.d.ts.map +1 -1
  84. package/dist/core/inbox/peer-messages.js +372 -15
  85. package/dist/core/inbox/service.d.ts +8 -1
  86. package/dist/core/inbox/service.d.ts.map +1 -1
  87. package/dist/core/inbox/service.js +83 -3
  88. package/dist/core/inbox/targets.d.ts +17 -3
  89. package/dist/core/inbox/targets.d.ts.map +1 -1
  90. package/dist/core/inbox/targets.js +77 -2
  91. package/dist/core/presence/git.d.ts +18 -12
  92. package/dist/core/presence/git.d.ts.map +1 -1
  93. package/dist/core/presence/git.js +32 -38
  94. package/dist/core/presence/index.d.ts +2 -2
  95. package/dist/core/presence/index.d.ts.map +1 -1
  96. package/dist/core/presence/index.js +8 -6
  97. package/dist/core/presence/mail.d.ts +133 -0
  98. package/dist/core/presence/mail.d.ts.map +1 -0
  99. package/dist/core/presence/mail.js +464 -0
  100. package/dist/core/semantic/contract.d.ts +5 -5
  101. package/dist/core/storage/modes.d.ts +4 -0
  102. package/dist/core/storage/modes.d.ts.map +1 -1
  103. package/dist/core/storage/modes.js +4 -0
  104. package/dist/core/storage/segments.d.ts.map +1 -1
  105. package/dist/core/storage/segments.js +5 -4
  106. package/dist/lib/admission-history.d.ts +96 -0
  107. package/dist/lib/admission-history.d.ts.map +1 -0
  108. package/dist/lib/admission-history.js +196 -0
  109. package/dist/lib/admission.d.ts +56 -7
  110. package/dist/lib/admission.d.ts.map +1 -1
  111. package/dist/lib/admission.js +161 -19
  112. package/dist/lib/browser/client.d.ts +18 -0
  113. package/dist/lib/browser/client.d.ts.map +1 -1
  114. package/dist/lib/browser/client.js +100 -12
  115. package/dist/lib/browser/content-checks.d.ts +3 -1
  116. package/dist/lib/browser/content-checks.d.ts.map +1 -1
  117. package/dist/lib/browser/content-checks.js +10 -2
  118. package/dist/lib/browser/critique.d.ts +32 -0
  119. package/dist/lib/browser/critique.d.ts.map +1 -1
  120. package/dist/lib/browser/critique.js +72 -3
  121. package/dist/lib/browser/geometry.d.ts +2 -0
  122. package/dist/lib/browser/geometry.d.ts.map +1 -1
  123. package/dist/lib/browser/geometry.js +61 -15
  124. package/dist/lib/browser/index.d.ts +1 -1
  125. package/dist/lib/browser/index.d.ts.map +1 -1
  126. package/dist/lib/browser/index.js +1 -1
  127. package/dist/lib/browser/page-review-contracts.d.ts +6 -1
  128. package/dist/lib/browser/page-review-contracts.d.ts.map +1 -1
  129. package/dist/lib/browser/page-review-contracts.js +6 -1
  130. package/dist/lib/browser/page-review-judge.d.ts +3 -1
  131. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  132. package/dist/lib/browser/page-review-judge.js +41 -11
  133. package/dist/lib/browser/page-review-pack.d.ts +3 -0
  134. package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
  135. package/dist/lib/browser/page-review-pack.js +5 -2
  136. package/dist/lib/browser/qa-run-contracts.d.ts +11 -0
  137. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  138. package/dist/lib/browser/qa-run-contracts.js +11 -4
  139. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  140. package/dist/lib/browser/qa-run.js +73 -16
  141. package/dist/lib/browser/runts.d.ts +4 -2
  142. package/dist/lib/browser/runts.d.ts.map +1 -1
  143. package/dist/lib/browser/runts.js +84 -33
  144. package/dist/lib/browser/windows-chrome.d.ts +14 -0
  145. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  146. package/dist/lib/browser/windows-chrome.js +42 -0
  147. package/dist/lib/browser/windows-login-sync.d.ts +110 -0
  148. package/dist/lib/browser/windows-login-sync.d.ts.map +1 -0
  149. package/dist/lib/browser/windows-login-sync.js +171 -0
  150. package/dist/lib/cookies/client.d.ts +30 -5
  151. package/dist/lib/cookies/client.d.ts.map +1 -1
  152. package/dist/lib/cookies/client.js +55 -10
  153. package/dist/lib/cookies/index.d.ts +1 -1
  154. package/dist/lib/cookies/index.d.ts.map +1 -1
  155. package/dist/lib/cookies/index.js +1 -1
  156. package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
  157. package/dist/lib/docs-metadata-sync.js +41 -2
  158. package/dist/lib/durable-job.d.ts +8 -0
  159. package/dist/lib/durable-job.d.ts.map +1 -1
  160. package/dist/lib/durable-job.js +10 -0
  161. package/dist/lib/http/client.d.ts +9 -8
  162. package/dist/lib/http/client.d.ts.map +1 -1
  163. package/dist/lib/http/client.js +44 -2
  164. package/dist/lib/instructions/templates.js +4 -4
  165. package/package.json +2 -2
  166. package/src/commander.ts +13 -0
  167. package/src/commands/admission.ts +272 -29
  168. package/src/commands/agents.ts +132 -27
  169. package/src/commands/browse.ts +208 -6
  170. package/src/commands/fetch.ts +1 -0
  171. package/src/commands/ledger-v3.ts +8 -2
  172. package/src/commands/presence.ts +49 -0
  173. package/src/commands/qa-run.ts +4 -3
  174. package/src/commands/review-pack.ts +41 -5
  175. package/src/core/agents/index.ts +6 -0
  176. package/src/core/agents/live-authority-v3.ts +11 -4
  177. package/src/core/agents/render/session-context.ts +1 -7
  178. package/src/core/agents/rules/claim-conflict.ts +50 -18
  179. package/src/core/agents/rules/stop-hook.ts +28 -1
  180. package/src/core/agents/session-events.ts +18 -3
  181. package/src/core/agents/state/heartbeat-reader.ts +3 -0
  182. package/src/core/agents/state/live-coordination-view.ts +13 -6
  183. package/src/core/agents/state/parent-reference-contract.ts +14 -0
  184. package/src/core/agents/state/parent-reference.ts +179 -0
  185. package/src/core/config.ts +85 -0
  186. package/src/core/diagnostics/path-identity.ts +52 -6
  187. package/src/core/events/v3/canonical.ts +46 -13
  188. package/src/core/events/v3/index.ts +3 -0
  189. package/src/core/events/v3/producers/recorder.ts +151 -13
  190. package/src/core/events/v3/reader.ts +55 -11
  191. package/src/core/events/v3/validate.ts +42 -1
  192. package/src/core/hooks/adapter/native-parent.ts +109 -0
  193. package/src/core/hooks/cli.ts +110 -4
  194. package/src/core/hooks/resolve/owner.ts +0 -23
  195. package/src/core/hooks/resolve/transcript.ts +61 -0
  196. package/src/core/hooks/shell-admission.ts +634 -0
  197. package/src/core/hooks/shell-commit-tree.ts +102 -0
  198. package/src/core/inbox/contract.ts +18 -0
  199. package/src/core/inbox/peer-messages.ts +501 -15
  200. package/src/core/inbox/service.ts +108 -3
  201. package/src/core/inbox/targets.ts +94 -3
  202. package/src/core/presence/git.ts +45 -37
  203. package/src/core/presence/index.ts +16 -8
  204. package/src/core/presence/mail.ts +620 -0
  205. package/src/core/storage/modes.ts +6 -0
  206. package/src/core/storage/segments.ts +5 -4
  207. package/src/lib/admission-history.ts +267 -0
  208. package/src/lib/admission.ts +217 -19
  209. package/src/lib/browser/client.ts +147 -35
  210. package/src/lib/browser/content-checks.ts +14 -3
  211. package/src/lib/browser/critique.ts +113 -3
  212. package/src/lib/browser/geometry.ts +63 -14
  213. package/src/lib/browser/index.ts +5 -0
  214. package/src/lib/browser/page-review-contracts.ts +6 -1
  215. package/src/lib/browser/page-review-judge.ts +58 -13
  216. package/src/lib/browser/page-review-pack.ts +13 -2
  217. package/src/lib/browser/qa-run-contracts.ts +30 -4
  218. package/src/lib/browser/qa-run.ts +78 -17
  219. package/src/lib/browser/runts.ts +85 -37
  220. package/src/lib/browser/windows-chrome.ts +50 -0
  221. package/src/lib/browser/windows-login-sync.ts +275 -0
  222. package/src/lib/cookies/client.ts +62 -10
  223. package/src/lib/cookies/index.ts +3 -0
  224. package/src/lib/docs-metadata-sync.ts +44 -2
  225. package/src/lib/durable-job.ts +15 -0
  226. package/src/lib/http/client.ts +63 -1
  227. package/src/lib/instructions/templates.ts +4 -4
@@ -125,7 +125,15 @@ function sessionHashForSignal(input, context) {
125
125
  // single live producer authority instead of hashing the instance into a
126
126
  // second state path. Ambiguous or absent matches keep the fail-closed fallback.
127
127
  if (adapterBehavior(input.adapter).sessionIdentityOnStartOnly) {
128
- const matches = listHookProducerStateRecordsV3(input.coordRoot).filter(({ state }) => state.adapter === input.adapter && state.instance_id === input.instance_id);
128
+ const scan = scanHookProducerStateRecordsV3(input.coordRoot);
129
+ // This instance's own state is unreadable: fail closed rather than hash
130
+ // the instance into a second state path beside it.
131
+ if (scan.invalid.some((file) => file.adapter === input.adapter && file.instance_id === input.instance_id)) {
132
+ throw new Error("V3 producer state is invalid");
133
+ }
134
+ for (const file of scan.invalid)
135
+ recordInvalidProducerStateDiagnostic(input.coordRoot, file);
136
+ const matches = scan.records.filter(({ state }) => state.adapter === input.adapter && state.instance_id === input.instance_id);
129
137
  if (matches.length === 1) {
130
138
  const name = basename(matches[0].path);
131
139
  if (/^hid_[a-f0-9]{64}\.json$/.test(name)) {
@@ -2559,14 +2567,23 @@ export function salvageOpenSpansV3(input) {
2559
2567
  lease.release();
2560
2568
  }
2561
2569
  }
2562
- export function listHookProducerStateRecordsV3(coordRoot, options = {}) {
2570
+ /**
2571
+ * Scan every producer-state file of the active epoch. A file the validator
2572
+ * refuses is reported in `invalid` instead of failing the scan, so one
2573
+ * session's bad state cannot take coordination away from every other session
2574
+ * in the checkout. An unsafe adapter directory still throws.
2575
+ */
2576
+ export function scanHookProducerStateRecordsV3(coordRoot, options = {}) {
2563
2577
  const control = readEventV3ControlState(coordRoot);
2564
- if (control.state !== "candidate" && control.state !== "active")
2565
- return [];
2578
+ if (control.state !== "candidate" && control.state !== "active") {
2579
+ return { records: [], invalid: [] };
2580
+ }
2581
+ const genesisId = control.genesis.event.payload.genesis_id;
2566
2582
  const producerRoot = join(resolve(coordRoot), EVENT_V3_LEDGER_RELATIVE_ROOT, "private-producers");
2567
2583
  if (!existsSync(producerRoot))
2568
- return [];
2584
+ return { records: [], invalid: [] };
2569
2585
  const records = [];
2586
+ const invalid = [];
2570
2587
  for (const adapter of EVENT_ADAPTER_IDS_V3) {
2571
2588
  const directory = join(producerRoot, adapter);
2572
2589
  if (!existsSync(directory))
@@ -2577,18 +2594,79 @@ export function listHookProducerStateRecordsV3(coordRoot, options = {}) {
2577
2594
  }
2578
2595
  for (const name of readdirSync(directory).filter((entry) => /^hid_[a-f0-9]{64}\.json$/.test(entry))) {
2579
2596
  const path = join(directory, name);
2580
- const state = readProducerState(path);
2597
+ let state;
2598
+ try {
2599
+ state = readProducerState(path);
2600
+ }
2601
+ catch (error) {
2602
+ const raw = rawProducerStateIdentity(path);
2603
+ // A refused file from a replaced epoch would have been skipped anyway.
2604
+ if (raw.epoch_genesis_id !== undefined && raw.epoch_genesis_id !== genesisId)
2605
+ continue;
2606
+ invalid.push({
2607
+ path,
2608
+ adapter,
2609
+ session_hash: name.slice(0, -".json".length),
2610
+ reason: error instanceof Error ? error.message : String(error),
2611
+ ...(raw.instance_id ? { instance_id: raw.instance_id } : {}),
2612
+ });
2613
+ continue;
2614
+ }
2581
2615
  // Skip authorities left behind by a replaced epoch: their sequences and
2582
2616
  // causal links belong to the archived ledger, not this one.
2583
- if (state.epoch_genesis_id !== control.genesis.event.payload.genesis_id)
2617
+ if (state.epoch_genesis_id !== genesisId)
2584
2618
  continue;
2585
2619
  if (!options.includeTerminal && state.terminal)
2586
2620
  continue;
2587
2621
  records.push({ path, modified_at_ms: lstatSync(path).mtimeMs, state });
2588
2622
  }
2589
2623
  }
2590
- return records.sort((left, right) => left.state.generation_id.localeCompare(right.state.generation_id) ||
2624
+ records.sort((left, right) => left.state.generation_id.localeCompare(right.state.generation_id) ||
2591
2625
  left.path.localeCompare(right.path));
2626
+ invalid.sort((left, right) => left.path.localeCompare(right.path));
2627
+ return { records, invalid };
2628
+ }
2629
+ /**
2630
+ * List the valid producer states of the active epoch. An invalid file is
2631
+ * skipped and recorded as a `producer_state_invalid_skipped` diagnostic that
2632
+ * names its adapter and session hash; readers of that one session's state
2633
+ * still fail closed through `readHookProducerStateV3`.
2634
+ */
2635
+ export function listHookProducerStateRecordsV3(coordRoot, options = {}) {
2636
+ const scan = scanHookProducerStateRecordsV3(coordRoot, options);
2637
+ for (const file of scan.invalid)
2638
+ recordInvalidProducerStateDiagnostic(coordRoot, file);
2639
+ return scan.records;
2640
+ }
2641
+ function recordInvalidProducerStateDiagnostic(coordRoot, file) {
2642
+ writeProducerDiagnosticV3(coordRoot, "producer_state_invalid_skipped", {
2643
+ adapter: file.adapter,
2644
+ session_hash: file.session_hash,
2645
+ reason: file.reason,
2646
+ ...(file.instance_id ? { instance_id: file.instance_id } : {}),
2647
+ });
2648
+ }
2649
+ function rawProducerStateIdentity(path) {
2650
+ try {
2651
+ const metadata = lstatSync(path);
2652
+ if (!metadata.isFile() || metadata.isSymbolicLink())
2653
+ return {};
2654
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
2655
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
2656
+ return {};
2657
+ return {
2658
+ ...(typeof parsed.instance_id === "string" &&
2659
+ /^inst_[a-zA-Z0-9._-]{1,128}$/.test(parsed.instance_id)
2660
+ ? { instance_id: parsed.instance_id }
2661
+ : {}),
2662
+ ...(typeof parsed.epoch_genesis_id === "string"
2663
+ ? { epoch_genesis_id: parsed.epoch_genesis_id }
2664
+ : {}),
2665
+ };
2666
+ }
2667
+ catch {
2668
+ return {};
2669
+ }
2592
2670
  }
2593
2671
  /**
2594
2672
  * Re-anchor every live archived hook producer into a newly activated epoch.
@@ -3194,17 +3272,30 @@ function acquireStateLease(coordRoot, statePath) {
3194
3272
  },
3195
3273
  };
3196
3274
  }
3275
+ /** Test seam: publish a producer state through the real write-side validator. */
3276
+ export function publishHookProducerStateForTestV3(coordRoot, path, state) {
3277
+ publishProducerState(coordRoot, path, state);
3278
+ }
3197
3279
  function publishProducerState(coordRoot, path, state) {
3198
3280
  const control = readEventV3ControlState(coordRoot);
3199
3281
  if ((control.state === "candidate" || control.state === "active") &&
3200
3282
  state.epoch_genesis_id !== control.genesis.event.payload.genesis_id) {
3201
3283
  return;
3202
3284
  }
3285
+ const serialized = JSON.stringify(state);
3286
+ // Validate the exact bytes about to land with the reader's own validator.
3287
+ // The previous file stays in place when this refuses.
3288
+ try {
3289
+ validateProducerStateDocument(JSON.parse(serialized));
3290
+ }
3291
+ catch {
3292
+ throw new Error("V3 producer state is invalid; refusing to publish");
3293
+ }
3203
3294
  const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
3204
3295
  let fd;
3205
3296
  try {
3206
3297
  fd = openSync(temporary, "wx", stateFileMode());
3207
- writeFileSync(fd, `${JSON.stringify(state)}\n`, "utf8");
3298
+ writeFileSync(fd, `${serialized}\n`, "utf8");
3208
3299
  fsyncSync(fd);
3209
3300
  closeSync(fd);
3210
3301
  fd = undefined;
@@ -3233,6 +3324,15 @@ function readProducerState(path) {
3233
3324
  catch {
3234
3325
  throw new Error("V3 producer state is unreadable");
3235
3326
  }
3327
+ return validateProducerStateDocument(parsed);
3328
+ }
3329
+ /**
3330
+ * The single producer-state validator. Readers run it on every file they
3331
+ * load, and `publishProducerState` runs it on the serialized document before
3332
+ * the rename, so a state no reader would accept fails in the producing hook
3333
+ * instead of in every later reader of the checkout.
3334
+ */
3335
+ function validateProducerStateDocument(parsed) {
3236
3336
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3237
3337
  throw new Error("V3 producer state is invalid");
3238
3338
  }
@@ -3476,8 +3576,10 @@ function validPendingRuntimeContext(value) {
3476
3576
  Number.isFinite(Date.parse(pending.terminal_observed_at)) &&
3477
3577
  typeof pending.turn_id === "string" &&
3478
3578
  /^tid_[a-f0-9]{64}$/.test(pending.turn_id) &&
3479
- (pending.run_id === undefined || /^run_[0-9a-f-]{36}$/.test(pending.run_id)) &&
3480
- (pending.workflow_id === undefined || /^wf_[0-9a-f-]{36}$/.test(pending.workflow_id)) &&
3579
+ // Same shapes the event contract accepts: workflow children carry
3580
+ // stableScopeId hashes (run_<64 hex>), not UUIDs.
3581
+ (pending.run_id === undefined || /^run_[a-zA-Z0-9._-]{1,128}$/.test(pending.run_id)) &&
3582
+ (pending.workflow_id === undefined || /^wf_[a-zA-Z0-9._-]{1,128}$/.test(pending.workflow_id)) &&
3481
3583
  (pending.workflow_agent_id === undefined ||
3482
3584
  /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(pending.workflow_agent_id)) &&
3483
3585
  typeof pending.native_session_id === "string" &&
@@ -3493,9 +3595,11 @@ function validPendingRuntimeContext(value) {
3493
3595
  (pending.runtime_version === undefined ||
3494
3596
  (typeof pending.runtime_version === "string" &&
3495
3597
  /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(pending.runtime_version))) &&
3598
+ // Externally driven reconciles (`deferRetryLimit`) keep counting past
3599
+ // RUNTIME_CONTEXT_RETRY_LIMIT until their own final attempt, and publish
3600
+ // those counts, so the reader must accept them.
3496
3601
  Number.isSafeInteger(pending.attempts) &&
3497
- pending.attempts >= 0 &&
3498
- pending.attempts < RUNTIME_CONTEXT_RETRY_LIMIT);
3602
+ pending.attempts >= 0);
3499
3603
  }
3500
3604
  function validActiveRuntimeContextProbe(value) {
3501
3605
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -1 +1 @@
1
- {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../../../src/core/events/v3/reader.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAG5B,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,sBAAsB,GAC9B,gBAAgB,GAChB,qBAAqB,GACrB,mBAAmB,GACnB,kBAAkB,GAClB,2BAA2B,GAC3B,0BAA0B,GAC1B,oBAAoB,GACpB,sBAAsB,GACtB,uBAAuB,GACvB,gCAAgC,GAChC,4BAA4B,GAC5B,uBAAuB,GACvB,wBAAwB,GACxB,iBAAiB,GACjB,kBAAkB,GAClB,mBAAmB,GACnB,yBAAyB,GACzB,oBAAoB,GACpB,6BAA6B,GAC7B,wBAAwB,GACxB,6BAA6B,GAC7B,4BAA4B,GAC5B,0BAA0B,GAC1B,yBAAyB,GACzB,yBAAyB,GACzB,yBAAyB,GACzB,iBAAiB,GACjB,iBAAiB,GACjB,yBAAyB,GACzB,0BAA0B,GAC1B,2BAA2B,GAC3B,iBAAiB,GACjB,yBAAyB,CAAC;AAE9B,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE;QACR,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,WAAW,EAAE,kBAAkB,EAAE,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,sBAAsB,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,yFAAyF;AACzF,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,sBAAsB,CAkCxB;AAED,qFAAqF;AACrF,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,MAAM,EAAE,CAwBlF;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACrC;AAsCD,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,UAAU,MAAM,EAAE,CAAC;IAChC,kBAAkB,EAAE,UAAU,MAAM,EAAE,CAAC;IACvC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mCAAmC;IAClD,MAAM,EAAE,+CAA+C,CAAC;IACxD,cAAc,EAAE,CAAC,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AA6BD,eAAO,MAAM,6BAA6B,EAAG,qBAA8B,CAAC;AAC5E,eAAO,MAAM,sCAAsC,kDACqB,CAAC;AACzE,eAAO,MAAM,yCAAyC,qDACqB,CAAC;AAE5E,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM;;;;;EAQ7C;AAED,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,8FAA8F;AAC9F,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,iFAAiF;AACjF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,SAAS,EAAE,MAAM,GAChB,gCAAgC,CAOlC;AAED,iFAAiF;AACjF,wBAAgB,uCAAuC,CACrD,SAAS,EAAE,MAAM,GAChB,mCAAmC,GAAG,SAAS,CAOjD;AAED;;;;GAIG;AACH,wBAAgB,0CAA0C,CACxD,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,mCAAmC,GAC9C,mCAAmC,GAAG,SAAS,CAoBjD;AAED,kFAAkF;AAClF,wBAAgB,wCAAwC,CACtD,KAAK,EAAE,OAAO,GACb,mCAAmC,GAAG,SAAS,CA8CjD;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAsCpB;AAYD,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,cAAc,EACvB,OAAO,GAAE,mBAAwB,GAChC,uBAAuB,CAOzB;AAgBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAEpB;AAyND,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,MAAM,CAAC,EAAE,cAAc,EACvB,OAAO,GAAE,mBAAwB,GAChC,uBAAuB,CAEzB"}
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../../../src/core/events/v3/reader.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { closeSync, existsSync, lstatSync, openSync, readdirSync, readFileSync,
4
4
  import { join, relative, resolve } from "node:path";
5
5
  import { canonicalJsonV3, sha256V3 } from "./canonical.js";
6
6
  import { readEventV3Catalog, readEventV3SegmentManifest, } from "./catalog.js";
7
+ import { readAdvanceableControlWitnessV3 } from "./control-witness.js";
7
8
  import { EVENT_V3_SCHEMA_DIGEST } from "./generated.js";
8
9
  import { eventV3RecoveryRecordsRoot, listEventV3RecoveryReceipts, } from "./recovery-record.js";
9
10
  import { validateEventV3 } from "./validate.js";
@@ -204,7 +205,8 @@ export function readLedgerV3(coordRoot, options = {}) {
204
205
  return resumed.result;
205
206
  }
206
207
  const discovered = discoverLedgerFramesV3(coordRoot);
207
- const validated = validateLedgerFramesV3(discovered.frames, options);
208
+ const validated = validateWitnessedFramesV3(coordRoot, storage, discovered, options) ??
209
+ validateLedgerFramesV3(discovered.frames, options);
208
210
  const recovery = discoverRecoveryReceiptsV3(coordRoot);
209
211
  const diagnostics = [
210
212
  ...discovered.diagnostics,
@@ -263,7 +265,35 @@ function discoverRecoveryReceiptsV3(coordRoot) {
263
265
  export function readLedgerFramesV3(frames, options = {}) {
264
266
  return validateLedgerFramesV3(frames, options).result;
265
267
  }
266
- function validateLedgerFramesV3(frames, options) {
268
+ /**
269
+ * Cold-process fast path. Writers advance an authenticated validation
270
+ * checkpoint under the append lease after every drain, so when the witness
271
+ * names exactly the storage this read observed, each frame already passed the
272
+ * contract and canonical-byte checks that dominate a full read. Those two
273
+ * checks are skipped; every ledger-level rule still runs and rebuilds the
274
+ * validator state. The result is used only if that state reproduces the
275
+ * witnessed checkpoint byte for byte with no diagnostics and storage did not
276
+ * move during the read; anything else returns undefined and the caller runs
277
+ * the canonical full validation.
278
+ */
279
+ function validateWitnessedFramesV3(coordRoot, storage, discovered, options) {
280
+ if (!discovered.active || discovered.diagnostics.length > 0)
281
+ return undefined;
282
+ const witness = readAdvanceableControlWitnessV3(coordRoot);
283
+ if (!witness || witness.payload.storage.fingerprint !== sha256V3(storage.fingerprint)) {
284
+ return undefined;
285
+ }
286
+ const trusted = validateLedgerFramesV3(discovered.frames, options, true);
287
+ if (trusted.state.diagnostics.length > 0)
288
+ return undefined;
289
+ const reproduced = checkpointFromValidationStateV3(trusted.state, discovered.active);
290
+ if (canonicalJsonV3(reproduced) !== canonicalJsonV3(witness.checkpoint))
291
+ return undefined;
292
+ if (ledgerStorageVersionV3(coordRoot).fingerprint !== storage.fingerprint)
293
+ return undefined;
294
+ return trusted;
295
+ }
296
+ function validateLedgerFramesV3(frames, options, witnessed = false) {
267
297
  const state = {
268
298
  diagnostics: [],
269
299
  events: [],
@@ -276,10 +306,14 @@ function validateLedgerFramesV3(frames, options) {
276
306
  clocks: new Map(),
277
307
  event_count: 0,
278
308
  };
279
- validateLedgerFramesIntoStateV3(frames, state);
309
+ validateLedgerFramesIntoStateV3(frames, state, witnessed);
280
310
  return { result: finishLedgerValidationV3(state, options, ledgerFramesBytesV3(frames)), state };
281
311
  }
282
- function validateLedgerFramesIntoStateV3(frames, state) {
312
+ /**
313
+ * `witnessed` skips only the per-frame contract and canonical-byte checks, and
314
+ * only validateWitnessedFramesV3 may pass it, after authenticating the frames.
315
+ */
316
+ function validateLedgerFramesIntoStateV3(frames, state, witnessed = false) {
283
317
  state.first_position ??= frames[0]?.position;
284
318
  const { diagnostics, events, advances, accepted, reader_build: readerBuild, seen_ids: seenIds, producer_sequences: producerSequences, attestations, clocks, } = state;
285
319
  for (const frame of frames) {
@@ -308,17 +342,23 @@ function validateLedgerFramesIntoStateV3(frames, state) {
308
342
  diagnostics.push(diagnostic("unsupported_major", frame));
309
343
  continue;
310
344
  }
311
- const validation = validateEventV3(parsed);
312
- if (!validation.ok || !validation.event) {
313
- diagnostics.push(diagnostic("invalid_contract", frame));
314
- continue;
345
+ let event;
346
+ if (witnessed) {
347
+ event = parsed;
315
348
  }
316
- const event = validation.event;
317
- const shape = event;
318
- if (canonicalJsonV3(event) !== frame.raw) {
319
- diagnostics.push(diagnostic("noncanonical_frame", frame, shape.event_id));
320
- continue;
349
+ else {
350
+ const validation = validateEventV3(parsed);
351
+ if (!validation.ok || !validation.event) {
352
+ diagnostics.push(diagnostic("invalid_contract", frame));
353
+ continue;
354
+ }
355
+ event = validation.event;
356
+ if (canonicalJsonV3(event) !== frame.raw) {
357
+ diagnostics.push(diagnostic("noncanonical_frame", frame, event.event_id));
358
+ continue;
359
+ }
321
360
  }
361
+ const shape = event;
322
362
  const priorFrame = seenIds.get(shape.event_id);
323
363
  if (priorFrame !== undefined) {
324
364
  if (priorFrame !== frame.raw) {
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/core/events/v3/validate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,eAAe,CAAC;AAE5D,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAuCD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,CAkBvE;AAkSD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAKtE"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/core/events/v3/validate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,eAAe,CAAC;AAE5D,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAgFD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,CAkBvE;AAkSD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAKtE"}
@@ -1,8 +1,43 @@
1
1
  import { Value } from "@sinclair/typebox/value";
2
2
  import { canonicalJsonV3 } from "./canonical.js";
3
3
  import { EventV3Schema } from "./contract.js";
4
+ /**
5
+ * Every union member requires its own literal `event_type`, so only the member
6
+ * named by a value's `event_type` can accept it. Checking that one member gives
7
+ * the union's verdict without trying the other variants, which more than halves
8
+ * a full-ledger read. Returns null, falling back to the whole union, if a member
9
+ * ever stops carrying a required unique literal.
10
+ */
11
+ let eventVariantsByType;
12
+ function eventVariantsByTypeV3() {
13
+ if (eventVariantsByType !== undefined)
14
+ return eventVariantsByType;
15
+ const variants = new Map();
16
+ for (const variant of EventV3Schema.anyOf) {
17
+ const literal = variant.properties.event_type?.const;
18
+ if (typeof literal !== "string" ||
19
+ !variant.required?.includes("event_type") ||
20
+ variants.has(literal)) {
21
+ eventVariantsByType = null;
22
+ return null;
23
+ }
24
+ variants.set(literal, variant);
25
+ }
26
+ eventVariantsByType = variants;
27
+ return variants;
28
+ }
29
+ function checkEventSchemaV3(value) {
30
+ const variants = eventVariantsByTypeV3();
31
+ if (!variants)
32
+ return Value.Check(EventV3Schema, value);
33
+ const eventType = typeof value === "object" && value !== null
34
+ ? value.event_type
35
+ : undefined;
36
+ const variant = typeof eventType === "string" ? variants.get(eventType) : undefined;
37
+ return variant !== undefined && Value.Check(variant, value);
38
+ }
4
39
  export function validateEventV3(value) {
5
- if (!Value.Check(EventV3Schema, value)) {
40
+ if (!checkEventSchemaV3(value)) {
6
41
  return {
7
42
  ok: false,
8
43
  issues: [...Value.Errors(EventV3Schema, value)]
@@ -0,0 +1,23 @@
1
+ export type NativeParentObservation = {
2
+ state: "observed";
3
+ parent_session_id: string;
4
+ source_witness: string;
5
+ } | {
6
+ state: "none";
7
+ reason: string;
8
+ source_witness: string;
9
+ } | {
10
+ state: "unknown";
11
+ reason: string;
12
+ };
13
+ export interface NativeParentRequest {
14
+ adapter: string;
15
+ native_session_id?: string;
16
+ transcript_path?: string;
17
+ }
18
+ export interface NativeParentReaderOptions {
19
+ discoverTranscript?: (sessionId: string, transcriptPath?: string) => string | undefined;
20
+ }
21
+ /** Native relationship evidence only; no prompt bodies, state writes or recipient authority. */
22
+ export declare function readNativeParentReference(request: NativeParentRequest, options?: NativeParentReaderOptions): NativeParentObservation;
23
+ //# sourceMappingURL=native-parent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-parent.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/native-parent.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,uBAAuB,GAC/B;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,yBAAyB;IACxC,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACzF;AAYD,gGAAgG;AAChG,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,yBAA8B,GACtC,uBAAuB,CAuDzB"}
@@ -0,0 +1,91 @@
1
+ import { createHash } from "node:crypto";
2
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "node:fs";
3
+ import { discoverCodexSessionTranscript } from "./runtime-telemetry.js";
4
+ const HEADER_LIMIT = 1024 * 1024;
5
+ const NATIVE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
+ const ORDINARY_SOURCES = new Set(["cli", "exec", "vscode", "app-server"]);
7
+ function object(value) {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value)
9
+ ? value
10
+ : undefined;
11
+ }
12
+ /** Native relationship evidence only; no prompt bodies, state writes or recipient authority. */
13
+ export function readNativeParentReference(request, options = {}) {
14
+ if (request.adapter !== "codex")
15
+ return unknown("native_parent_adapter_unsupported");
16
+ const child = request.native_session_id;
17
+ if (!child || !NATIVE_ID.test(child))
18
+ return unknown("native_child_identity_unavailable");
19
+ let fd;
20
+ try {
21
+ const path = (options.discoverTranscript ?? discoverCodexSessionTranscript)(child, request.transcript_path);
22
+ if (!path)
23
+ return unknown("native_parent_transcript_unavailable");
24
+ const entry = lstatSync(path);
25
+ if (!entry.isFile() || entry.isSymbolicLink())
26
+ return unknown("native_parent_not_regular");
27
+ fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
28
+ const before = fstatSync(fd);
29
+ if (!before.isFile() || before.dev !== entry.dev || before.ino !== entry.ino) {
30
+ return unknown("native_parent_file_changed");
31
+ }
32
+ const header = readHeader(fd);
33
+ if (!header)
34
+ return unknown("native_parent_header_incomplete");
35
+ // Appending later rows is normal. The same header must remain unchanged.
36
+ const checked = readHeader(fd);
37
+ const after = lstatSync(path);
38
+ if (!checked?.equals(header) ||
39
+ !after.isFile() ||
40
+ after.isSymbolicLink() ||
41
+ before.dev !== after.dev ||
42
+ before.ino !== after.ino) {
43
+ return unknown("native_parent_file_changed");
44
+ }
45
+ const row = object(JSON.parse(header.toString("utf8")));
46
+ const payload = object(row?.payload);
47
+ if (row?.type !== "session_meta" || payload?.id !== child) {
48
+ return unknown("native_parent_child_mismatch");
49
+ }
50
+ const source_witness = createHash("sha256").update(header).digest("hex");
51
+ if (typeof payload.source === "string" && ORDINARY_SOURCES.has(payload.source)) {
52
+ return { state: "none", reason: "native_ordinary_session", source_witness };
53
+ }
54
+ const source = object(payload.source);
55
+ const spawn = object(object(source?.subagent)?.thread_spawn);
56
+ const parent = spawn?.parent_thread_id;
57
+ if (typeof parent !== "string" || !NATIVE_ID.test(parent)) {
58
+ return unknown("native_parent_not_reported");
59
+ }
60
+ if (parent.toLowerCase() === child.toLowerCase())
61
+ return unknown("native_parent_self_reference");
62
+ return { state: "observed", parent_session_id: parent, source_witness };
63
+ }
64
+ catch {
65
+ return unknown("native_parent_metadata_unreadable");
66
+ }
67
+ finally {
68
+ if (fd !== undefined)
69
+ closeSync(fd);
70
+ }
71
+ }
72
+ function readHeader(fd) {
73
+ const chunks = [];
74
+ let offset = 0;
75
+ while (offset < HEADER_LIMIT) {
76
+ const buffer = Buffer.alloc(Math.min(16 * 1024, HEADER_LIMIT - offset));
77
+ const bytes = readSync(fd, buffer, 0, buffer.length, offset);
78
+ if (bytes === 0)
79
+ return undefined;
80
+ const data = buffer.subarray(0, bytes);
81
+ const newline = data.indexOf(10);
82
+ if (newline >= 0)
83
+ return Buffer.concat([...chunks, data.subarray(0, newline)]);
84
+ chunks.push(data);
85
+ offset += bytes;
86
+ }
87
+ return undefined;
88
+ }
89
+ function unknown(reason) {
90
+ return { state: "unknown", reason };
91
+ }