@stigmer/runner 3.0.9-dev.20260616060535 → 3.1.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 (328) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
  3. package/dist/__test-utils__/approval-contract/types.js +24 -0
  4. package/dist/__test-utils__/approval-contract/types.js.map +1 -0
  5. package/dist/activities/call-agent-status.d.ts +19 -1
  6. package/dist/activities/call-agent-status.js +64 -5
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-llm.js +19 -53
  9. package/dist/activities/call-llm.js.map +1 -1
  10. package/dist/activities/classify-tool-approvals.d.ts +24 -0
  11. package/dist/activities/classify-tool-approvals.js +69 -17
  12. package/dist/activities/classify-tool-approvals.js.map +1 -1
  13. package/dist/activities/discover-mcp-server.d.ts +7 -0
  14. package/dist/activities/discover-mcp-server.js +11 -1
  15. package/dist/activities/discover-mcp-server.js.map +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
  17. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
  18. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
  19. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
  20. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
  21. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
  22. package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
  23. package/dist/activities/execute-cursor/approval-policy.js +8 -120
  24. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  25. package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
  26. package/dist/activities/execute-cursor/approval-state.js +233 -17
  27. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  28. package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
  29. package/dist/activities/execute-cursor/capture-flow.js +234 -0
  30. package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
  31. package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
  32. package/dist/activities/execute-cursor/cas-observations.js +184 -0
  33. package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
  34. package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
  35. package/dist/activities/execute-cursor/command-provenance.js +128 -0
  36. package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
  37. package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
  38. package/dist/activities/execute-cursor/exact-apply.js +204 -0
  39. package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
  40. package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
  41. package/dist/activities/execute-cursor/hook-script.js +310 -47
  42. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  43. package/dist/activities/execute-cursor/index.d.ts +8 -1
  44. package/dist/activities/execute-cursor/index.js +500 -64
  45. package/dist/activities/execute-cursor/index.js.map +1 -1
  46. package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
  47. package/dist/activities/execute-cursor/message-translator.js +836 -90
  48. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  49. package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
  50. package/dist/activities/execute-cursor/prompt-builder.js +74 -15
  51. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  52. package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
  53. package/dist/activities/execute-cursor/workspace-setup.js +212 -33
  54. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
  56. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
  57. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
  58. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
  59. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
  60. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
  61. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
  62. package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
  63. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
  64. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
  65. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
  66. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
  68. package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
  69. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
  70. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
  71. package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
  72. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
  73. package/dist/activities/execute-deep-agent/hitl.d.ts +10 -0
  74. package/dist/activities/execute-deep-agent/hitl.js +5 -1
  75. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/index.d.ts +2 -1
  77. package/dist/activities/execute-deep-agent/index.js +370 -56
  78. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
  80. package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
  81. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
  83. package/dist/activities/execute-deep-agent/setup.js +149 -92
  84. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
  86. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
  87. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
  88. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
  89. package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
  90. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  91. package/dist/activities/execute-deep-agent/status-builder.d.ts +11 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +6 -2
  93. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  94. package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
  95. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/streaming.js +3 -15
  97. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  98. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
  99. package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
  100. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  101. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
  102. package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
  103. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -2
  105. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  106. package/dist/claimcheck/payload-codec.js +9 -5
  107. package/dist/claimcheck/payload-codec.js.map +1 -1
  108. package/dist/client/stigmer-client.d.ts +2 -0
  109. package/dist/client/stigmer-client.js +2 -0
  110. package/dist/client/stigmer-client.js.map +1 -1
  111. package/dist/middleware/approval-gate.d.ts +85 -4
  112. package/dist/middleware/approval-gate.js +165 -38
  113. package/dist/middleware/approval-gate.js.map +1 -1
  114. package/dist/middleware/types.d.ts +2 -5
  115. package/dist/shared/activity-input.d.ts +43 -0
  116. package/dist/shared/activity-input.js +17 -0
  117. package/dist/shared/activity-input.js.map +1 -0
  118. package/dist/shared/approval-canonicalize.d.ts +19 -0
  119. package/dist/shared/approval-canonicalize.js +119 -0
  120. package/dist/shared/approval-canonicalize.js.map +1 -0
  121. package/dist/shared/approval-fingerprint.d.ts +106 -0
  122. package/dist/shared/approval-fingerprint.js +113 -0
  123. package/dist/shared/approval-fingerprint.js.map +1 -0
  124. package/dist/shared/approval-policy.d.ts +182 -12
  125. package/dist/shared/approval-policy.js +213 -27
  126. package/dist/shared/approval-policy.js.map +1 -1
  127. package/dist/shared/args-preview.d.ts +52 -0
  128. package/dist/shared/args-preview.js +93 -0
  129. package/dist/shared/args-preview.js.map +1 -0
  130. package/dist/shared/artifact-storage.d.ts +19 -1
  131. package/dist/shared/artifact-storage.js +48 -11
  132. package/dist/shared/artifact-storage.js.map +1 -1
  133. package/dist/shared/file-change.d.ts +44 -0
  134. package/dist/shared/file-change.js +57 -0
  135. package/dist/shared/file-change.js.map +1 -0
  136. package/dist/shared/file-tools.d.ts +107 -0
  137. package/dist/shared/file-tools.js +168 -0
  138. package/dist/shared/file-tools.js.map +1 -0
  139. package/dist/shared/filereview/capture.d.ts +202 -0
  140. package/dist/shared/filereview/capture.js +498 -0
  141. package/dist/shared/filereview/capture.js.map +1 -0
  142. package/dist/shared/filereview/cas-substrate.d.ts +190 -0
  143. package/dist/shared/filereview/cas-substrate.js +284 -0
  144. package/dist/shared/filereview/cas-substrate.js.map +1 -0
  145. package/dist/shared/filereview/digest.d.ts +40 -0
  146. package/dist/shared/filereview/digest.js +66 -0
  147. package/dist/shared/filereview/digest.js.map +1 -0
  148. package/dist/shared/filereview/events.d.ts +170 -0
  149. package/dist/shared/filereview/events.js +298 -0
  150. package/dist/shared/filereview/events.js.map +1 -0
  151. package/dist/shared/filereview/git-substrate.d.ts +175 -0
  152. package/dist/shared/filereview/git-substrate.js +439 -0
  153. package/dist/shared/filereview/git-substrate.js.map +1 -0
  154. package/dist/shared/filereview/index.d.ts +11 -0
  155. package/dist/shared/filereview/index.js +12 -0
  156. package/dist/shared/filereview/index.js.map +1 -0
  157. package/dist/shared/filereview/secret-paths.d.ts +63 -0
  158. package/dist/shared/filereview/secret-paths.js +105 -0
  159. package/dist/shared/filereview/secret-paths.js.map +1 -0
  160. package/dist/shared/fingerprint-secret.d.ts +26 -0
  161. package/dist/shared/fingerprint-secret.js +47 -0
  162. package/dist/shared/fingerprint-secret.js.map +1 -0
  163. package/dist/shared/model-client.d.ts +51 -0
  164. package/dist/shared/model-client.js +77 -0
  165. package/dist/shared/model-client.js.map +1 -0
  166. package/dist/shared/plan-artifact.js +0 -2
  167. package/dist/shared/plan-artifact.js.map +1 -1
  168. package/dist/shared/status-offload.d.ts +83 -9
  169. package/dist/shared/status-offload.js +399 -79
  170. package/dist/shared/status-offload.js.map +1 -1
  171. package/dist/shared/status.js +14 -1
  172. package/dist/shared/status.js.map +1 -1
  173. package/dist/shared/tool-kind.d.ts +19 -0
  174. package/dist/shared/tool-kind.js +13 -0
  175. package/dist/shared/tool-kind.js.map +1 -1
  176. package/dist/shared/tool-row.d.ts +88 -0
  177. package/dist/shared/tool-row.js +127 -0
  178. package/dist/shared/tool-row.js.map +1 -0
  179. package/dist/shared/workspace/platform-dir.d.ts +25 -0
  180. package/dist/shared/workspace/platform-dir.js +38 -2
  181. package/dist/shared/workspace/platform-dir.js.map +1 -1
  182. package/dist/workflows/call-agent-orchestrator.js +56 -7
  183. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  184. package/dist/workflows/connect-mcp-server.d.ts +50 -0
  185. package/dist/workflows/connect-mcp-server.js +136 -15
  186. package/dist/workflows/connect-mcp-server.js.map +1 -1
  187. package/dist/workflows/types.d.ts +8 -0
  188. package/package.json +2 -2
  189. package/src/__test-utils__/approval-contract/contract.ts +224 -0
  190. package/src/__test-utils__/approval-contract/types.ts +179 -0
  191. package/src/__test-utils__/fake-artifact-storage.ts +72 -0
  192. package/src/__tests__/approval-gateway-contract.test.ts +29 -0
  193. package/src/__tests__/claimcheck-codec.test.ts +16 -53
  194. package/src/__tests__/golden-e2e.test.ts +2 -0
  195. package/src/__tests__/runner-token-coordinator.test.ts +3 -3
  196. package/src/activities/__tests__/call-agent-status.test.ts +135 -0
  197. package/src/activities/__tests__/call-llm.test.ts +1 -1
  198. package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
  199. package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
  200. package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
  201. package/src/activities/call-agent-status.ts +74 -4
  202. package/src/activities/call-llm.ts +18 -63
  203. package/src/activities/classify-tool-approvals.ts +101 -19
  204. package/src/activities/discover-mcp-server.ts +29 -1
  205. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
  206. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
  207. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
  208. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
  209. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
  210. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
  211. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
  212. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
  213. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
  214. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
  215. package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
  216. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
  217. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
  218. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
  219. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
  220. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
  221. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
  222. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
  223. package/src/activities/execute-cursor/approval-policy.ts +28 -159
  224. package/src/activities/execute-cursor/approval-state.ts +366 -18
  225. package/src/activities/execute-cursor/capture-flow.ts +323 -0
  226. package/src/activities/execute-cursor/cas-observations.ts +204 -0
  227. package/src/activities/execute-cursor/command-provenance.ts +168 -0
  228. package/src/activities/execute-cursor/exact-apply.ts +253 -0
  229. package/src/activities/execute-cursor/hook-script.ts +317 -51
  230. package/src/activities/execute-cursor/index.ts +575 -67
  231. package/src/activities/execute-cursor/message-translator.ts +963 -89
  232. package/src/activities/execute-cursor/prompt-builder.ts +80 -14
  233. package/src/activities/execute-cursor/workspace-setup.ts +257 -42
  234. package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
  235. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
  236. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
  237. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
  238. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
  239. package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
  240. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
  241. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
  242. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +2 -5
  243. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  244. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  245. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  246. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +12 -11
  247. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  248. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  249. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  251. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  252. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  253. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  254. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  255. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  256. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  257. package/src/activities/execute-deep-agent/hitl.ts +15 -1
  258. package/src/activities/execute-deep-agent/index.ts +434 -64
  259. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  260. package/src/activities/execute-deep-agent/setup.ts +223 -125
  261. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  262. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  263. package/src/activities/execute-deep-agent/status-builder.ts +19 -7
  264. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  265. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  266. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  267. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  268. package/src/activities/execute-deep-agent/v3-status-builder.ts +8 -2
  269. package/src/claimcheck/payload-codec.ts +8 -8
  270. package/src/client/stigmer-client.ts +9 -1
  271. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  272. package/src/middleware/approval-gate.ts +247 -38
  273. package/src/middleware/types.ts +5 -5
  274. package/src/shared/__tests__/activity-input.test.ts +78 -0
  275. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  276. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  277. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  278. package/src/shared/__tests__/args-preview.test.ts +78 -0
  279. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  280. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  281. package/src/shared/__tests__/file-change.test.ts +85 -0
  282. package/src/shared/__tests__/file-tools.test.ts +90 -0
  283. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  284. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  285. package/src/shared/__tests__/model-client.test.ts +162 -0
  286. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  287. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  288. package/src/shared/__tests__/status-offload.test.ts +573 -16
  289. package/src/shared/__tests__/status.test.ts +4 -5
  290. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  291. package/src/shared/__tests__/tool-row.test.ts +221 -0
  292. package/src/shared/activity-input.ts +57 -0
  293. package/src/shared/approval-canonicalize.ts +159 -0
  294. package/src/shared/approval-fingerprint.ts +148 -0
  295. package/src/shared/approval-policy.ts +303 -27
  296. package/src/shared/args-preview.ts +98 -0
  297. package/src/shared/artifact-storage.ts +62 -11
  298. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  299. package/src/shared/file-change.ts +64 -0
  300. package/src/shared/file-tools.ts +169 -0
  301. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  302. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  303. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  304. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  305. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  306. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  307. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  308. package/src/shared/filereview/capture.ts +727 -0
  309. package/src/shared/filereview/cas-substrate.ts +401 -0
  310. package/src/shared/filereview/digest.ts +83 -0
  311. package/src/shared/filereview/events.ts +449 -0
  312. package/src/shared/filereview/git-substrate.ts +555 -0
  313. package/src/shared/filereview/index.ts +60 -0
  314. package/src/shared/filereview/secret-paths.ts +121 -0
  315. package/src/shared/fingerprint-secret.ts +53 -0
  316. package/src/shared/model-client.ts +122 -0
  317. package/src/shared/plan-artifact.ts +0 -2
  318. package/src/shared/status-offload.ts +433 -77
  319. package/src/shared/status.ts +13 -0
  320. package/src/shared/tool-kind.ts +33 -0
  321. package/src/shared/tool-row.ts +135 -0
  322. package/src/shared/workspace/platform-dir.ts +41 -2
  323. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  324. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  325. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  326. package/src/workflows/call-agent-orchestrator.ts +53 -6
  327. package/src/workflows/connect-mcp-server.ts +179 -24
  328. package/src/workflows/types.ts +8 -0
