harnery 0.45.2 → 0.46.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 (216) 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/session-events.d.ts.map +1 -1
  29. package/dist/core/agents/session-events.js +12 -4
  30. package/dist/core/agents/state/heartbeat-reader.d.ts +3 -0
  31. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  32. package/dist/core/agents/state/live-coordination-view.d.ts +3 -1
  33. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  34. package/dist/core/agents/state/live-coordination-view.js +6 -2
  35. package/dist/core/agents/state/parent-reference-contract.d.ts +16 -0
  36. package/dist/core/agents/state/parent-reference-contract.d.ts.map +1 -0
  37. package/dist/core/agents/state/parent-reference-contract.js +1 -0
  38. package/dist/core/agents/state/parent-reference.d.ts +18 -0
  39. package/dist/core/agents/state/parent-reference.d.ts.map +1 -0
  40. package/dist/core/agents/state/parent-reference.js +119 -0
  41. package/dist/core/config.d.ts +39 -0
  42. package/dist/core/config.d.ts.map +1 -1
  43. package/dist/core/config.js +63 -0
  44. package/dist/core/diagnostics/path-identity.d.ts +1 -1
  45. package/dist/core/diagnostics/path-identity.d.ts.map +1 -1
  46. package/dist/core/diagnostics/path-identity.js +51 -6
  47. package/dist/core/events/v3/canonical.js +43 -14
  48. package/dist/core/events/v3/index.d.ts +1 -1
  49. package/dist/core/events/v3/index.d.ts.map +1 -1
  50. package/dist/core/events/v3/index.js +1 -1
  51. package/dist/core/events/v3/producers/recorder.d.ts +34 -0
  52. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  53. package/dist/core/events/v3/producers/recorder.js +117 -13
  54. package/dist/core/events/v3/reader.d.ts.map +1 -1
  55. package/dist/core/events/v3/reader.js +53 -13
  56. package/dist/core/events/v3/validate.d.ts.map +1 -1
  57. package/dist/core/events/v3/validate.js +36 -1
  58. package/dist/core/hooks/adapter/native-parent.d.ts +23 -0
  59. package/dist/core/hooks/adapter/native-parent.d.ts.map +1 -0
  60. package/dist/core/hooks/adapter/native-parent.js +91 -0
  61. package/dist/core/hooks/cli.js +79 -4
  62. package/dist/core/hooks/resolve/owner.d.ts +0 -11
  63. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  64. package/dist/core/hooks/resolve/owner.js +0 -23
  65. package/dist/core/hooks/shell-admission.d.ts +84 -0
  66. package/dist/core/hooks/shell-admission.d.ts.map +1 -0
  67. package/dist/core/hooks/shell-admission.js +622 -0
  68. package/dist/core/hooks/shell-commit-tree.d.ts +2 -0
  69. package/dist/core/hooks/shell-commit-tree.d.ts.map +1 -0
  70. package/dist/core/hooks/shell-commit-tree.js +104 -0
  71. package/dist/core/inbox/contract.d.ts +12 -0
  72. package/dist/core/inbox/contract.d.ts.map +1 -1
  73. package/dist/core/inbox/contract.js +16 -0
  74. package/dist/core/inbox/peer-messages.d.ts +41 -16
  75. package/dist/core/inbox/peer-messages.d.ts.map +1 -1
  76. package/dist/core/inbox/peer-messages.js +372 -15
  77. package/dist/core/inbox/service.d.ts +8 -1
  78. package/dist/core/inbox/service.d.ts.map +1 -1
  79. package/dist/core/inbox/service.js +83 -3
  80. package/dist/core/inbox/targets.d.ts +17 -3
  81. package/dist/core/inbox/targets.d.ts.map +1 -1
  82. package/dist/core/inbox/targets.js +77 -2
  83. package/dist/core/presence/git.d.ts +18 -12
  84. package/dist/core/presence/git.d.ts.map +1 -1
  85. package/dist/core/presence/git.js +32 -38
  86. package/dist/core/presence/index.d.ts +2 -2
  87. package/dist/core/presence/index.d.ts.map +1 -1
  88. package/dist/core/presence/index.js +8 -6
  89. package/dist/core/presence/mail.d.ts +133 -0
  90. package/dist/core/presence/mail.d.ts.map +1 -0
  91. package/dist/core/presence/mail.js +464 -0
  92. package/dist/core/semantic/contract.d.ts +5 -5
  93. package/dist/core/storage/modes.d.ts +4 -0
  94. package/dist/core/storage/modes.d.ts.map +1 -1
  95. package/dist/core/storage/modes.js +4 -0
  96. package/dist/core/storage/segments.d.ts.map +1 -1
  97. package/dist/core/storage/segments.js +5 -4
  98. package/dist/lib/admission-history.d.ts +96 -0
  99. package/dist/lib/admission-history.d.ts.map +1 -0
  100. package/dist/lib/admission-history.js +196 -0
  101. package/dist/lib/admission.d.ts +56 -7
  102. package/dist/lib/admission.d.ts.map +1 -1
  103. package/dist/lib/admission.js +161 -19
  104. package/dist/lib/browser/client.d.ts +18 -0
  105. package/dist/lib/browser/client.d.ts.map +1 -1
  106. package/dist/lib/browser/client.js +100 -12
  107. package/dist/lib/browser/content-checks.d.ts +3 -1
  108. package/dist/lib/browser/content-checks.d.ts.map +1 -1
  109. package/dist/lib/browser/content-checks.js +10 -2
  110. package/dist/lib/browser/critique.d.ts +32 -0
  111. package/dist/lib/browser/critique.d.ts.map +1 -1
  112. package/dist/lib/browser/critique.js +72 -3
  113. package/dist/lib/browser/geometry.d.ts +2 -0
  114. package/dist/lib/browser/geometry.d.ts.map +1 -1
  115. package/dist/lib/browser/geometry.js +61 -15
  116. package/dist/lib/browser/index.d.ts +1 -1
  117. package/dist/lib/browser/index.d.ts.map +1 -1
  118. package/dist/lib/browser/index.js +1 -1
  119. package/dist/lib/browser/page-review-contracts.d.ts +6 -1
  120. package/dist/lib/browser/page-review-contracts.d.ts.map +1 -1
  121. package/dist/lib/browser/page-review-contracts.js +6 -1
  122. package/dist/lib/browser/page-review-judge.d.ts +3 -1
  123. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  124. package/dist/lib/browser/page-review-judge.js +41 -11
  125. package/dist/lib/browser/page-review-pack.d.ts +3 -0
  126. package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
  127. package/dist/lib/browser/page-review-pack.js +5 -2
  128. package/dist/lib/browser/qa-run-contracts.d.ts +11 -0
  129. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  130. package/dist/lib/browser/qa-run-contracts.js +11 -4
  131. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  132. package/dist/lib/browser/qa-run.js +73 -16
  133. package/dist/lib/browser/runts.d.ts +4 -2
  134. package/dist/lib/browser/runts.d.ts.map +1 -1
  135. package/dist/lib/browser/runts.js +84 -33
  136. package/dist/lib/browser/windows-chrome.d.ts +14 -0
  137. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  138. package/dist/lib/browser/windows-chrome.js +42 -0
  139. package/dist/lib/browser/windows-login-sync.d.ts +110 -0
  140. package/dist/lib/browser/windows-login-sync.d.ts.map +1 -0
  141. package/dist/lib/browser/windows-login-sync.js +171 -0
  142. package/dist/lib/cookies/client.d.ts +30 -5
  143. package/dist/lib/cookies/client.d.ts.map +1 -1
  144. package/dist/lib/cookies/client.js +55 -10
  145. package/dist/lib/cookies/index.d.ts +1 -1
  146. package/dist/lib/cookies/index.d.ts.map +1 -1
  147. package/dist/lib/cookies/index.js +1 -1
  148. package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
  149. package/dist/lib/docs-metadata-sync.js +41 -2
  150. package/dist/lib/durable-job.d.ts +8 -0
  151. package/dist/lib/durable-job.d.ts.map +1 -1
  152. package/dist/lib/durable-job.js +10 -0
  153. package/dist/lib/http/client.d.ts +9 -8
  154. package/dist/lib/http/client.d.ts.map +1 -1
  155. package/dist/lib/http/client.js +44 -2
  156. package/dist/lib/instructions/templates.js +4 -4
  157. package/package.json +2 -2
  158. package/src/commander.ts +13 -0
  159. package/src/commands/admission.ts +272 -29
  160. package/src/commands/agents.ts +132 -27
  161. package/src/commands/browse.ts +208 -6
  162. package/src/commands/fetch.ts +1 -0
  163. package/src/commands/ledger-v3.ts +8 -2
  164. package/src/commands/presence.ts +49 -0
  165. package/src/commands/qa-run.ts +4 -3
  166. package/src/commands/review-pack.ts +41 -5
  167. package/src/core/agents/index.ts +6 -0
  168. package/src/core/agents/live-authority-v3.ts +11 -4
  169. package/src/core/agents/render/session-context.ts +1 -7
  170. package/src/core/agents/session-events.ts +18 -3
  171. package/src/core/agents/state/heartbeat-reader.ts +3 -0
  172. package/src/core/agents/state/live-coordination-view.ts +13 -6
  173. package/src/core/agents/state/parent-reference-contract.ts +14 -0
  174. package/src/core/agents/state/parent-reference.ts +179 -0
  175. package/src/core/config.ts +85 -0
  176. package/src/core/diagnostics/path-identity.ts +52 -6
  177. package/src/core/events/v3/canonical.ts +46 -13
  178. package/src/core/events/v3/index.ts +3 -0
  179. package/src/core/events/v3/producers/recorder.ts +151 -13
  180. package/src/core/events/v3/reader.ts +55 -11
  181. package/src/core/events/v3/validate.ts +42 -1
  182. package/src/core/hooks/adapter/native-parent.ts +109 -0
  183. package/src/core/hooks/cli.ts +83 -4
  184. package/src/core/hooks/resolve/owner.ts +0 -23
  185. package/src/core/hooks/shell-admission.ts +634 -0
  186. package/src/core/hooks/shell-commit-tree.ts +102 -0
  187. package/src/core/inbox/contract.ts +18 -0
  188. package/src/core/inbox/peer-messages.ts +501 -15
  189. package/src/core/inbox/service.ts +108 -3
  190. package/src/core/inbox/targets.ts +94 -3
  191. package/src/core/presence/git.ts +45 -37
  192. package/src/core/presence/index.ts +16 -8
  193. package/src/core/presence/mail.ts +620 -0
  194. package/src/core/storage/modes.ts +6 -0
  195. package/src/core/storage/segments.ts +5 -4
  196. package/src/lib/admission-history.ts +267 -0
  197. package/src/lib/admission.ts +217 -19
  198. package/src/lib/browser/client.ts +147 -35
  199. package/src/lib/browser/content-checks.ts +14 -3
  200. package/src/lib/browser/critique.ts +113 -3
  201. package/src/lib/browser/geometry.ts +63 -14
  202. package/src/lib/browser/index.ts +5 -0
  203. package/src/lib/browser/page-review-contracts.ts +6 -1
  204. package/src/lib/browser/page-review-judge.ts +58 -13
  205. package/src/lib/browser/page-review-pack.ts +13 -2
  206. package/src/lib/browser/qa-run-contracts.ts +30 -4
  207. package/src/lib/browser/qa-run.ts +78 -17
  208. package/src/lib/browser/runts.ts +85 -37
  209. package/src/lib/browser/windows-chrome.ts +50 -0
  210. package/src/lib/browser/windows-login-sync.ts +275 -0
  211. package/src/lib/cookies/client.ts +62 -10
  212. package/src/lib/cookies/index.ts +3 -0
  213. package/src/lib/docs-metadata-sync.ts +44 -2
  214. package/src/lib/durable-job.ts +15 -0
  215. package/src/lib/http/client.ts +63 -1
  216. 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":"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"}
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
+ }
@@ -32,12 +32,13 @@ import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.
32
32
  import { ensureLiveCoordinationHeartbeat } from "../agents/state/live-coordination-writer.js";
