@stigmer/runner 3.15.1 → 3.15.2-dev.20260913223433

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 (518) hide show
  1. package/README.md +2 -4
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/__test-utils__/execution-record-fixture.d.ts +14 -0
  4. package/dist/__test-utils__/execution-record-fixture.js +4 -0
  5. package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
  6. package/dist/__test-utils__/file-review-projection.d.ts +40 -0
  7. package/dist/__test-utils__/file-review-projection.js +136 -0
  8. package/dist/__test-utils__/file-review-projection.js.map +1 -0
  9. package/dist/__test-utils__/harness-contract/types.d.ts +23 -1
  10. package/dist/__test-utils__/harness-contract/types.js +15 -0
  11. package/dist/__test-utils__/harness-contract/types.js.map +1 -1
  12. package/dist/__test-utils__/hermetic-activity.d.ts +44 -12
  13. package/dist/__test-utils__/hermetic-activity.js +52 -14
  14. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  15. package/dist/__test-utils__/model-registry-fixture.d.ts +32 -7
  16. package/dist/__test-utils__/model-registry-fixture.js +31 -7
  17. package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
  18. package/dist/__test-utils__/turn-input-fixture.d.ts +1 -1
  19. package/dist/__test-utils__/turn-input-fixture.js +1 -1
  20. package/dist/__test-utils__/turn-input-fixture.js.map +1 -1
  21. package/dist/activities/call-agent.js +2 -2
  22. package/dist/activities/call-agent.js.map +1 -1
  23. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +45 -3
  24. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -1
  25. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +0 -9
  26. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +2 -41
  27. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  28. package/dist/activities/execute-cursor/adapter.d.ts +7 -5
  29. package/dist/activities/execute-cursor/adapter.js +7 -5
  30. package/dist/activities/execute-cursor/adapter.js.map +1 -1
  31. package/dist/activities/execute-cursor/approval-policy.d.ts +5 -2
  32. package/dist/activities/execute-cursor/approval-policy.js +5 -2
  33. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  34. package/dist/activities/execute-cursor/approval-state.d.ts +16 -8
  35. package/dist/activities/execute-cursor/approval-state.js +6 -3
  36. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  37. package/dist/activities/execute-cursor/cas-observations.d.ts +14 -2
  38. package/dist/activities/execute-cursor/cas-observations.js +19 -2
  39. package/dist/activities/execute-cursor/cas-observations.js.map +1 -1
  40. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +8 -4
  41. package/dist/activities/execute-cursor/cursor-capabilities.js +21 -4
  42. package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -1
  43. package/dist/activities/execute-cursor/message-translator.d.ts +12 -20
  44. package/dist/activities/execute-cursor/message-translator.js +19 -32
  45. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  46. package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -58
  47. package/dist/activities/execute-cursor/prompt-builder.js +76 -95
  48. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  49. package/dist/activities/execute-cursor/turn-boundary.d.ts +27 -55
  50. package/dist/activities/execute-cursor/turn-boundary.js +30 -90
  51. package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
  52. package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
  53. package/dist/activities/execute-cursor/turn-settle.js +37 -49
  54. package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
  55. package/dist/activities/execute-cursor/turn-setup.d.ts +16 -24
  56. package/dist/activities/execute-cursor/turn-setup.js +29 -59
  57. package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
  58. package/dist/activities/execute-cursor/turn-stream.d.ts +3 -8
  59. package/dist/activities/execute-cursor/turn-stream.js +9 -22
  60. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  61. package/dist/activities/execute-cursor/turn.js +5 -1
  62. package/dist/activities/execute-cursor/turn.js.map +1 -1
  63. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +93 -0
  64. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +348 -0
  65. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js.map +1 -0
  66. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +170 -0
  67. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +225 -0
  68. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -0
  69. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.d.ts +24 -0
  70. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +41 -0
  71. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -0
  72. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +54 -0
  73. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +72 -0
  74. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -0
  75. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +187 -23
  76. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +238 -27
  77. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
  78. package/dist/activities/execute-deep-agent/adapter.d.ts +41 -0
  79. package/dist/activities/execute-deep-agent/adapter.js +78 -0
  80. package/dist/activities/execute-deep-agent/adapter.js.map +1 -0
  81. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +8 -0
  82. package/dist/activities/execute-deep-agent/cas-capture-observer.js +9 -0
  83. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -1
  84. package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +31 -0
  85. package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +40 -0
  86. package/dist/activities/execute-deep-agent/deep-agent-capabilities.js.map +1 -0
  87. package/dist/activities/execute-deep-agent/execution-state.d.ts +4 -11
  88. package/dist/activities/execute-deep-agent/execution-state.js +4 -12
  89. package/dist/activities/execute-deep-agent/execution-state.js.map +1 -1
  90. package/dist/activities/execute-deep-agent/hitl.d.ts +49 -47
  91. package/dist/activities/execute-deep-agent/hitl.js +77 -103
  92. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  93. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +51 -23
  94. package/dist/activities/execute-deep-agent/prompt-builder.js +115 -76
  95. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +18 -32
  97. package/dist/activities/execute-deep-agent/status-builder-shared.js +10 -65
  98. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  99. package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +10 -8
  100. package/dist/activities/execute-deep-agent/streaming-side-effects.js +12 -14
  101. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  102. package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +36 -13
  103. package/dist/activities/execute-deep-agent/subagent-tracker.js +76 -39
  104. package/dist/activities/execute-deep-agent/subagent-tracker.js.map +1 -1
  105. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -28
  106. package/dist/activities/execute-deep-agent/subagent-transformer.js +29 -82
  107. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  108. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -2
  109. package/dist/activities/execute-deep-agent/subagent-wiring.js +2 -2
  110. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  111. package/dist/activities/execute-deep-agent/turn-settle.d.ts +42 -0
  112. package/dist/activities/execute-deep-agent/turn-settle.js +146 -0
  113. package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -0
  114. package/dist/activities/execute-deep-agent/turn-setup.d.ts +212 -0
  115. package/dist/activities/execute-deep-agent/turn-setup.js +430 -0
  116. package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -0
  117. package/dist/activities/execute-deep-agent/turn-stream.d.ts +97 -0
  118. package/dist/activities/execute-deep-agent/turn-stream.js +242 -0
  119. package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -0
  120. package/dist/activities/execute-deep-agent/turn.d.ts +42 -0
  121. package/dist/activities/execute-deep-agent/turn.js +116 -0
  122. package/dist/activities/execute-deep-agent/turn.js.map +1 -0
  123. package/dist/activities/execute-deep-agent/v3-event-recorder.d.ts +6 -5
  124. package/dist/activities/execute-deep-agent/v3-event-recorder.js +6 -5
  125. package/dist/activities/execute-deep-agent/v3-event-recorder.js.map +1 -1
  126. package/dist/activities/execute-deep-agent/v3-events.d.ts +1 -1
  127. package/dist/activities/execute-deep-agent/v3-events.js +1 -1
  128. package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +60 -17
  129. package/dist/activities/execute-deep-agent/v3-status-builder.js +72 -57
  130. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  131. package/dist/harness/approval-decisions.d.ts +61 -0
  132. package/dist/harness/approval-decisions.js +102 -0
  133. package/dist/harness/approval-decisions.js.map +1 -0
  134. package/dist/harness/capabilities.d.ts +16 -5
  135. package/dist/harness/capabilities.js +9 -0
  136. package/dist/harness/capabilities.js.map +1 -1
  137. package/dist/harness/capture.d.ts +144 -0
  138. package/dist/harness/capture.js +219 -0
  139. package/dist/harness/capture.js.map +1 -0
  140. package/dist/harness/persist-chokepoint.d.ts +19 -4
  141. package/dist/harness/persist-chokepoint.js +17 -4
  142. package/dist/harness/persist-chokepoint.js.map +1 -1
  143. package/dist/harness/run-turn.js +115 -29
  144. package/dist/harness/run-turn.js.map +1 -1
  145. package/dist/harness/terminal-table.d.ts +40 -11
  146. package/dist/harness/terminal-table.js +58 -19
  147. package/dist/harness/terminal-table.js.map +1 -1
  148. package/dist/harness/turn-context.d.ts +141 -76
  149. package/dist/harness/turn-context.js +194 -99
  150. package/dist/harness/turn-context.js.map +1 -1
  151. package/dist/harness/types.d.ts +65 -12
  152. package/dist/harness/types.js +8 -2
  153. package/dist/harness/types.js.map +1 -1
  154. package/dist/harness-adapters.d.ts +12 -8
  155. package/dist/harness-adapters.js +17 -9
  156. package/dist/harness-adapters.js.map +1 -1
  157. package/dist/middleware/approval-gate.d.ts +1 -1
  158. package/dist/middleware/approval-gate.js +1 -1
  159. package/dist/middleware/approval-gate.js.map +1 -1
  160. package/dist/middleware/cost-advisory.d.ts +39 -0
  161. package/dist/middleware/{cost-cap.js → cost-advisory.js} +37 -55
  162. package/dist/middleware/cost-advisory.js.map +1 -0
  163. package/dist/middleware/index.d.ts +23 -12
  164. package/dist/middleware/index.js +26 -18
  165. package/dist/middleware/index.js.map +1 -1
  166. package/dist/middleware/types.d.ts +3 -2
  167. package/dist/runner-manager.js +1 -5
  168. package/dist/runner-manager.js.map +1 -1
  169. package/dist/runner.js +1 -5
  170. package/dist/runner.js.map +1 -1
  171. package/dist/shared/attachment-resolver.d.ts +41 -18
  172. package/dist/shared/attachment-resolver.js +178 -46
  173. package/dist/shared/attachment-resolver.js.map +1 -1
  174. package/dist/shared/attachment-zip.d.ts +85 -0
  175. package/dist/shared/attachment-zip.js +185 -0
  176. package/dist/shared/attachment-zip.js.map +1 -0
  177. package/dist/shared/connect-backfill.js +5 -4
  178. package/dist/shared/connect-backfill.js.map +1 -1
  179. package/dist/shared/cost-guard.d.ts +3 -3
  180. package/dist/shared/cost-guard.js +3 -3
  181. package/dist/shared/execution-status-writer.d.ts +5 -5
  182. package/dist/shared/execution-status-writer.js +5 -5
  183. package/dist/shared/extract-structured-output.d.ts +8 -7
  184. package/dist/shared/extract-structured-output.js +8 -7
  185. package/dist/shared/extract-structured-output.js.map +1 -1
  186. package/dist/shared/filereview/cas-progress.d.ts +1 -18
  187. package/dist/shared/filereview/cas-progress.js +4 -0
  188. package/dist/shared/filereview/cas-progress.js.map +1 -1
  189. package/dist/shared/filereview/cas-touched.d.ts +56 -0
  190. package/dist/shared/filereview/cas-touched.js +63 -0
  191. package/dist/shared/filereview/cas-touched.js.map +1 -0
  192. package/dist/shared/filereview/index.d.ts +2 -1
  193. package/dist/shared/filereview/index.js +2 -1
  194. package/dist/shared/filereview/index.js.map +1 -1
  195. package/dist/shared/mcp-resolver.d.ts +4 -5
  196. package/dist/shared/mcp-resolver.js +4 -5
  197. package/dist/shared/mcp-resolver.js.map +1 -1
  198. package/dist/shared/persist-decision.d.ts +3 -4
  199. package/dist/shared/persist-decision.js +3 -4
  200. package/dist/shared/persist-decision.js.map +1 -1
  201. package/dist/shared/plan-mode-permissions.d.ts +2 -2
  202. package/dist/shared/plan-mode-permissions.js +2 -2
  203. package/dist/shared/plan-mode-prompt.d.ts +1 -1
  204. package/dist/shared/plan-mode-prompt.js +1 -1
  205. package/dist/shared/prompt-sections.d.ts +163 -0
  206. package/dist/shared/prompt-sections.js +164 -0
  207. package/dist/shared/prompt-sections.js.map +1 -0
  208. package/dist/shared/skill-resolver.d.ts +4 -2
  209. package/dist/shared/skill-resolver.js +4 -2
  210. package/dist/shared/skill-resolver.js.map +1 -1
  211. package/dist/shared/tool-rounds.d.ts +32 -0
  212. package/dist/shared/tool-rounds.js +43 -0
  213. package/dist/shared/tool-rounds.js.map +1 -1
  214. package/dist/shared/tool-row.d.ts +34 -0
  215. package/dist/shared/tool-row.js +52 -0
  216. package/dist/shared/tool-row.js.map +1 -1
  217. package/dist/shared/workspace/session-provision.d.ts +3 -3
  218. package/dist/shared/workspace/session-provision.js +3 -3
  219. package/dist/shared/workspace/stigmer-link.d.ts +3 -3
  220. package/dist/shared/workspace/stigmer-link.js +3 -3
  221. package/dist/shared/workspace/writeback-coordinator.d.ts +15 -22
  222. package/dist/shared/workspace/writeback-coordinator.js +16 -53
  223. package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
  224. package/dist/tools/index.d.ts +1 -1
  225. package/dist/tools/index.js +1 -1
  226. package/package.json +4 -4
  227. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +1 -0
  228. package/src/__test-utils__/execution-record-fixture.ts +18 -2
  229. package/src/__test-utils__/file-review-projection.ts +156 -0
  230. package/src/__test-utils__/git-workspace-fixture.ts +58 -0
  231. package/src/__test-utils__/harness-boot-order-child.ts +7 -2
  232. package/src/__test-utils__/harness-contract/contract.ts +33 -20
  233. package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
  234. package/src/__test-utils__/harness-contract/runtime-contract.ts +328 -10
  235. package/src/__test-utils__/harness-contract/scripted-adapter.ts +38 -6
  236. package/src/__test-utils__/harness-contract/types.ts +27 -3
  237. package/src/__test-utils__/hermetic-activity.ts +52 -14
  238. package/src/__test-utils__/model-registry-fixture.ts +32 -7
  239. package/src/__test-utils__/module-specifiers.ts +24 -0
  240. package/src/__test-utils__/turn-input-fixture.ts +2 -2
  241. package/src/__tests__/harness-boot-order.test.ts +9 -2
  242. package/src/activities/call-agent.ts +2 -2
  243. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +47 -3
  244. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +2 -48
  245. package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +1 -1
  246. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +6 -1
  247. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +52 -27
  248. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +25 -0
  249. package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.everything.prompt.md +166 -0
  250. package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.plan-mode.prompt.md +186 -0
  251. package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.recovery.prompt.md +191 -0
  252. package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.reinvocation.prompt.md +13 -0
  253. package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.prefixed.prompt.md +32 -0
  254. package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.raw.prompt.md +1 -0
  255. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -2
  256. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +4 -0
  257. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +24 -4
  258. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +6 -5
  259. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +9 -0
  260. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +11 -8
  261. package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +229 -0
  262. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +3 -3
  263. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +2 -2
  264. package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +78 -234
  265. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +1 -26
  266. package/src/activities/execute-cursor/adapter.ts +7 -5
  267. package/src/activities/execute-cursor/approval-policy.ts +5 -2
  268. package/src/activities/execute-cursor/approval-state.ts +16 -8
  269. package/src/activities/execute-cursor/cas-observations.ts +20 -2
  270. package/src/activities/execute-cursor/cursor-capabilities.ts +24 -5
  271. package/src/activities/execute-cursor/message-translator.ts +29 -41
  272. package/src/activities/execute-cursor/prompt-builder.ts +99 -157
  273. package/src/activities/execute-cursor/turn-boundary.ts +34 -140
  274. package/src/activities/execute-cursor/turn-settle.ts +37 -51
  275. package/src/activities/execute-cursor/turn-setup.ts +30 -70
  276. package/src/activities/execute-cursor/turn-stream.ts +8 -31
  277. package/src/activities/execute-cursor/turn.ts +5 -1
  278. package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +391 -0
  279. package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +313 -0
  280. package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +48 -0
  281. package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +85 -0
  282. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +383 -36
  283. package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +76 -0
  284. package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +47 -0
  285. package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +80 -0
  286. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +1 -1
  287. package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -2
  288. package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -3
  289. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +172 -0
  290. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +167 -0
  291. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md +36 -0
  292. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +178 -0
  293. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +158 -0
  294. package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +210 -0
  295. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +74 -0
  296. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +104 -0
  297. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +78 -0
  298. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +138 -0
  299. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +68 -0
  300. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +154 -0
  301. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +116 -0
  302. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +69 -0
  303. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +67 -0
  304. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/plain-turn.status.json +47 -0
  305. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +68 -0
  306. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +280 -0
  307. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +77 -0
  308. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
  309. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +99 -0
  310. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +109 -0
  311. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +76 -0
  312. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.text-fallback.status.json +51 -0
  313. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tier2.status.json +51 -0
  314. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tool-strategy.status.json +56 -0
  315. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +90 -0
  316. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +70 -0
  317. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +71 -0
  318. package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +144 -0
  319. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +184 -0
  320. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +203 -0
  321. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +170 -0
  322. package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +175 -0
  323. package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +206 -0
  324. package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +142 -0
  325. package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +126 -0
  326. package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +139 -0
  327. package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +122 -0
  328. package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +236 -0
  329. package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +161 -0
  330. package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +157 -0
  331. package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +152 -0
  332. package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +50 -122
  333. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +8 -6
  334. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +2 -2
  335. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +35 -35
  336. package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +247 -0
  337. package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +329 -0
  338. package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +1 -20
  339. package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +78 -0
  340. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +1 -1
  341. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +2 -2
  342. package/src/activities/execute-deep-agent/__tests__/subagent-tracker.test.ts +57 -31
  343. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +19 -79
  344. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +15 -15
  345. package/src/activities/execute-deep-agent/__tests__/summarization-verification.test.ts +1 -1
  346. package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +391 -0
  347. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +343 -38
  348. package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +5 -5
  349. package/src/activities/execute-deep-agent/adapter.ts +94 -0
  350. package/src/activities/execute-deep-agent/cas-capture-observer.ts +11 -0
  351. package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +43 -0
  352. package/src/activities/execute-deep-agent/execution-state.ts +4 -13
  353. package/src/activities/execute-deep-agent/hitl.ts +100 -123
  354. package/src/activities/execute-deep-agent/prompt-builder.ts +141 -115
  355. package/src/activities/execute-deep-agent/status-builder-shared.ts +21 -84
  356. package/src/activities/execute-deep-agent/streaming-side-effects.ts +13 -19
  357. package/src/activities/execute-deep-agent/subagent-tracker.ts +77 -42
  358. package/src/activities/execute-deep-agent/subagent-transformer.ts +46 -105
  359. package/src/activities/execute-deep-agent/subagent-wiring.ts +5 -4
  360. package/src/activities/execute-deep-agent/turn-settle.ts +164 -0
  361. package/src/activities/execute-deep-agent/turn-setup.ts +541 -0
  362. package/src/activities/execute-deep-agent/turn-stream.ts +313 -0
  363. package/src/activities/execute-deep-agent/turn.ts +131 -0
  364. package/src/activities/execute-deep-agent/v3-event-recorder.ts +6 -5
  365. package/src/activities/execute-deep-agent/v3-events.ts +1 -1
  366. package/src/activities/execute-deep-agent/v3-status-builder.ts +84 -63
  367. package/src/harness/__tests__/approval-decisions.test.ts +116 -0
  368. package/src/harness/__tests__/capture.test.ts +268 -0
  369. package/src/harness/__tests__/mount-skills.test.ts +169 -0
  370. package/src/harness/__tests__/persist-chokepoint.test.ts +38 -2
  371. package/src/harness/__tests__/run-turn-deterministic.test.ts +94 -0
  372. package/src/harness/__tests__/run-turn.test.ts +39 -23
  373. package/src/harness/__tests__/turn-attachments-vision.test.ts +80 -0
  374. package/src/harness/__tests__/turn-context.test.ts +73 -26
  375. package/src/harness/approval-decisions.ts +104 -0
  376. package/src/harness/capabilities.ts +16 -5
  377. package/src/harness/capture.ts +301 -0
  378. package/src/harness/persist-chokepoint.ts +24 -4
  379. package/src/harness/run-turn.ts +120 -29
  380. package/src/harness/terminal-table.ts +61 -20
  381. package/src/harness/turn-context.ts +208 -111
  382. package/src/harness/types.ts +66 -12
  383. package/src/harness-adapters.ts +17 -9
  384. package/src/middleware/__tests__/approval-gate.test.ts +1 -1
  385. package/src/middleware/__tests__/cost-advisory.test.ts +113 -0
  386. package/src/middleware/approval-gate.ts +2 -2
  387. package/src/middleware/{cost-cap.ts → cost-advisory.ts} +40 -70
  388. package/src/middleware/index.ts +29 -29
  389. package/src/middleware/types.ts +3 -2
  390. package/src/runner-manager.ts +0 -8
  391. package/src/runner.ts +0 -8
  392. package/src/shared/__tests__/attachment-naming.test.ts +6 -19
  393. package/src/shared/__tests__/attachment-resolver.test.ts +158 -8
  394. package/src/shared/__tests__/attachment-zip.test.ts +236 -0
  395. package/src/shared/__tests__/bedrock-adapter.test.ts +1 -1
  396. package/src/shared/__tests__/blueprint-resolver.test.ts +43 -0
  397. package/src/shared/__tests__/cost-guard.test.ts +3 -3
  398. package/src/shared/__tests__/prompt-sections.test.ts +167 -0
  399. package/src/shared/__tests__/secret-leak-scan.test.ts +2 -2
  400. package/src/{activities/execute-deep-agent → shared}/__tests__/stamp-flowed-rows.test.ts +33 -10
  401. package/src/shared/__tests__/vertex-adapter.test.ts +1 -1
  402. package/src/shared/attachment-resolver.ts +218 -55
  403. package/src/shared/attachment-zip.ts +281 -0
  404. package/src/shared/connect-backfill.ts +9 -11
  405. package/src/shared/cost-guard.ts +3 -3
  406. package/src/shared/execution-status-writer.ts +5 -5
  407. package/src/shared/extract-structured-output.ts +8 -7
  408. package/src/shared/filereview/__tests__/capture.test.ts +86 -4
  409. package/src/shared/filereview/__tests__/cas-progress.test.ts +2 -1
  410. package/src/shared/filereview/cas-progress.ts +5 -19
  411. package/src/shared/filereview/cas-touched.ts +89 -0
  412. package/src/shared/filereview/index.ts +4 -2
  413. package/src/shared/mcp-resolver.ts +4 -5
  414. package/src/shared/persist-decision.ts +3 -4
  415. package/src/shared/plan-mode-permissions.ts +2 -2
  416. package/src/shared/plan-mode-prompt.ts +1 -1
  417. package/src/shared/prompt-sections.ts +256 -0
  418. package/src/shared/skill-resolver.ts +4 -2
  419. package/src/shared/tool-rounds.ts +48 -0
  420. package/src/shared/tool-row.ts +61 -0
  421. package/src/shared/workspace/__tests__/stigmer-link.test.ts +7 -22
  422. package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +20 -61
  423. package/src/shared/workspace/session-provision.ts +3 -3
  424. package/src/shared/workspace/stigmer-link.ts +3 -3
  425. package/src/shared/workspace/writeback-coordinator.ts +16 -64
  426. package/src/tools/index.ts +1 -1
  427. package/dist/activities/execute-cursor/capture-flow.d.ts +0 -142
  428. package/dist/activities/execute-cursor/capture-flow.js +0 -285
  429. package/dist/activities/execute-cursor/capture-flow.js.map +0 -1
  430. package/dist/activities/execute-cursor/command-provenance.d.ts +0 -48
  431. package/dist/activities/execute-cursor/command-provenance.js +0 -38
  432. package/dist/activities/execute-cursor/command-provenance.js.map +0 -1
  433. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +0 -134
  434. package/dist/activities/execute-deep-agent/attachment-injector.js +0 -386
  435. package/dist/activities/execute-deep-agent/attachment-injector.js.map +0 -1
  436. package/dist/activities/execute-deep-agent/command-provenance.d.ts +0 -61
  437. package/dist/activities/execute-deep-agent/command-provenance.js +0 -72
  438. package/dist/activities/execute-deep-agent/command-provenance.js.map +0 -1
  439. package/dist/activities/execute-deep-agent/environment.d.ts +0 -24
  440. package/dist/activities/execute-deep-agent/environment.js +0 -58
  441. package/dist/activities/execute-deep-agent/environment.js.map +0 -1
  442. package/dist/activities/execute-deep-agent/event-recorder.d.ts +0 -21
  443. package/dist/activities/execute-deep-agent/event-recorder.js +0 -67
  444. package/dist/activities/execute-deep-agent/event-recorder.js.map +0 -1
  445. package/dist/activities/execute-deep-agent/index.d.ts +0 -15
  446. package/dist/activities/execute-deep-agent/index.js +0 -918
  447. package/dist/activities/execute-deep-agent/index.js.map +0 -1
  448. package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -28
  449. package/dist/activities/execute-deep-agent/mcp-gate.js +0 -22
  450. package/dist/activities/execute-deep-agent/mcp-gate.js.map +0 -1
  451. package/dist/activities/execute-deep-agent/post-stream.d.ts +0 -23
  452. package/dist/activities/execute-deep-agent/post-stream.js +0 -71
  453. package/dist/activities/execute-deep-agent/post-stream.js.map +0 -1
  454. package/dist/activities/execute-deep-agent/setup.d.ts +0 -136
  455. package/dist/activities/execute-deep-agent/setup.js +0 -767
  456. package/dist/activities/execute-deep-agent/setup.js.map +0 -1
  457. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +0 -36
  458. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +0 -56
  459. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +0 -1
  460. package/dist/activities/execute-deep-agent/status-builder.d.ts +0 -93
  461. package/dist/activities/execute-deep-agent/status-builder.js +0 -365
  462. package/dist/activities/execute-deep-agent/status-builder.js.map +0 -1
  463. package/dist/activities/execute-deep-agent/streaming-terminal.d.ts +0 -21
  464. package/dist/activities/execute-deep-agent/streaming-terminal.js +0 -80
  465. package/dist/activities/execute-deep-agent/streaming-terminal.js.map +0 -1
  466. package/dist/activities/execute-deep-agent/streaming-v3.d.ts +0 -13
  467. package/dist/activities/execute-deep-agent/streaming-v3.js +0 -174
  468. package/dist/activities/execute-deep-agent/streaming-v3.js.map +0 -1
  469. package/dist/activities/execute-deep-agent/streaming.d.ts +0 -81
  470. package/dist/activities/execute-deep-agent/streaming.js +0 -160
  471. package/dist/activities/execute-deep-agent/streaming.js.map +0 -1
  472. package/dist/middleware/cost-cap.d.ts +0 -22
  473. package/dist/middleware/cost-cap.js.map +0 -1
  474. package/dist/middleware/graceful-stop.d.ts +0 -17
  475. package/dist/middleware/graceful-stop.js +0 -63
  476. package/dist/middleware/graceful-stop.js.map +0 -1
  477. package/dist/shared/skill-writer.d.ts +0 -75
  478. package/dist/shared/skill-writer.js +0 -207
  479. package/dist/shared/skill-writer.js.map +0 -1
  480. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +0 -1032
  481. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +0 -240
  482. package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +0 -169
  483. package/src/activities/execute-cursor/capture-flow.ts +0 -379
  484. package/src/activities/execute-cursor/command-provenance.ts +0 -73
  485. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +0 -1161
  486. package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +0 -252
  487. package/src/activities/execute-deep-agent/__tests__/environment.test.ts +0 -108
  488. package/src/activities/execute-deep-agent/__tests__/event-recorder.test.ts +0 -150
  489. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +0 -226
  490. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -318
  491. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -351
  492. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -293
  493. package/src/activities/execute-deep-agent/__tests__/index.test.ts +0 -99
  494. package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +0 -44
  495. package/src/activities/execute-deep-agent/__tests__/post-stream.test.ts +0 -112
  496. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -358
  497. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +0 -1933
  498. package/src/activities/execute-deep-agent/__tests__/streaming-terminal.test.ts +0 -40
  499. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +0 -597
  500. package/src/activities/execute-deep-agent/__tests__/streaming.test.ts +0 -508
  501. package/src/activities/execute-deep-agent/attachment-injector.ts +0 -627
  502. package/src/activities/execute-deep-agent/command-provenance.ts +0 -102
  503. package/src/activities/execute-deep-agent/environment.ts +0 -76
  504. package/src/activities/execute-deep-agent/event-recorder.ts +0 -95
  505. package/src/activities/execute-deep-agent/index.ts +0 -1072
  506. package/src/activities/execute-deep-agent/mcp-gate.ts +0 -37
  507. package/src/activities/execute-deep-agent/post-stream.ts +0 -109
  508. package/src/activities/execute-deep-agent/setup.ts +0 -1081
  509. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +0 -64
  510. package/src/activities/execute-deep-agent/status-builder.ts +0 -481
  511. package/src/activities/execute-deep-agent/streaming-terminal.ts +0 -106
  512. package/src/activities/execute-deep-agent/streaming-v3.ts +0 -277
  513. package/src/activities/execute-deep-agent/streaming.ts +0 -313
  514. package/src/middleware/__tests__/cost-cap.test.ts +0 -192
  515. package/src/middleware/__tests__/graceful-stop.test.ts +0 -105
  516. package/src/middleware/graceful-stop.ts +0 -86
  517. package/src/shared/__tests__/skill-writer.test.ts +0 -372
  518. package/src/shared/skill-writer.ts +0 -266
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The harness contract kit, BOTH halves, against the REAL native deep-agent
3
+ * adapter under the sqlite checkpointer.
4
+ *
5
+ * The adapter-side half (`__test-utils__/harness-contract/contract.ts`) is the
6
+ * single statement of what every `HarnessAdapter` owes the runtime;
7
+ * `src/__tests__/harness-contract.test.ts` runs it against the scripted fake
8
+ * under both pause primitives, `execute-cursor/__tests__/hermetic/
9
+ * harness-contract.test.ts` against the real Cursor adapter. The runtime-side
10
+ * half (`runtime-contract.ts`) is what the turn runtime owes every harness,
11
+ * through the real activity under `MockActivityEnvironment`. This file runs
12
+ * BOTH against `createDeepAgentAdapter()` through the native subject
13
+ * (`__test-utils__/contract-subject.ts`), which translates the kit's
14
+ * scenarios onto the `ScriptedModel` double and lets deepagents' real
15
+ * built-ins run over the real approval gate. Nothing of the adapter is
16
+ * mocked; the model client and the control-plane client are the two doubles
17
+ * every native hermetic golden already substitutes.
18
+ *
19
+ * The posture is sqlite, the production local default (`config.ts`): the
20
+ * test-only memory saver re-interrupts on replay and duplicates the gated
21
+ * row (S3 M0 finding F-M0-3), so it cannot carry the HITL arms. One sqlite
22
+ * file per session lands under the hermetic `HOME`, opened and closed per
23
+ * turn by the adapter.
24
+ *
25
+ * What this subject declares it cannot produce, so the kit reports the arm
26
+ * SKIPPED and never silently passes: the `engine` and `actionable` failure
27
+ * surfaces (`turn.ts` classifies every escaped error `internal` — the
28
+ * adapter has one surface), and an engine-side cancel (no native path ends a
29
+ * turn `cancelled`).
30
+ *
31
+ * Beside the kit, the native-only observations its vocabulary cannot
32
+ * express — the interrupt primitive's own facts: an APPROVED pending action
33
+ * executes at the resume, before the model is asked (the order the kit's
34
+ * invariant 4 used to assert the other way round, until this adapter showed
35
+ * it is the primitive's and not the contract's — S3 M3 F-M3-1, Q-M3-2); a
36
+ * REJECT reaches the model as the gate's denial `ToolMessage` in the
37
+ * conversation, and the model proposes the call exactly once (the denial is
38
+ * a tool result the model reads, never a second gate); and one sqlite
39
+ * checkpoint per session lands under the hermetic `HOME`, none for a session
40
+ * never served.
41
+ *
42
+ * No golden: the kit asserts behaviour, the goldens under `goldens/` pin
43
+ * transcripts. The hermetic environment is created at module level because
44
+ * the subject's `config` is read at collection time; the forks pool keeps it
45
+ * private to this file.
46
+ */
47
+
48
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
49
+ import { existsSync } from "node:fs";
50
+ import { isAIMessage, isToolMessage } from "@langchain/core/messages";
51
+ import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
52
+
53
+ vi.mock("../../../../shared/model-client.js", async () =>
54
+ (await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
55
+ );
56
+ vi.mock("../../../../client/stigmer-client.js", async () =>
57
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
58
+ );
59
+
60
+ import { ExecutionDriver, describeHarnessContract } from "../../../../__test-utils__/harness-contract/contract.js";
61
+ import { describeHarnessRuntimeContract } from "../../../../__test-utils__/harness-contract/runtime-contract.js";
62
+ import { scenario } from "../../../../__test-utils__/harness-contract/types.js";
63
+ import { ScriptedClock, createHermeticEnvironment } from "../../../../__test-utils__/hermetic-activity.js";
64
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
65
+ import { findToolCallRow } from "../../../../__test-utils__/proto-helpers.js";
66
+ import { getCheckpointDbPath } from "../../../../shared/workspace/platform-dir.js";
67
+ import { createDeepAgentContractSubject } from "../../__test-utils__/contract-subject.js";
68
+
69
+ const env = createHermeticEnvironment();
70
+ const clock = new ScriptedClock();
71
+ const subject = createDeepAgentContractSubject(env);
72
+ const WRITE_GAMMA = { kind: "write", resource: "/work/gamma.txt" } as const;
73
+
74
+ describe("ExecuteDeepAgent hermetic — the harness contract kit against the real adapter", () => {
75
+ let registry: ReturnType<typeof stubRegistryFetch>;
76
+
77
+ beforeAll(() => {
78
+ registry = stubRegistryFetch();
79
+ clock.install();
80
+ });
81
+
82
+ afterAll(() => {
83
+ clock.uninstall();
84
+ registry.restore();
85
+ env.dispose();
86
+ });
87
+
88
+ describeHarnessContract(subject);
89
+ describeHarnessRuntimeContract({ subject, env, clock }, { failureSurfaces: ["internal"], engineCancel: false });
90
+
91
+ describe("native-only observations", () => {
92
+ beforeAll(async () => {
93
+ await subject.adapter.boot(subject.config);
94
+ });
95
+
96
+ it("executes an APPROVED pending action at the resume, before the model is asked — even when the model then hangs and the turn is stopped (interrupt order, F-M3-1)", async () => {
97
+ const driver = new ExecutionDriver(subject, "obs-approved-at-resume");
98
+ const id = "obs-approved-at-resume-write";
99
+
100
+ const proposed = await driver.turn([scenario.propose(id, WRITE_GAMMA)]);
101
+ expect(proposed.outcome.kind).toBe("awaiting_approval");
102
+ driver.decide(id, ApprovalAction.APPROVE);
103
+
104
+ const hanging = driver.begin([scenario.say("working"), scenario.hang(), scenario.propose(id, WRITE_GAMMA)], { stopWhenHanging: "kit: user pause" });
105
+ const outcome = await hanging.settled;
106
+
107
+ expect(outcome.kind).toBe("interrupted");
108
+ expect(subject.executionCount(id), "the resume completed the pending task before the hang; the stop came after").toBe(1);
109
+ expect(findToolCallRow(hanging.sink.status, id)?.status, "and the row on the interrupted turn's status says so").toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
110
+ });
111
+
112
+ it("hands a REJECT to the model as the gate's denial ToolMessage; the call is proposed exactly once and never re-gated", async () => {
113
+ const driver = new ExecutionDriver(subject, "obs-reject-as-denial");
114
+ const id = "obs-reject-as-denial-write";
115
+
116
+ const proposed = await driver.turn([scenario.propose(id, WRITE_GAMMA)]);
117
+ expect(proposed.outcome.kind).toBe("awaiting_approval");
118
+ driver.decide(id, ApprovalAction.REJECT);
119
+ const resumed = await driver.turn([scenario.propose(id, WRITE_GAMMA), scenario.say("moving on")]);
120
+ expect(resumed.outcome.kind).toBe("completed");
121
+
122
+ const transcript = subject.lastTranscriptOf(driver.executionId)!;
123
+ const denials = transcript.filter((m) => isToolMessage(m) && m.tool_call_id === id);
124
+ const proposals = transcript.filter((m) => isAIMessage(m) && (m.tool_calls ?? []).some((tc) => tc.id === id));
125
+ expect(denials, "the model read the gate's answer as a tool result").toHaveLength(1);
126
+ expect(proposals, "and had proposed the call exactly once").toHaveLength(1);
127
+ expect(subject.executionCount(id)).toBe(0);
128
+ });
129
+
130
+ it("keeps one sqlite checkpoint per session under the hermetic HOME, none for a session never served", async () => {
131
+ const served = new ExecutionDriver(subject, "obs-sqlite-a");
132
+ const other = new ExecutionDriver(subject, "obs-sqlite-b");
133
+ expect((await served.turn([scenario.say("first")])).outcome.kind).toBe("completed");
134
+ expect((await other.turn([scenario.say("second")])).outcome.kind).toBe("completed");
135
+
136
+ const [a, b] = [getCheckpointDbPath(served.sessionId), getCheckpointDbPath(other.sessionId)];
137
+ expect(a.startsWith(env.home), "the checkpoint lives under the runner-owned HOME tree").toBe(true);
138
+ expect(a).not.toBe(b);
139
+ expect(existsSync(a)).toBe(true);
140
+ expect(existsSync(b)).toBe(true);
141
+ expect(existsSync(getCheckpointDbPath("ses-never-served")), "a session never served has no checkpoint").toBe(false);
142
+ });
143
+ });
144
+ });
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Hermetic golden: APPROVE_ALL leases the gated action's CLASS for the rest
3
+ * of the run — two invocations on the durable (sqlite) checkpointer.
4
+ *
5
+ * Turn 1: the model thinks (a leading reasoning block, persisted as a
6
+ * THINKING message), says it will run two commands, and proposes the first
7
+ * gated `execute`; the gate pauses; WAITING_FOR_APPROVAL.
8
+ *
9
+ * Between turns: APPROVE_ALL on the WAITING row. `deriveActiveLeases`
10
+ * reduces it to the scope of the tool it was made on — for a built-in, its
11
+ * approval category (`shared/approval-policy.ts` `deriveLeaseScope`), here
12
+ * SHELL — and the reinvocation's gate carries that lease.
13
+ *
14
+ * Turn 2: the graph resumes inside the gate and runs command A; the model
15
+ * proposes command B; the gate finds SHELL leased and runs B WITHOUT a second
16
+ * interrupt (`ToolCall.approvalPolicySource` records the lease as the
17
+ * authorization); the model closes; COMPLETED.
18
+ *
19
+ * Invariants carried from the retired `hitl-resume-approve-all.test.ts`
20
+ * (the user-reported "leading thinking block and the first tool call vanish
21
+ * after approve-all" regression): the final status is a strict SUPERSET of
22
+ * run 1's transcript — the THINKING message survives, the gated call is
23
+ * reconciled in place (exactly one copy, now COMPLETED), and the leased
24
+ * follow-up is appended COMPLETED with no interrupt. That test leased an MCP
25
+ * SERVER (`getAppState` on open-computer-use); this arm leases a CATEGORY,
26
+ * the same `deriveLeaseScope` path with the other scope kind (S3 M0 ruling
27
+ * Q-M0-4). The server-scoped lease stays pinned by `approval-policy`'s unit
28
+ * tests and the conformance execution class.
29
+ *
30
+ * What every reinvocation golden here shows since S3 M2a (the runtime over
31
+ * the native adapter; Q-S3-16 and Q-M2a-2): `startedAt` is THIS turn's own,
32
+ * not turn 1's carried by the orchestrator's whole-status clone; the
33
+ * `fileReviewEventStream` carries only the events THIS turn authored (the
34
+ * runtime seeds messages, sub-agents, artifacts, write-backs, todos, usage,
35
+ * the memory report and the structured output — never the server-owned
36
+ * ledgers, which the server appends to by event id in `update-status.ts`
37
+ * `appendRunnerEvents`; the hermetic record replaces wholesale, so a
38
+ * golden reads as what the runner SENT); and `streamingUsage` carries the
39
+ * runtime accountant's fields (`model`, `estimatedCostUsd`, the requested
40
+ * tier and thinking mode).
41
+ *
42
+ * Regenerate ONLY after a deliberate behavior change:
43
+ * npx vitest run src/activities/execute-deep-agent/__tests__/hermetic -u
44
+ */
45
+
46
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
47
+ import { toJson } from "@bufbuild/protobuf";
48
+ import {
49
+ AgentExecutionStatusSchema,
50
+ type AgentExecutionStatus,
51
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
52
+ import {
53
+ ApprovalAction,
54
+ ApprovalPolicySource,
55
+ ExecutionPhase,
56
+ MessageType,
57
+ ToolCallStatus,
58
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
59
+
60
+ vi.mock("../../../../shared/model-client.js", async () =>
61
+ (await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
62
+ );
63
+ vi.mock("../../../../client/stigmer-client.js", async () =>
64
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
65
+ );
66
+
67
+ import {
68
+ ScriptedClock,
69
+ createHermeticEnvironment,
70
+ type HermeticEnvironment,
71
+ } from "../../../../__test-utils__/hermetic-activity.js";
72
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
73
+ import {
74
+ beginDeepAgentScenario,
75
+ deepAgentExecutionRecord,
76
+ runDeepAgentTurn,
77
+ } from "../../__test-utils__/hermetic-deep-agent.js";
78
+ import { CLOSING_TURN, EXECUTE_CALL_A, EXECUTE_CALL_B } from "../../__test-utils__/hitl-script.js";
79
+
80
+ const USER_MESSAGE = "Run both commands.";
81
+ const REASONING = "Two commands are needed; I will run them in order.";
82
+ const OPENING_TEXT = "I will run the two commands.";
83
+ const DECIDED_AT = "2026-01-01T00:00:30.000Z";
84
+
85
+ function statusJson(status: AgentExecutionStatus): string {
86
+ return JSON.stringify(toJson(AgentExecutionStatusSchema, status), null, 2) + "\n";
87
+ }
88
+
89
+ describe("ExecuteDeepAgent hermetic — APPROVE_ALL leases the class (sqlite)", () => {
90
+ let env: HermeticEnvironment;
91
+ let registry: ReturnType<typeof stubRegistryFetch>;
92
+ const clock = new ScriptedClock();
93
+
94
+ beforeAll(() => {
95
+ env = createHermeticEnvironment();
96
+ registry = stubRegistryFetch();
97
+ clock.install();
98
+ });
99
+
100
+ afterAll(() => {
101
+ clock.uninstall();
102
+ registry.restore();
103
+ env.dispose();
104
+ });
105
+
106
+ it("runs the approved call and the leased follow-up in one resume, keeping the leading thinking", async () => {
107
+ // ── Arrange ──────────────────────────────────────────────────────────────
108
+ const record = deepAgentExecutionRecord({ message: USER_MESSAGE });
109
+ const scenario = beginDeepAgentScenario({
110
+ env,
111
+ clock,
112
+ record,
113
+ checkpointer: "sqlite",
114
+ script: () => ({
115
+ turns: [
116
+ {
117
+ reasoning: REASONING,
118
+ text: OPENING_TEXT,
119
+ toolCalls: [EXECUTE_CALL_A],
120
+ usage: { inputTokens: 1_400, outputTokens: 80 },
121
+ },
122
+ { toolCalls: [EXECUTE_CALL_B], usage: { inputTokens: 1_600, outputTokens: 40 } },
123
+ CLOSING_TURN,
124
+ ],
125
+ }),
126
+ });
127
+
128
+ // ── Act 1 ────────────────────────────────────────────────────────────────
129
+ const turn1 = await runDeepAgentTurn(scenario, { turnSeq: 0 });
130
+
131
+ // ── Assert 1: paused on A, the thinking persisted ────────────────────────
132
+ expect(turn1.outcome.kind).toBe("returned");
133
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
134
+ const run1 = record.lastFullStatus!;
135
+ const thinking = run1.messages.filter((m) => m.type === MessageType.MESSAGE_THINKING);
136
+ expect(thinking.map((m) => m.content), "the reasoning block is exactly one THINKING message").toEqual([REASONING]);
137
+ expect(run1.messages[0].type, "and it leads the transcript").toBe(MessageType.MESSAGE_THINKING);
138
+ expect(record.waitingToolCalls().map((tc) => tc.id)).toEqual([EXECUTE_CALL_A.id]);
139
+ await expect(statusJson(run1)).toMatchFileSnapshot("./goldens/approve-all-lease.turn1.status.json");
140
+
141
+ // ── Between turns: APPROVE_ALL on A ──────────────────────────────────────
142
+ expect(record.decideWaitingToolCalls(ApprovalAction.APPROVE_ALL, DECIDED_AT)).toBe(1);
143
+
144
+ // ── Act 2 ────────────────────────────────────────────────────────────────
145
+ const turn2 = await runDeepAgentTurn(scenario, { turnSeq: 1 });
146
+
147
+ // ── Assert 2: A ran, B ran under the lease, no second interrupt ──────────
148
+ expect(turn2.outcome.kind).toBe("returned");
149
+ expect((turn2.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
150
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_COMPLETED);
151
+ expect(record.persistedPhases.filter((p) => p === ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL)).toHaveLength(1);
152
+
153
+ const final = record.lastFullStatus!;
154
+ const rows = final.messages.flatMap((m) => m.toolCalls);
155
+ expect(rows.map((tc) => tc.id), "A reconciled in place, B appended; one copy each").toEqual([
156
+ EXECUTE_CALL_A.id,
157
+ EXECUTE_CALL_B.id,
158
+ ]);
159
+ const [a, b] = rows;
160
+ expect(a.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
161
+ expect(a.approvalAction).toBe(ApprovalAction.APPROVE_ALL);
162
+ expect(a.result).toContain("hermetic-a");
163
+ expect(b.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
164
+ expect(b.requiresApproval, "the leased class needs no interrupt").toBe(false);
165
+ expect(b.approvalPolicySource, "the lease is B's recorded authorization").toBe(
166
+ ApprovalPolicySource.APPROVAL_LEASE,
167
+ );
168
+ expect(b.approvalAction, "B was never decided by a person").toBe(ApprovalAction.UNSPECIFIED);
169
+ expect(b.result).toContain("hermetic-b");
170
+
171
+ // The superset invariant: every run-1 message survives, in order, at the front.
172
+ const run1Shape = run1.messages.map((m) => [m.type, m.content]);
173
+ const finalShape = final.messages.map((m) => [m.type, m.content]);
174
+ expect(finalShape.slice(0, run1Shape.length), "run 1's transcript is a prefix of the final").toEqual(run1Shape);
175
+ expect(final.messages[0].type, "the leading thinking survives the resume").toBe(MessageType.MESSAGE_THINKING);
176
+ expect(finalShape.at(-1)).toEqual([MessageType.MESSAGE_AI, CLOSING_TURN.text]);
177
+
178
+ // ── Assert: hermeticity ──────────────────────────────────────────────────
179
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
180
+
181
+ // ── Assert: the golden ───────────────────────────────────────────────────
182
+ await expect(statusJson(final)).toMatchFileSnapshot("./goldens/approve-all-lease.turn2.status.json");
183
+ });
184
+ });
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Hermetic goldens: the three HITL round trips — APPROVE, SKIP, REJECT — each
3
+ * two invocations of the whole `ExecuteDeepAgent` activity on the durable
4
+ * (sqlite) checkpointer, the production local posture (`config.ts` resolves
5
+ * `sqlite` in local mode and `http` in cloud; both resume the same way).
6
+ *
7
+ * Turn 1 (shared by every arm, ONE golden `gate.turn1.status.json`): the
8
+ * model proposes a gated `execute`; the gate's `interrupt()` pauses the graph;
9
+ * the post-stream interrupt detection seeds a WAITING_APPROVAL row carrying
10
+ * the gate's message and provenance; the activity persists
11
+ * WAITING_FOR_APPROVAL and RETURNS. That the approve, skip and reject arms all
12
+ * match the same file is the assertion that the decision to come is invisible
13
+ * to the first invocation.
14
+ *
15
+ * Between turns the test does what the server's `SubmitApproval` does — writes
16
+ * `approval_action` on the WAITING row (`ExecutionRecord.decideWaitingToolCalls`),
17
+ * nothing else — and the second invocation reads the real persisted status.
18
+ *
19
+ * Turn 2 (one golden per decision): the activity seeds its status from the
20
+ * persisted transcript (`shouldSeedFromPersistedTranscript`), reads the
21
+ * checkpoint's pending interrupt, builds `Command({ resume })` from the
22
+ * decision (`hitl.ts`), and the graph resumes INSIDE the gate:
23
+ * - APPROVE: the tool runs; the row is reconciled IN PLACE to COMPLETED with
24
+ * the command's output (exactly one copy of the gated call, the run-1
25
+ * transcript a strict prefix of the final one — `hitl-resume-history`'s
26
+ * invariant), the model closes, COMPLETED.
27
+ * - SKIP: the gate returns a "skipped by user" ToolMessage; the tool never
28
+ * runs; `reconcileNonExecutingDecisions` stamps the row SKIPPED after the
29
+ * stream; COMPLETED.
30
+ * - REJECT (issue #197, `hitl-reject`'s invariant): the gate returns a denial
31
+ * ToolMessage and the run CONTINUES; the row is stamped SKIPPED with
32
+ * `error: "Rejected by user"` and keeps `approvalAction = REJECT` for
33
+ * audit; COMPLETED, never stuck at WAITING_APPROVAL.
34
+ *
35
+ * Carried from the retired `hitl-reject.test.ts` (both arms), `hitl-resume-
36
+ * history.test.ts` (seed + single copy), `hitl-resume-approve-all.test.ts`'s
37
+ * superset assertion (the leased variant lives in `hitl-approve-all-lease`).
38
+ *
39
+ * The REJECT arm is what Q-S3-2 aligns the runtime to (REJECT continues on
40
+ * both harnesses); this golden is the native truth it must reproduce.
41
+ *
42
+ * What every reinvocation golden here shows since S3 M2a (the runtime over
43
+ * the native adapter; Q-S3-16 and Q-M2a-2): `startedAt` is THIS turn's own,
44
+ * not turn 1's carried by the orchestrator's whole-status clone; the
45
+ * `fileReviewEventStream` carries only the events THIS turn authored (the
46
+ * runtime seeds messages, sub-agents, artifacts, write-backs, todos, usage,
47
+ * the memory report and the structured output — never the server-owned
48
+ * ledgers, which the server appends to by event id in `update-status.ts`
49
+ * `appendRunnerEvents`; the hermetic record replaces wholesale, so a
50
+ * golden reads as what the runner SENT); and `streamingUsage` carries the
51
+ * runtime accountant's fields (`model`, `estimatedCostUsd`, the requested
52
+ * tier and thinking mode).
53
+ *
54
+ * Regenerate ONLY after a deliberate behavior change:
55
+ * npx vitest run src/activities/execute-deep-agent/__tests__/hermetic -u
56
+ */
57
+
58
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
59
+ import { toJson } from "@bufbuild/protobuf";
60
+ import {
61
+ AgentExecutionStatusSchema,
62
+ type AgentExecutionStatus,
63
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
64
+ import {
65
+ ApprovalAction,
66
+ ExecutionPhase,
67
+ MessageType,
68
+ ToolCallStatus,
69
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
70
+
71
+ vi.mock("../../../../shared/model-client.js", async () =>
72
+ (await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
73
+ );
74
+ vi.mock("../../../../client/stigmer-client.js", async () =>
75
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
76
+ );
77
+
78
+ import {
79
+ ScriptedClock,
80
+ createHermeticEnvironment,
81
+ type HermeticEnvironment,
82
+ } from "../../../../__test-utils__/hermetic-activity.js";
83
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
84
+ import {
85
+ beginDeepAgentScenario,
86
+ deepAgentExecutionRecord,
87
+ runDeepAgentTurn,
88
+ } from "../../__test-utils__/hermetic-deep-agent.js";
89
+ import {
90
+ CLOSING_TURN,
91
+ EXECUTE_CALL_A,
92
+ GATED_OPENING_TURN,
93
+ GATE_TURN1_GOLDEN,
94
+ executeApprovalMessage,
95
+ } from "../../__test-utils__/hitl-script.js";
96
+
97
+ const USER_MESSAGE = "Run the command for me.";
98
+ const DECIDED_AT = "2026-01-01T00:00:30.000Z";
99
+
100
+ function statusJson(status: AgentExecutionStatus): string {
101
+ return JSON.stringify(toJson(AgentExecutionStatusSchema, status), null, 2) + "\n";
102
+ }
103
+
104
+ describe("ExecuteDeepAgent hermetic — HITL round trips (sqlite)", () => {
105
+ let env: HermeticEnvironment;
106
+ let registry: ReturnType<typeof stubRegistryFetch>;
107
+ const clock = new ScriptedClock();
108
+
109
+ beforeAll(() => {
110
+ env = createHermeticEnvironment();
111
+ registry = stubRegistryFetch();
112
+ clock.install();
113
+ });
114
+
115
+ afterAll(() => {
116
+ clock.uninstall();
117
+ registry.restore();
118
+ env.dispose();
119
+ });
120
+
121
+ describe.each([
122
+ { name: "approve", action: ApprovalAction.APPROVE },
123
+ { name: "skip", action: ApprovalAction.SKIP },
124
+ { name: "reject", action: ApprovalAction.REJECT },
125
+ ])("$name", ({ name, action }) => {
126
+ it(`pauses on the gate, then ${name} carries the decision to COMPLETED`, async () => {
127
+ // ── Arrange ────────────────────────────────────────────────────────────
128
+ clock.reset();
129
+ const record = deepAgentExecutionRecord({ message: USER_MESSAGE });
130
+ const scenario = beginDeepAgentScenario({
131
+ env,
132
+ clock,
133
+ record,
134
+ checkpointer: "sqlite",
135
+ script: () => ({ turns: [GATED_OPENING_TURN, CLOSING_TURN] }),
136
+ });
137
+
138
+ // ── Act 1: the gate pauses the turn ────────────────────────────────────
139
+ const turn1 = await runDeepAgentTurn(scenario, { turnSeq: 0 });
140
+
141
+ // ── Assert 1: WAITING_FOR_APPROVAL, one WAITING row, returned ──────────
142
+ expect(turn1.outcome.kind, "an approval pause RETURNS").toBe("returned");
143
+ expect((turn1.outcome as { value: Record<string, unknown> }).value.phase).toBe(
144
+ "EXECUTION_WAITING_FOR_APPROVAL",
145
+ );
146
+ expect(record.persistedPhases).toEqual([
147
+ ExecutionPhase.EXECUTION_IN_PROGRESS,
148
+ ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
149
+ ]);
150
+ const waiting = record.waitingToolCalls();
151
+ expect(waiting).toHaveLength(1);
152
+ expect(waiting[0].id).toBe(EXECUTE_CALL_A.id);
153
+ expect(waiting[0].name).toBe("execute");
154
+ expect(waiting[0].requiresApproval).toBe(true);
155
+ expect(waiting[0].approvalMessage).toBe(executeApprovalMessage(EXECUTE_CALL_A));
156
+ expect(waiting[0].approvalAction, "no decision yet").toBe(ApprovalAction.UNSPECIFIED);
157
+ const run1 = record.lastFullStatus!;
158
+ await expect(statusJson(run1), "every decision's first turn is the same status").toMatchFileSnapshot(
159
+ GATE_TURN1_GOLDEN,
160
+ );
161
+
162
+ // ── Between turns: what SubmitApproval does to the row ─────────────────
163
+ expect(record.decideWaitingToolCalls(action, DECIDED_AT)).toBe(1);
164
+
165
+ // ── Act 2: the reinvocation resumes inside the gate ────────────────────
166
+ const turn2 = await runDeepAgentTurn(scenario, { turnSeq: 1 });
167
+
168
+ // ── Assert 2: COMPLETED, the row terminal, one copy, run 1 preserved ───
169
+ expect(turn2.outcome.kind).toBe("returned");
170
+ expect((turn2.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
171
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_COMPLETED);
172
+
173
+ const final = record.lastFullStatus!;
174
+ const rows = final.messages.flatMap((m) => m.toolCalls).filter((tc) => tc.id === EXECUTE_CALL_A.id);
175
+ expect(rows, "exactly one copy of the gated call — reconciled in place, never duplicated").toHaveLength(1);
176
+ const row = rows[0];
177
+ expect(row.approvalAction, "the decision is preserved for audit").toBe(action);
178
+ expect(row.approvalDecidedAt).toBe(DECIDED_AT);
179
+ expect(row.status, "never left at WAITING_APPROVAL").not.toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
180
+ if (action === ApprovalAction.APPROVE) {
181
+ expect(row.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
182
+ expect(row.result, "the command ran").toContain("hermetic-a");
183
+ expect(row.error).toBe("");
184
+ } else {
185
+ expect(row.status).toBe(ToolCallStatus.TOOL_CALL_SKIPPED);
186
+ expect(row.result, "the tool never ran").toBe("");
187
+ expect(row.error).toBe(action === ApprovalAction.REJECT ? "Rejected by user" : "");
188
+ }
189
+
190
+ const run1Ai = run1.messages.filter((m) => m.type === MessageType.MESSAGE_AI).map((m) => m.content);
191
+ const finalAi = final.messages.filter((m) => m.type === MessageType.MESSAGE_AI).map((m) => m.content);
192
+ expect(finalAi.slice(0, run1Ai.length), "run 1's transcript survives the resume as a prefix").toEqual(run1Ai);
193
+ expect(finalAi.at(-1)).toBe(CLOSING_TURN.text);
194
+ expect(final.completedAt).not.toBe("");
195
+
196
+ // ── Assert: hermeticity ────────────────────────────────────────────────
197
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
198
+
199
+ // ── Assert: the golden ─────────────────────────────────────────────────
200
+ await expect(statusJson(final)).toMatchFileSnapshot(`./goldens/${name}.turn2.status.json`);
201
+ });
202
+ });
203
+ });