@@ -29,6 +29,115 @@ function classifyWithTimeout(numTools) {
29
29
  });
30
30
  }
31
31
  // ─────────────────────────────────────────────────────────────────────────────
32
+ // Incremental classification planner (pure, deterministic, sandbox-safe)
33
+ // ─────────────────────────────────────────────────────────────────────────────
34
+ /**
35
+ * Canonical, order-stable signature of a single tool's definition.
36
+ *
37
+ * Mirrors the shape `toolsFingerprint` hashes (name + description + input_schema)
38
+ * so "unchanged" here means the same thing it means for the whole-server
39
+ * fingerprint. Uses only JSON — no `node:crypto` — so it is safe to call from
40
+ * inside the Temporal deterministic V8 isolate.
41
+ */
42
+ function toolSignature(tool) {
43
+ return JSON.stringify({
44
+ name: tool.name,
45
+ description: tool.description,
46
+ input_schema: tool.inputSchema ?? null,
47
+ });
48
+ }
49
+ /**
50
+ * Partition the freshly discovered tools into those that must be classified and
51
+ * the prior approval decisions that can be carried forward verbatim.
52
+ *
53
+ * Reuse is **content-addressed**: a prior decision is kept only when a tool's
54
+ * name AND full definition are byte-identical to the previous connect. This is
55
+ * deliberately stricter than reusing by name alone — a tool can keep its name
56
+ * while its schema changes from benign to destructive, and such a tool MUST be
57
+ * re-evaluated rather than left with a stale "auto-approve". Tools that are
58
+ * unchanged are never re-classified (stable, deterministic, no LLM cost; no
59
+ * flapping for borderline tools), and a tool present last time but gone now is
60
+ * simply absent from both outputs (dropped).
61
+ *
62
+ * The previous approval list is a presence-set of *gated* tools (a tool in
63
+ * `previousToolApprovals` requires approval; a known tool absent from it was
64
+ * auto-approved). So a reused tool emits a carried-forward entry only when it
65
+ * was gated; reused auto-approved tools emit nothing, which correctly keeps them
66
+ * un-gated. `ClassifyToolApprovals` likewise returns only gated entries, so the
67
+ * union `[...carriedForward, ...classified]` is the complete gated set.
68
+ *
69
+ * Pure and free of Temporal APIs so it can be exhaustively unit-tested and is
70
+ * safe to evaluate inside the workflow sandbox.
71
+ */
72
+ export function planIncrementalClassification(previousTools, previousToolApprovals, currentTools) {
73
+ const prevSigByName = new Map();
74
+ for (const tool of previousTools) {
75
+ prevSigByName.set(tool.name, toolSignature(tool));
76
+ }
77
+ const prevGatedByName = new Map();
78
+ for (const approval of previousToolApprovals) {
79
+ prevGatedByName.set(approval.toolName, approval);
80
+ }
81
+ const toolsToClassify = [];
82
+ const carriedForward = [];
83
+ for (const tool of currentTools) {
84
+ const prevSig = prevSigByName.get(tool.name);
85
+ const unchanged = prevSig !== undefined && prevSig === toolSignature(tool);
86
+ if (!unchanged) {
87
+ toolsToClassify.push(tool);
88
+ continue;
89
+ }
90
+ const gated = prevGatedByName.get(tool.name);
91
+ if (gated) {
92
+ carriedForward.push({
93
+ tool_name: gated.toolName,
94
+ requires_approval: true,
95
+ message: gated.message,
96
+ });
97
+ }
98
+ // An unchanged tool that was not gated stays auto-approved — emit nothing.
99
+ }
100
+ return { toolsToClassify, carriedForward };
101
+ }
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+ // destructiveHint fail-closed tightener (pure, deterministic, sandbox-safe)
104
+ // ─────────────────────────────────────────────────────────────────────────────
105
+ /**
106
+ * Force-gate any tool whose live MCP annotation declares `destructiveHint:true`
107
+ * but that the classifier (or carry-forward) left un-gated.
108
+ *
109
+ * This is the ONLY way annotations influence policy, and it is deliberately
110
+ * one-directional. The MCP spec warns that clients must never make tool-use
111
+ * decisions on annotations from untrusted servers; trusting a server's
112
+ * "I am destructive" claim only ever ADDS an approval prompt (the safe
113
+ * direction), so it cannot be abused. The inverse — trusting `readOnlyHint` to
114
+ * AUTO-APPROVE — is exactly the unsafe direction the spec forbids, so a spoofed
115
+ * `readOnlyHint:true` on a destructive tool must never relax it. Read-only
116
+ * auto-approval authority lives solely with the trusted LLM classifier.
117
+ *
118
+ * Recomputed from live discovery on every connect, so it has zero coupling to
119
+ * `toolSignature`/incremental reuse and needs no persistence. Pure JS so it is
120
+ * safe to evaluate inside the Temporal deterministic isolate.
121
+ */
122
+ export function applyDestructiveHintTightener(gated, currentTools) {
123
+ const gatedNames = new Set(gated.map((g) => g.tool_name));
124
+ const tightened = [...gated];
125
+ let addedCount = 0;
126
+ for (const tool of currentTools) {
127
+ if (tool.annotations?.destructiveHint === true && !gatedNames.has(tool.name)) {
128
+ tightened.push({
129
+ tool_name: tool.name,
130
+ requires_approval: true,
131
+ message: `Execute ${tool.name}`,
132
+ from_destructive_hint: true,
133
+ });
134
+ gatedNames.add(tool.name);
135
+ addedCount++;
136
+ }
137
+ }
138
+ return { tightened, addedCount };
139
+ }
140
+ // ─────────────────────────────────────────────────────────────────────────────
32
141
  // ConnectMcpServerWorkflow — primary connect flow