33
33
  import { assignName, readForkParent, readForkState, recordForkDetection, resolveName, } from "../agents/state/names.js";
34
34
  import { writePidmapRow } from "../agents/state/pidmap.js";
35
- import { agentsRequireGitFinalization, coordFreshnessSeconds, hostPromptContextConfig, resolveBinName, } from "../config.js";
35
+ import { agentsRequireGitFinalization, coordFreshnessSeconds, hostPromptContextConfig, readAdmissionConfig, resolveBinName, } from "../config.js";
36
36
  import { checkpointContext, completeContextRecovery, extractContextSample, markContextCompactionCompleted, prepareContextRecovery, readContextState, recordContextSample, } from "../context/index.js";
37
37
  import { tryWriteLiveDisplayV3 } from "../events/v3/live-feed.js";
38
38
  import { liveHookSignalDefersDrainV3, reconcileLivePendingRuntimeContextV3, recordLiveDelegatedChildSessionV3, recordLiveHookSignalV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
39
39
  import { captureSpanClockV3 } from "../events/v3/span-state.js";
40
40
  import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.js";
41
+ import { importMail, publishMail } from "../presence/mail.js";
41
42
  import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.js";
42
43
  import { stableScopeId } from "../workflow/scope-id.js";
43
44
  import { adapterBehavior } from "./adapter/behaviors/index.js";
@@ -57,6 +58,7 @@ import { resolveOwner } from "./resolve/owner.js";
57
58
  import { inspectSessionNameDisplayImmediately, scanAssistantStatusBoxPresent, scanStatusBoxPresent, scanTranscriptRuntime, } from "./resolve/transcript.js";
58
59
  import { clearHookRunMarker, writeHookRunMarker } from "./run-markers.js";
59
60
  import { sessionNamePresence } from "./session-name-presence.js";
61
+ import { shellCommitTreeReason } from "./shell-commit-tree.js";
60
62
  import { shellRmReason } from "./shell-rm.js";
61
63
  import { shellWaiterReason } from "./shell-waiter.js";
62
64
  let hookHealthState;
@@ -227,7 +229,18 @@ function healForkLineage(coordRoot, instanceId, payload) {
227
229
  function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter, payload) {
228
230
  if (coordEnv("WORKFLOW_CHILD") === "1")
229
231
  return;
230
- const row = readLiveCoordinationRow(coordRoot, instanceId) ?? readHeartbeat(coordRoot, instanceId);
232
+ // This runs on every tool call. The heartbeat cache is the surface this
233
+ // function stamps, so a cache that already carries a name, or marks a
234
+ // session that never takes one, means there is nothing to do. Only a cache
235
+ // that cannot answer pays for the authoritative read, which re-validates the
236
+ // whole active ledger and cost about half a second per call at 22 MB.
237
+ const cached = readHeartbeat(coordRoot, instanceId);
238
+ if (cached?.kind === "subagent" || cached?.kind === "transient" || cached?.workflow_run_id) {
239
+ return;
240
+ }
241
+ if (cached?.name?.trim())
242
+ return;
243
+ const row = readLiveCoordinationRow(coordRoot, instanceId) ?? cached;
231
244
  if (row?.kind === "subagent" || row?.kind === "transient" || row?.workflow_run_id)
232
245
  return;
233
246
  if (row?.name?.trim())
@@ -1009,8 +1022,23 @@ function recordContextTelemetry(run) {
1009
1022
  }
1010
1023
  }
1011
1024
  }
1025
+ /** Cross-machine peer messages (ADR 0222) ride the presence fetch: import
1026
+ * whatever mail refs are already local. Reads local refs only, at most once
1027
+ * per import interval; never blocks on the network. */
1028
+ async function receiveRemoteMail(coordRoot) {
1029
+ const { peerInbox } = await import("../inbox/peer-messages.js");
1030
+ importMail(coordRoot, peerInbox(coordRoot), { throttle: true });
1031
+ }
1012
1032
  async function handleSessionStarted(run) {
1013
1033
  const { coordRoot, adapter, payload, owner, sessionId, data } = run;
1034
+ // Import mail from refs fetched earlier, so a message sent from another
1035
+ // machine while this task was away is in the inbox this context renders.
1036
+ try {
1037
+ await receiveRemoteMail(coordRoot);
1038
+ }
1039
+ catch (err) {
1040
+ logError(coordRoot, err, { phase: "session-start-mail" });
1041
+ }
1014
1042
  // A configured backup runs out of process and is freshness-gated per host.
1015
1043
  // Startup never waits for restic or a remote provider; the child owns no
1016
1044
  // hook stdio, so the harness is not held open until it finishes. A failed
@@ -1124,6 +1152,7 @@ async function handleTurnStarted(run) {
1124
1152
  // interval; the systemMessage below reads whatever is locally known).
1125
1153
  try {
1126
1154
  fetchPresence(coordRoot);
1155
+ await receiveRemoteMail(coordRoot);
1127
1156
  }
1128
1157
  catch (err) {
1129
1158
  logError(coordRoot, err, { phase: "user-prompt-submit-presence" });
@@ -1187,6 +1216,7 @@ async function handleStop(run) {
1187
1216
  // its daemon intentionally exits when a machine has no live sessions.
1188
1217
  try {
1189
1218
  publishPresence(coordRoot);
1219
+ publishMail(coordRoot);
1190
1220
  ensureRelayDaemon(coordRoot);
1191
1221
  }
1192
1222
  catch (err) {
@@ -1310,6 +1340,12 @@ async function handleToolRequested(run) {
1310
1340
  emitDeny(adapter, rmReason);
1311
1341
  return 0;
1312
1342
  }
1343
+ const commitTreeReason = shellCommitTreeReason(payload?.tool_name, payload?.tool_input, resolveBinName(coordRoot));
1344
+ if (commitTreeReason) {
1345
+ const { emitDeny } = await import("./adapter/output.js");
1346
+ emitDeny(adapter, commitTreeReason);
1347
+ return 0;
1348
+ }
1313
1349
  // G-guard for ALL adapters. Claude Code previously ran this via a
1314
1350
  // pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
1315
1351
  // that adapter is now deleted, so agent-hook owns the deny for every adapter.
@@ -1327,8 +1363,38 @@ async function handleToolRequested(run) {
1327
1363
  }
1328
1364
  if (!guardAllowed)
1329
1365
  return 0;
1366
+ // Heavy shell checks queue for a machine-wide admission slot when the
1367
+ // project configures one. Fails open: any error runs the command as written.
1368
+ try {
1369
+ if (await emitAdmissionRewrite(coordRoot, adapter, payload))
1370
+ return 0;
1371
+ }
1372
+ catch (err) {
1373
+ logError(coordRoot, err, { phase: "pre-tool-use-admission" });
1374
+ }
1330
1375
  return undefined;
1331
1376
  }
1377
+ async function emitAdmissionRewrite(coordRoot, adapter, payload) {
1378
+ const resource = readAdmissionConfig(coordRoot)?.guard?.resource;
1379
+ if (!resource)
1380
+ return false;
1381
+ const { admissionHookOutput } = await import("./shell-admission.js");
1382
+ const output = admissionHookOutput({
1383
+ adapter,
1384
+ resource,
1385
+ bin: resolveBinName(coordRoot),
1386
+ projectRoot: coordRoot,
1387
+ toolName: payload?.tool_name,
1388
+ toolInput: payload?.tool_input,
1389
+ cwd: payload?.cwd,
1390
+ skip: coordEnv("ADMISSION_SKIP") === "1" || Boolean(coordEnv("ADMISSION_HELD")),
1391
+ platform: process.platform,
1392
+ });
1393
+ if (!output)
1394
+ return false;
1395
+ process.stdout.write(`${JSON.stringify(output)}\n`);
1396
+ return true;
1397
+ }
1332
1398
  async function handlePostToolUse(run) {
1333
1399
  const { coordRoot, adapter, eventName, payload, owner } = run;
1334
1400
  // Inject the full instruction only at the successful tool boundary that
@@ -1348,9 +1414,18 @@ async function handlePostToolUse(run) {
1348
1414
  // retry response already carries the full instruction and an end-turn
1349
1415
  // status is the wrong place to ask.
1350
1416
  try {
1351
- const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
1352
1417
  // A workflow child has no user-visible title to set.
1353
- const name = coordEnv("WORKFLOW_CHILD") === "1" ? undefined : sessionNameDisplayPending(row);
1418
+ if (coordEnv("WORKFLOW_CHILD") === "1")
1419
+ return;
1420
+ // The live row copies every name-display field from a current heartbeat
1421
+ // cache and leaves them unset otherwise, so a raw cache with nothing
1422
+ // pending proves the row has nothing pending either. That skips a full
1423
+ // ledger read on almost every tool call; only a pending title pays for
1424
+ // the authoritative check below.
1425
+ if (!sessionNameDisplayPending(readHeartbeat(coordRoot, owner.instance_id)))
1426
+ return;
1427
+ const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
1428
+ const name = sessionNameDisplayPending(row);
1354
1429
  if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
1355
1430
  // Record the title before asking for it. The suggestion can still
1356
1431
  // change afterwards (an assigned-name rewrite, a lifecycle re-mint, a