33
142
  // ─────────────────────────────────────────────────────────────────────────────
34
143
  export async function connectMcpServer(input) {
@@ -37,23 +146,22 @@ export async function connectMcpServer(input) {
37
146
  executionContextId: input.execution_context_id ?? null,
38
147
  invokerIdentityAccountId: input.invoker_identity_account_id ?? null,
39
148
  });
40
- const canReusePreviousApprovals = discovery.newToolsFingerprint !== "" &&
41
- discovery.newToolsFingerprint === discovery.previousToolsFingerprint &&
42
- discovery.previousToolApprovals.length > 0;
149
+ // Content-addressed incremental classification: reuse prior decisions for
150
+ // tools whose definition is unchanged, and classify only the new or changed
151
+ // ones. Keeps decisions stable/deterministic across reconnects and avoids
152
+ // redundant LLM calls, while still re-evaluating a tool whose schema changed.
153
+ const { toolsToClassify, carriedForward } = planIncrementalClassification(discovery.previousTools, discovery.previousToolApprovals, discovery.tools);
43
154
  let toolApprovals;
44
- if (canReusePreviousApprovals) {
45
- log.info(`Tools unchanged for '${input.mcp_server_id}' ` +
46
- `(fingerprint ${discovery.newToolsFingerprint.slice(0, 12)}) ` +
47
- `reusing ${discovery.previousToolApprovals.length} previous approval(s)`);
48
- toolApprovals = discovery.previousToolApprovals.map((a) => ({
49
- tool_name: a.toolName,
50
- requires_approval: a.requiresApproval,
51
- message: a.message,
52
- }));
155
+ if (toolsToClassify.length === 0) {
156
+ log.info(`Tools unchanged for '${input.mcp_server_id}' — reusing ` +
157
+ `${carriedForward.length} previous approval(s), no classification needed`);
158
+ toolApprovals = carriedForward;
53
159
  }
54
160
  else {
161
+ log.info(`Classifying ${toolsToClassify.length} new/changed tool(s) for ` +
162
+ `'${input.mcp_server_id}', reusing ${carriedForward.length} prior decision(s)`);
55
163
  const classifyInput = {
56
- tools: discovery.tools.map((t) => ({
164
+ tools: toolsToClassify.map((t) => ({
57
165
  name: t.name,
58
166
  description: t.description,
59
167
  input_schema: t.inputSchema ?? null,
@@ -62,9 +170,22 @@ export async function connectMcpServer(input) {
62
170
  serverDescription: "",
63
171
  mcpServerId: input.mcp_server_id,
64
172
  };
65
- const classify = classifyWithTimeout(discovery.tools.length);
66
- toolApprovals = await classify.ClassifyToolApprovals(classifyInput);
173
+ const classify = classifyWithTimeout(toolsToClassify.length);
174
+ const classified = await classify.ClassifyToolApprovals(classifyInput);
175
+ toolApprovals = [...carriedForward, ...classified];
176
+ }
177
+ // Fail-closed tightener over the FULL live tool set: a tool the server's own
178
+ // annotation marks destructiveHint=true is force-gated if it slipped through
179
+ // un-gated. Runs on live discovery (not the reused/classified subset), so it
180
+ // also re-asserts gating for carried-forward tools whose server later flips a
181
+ // tool to destructive. We never trust readOnlyHint to relax — see the
182
+ // tightener's contract for the MCP untrusted-hints rationale.
183
+ const { tightened, addedCount } = applyDestructiveHintTightener(toolApprovals, discovery.tools);
184
+ if (addedCount > 0) {
185
+ log.info(`Force-gated ${addedCount} tool(s) via destructiveHint annotation for ` +
186
+ `'${input.mcp_server_id}'`);
67
187
  }
188
+ toolApprovals = tightened;
68
189
  return {
69
190
  tools: discovery.tools.map((t) => ({
70
191
  name: t.name,
@@ -1 +1 @@
1
- {"version":3,"file":"connect-mcp-server.js","sourceRoot":"","sources":["../../src/workflows/connect-mcp-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connect-mcp-server.js","sourceRoot":"","sources":["../../src/workflows/connect-mcp-server.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,14 @@ export interface WireToolApproval {
36
36
  tool_name: string;
37
37
  requires_approval: boolean;
38
38
  message: string;
39
+ /**
40
+ * True when the connect-time destructiveHint tightener force-gated this tool
41
+ * (see applyDestructiveHintTightener). Persisted to
42
+ * ToolApprovalPolicy.from_destructive_hint so the runner attributes the gate to
43
+ * the annotation rather than the classifier. Omitted on classifier/pinned
44
+ * entries.
45
+ */
46
+ from_destructive_hint?: boolean;
39
47
  }
40
48
  export interface DiscoverMcpServerWorkflowOutput {
41
49
  tools: WireToolResult[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner",
3
- "version": "3.0.9-dev.20260616060535",
3
+ "version": "3.1.0",
4
4
  "description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -86,7 +86,7 @@
86
86
  "@opentelemetry/resources": "^2.0.0",
87
87
  "@opentelemetry/sdk-trace-base": "^2.0.0",
88
88
  "@opentelemetry/sdk-trace-node": "^2.0.0",
89
- "@stigmer/protos": "3.0.9-dev.20260616060535",
89
+ "@stigmer/protos": "3.1.0",
90
90
  "@temporalio/activity": "^1.11.0",
91
91
  "@temporalio/client": "^1.11.0",
92
92
  "@temporalio/common": "^1.11.0",
@@ -0,0 +1,224 @@
1
+ /**
2
+ * The HITL gateway P0 safety contract — the single authoritative statement of
3
+ * "these are THE tool-approval safety invariants, and every enforcement
4
+ * substrate must satisfy them."
5
+ *
6
+ * {@link describeGatewayContract} runs the invariants below against any
7
+ * {@link GatewaySubstrate}; {@link describeCrossSubstrateAgreement} proves the
8
+ * substrates reach the SAME decision for the SAME logical action. Both drive real
9
+ * production code through thin adapters, so reverting any P0 behavior fails here.
10
+ *
11
+ * ── Canonical P0 invariant catalog ──────────────────────────────────────────
12
+ * 1. No side effect without a backing authorization (the umbrella the rest serve).
13
+ * 2. approve → executes exactly once (count asserted where observable).
14
+ * 3. reject → never executes.
15
+ * 4. skip → never executes.
16
+ * 5. unknown decision → never executes (fail-closed on an unrecognized verdict).
17
+ * 6. mutating built-in, no explicit approval → gated (fail-closed BY CATEGORY,
18
+ * so a brand-new mutating tool is gated by default, not allow-listed).
19
+ * 7. read-only / non-mutating built-in → executes with no gate.
20
+ * 8. auto-approved MCP tool → executes with no gate.
21
+ * 9. cross-tool isolation: approving a write never authorizes a shell
22
+ * (capability: enforcesExactResource).
23
+ * 10. exact-resource: approving write /a never authorizes write /b
24
+ * (capability: enforcesExactResource).
25
+ * 11. class-lease isolation: APPROVE_ALL on class A auto-approves later class-A
26
+ * actions but never class B (capability: appliesRunLifetimeLease).
27
+ *
28
+ * Capability-gated invariants (9, 10, 11) run only where the substrate supports
29
+ * the relevant lease; the "executes exactly once" count in (2) is asserted only
30
+ * where the substrate observes execution. Differences are gated, never forked —
31
+ * the same philosophy as the conformance suite's CapabilityFlags.
32
+ */
33
+
34
+ import { describe, it, expect } from "vitest";
35
+ import type { GatewayDecision, GatewaySubstrate, ProposedAction } from "./types.js";
36
+
37
+ /** Representative actions shared by the per-substrate and cross-substrate suites. */
38
+ const WRITE_A: ProposedAction = { kind: "write", resource: "/work/alpha.txt" };
39
+ const WRITE_B: ProposedAction = { kind: "write", resource: "/work/beta.txt" };
40
+ // Same file as WRITE_A, DIFFERENT content — the sibling-isolation probe (a
41
+ // second distinct edit to an already-approved file must re-gate).
42
+ const WRITE_A_V2: ProposedAction = { kind: "write", resource: "/work/alpha.txt", content: "a different body" };
43
+ const SHELL: ProposedAction = { kind: "shell", resource: "rm -rf build" };
44
+ const DELETE: ProposedAction = { kind: "delete", resource: "/work/gamma.txt" };
45
+ const READ: ProposedAction = { kind: "read", resource: "/work/alpha.txt" };
46
+ const MCP: ProposedAction = {
47
+ kind: "mcp",
48
+ resource: "",
49
+ mcpServerSlug: "github",
50
+ mcpToolName: "search_issues",
51
+ };
52
+
53
+ /**
54
+ * Register the P0 safety invariants against one substrate. The suite is skipped
55
+ * (not failed) when the substrate is unavailable in this environment.
56
+ */
57
+ export function describeGatewayContract(substrate: GatewaySubstrate): void {
58
+ const suite = substrate.available ? describe : describe.skip;
59
+
60
+ suite(`gateway contract — ${substrate.name}`, () => {
61
+ // Invariants 1 + 6: a mutating built-in with no backing authorization is
62
+ // gated and does not execute (fail-closed by category, every category).
63
+ it("gates every mutating built-in with no backing authorization", async () => {
64
+ for (const action of [WRITE_A, SHELL, DELETE]) {
65
+ const outcome = await substrate.authorize(action, "none");
66
+ expect(outcome.gated, `${substrate.name}: ${action.kind} must be gated without an authorization`).toBe(true);
67
+ expect(outcome.executed, `${substrate.name}: ${action.kind} must NOT execute without an authorization`).toBe(false);
68
+ }
69
+ });
70
+
71
+ // Invariant 2: approve → executes exactly once (count asserted where observable).
72
+ it("executes a mutating built-in exactly once after approve", async () => {
73
+ const outcome = await substrate.authorize(WRITE_A, "approve");
74
+ expect(outcome.executed, `${substrate.name}: an approved write must execute`).toBe(true);
75
+ if (substrate.capabilities.observesExecution) {
76
+ expect(outcome.executionCount, `${substrate.name}: an approved write must execute exactly once`).toBe(1);
77
+ }
78
+ });
79
+
80
+ // Invariants 3, 4, 5: no non-approving verdict may execute.
81
+ for (const decision of ["reject", "skip", "unknown"] as const) {
82
+ it(`never executes after a ${decision} decision`, async () => {
83
+ const outcome = await substrate.authorize(WRITE_A, decision);
84
+ expect(outcome.executed, `${substrate.name}: a ${decision}-ed write must never execute`).toBe(false);
85
+ });
86
+ }
87
+
88
+ // Invariant 1 (provenance corollary): every side effect the gate withholds
89
+ // carries a non-UNSPECIFIED authorization provenance — so an authorized side
90
+ // effect is always auditable to the policy layer that governed it. Gated to
91
+ // substrates that decide the source at the gate (the deep-agent gate); the
92
+ // Cursor substrate projects provenance at reconstruction time and is covered
93
+ // by the message-translator + corpus suites.
94
+ if (substrate.capabilities.surfacesGatePolicySource) {
95
+ it("tags every gated side effect with a non-UNSPECIFIED policy source", async () => {
96
+ for (const action of [WRITE_A, SHELL, DELETE]) {
97
+ const outcome = await substrate.authorize(action, "none");
98
+ expect(outcome.gated, `${substrate.name}: ${action.kind} must be gated`).toBe(true);
99
+ expect(
100
+ outcome.policySource,
101
+ `${substrate.name}: a gated ${action.kind} must carry a policy source`,
102
+ ).toBeTruthy();
103
+ expect(
104
+ outcome.policySource,
105
+ `${substrate.name}: a gated ${action.kind}'s source must not be UNSPECIFIED`,
106
+ ).not.toBe("unspecified");
107
+ }
108
+ });
109
+ }
110
+
111
+ // Invariant 7: a non-mutating built-in runs without a gate.
112
+ it("executes a non-mutating built-in without a gate", async () => {
113
+ const outcome = await substrate.authorize(READ, "none");
114
+ expect(outcome.gated, `${substrate.name}: a read must not be gated`).toBe(false);
115
+ expect(outcome.executed, `${substrate.name}: a read must execute`).toBe(true);
116
+ });
117
+
118
+ // Invariant 8: an auto-approved MCP tool runs without a gate.
119
+ it("executes an auto-approved MCP tool without a gate", async () => {
120
+ const outcome = await substrate.authorize(MCP, "none");
121
+ expect(outcome.gated, `${substrate.name}: an auto-approved MCP tool must not be gated`).toBe(false);
122
+ expect(outcome.executed, `${substrate.name}: an auto-approved MCP tool must execute`).toBe(true);
123
+ });
124
+
125
+ // Invariants 9, 10: lease isolation — only meaningful where the substrate
126
+ // binds the exact resource. Gated honestly rather than asserted everywhere.
127
+ if (substrate.capabilities.enforcesExactResource && substrate.authorizeAfterGrant) {
128
+ const afterGrant = substrate.authorizeAfterGrant.bind(substrate);
129
+
130
+ it("honors a grant only for the exact approved resource (invariant 10)", async () => {
131
+ const sameResource = await afterGrant(WRITE_A, WRITE_A);
132
+ expect(sameResource.executed, `${substrate.name}: the exact granted resource must be allowed`).toBe(true);
133
+
134
+ const otherResource = await afterGrant(WRITE_A, WRITE_B);
135
+ expect(otherResource.executed, `${substrate.name}: a different resource must be re-gated, not allowed`).toBe(false);
136
+ });
137
+
138
+ it("never lets one tool's approval authorize another tool (invariant 9)", async () => {
139
+ const crossTool = await afterGrant(WRITE_A, SHELL);
140
+ expect(crossTool.executed, `${substrate.name}: approving a write must never authorize a shell`).toBe(false);
141
+ });
142
+ }
143
+
144
+ // Invariant 12: content-exact isolation — approving ONE edit to a file never
145
+ // authorizes a DIFFERENT edit to the SAME file (the deny-only "sibling hole").
146
+ // Only meaningful where the grant binds content; gated, never forked.
147
+ if (substrate.capabilities.enforcesExactContent && substrate.authorizeAfterGrant) {
148
+ const afterGrant = substrate.authorizeAfterGrant.bind(substrate);
149
+
150
+ it("honors a grant only for the exact approved content (invariant 12: sibling isolation)", async () => {
151
+ const sameContent = await afterGrant(WRITE_A, WRITE_A);
152
+ expect(sameContent.executed, `${substrate.name}: re-issuing the exact approved edit must be allowed`).toBe(true);
153
+
154
+ const otherContent = await afterGrant(WRITE_A, WRITE_A_V2);
155
+ expect(otherContent.executed, `${substrate.name}: a DIFFERENT edit to the same file must re-gate`).toBe(false);
156
+ });
157
+ }
158
+
159
+ // Invariant 11: a run-lifetime CLASS lease (APPROVE_ALL) auto-approves later
160
+ // actions of the SAME class but never a different class — the core Phase-7
161
+ // scoped-lease safety property, enforced independently on each substrate (the
162
+ // gate clears leased categories; the hook reads leasedCategories).
163
+ if (substrate.capabilities.appliesRunLifetimeLease && substrate.authorizeUnderClassLease) {
164
+ const underLease = substrate.authorizeUnderClassLease.bind(substrate);
165
+
166
+ it("a class lease auto-approves that class and ONLY that class (invariant 11)", async () => {
167
+ // Lease "write": a later write of a DIFFERENT resource runs ungated...
168
+ const sameClass = await underLease(WRITE_A, WRITE_B);
169
+ expect(sameClass.executed, `${substrate.name}: a write lease must auto-approve another write`).toBe(true);
170
+
171
+ // ...but later actions of OTHER classes are still gated, never leaked.
172
+ const shellUnderWrite = await underLease(WRITE_A, SHELL);
173
+ expect(shellUnderWrite.executed, `${substrate.name}: a write lease must NOT authorize a shell`).toBe(false);
174
+
175
+ const deleteUnderWrite = await underLease(WRITE_A, DELETE);
176
+ expect(deleteUnderWrite.executed, `${substrate.name}: a write lease must NOT authorize a delete`).toBe(false);
177
+
178
+ // Symmetry: a shell lease auto-approves shell but not write.
179
+ const shellUnderShell = await underLease(SHELL, { kind: "shell", resource: "make build" });
180
+ expect(shellUnderShell.executed, `${substrate.name}: a shell lease must auto-approve another shell`).toBe(true);
181
+
182
+ const writeUnderShell = await underLease(SHELL, WRITE_A);
183
+ expect(writeUnderShell.executed, `${substrate.name}: a shell lease must NOT authorize a write`).toBe(false);
184
+ });
185
+ }
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Prove the substrates AGREE: the same logical action under the same decision
191
+ * yields the same execution outcome on every available substrate. This is the
192
+ * real consolidation win — one place that says "the in-process gate and the
193
+ * out-of-process deny-oracle enforce the same policy," including the cross-taxonomy
194
+ * collapse (a grant minted from the stream-side identity is honored by the
195
+ * hook-side for the same action).
196
+ *
197
+ * Only `executed` is compared: it is the safety-critical observable and is
198
+ * substrate-comparable, whereas `gated` carries a substrate-specific meaning on
199
+ * an approve (the in-process gate still "paused"; the deny-oracle did not "deny").
200
+ */
201
+ export function describeCrossSubstrateAgreement(substrates: GatewaySubstrate[]): void {
202
+ const available = substrates.filter((s) => s.available);
203
+ const suite = available.length >= 2 ? describe : describe.skip;
204
+
205
+ suite("gateway contract — cross-substrate agreement", () => {
206
+ const cases: Array<{ label: string; action: ProposedAction; decision: GatewayDecision }> = [
207
+ { label: "a fresh write is withheld", action: WRITE_A, decision: "none" },
208
+ { label: "an approved write executes", action: WRITE_A, decision: "approve" },
209
+ { label: "a fresh shell is withheld", action: SHELL, decision: "none" },
210
+ { label: "a rejected write never executes", action: WRITE_A, decision: "reject" },
211
+ { label: "a read runs ungated", action: READ, decision: "none" },
212
+ { label: "an auto-approved MCP tool runs ungated", action: MCP, decision: "none" },
213
+ ];
214
+
215
+ for (const { label, action, decision } of cases) {
216
+ it(`both substrates agree: ${label}`, async () => {
217
+ const outcomes = await Promise.all(available.map((s) => s.authorize(action, decision)));
218
+ const executed = outcomes.map((o) => o.executed);
219
+ const detail = available.map((s, i) => `${s.name}=${executed[i]}`).join(", ");
220
+ expect(new Set(executed).size, `substrates disagree on "${label}" (executed): ${detail}`).toBe(1);
221
+ });
222
+ }
223
+ });
224
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * The substrate-agnostic seam for the HITL gateway Contract Test Kit.
3
+ *
4
+ * Stigmer enforces tool approval through two real substrates with very different
5
+ * mechanics:
6
+ * - the in-process deep-agent gate (a LangGraph middleware that IS the side
7
+ * effect), and
8
+ * - the out-of-process Cursor deny-oracle (a bash preToolUse hook that allows or
9
+ * denies a tool the agent runs inside cursor-agent).
10
+ *
11
+ * The safety invariants they must uphold are identical, so the contract describes
12
+ * them once and runs them against anything that implements {@link GatewaySubstrate}.
13
+ * A future substrate (e.g. the T04 TS server's runner) joins the safety net by
14
+ * implementing this one interface — it does not get to redefine the invariants.
15
+ *
16
+ * This seam intentionally does NOT carry raw, harness-specific tool names. The
17
+ * two substrates name the same operation differently (the Cursor hook says
18
+ * `Write`/`Shell`/`Delete`; the deep-agent stream says `edit`/`shell`/`delete`),
19
+ * so the contract speaks in the abstract {@link ProposedAction} and each adapter
20
+ * translates it into its own taxonomy. Keeping the contract taxonomy-free is what
21
+ * makes "both substrates agree on the same logical action" a meaningful assertion.
22
+ */
23
+
24
+ /**
25
+ * A logical action a model proposes, expressed independently of any harness
26
+ * taxonomy. Each adapter maps it to its substrate's concrete tool name and args.
27
+ */
28
+ export interface ProposedAction {
29
+ /**
30
+ * The action's approval-relevant kind. `write`/`shell`/`delete` are the gated
31
+ * mutating categories; `read` is any non-mutating built-in; `mcp` is an
32
+ * MCP-server tool.
33
+ */
34
+ readonly kind: "write" | "shell" | "delete" | "read" | "mcp";
35
+ /**
36
+ * The resource the action acts on — the absolute file path for file actions,
37
+ * the command string for shell. Empty for `mcp` (MCP identity is name-scoped).
38
+ * This is the "salient" value the exact-resource lease binds to.
39
+ */
40
+ readonly resource: string;
41
+ /**
42
+ * The edit content for a `write` action — the body whose digest the
43
+ * content-exact grant binds to. Two `write`s to the same `resource` with
44
+ * DIFFERENT `content` are distinct identities (the sibling-isolation probe);
45
+ * omitted, a substrate uses a fixed placeholder. Ignored for non-write kinds.
46
+ */
47
+ readonly content?: string;
48
+ /** MCP server slug — required for `kind: "mcp"`, ignored otherwise. */
49
+ readonly mcpServerSlug?: string;
50
+ /** MCP tool name — required for `kind: "mcp"`, ignored otherwise. */
51
+ readonly mcpToolName?: string;
52
+ }
53
+
54
+ /**
55
+ * The decision applied to a proposed action.
56
+ * - `none` — no decision yet: run the action up to the gate and stop (the
57
+ * "what happens with no backing authorization" probe).
58
+ * - `approve`— authorize THIS action.
59
+ * - `skip` / `reject` / `unknown` — every non-approving outcome; none may execute.
60
+ */
61
+ export type GatewayDecision = "none" | "approve" | "skip" | "reject" | "unknown";
62
+
63
+ /**
64
+ * The observable result of putting one proposed action through a substrate.
65
+ */
66
+ export interface GatewayOutcome {
67
+ /**
68
+ * Whether the side effect ran (or, for an out-of-process substrate, whether
69
+ * the substrate ALLOWED it to run). This is the safety-critical observable and
70
+ * is comparable across substrates.
71
+ */
72
+ readonly executed: boolean;
73
+ /**
74
+ * Whether the substrate withheld the action pending approval. Note the precise
75
+ * meaning is substrate-specific (the in-process gate "paused at an interrupt";
76
+ * the deny-oracle "denied this call"), so it is only compared across substrates
77
+ * for `none`-decision probes, where both mean "withheld".
78
+ */
79
+ readonly gated: boolean;
80
+ /**
81
+ * Exact number of times the side effect ran. Populated only when
82
+ * {@link SubstrateCapabilities.observesExecution} is true (an in-process
83
+ * substrate can count; an out-of-process one cannot observe execution at all).
84
+ */
85
+ readonly executionCount?: number;
86
+ /**
87
+ * The authorization provenance the gate attached when it withheld the action —
88
+ * the PolicySource union string (e.g. "builtin_category", "agent_override").
89
+ * Populated only when {@link SubstrateCapabilities.surfacesGatePolicySource} is
90
+ * true; empty/undefined otherwise. Lets the contract assert every gated side
91
+ * effect is provenance-tagged.
92
+ */
93
+ readonly policySource?: string;
94
+ }
95
+
96
+ /**
97
+ * Capability flags for invariants that legitimately differ by substrate, gated
98
+ * rather than forked — mirroring the conformance suite's `CapabilityFlags`.
99
+ */
100
+ export interface SubstrateCapabilities {
101
+ /**
102
+ * True when the runner executes the tool in-process and can therefore observe
103
+ * (and count) the side effect directly. The deep-agent gate is in-process
104
+ * (`true`); the Cursor hook authorizes a tool that runs in another process
105
+ * (`false`, receipts are best-effort).
106
+ */
107
+ readonly observesExecution: boolean;
108
+ /**
109
+ * True when an approval is bound to the exact resource it was granted for, so
110
+ * approving one resource never authorizes another. The Cursor grant token binds
111
+ * the resource (`true`); the deep-agent gate re-checks every distinct call and
112
+ * relies on checkpoint replay for sameness, so per-resource lease isolation is
113
+ * not a property of that gate (`false`).
114
+ */
115
+ readonly enforcesExactResource: boolean;
116
+ /**
117
+ * True when an approval is bound to the exact CONTENT of the action, not just
118
+ * its resource — so approving one edit to a file never authorizes a DIFFERENT
119
+ * edit to the SAME file. The Cursor grant binds (category, path, contentDigest)
120
+ * (`true`); the deep-agent gate relies on checkpoint replay for sameness, so
121
+ * content isolation is not a property of that gate (`false`).
122
+ */
123
+ readonly enforcesExactContent: boolean;
124
+ /**
125
+ * True when the substrate honors a run-lifetime CLASS lease: an APPROVE_ALL on
126
+ * one action auto-approves later actions of the SAME class (built-in category)
127
+ * for the rest of the run, while a DIFFERENT class stays gated. Both production
128
+ * substrates enforce this (the deep-agent gate clears leased categories; the
129
+ * Cursor hook reads `leasedCategories` from its state file), so both set `true`
130
+ * and implement {@link GatewaySubstrate.authorizeUnderClassLease}.
131
+ */
132
+ readonly appliesRunLifetimeLease: boolean;
133
+ /**
134
+ * True when the substrate attaches authorization provenance
135
+ * (approval_policy_source) at the gate, so a withheld action's
136
+ * {@link GatewayOutcome.policySource} is populated. The deep-agent gate decides
137
+ * and stamps the source at interrupt time (`true`); the Cursor substrate's
138
+ * provenance is a reconstruction-time projection over the persisted tool call,
139
+ * not a property of the hook's deny decision, so it is `false` here and is
140
+ * covered instead by the message-translator and corpus suites.
141
+ */
142
+ readonly surfacesGatePolicySource: boolean;
143
+ }
144
+
145
+ /**
146
+ * A real enforcement substrate, wired to its production code. Adapters translate
147
+ * abstract actions/decisions into substrate-specific drives and report a uniform
148
+ * {@link GatewayOutcome}; they never reimplement enforcement logic.
149
+ */
150
+ export interface GatewaySubstrate {
151
+ /** Stable substrate name, used in suite titles and diagnostics. */
152
+ readonly name: string;
153
+ /** Honest per-substrate capability flags. */
154
+ readonly capabilities: SubstrateCapabilities;
155
+ /**
156
+ * Whether this substrate can run in the current environment (e.g. the Cursor
157
+ * deny-oracle needs `bash`). The contract skips its suite when `false`.
158
+ */
159
+ readonly available: boolean;
160
+ /**
161
+ * Put a single proposed action through the substrate under a decision and
162
+ * report what happened.
163
+ */
164
+ authorize(action: ProposedAction, decision: GatewayDecision): Promise<GatewayOutcome>;
165
+ /**
166
+ * Approve `granted`, then probe `probe` against that standing authorization —
167
+ * the lease-isolation drive. Implemented only when
168
+ * {@link SubstrateCapabilities.enforcesExactResource} is true.
169
+ */
170
+ authorizeAfterGrant?(granted: ProposedAction, probe: ProposedAction): Promise<GatewayOutcome>;
171
+ /**
172
+ * Grant a run-lifetime CLASS lease by choosing APPROVE_ALL on `leased`, then
173
+ * probe `probe` against that standing lease — the scoped-lease drive that
174
+ * proves "approving all of class A never auto-approves class B." `leased` must
175
+ * be a gated built-in (write/shell/delete). Implemented only when
176
+ * {@link SubstrateCapabilities.appliesRunLifetimeLease} is true.
177
+ */
178
+ authorizeUnderClassLease?(leased: ProposedAction, probe: ProposedAction): Promise<GatewayOutcome>;
179
+ }