@elie-laloum/outpost 11.1.2 → 11.2.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 (596) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +2 -1
  3. package/SECURITY.md +5 -1
  4. package/dist/adapters/agents/antigravity/antigravity-adapter.js +9 -0
  5. package/dist/adapters/agents/antigravity/antigravity-adapter.js.map +1 -1
  6. package/dist/adapters/agents/antigravity/antigravity-hook-plan.d.ts +3 -0
  7. package/dist/adapters/agents/antigravity/antigravity-hook-plan.js +39 -0
  8. package/dist/adapters/agents/antigravity/antigravity-hook-plan.js.map +1 -0
  9. package/dist/adapters/agents/antigravity/antigravity-hooks.constants.d.ts +3 -0
  10. package/dist/adapters/agents/antigravity/antigravity-hooks.constants.js +394 -0
  11. package/dist/adapters/agents/antigravity/antigravity-hooks.constants.js.map +1 -0
  12. package/dist/adapters/agents/antigravity/antigravity-hooks.d.ts +2 -0
  13. package/dist/adapters/agents/antigravity/antigravity-hooks.js +6 -0
  14. package/dist/adapters/agents/antigravity/antigravity-hooks.js.map +1 -0
  15. package/dist/adapters/agents/antigravity/antigravity-hooks.types.d.ts +114 -0
  16. package/dist/adapters/agents/antigravity/antigravity-hooks.types.js +2 -0
  17. package/dist/adapters/agents/antigravity/antigravity-hooks.types.js.map +1 -0
  18. package/dist/adapters/agents/antigravity/antigravity.types.d.ts +1 -0
  19. package/dist/adapters/agents/antigravity/antigravity.types.js.map +1 -1
  20. package/dist/adapters/agents/authentication.js +1 -0
  21. package/dist/adapters/agents/authentication.js.map +1 -1
  22. package/dist/adapters/agents/claude/claude-adapter.js +15 -0
  23. package/dist/adapters/agents/claude/claude-adapter.js.map +1 -1
  24. package/dist/adapters/agents/claude/claude-hook-plan.constants.d.ts +1 -0
  25. package/dist/adapters/agents/claude/claude-hook-plan.constants.js +20 -0
  26. package/dist/adapters/agents/claude/claude-hook-plan.constants.js.map +1 -0
  27. package/dist/adapters/agents/claude/claude-hook-plan.d.ts +4 -0
  28. package/dist/adapters/agents/claude/claude-hook-plan.js +60 -0
  29. package/dist/adapters/agents/claude/claude-hook-plan.js.map +1 -0
  30. package/dist/adapters/agents/claude/claude-hooks.constants.d.ts +3 -0
  31. package/dist/adapters/agents/claude/claude-hooks.constants.js +2968 -0
  32. package/dist/adapters/agents/claude/claude-hooks.constants.js.map +1 -0
  33. package/dist/adapters/agents/claude/claude-hooks.d.ts +2 -0
  34. package/dist/adapters/agents/claude/claude-hooks.js +6 -0
  35. package/dist/adapters/agents/claude/claude-hooks.js.map +1 -0
  36. package/dist/adapters/agents/claude/claude-hooks.types.d.ts +900 -0
  37. package/dist/adapters/agents/claude/claude-hooks.types.js +2 -0
  38. package/dist/adapters/agents/claude/claude-hooks.types.js.map +1 -0
  39. package/dist/adapters/agents/claude/claude-profile.d.ts +1 -0
  40. package/dist/adapters/agents/claude/claude-profile.js +23 -11
  41. package/dist/adapters/agents/claude/claude-profile.js.map +1 -1
  42. package/dist/adapters/agents/codex/codex-adapter.js +17 -1
  43. package/dist/adapters/agents/codex/codex-adapter.js.map +1 -1
  44. package/dist/adapters/agents/codex/codex-authentication.js +1 -0
  45. package/dist/adapters/agents/codex/codex-authentication.js.map +1 -1
  46. package/dist/adapters/agents/codex/codex-conversations.constants.d.ts +1 -0
  47. package/dist/adapters/agents/codex/codex-conversations.constants.js +2 -0
  48. package/dist/adapters/agents/codex/codex-conversations.constants.js.map +1 -0
  49. package/dist/adapters/agents/codex/codex-conversations.js +8 -5
  50. package/dist/adapters/agents/codex/codex-conversations.js.map +1 -1
  51. package/dist/adapters/agents/codex/codex-hook-plan.d.ts +3 -0
  52. package/dist/adapters/agents/codex/codex-hook-plan.js +72 -0
  53. package/dist/adapters/agents/codex/codex-hook-plan.js.map +1 -0
  54. package/dist/adapters/agents/codex/codex-hooks.constants.d.ts +3 -0
  55. package/dist/adapters/agents/codex/codex-hooks.constants.js +942 -0
  56. package/dist/adapters/agents/codex/codex-hooks.constants.js.map +1 -0
  57. package/dist/adapters/agents/codex/codex-hooks.d.ts +2 -0
  58. package/dist/adapters/agents/codex/codex-hooks.js +6 -0
  59. package/dist/adapters/agents/codex/codex-hooks.js.map +1 -0
  60. package/dist/adapters/agents/codex/codex-hooks.types.d.ts +255 -0
  61. package/dist/adapters/agents/codex/codex-hooks.types.js +2 -0
  62. package/dist/adapters/agents/codex/codex-hooks.types.js.map +1 -0
  63. package/dist/adapters/agents/codex/codex-provider.js +2 -2
  64. package/dist/adapters/agents/codex/codex-provider.js.map +1 -1
  65. package/dist/adapters/agents/copilot/copilot-adapter.js +14 -0
  66. package/dist/adapters/agents/copilot/copilot-adapter.js.map +1 -1
  67. package/dist/adapters/agents/copilot/copilot-hook-plan.d.ts +3 -0
  68. package/dist/adapters/agents/copilot/copilot-hook-plan.js +26 -0
  69. package/dist/adapters/agents/copilot/copilot-hook-plan.js.map +1 -0
  70. package/dist/adapters/agents/copilot/copilot-hooks.constants.d.ts +3 -0
  71. package/dist/adapters/agents/copilot/copilot-hooks.constants.js +528 -0
  72. package/dist/adapters/agents/copilot/copilot-hooks.constants.js.map +1 -0
  73. package/dist/adapters/agents/copilot/copilot-hooks.d.ts +2 -0
  74. package/dist/adapters/agents/copilot/copilot-hooks.js +6 -0
  75. package/dist/adapters/agents/copilot/copilot-hooks.js.map +1 -0
  76. package/dist/adapters/agents/copilot/copilot-hooks.types.d.ts +166 -0
  77. package/dist/adapters/agents/copilot/copilot-hooks.types.js +2 -0
  78. package/dist/adapters/agents/copilot/copilot-hooks.types.js.map +1 -0
  79. package/dist/adapters/agents/copilot/copilot.types.d.ts +1 -0
  80. package/dist/adapters/agents/copilot/copilot.types.js.map +1 -1
  81. package/dist/adapters/agents/hook-groups.d.ts +2 -0
  82. package/dist/adapters/agents/hook-groups.js +17 -0
  83. package/dist/adapters/agents/hook-groups.js.map +1 -0
  84. package/dist/adapters/agents/kimi/kimi-adapter.js +16 -1
  85. package/dist/adapters/agents/kimi/kimi-adapter.js.map +1 -1
  86. package/dist/adapters/agents/kimi/kimi-hook-plan.d.ts +3 -0
  87. package/dist/adapters/agents/kimi/kimi-hook-plan.js +33 -0
  88. package/dist/adapters/agents/kimi/kimi-hook-plan.js.map +1 -0
  89. package/dist/adapters/agents/kimi/kimi-hooks.constants.d.ts +3 -0
  90. package/dist/adapters/agents/kimi/kimi-hooks.constants.js +747 -0
  91. package/dist/adapters/agents/kimi/kimi-hooks.constants.js.map +1 -0
  92. package/dist/adapters/agents/kimi/kimi-hooks.d.ts +2 -0
  93. package/dist/adapters/agents/kimi/kimi-hooks.js +6 -0
  94. package/dist/adapters/agents/kimi/kimi-hooks.js.map +1 -0
  95. package/dist/adapters/agents/kimi/kimi-hooks.types.d.ts +226 -0
  96. package/dist/adapters/agents/kimi/kimi-hooks.types.js +2 -0
  97. package/dist/adapters/agents/kimi/kimi-hooks.types.js.map +1 -0
  98. package/dist/adapters/agents/kimi/kimi-request.js +2 -0
  99. package/dist/adapters/agents/kimi/kimi-request.js.map +1 -1
  100. package/dist/adapters/agents/kimi/kimi.constants.d.ts +1 -0
  101. package/dist/adapters/agents/kimi/kimi.constants.js +1 -0
  102. package/dist/adapters/agents/kimi/kimi.constants.js.map +1 -1
  103. package/dist/adapters/agents/kimi/kimi.types.d.ts +1 -0
  104. package/dist/adapters/agents/kimi/kimi.types.js.map +1 -1
  105. package/dist/adapters/agents/native-hooks.d.ts +4 -0
  106. package/dist/adapters/agents/native-hooks.js +63 -0
  107. package/dist/adapters/agents/native-hooks.js.map +1 -0
  108. package/dist/adapters/agents/native-hooks.types.d.ts +22 -0
  109. package/dist/adapters/agents/native-hooks.types.js +2 -0
  110. package/dist/adapters/agents/native-hooks.types.js.map +1 -0
  111. package/dist/adapters/agents/settings.types.d.ts +2 -0
  112. package/dist/adapters/agents/settings.types.js.map +1 -1
  113. package/dist/adapters/decisions/system-one-provider.constants.d.ts +1 -1
  114. package/dist/adapters/decisions/system-one-provider.constants.js +1 -1
  115. package/dist/adapters/decisions/system-one-provider.constants.js.map +1 -1
  116. package/dist/adapters/decisions/system-one-provider.js +8 -21
  117. package/dist/adapters/decisions/system-one-provider.js.map +1 -1
  118. package/dist/adapters/models/anthropic-model-provider.constants.d.ts +1 -0
  119. package/dist/adapters/models/anthropic-model-provider.constants.js +4 -0
  120. package/dist/adapters/models/anthropic-model-provider.constants.js.map +1 -1
  121. package/dist/adapters/models/anthropic-model-provider.js +7 -1
  122. package/dist/adapters/models/anthropic-model-provider.js.map +1 -1
  123. package/dist/adapters/models/http-failure.d.ts +3 -0
  124. package/dist/adapters/models/http-failure.js +43 -0
  125. package/dist/adapters/models/http-failure.js.map +1 -0
  126. package/dist/adapters/models/http-provider.js +14 -20
  127. package/dist/adapters/models/http-provider.js.map +1 -1
  128. package/dist/adapters/models/model-protocol.types.d.ts +1 -0
  129. package/dist/adapters/models/model-protocol.types.js.map +1 -1
  130. package/dist/adapters/models/model.constants.d.ts +2 -0
  131. package/dist/adapters/models/model.constants.js +17 -0
  132. package/dist/adapters/models/model.constants.js.map +1 -1
  133. package/dist/adapters/models/openai-chat-stream.js +3 -1
  134. package/dist/adapters/models/openai-chat-stream.js.map +1 -1
  135. package/dist/adapters/models/openai-responses.js +4 -0
  136. package/dist/adapters/models/openai-responses.js.map +1 -1
  137. package/dist/adapters/secrets/aws.js +5 -4
  138. package/dist/adapters/secrets/aws.js.map +1 -1
  139. package/dist/adapters/secrets/azure.constants.d.ts +1 -0
  140. package/dist/adapters/secrets/azure.constants.js +2 -0
  141. package/dist/adapters/secrets/azure.constants.js.map +1 -0
  142. package/dist/adapters/secrets/azure.js +2 -1
  143. package/dist/adapters/secrets/azure.js.map +1 -1
  144. package/dist/adapters/secrets/gcp.constants.d.ts +1 -0
  145. package/dist/adapters/secrets/gcp.constants.js +2 -0
  146. package/dist/adapters/secrets/gcp.constants.js.map +1 -0
  147. package/dist/adapters/secrets/gcp.js +11 -9
  148. package/dist/adapters/secrets/gcp.js.map +1 -1
  149. package/dist/adapters/secrets/infisical.js +3 -2
  150. package/dist/adapters/secrets/infisical.js.map +1 -1
  151. package/dist/adapters/secrets/onepassword.constants.d.ts +1 -0
  152. package/dist/adapters/secrets/onepassword.constants.js +2 -0
  153. package/dist/adapters/secrets/onepassword.constants.js.map +1 -0
  154. package/dist/adapters/secrets/onepassword.js +2 -1
  155. package/dist/adapters/secrets/onepassword.js.map +1 -1
  156. package/dist/adapters/secrets/secret-source.js +3 -1
  157. package/dist/adapters/secrets/secret-source.js.map +1 -1
  158. package/dist/adapters/secrets/vault-response.js +5 -5
  159. package/dist/adapters/secrets/vault-response.js.map +1 -1
  160. package/dist/adapters/secrets/vault.js +4 -3
  161. package/dist/adapters/secrets/vault.js.map +1 -1
  162. package/dist/adapters/testing/scripted-agent.d.ts +3 -1
  163. package/dist/adapters/testing/scripted-agent.js +3 -1
  164. package/dist/adapters/testing/scripted-agent.js.map +1 -1
  165. package/dist/adapters/tools/edit-tools.js +3 -3
  166. package/dist/adapters/tools/edit-tools.js.map +1 -1
  167. package/dist/adapters/tools/git-tools.js +16 -1
  168. package/dist/adapters/tools/git-tools.js.map +1 -1
  169. package/dist/adapters/tools/mcp-launcher.constants.d.ts +1 -1
  170. package/dist/adapters/tools/mcp-launcher.constants.js +29 -7
  171. package/dist/adapters/tools/mcp-launcher.constants.js.map +1 -1
  172. package/dist/adapters/tools/mcp-resources.js +5 -1
  173. package/dist/adapters/tools/mcp-resources.js.map +1 -1
  174. package/dist/adapters/tools/mcp.constants.d.ts +0 -6
  175. package/dist/adapters/tools/mcp.constants.js +0 -6
  176. package/dist/adapters/tools/mcp.constants.js.map +1 -1
  177. package/dist/adapters/tools/mcp.js +1 -0
  178. package/dist/adapters/tools/mcp.js.map +1 -1
  179. package/dist/adapters/tools/sandbox-files.constants.d.ts +1 -0
  180. package/dist/adapters/tools/sandbox-files.constants.js +17 -0
  181. package/dist/adapters/tools/sandbox-files.constants.js.map +1 -0
  182. package/dist/adapters/tools/sandbox-files.d.ts +1 -0
  183. package/dist/adapters/tools/sandbox-files.js +14 -1
  184. package/dist/adapters/tools/sandbox-files.js.map +1 -1
  185. package/dist/adapters/tools/tools.constants.js +1 -0
  186. package/dist/adapters/tools/tools.constants.js.map +1 -1
  187. package/dist/adapters/triggers/github-webhook.js +14 -11
  188. package/dist/adapters/triggers/github-webhook.js.map +1 -1
  189. package/dist/adapters/triggers/gitlab-webhook.js +16 -13
  190. package/dist/adapters/triggers/gitlab-webhook.js.map +1 -1
  191. package/dist/adapters/triggers/slack-request.js +10 -7
  192. package/dist/adapters/triggers/slack-request.js.map +1 -1
  193. package/dist/adapters/triggers/standard-webhook.js +10 -7
  194. package/dist/adapters/triggers/standard-webhook.js.map +1 -1
  195. package/dist/application/agent-authentication.js +4 -1
  196. package/dist/application/agent-authentication.js.map +1 -1
  197. package/dist/application/agent-turn.js +20 -5
  198. package/dist/application/agent-turn.js.map +1 -1
  199. package/dist/application/cli-hooks.constants.d.ts +2 -0
  200. package/dist/application/cli-hooks.constants.js +69 -0
  201. package/dist/application/cli-hooks.constants.js.map +1 -0
  202. package/dist/application/cli-hooks.d.ts +3 -0
  203. package/dist/application/cli-hooks.js +168 -0
  204. package/dist/application/cli-hooks.js.map +1 -0
  205. package/dist/application/cli-steering.js +33 -2
  206. package/dist/application/cli-steering.js.map +1 -1
  207. package/dist/application/custom-turn.js +10 -1
  208. package/dist/application/custom-turn.js.map +1 -1
  209. package/dist/application/diagnostic-probe.d.ts +1 -1
  210. package/dist/application/diagnostic-probe.js +7 -3
  211. package/dist/application/diagnostic-probe.js.map +1 -1
  212. package/dist/application/dispatch-observation.js +4 -0
  213. package/dist/application/dispatch-observation.js.map +1 -1
  214. package/dist/application/doctor-agent.d.ts +1 -1
  215. package/dist/application/doctor-agent.js +2 -2
  216. package/dist/application/doctor-agent.js.map +1 -1
  217. package/dist/application/doctor-deadline.d.ts +1 -0
  218. package/dist/application/doctor-deadline.js +10 -0
  219. package/dist/application/doctor-deadline.js.map +1 -0
  220. package/dist/application/doctor-image.js +9 -7
  221. package/dist/application/doctor-image.js.map +1 -1
  222. package/dist/application/doctor-sandbox.js +4 -4
  223. package/dist/application/doctor-sandbox.js.map +1 -1
  224. package/dist/application/doctor.constants.d.ts +2 -0
  225. package/dist/application/doctor.constants.js +2 -0
  226. package/dist/application/doctor.constants.js.map +1 -1
  227. package/dist/application/doctor.js +12 -8
  228. package/dist/application/doctor.js.map +1 -1
  229. package/dist/application/doctor.types.d.ts +2 -0
  230. package/dist/application/doctor.types.js.map +1 -1
  231. package/dist/application/file-sandbox.js +49 -11
  232. package/dist/application/file-sandbox.js.map +1 -1
  233. package/dist/application/harness-context.js +4 -2
  234. package/dist/application/harness-context.js.map +1 -1
  235. package/dist/application/harness-hook-transforms.d.ts +8 -0
  236. package/dist/application/harness-hook-transforms.js +115 -0
  237. package/dist/application/harness-hook-transforms.js.map +1 -0
  238. package/dist/application/harness-hooks.d.ts +5 -3
  239. package/dist/application/harness-hooks.js +37 -50
  240. package/dist/application/harness-hooks.js.map +1 -1
  241. package/dist/application/harness-loop.d.ts +2 -1
  242. package/dist/application/harness-loop.js +95 -21
  243. package/dist/application/harness-loop.js.map +1 -1
  244. package/dist/application/harness-subagent.d.ts +1 -1
  245. package/dist/application/harness-subagent.js +63 -12
  246. package/dist/application/harness-subagent.js.map +1 -1
  247. package/dist/application/harness.types.d.ts +3 -0
  248. package/dist/application/harness.types.js.map +1 -1
  249. package/dist/application/hook-invocation.d.ts +4 -0
  250. package/dist/application/hook-invocation.js +59 -0
  251. package/dist/application/hook-invocation.js.map +1 -0
  252. package/dist/application/queue-worker.js +51 -9
  253. package/dist/application/queue-worker.js.map +1 -1
  254. package/dist/application/queue-worker.types.d.ts +1 -0
  255. package/dist/application/queue-worker.types.js.map +1 -1
  256. package/dist/application/recipes/native-schemas.constants.js +1439 -218
  257. package/dist/application/recipes/native-schemas.constants.js.map +1 -1
  258. package/dist/application/recovery-inspection.js +4 -1
  259. package/dist/application/recovery-inspection.js.map +1 -1
  260. package/dist/application/remote-download.js +15 -5
  261. package/dist/application/remote-download.js.map +1 -1
  262. package/dist/application/remote-paths.d.ts +1 -0
  263. package/dist/application/remote-paths.js +7 -0
  264. package/dist/application/remote-paths.js.map +1 -0
  265. package/dist/application/remote-validation.js +40 -9
  266. package/dist/application/remote-validation.js.map +1 -1
  267. package/dist/application/remote-workspace.js +17 -2
  268. package/dist/application/remote-workspace.js.map +1 -1
  269. package/dist/application/repetition-watchdog.js +3 -1
  270. package/dist/application/repetition-watchdog.js.map +1 -1
  271. package/dist/application/sandbox-attach.js +38 -9
  272. package/dist/application/sandbox-attach.js.map +1 -1
  273. package/dist/application/sandbox-dispatch.js +7 -1
  274. package/dist/application/sandbox-dispatch.js.map +1 -1
  275. package/dist/application/schedules.js +2 -0
  276. package/dist/application/schedules.js.map +1 -1
  277. package/dist/application/secrets.js +4 -0
  278. package/dist/application/secrets.js.map +1 -1
  279. package/dist/application/tool-execution.js +28 -8
  280. package/dist/application/tool-execution.js.map +1 -1
  281. package/dist/application/transport-retention.js +3 -3
  282. package/dist/application/transport-retention.js.map +1 -1
  283. package/dist/application/workspace-fingerprint.d.ts +1 -0
  284. package/dist/application/workspace-fingerprint.js +19 -4
  285. package/dist/application/workspace-fingerprint.js.map +1 -1
  286. package/dist/cli/doctor-command.js +12 -2
  287. package/dist/cli/doctor-command.js.map +1 -1
  288. package/dist/cli/image.js +2 -0
  289. package/dist/cli/image.js.map +1 -1
  290. package/dist/cli/main.constants.d.ts +14 -2
  291. package/dist/cli/main.constants.js +27 -1
  292. package/dist/cli/main.constants.js.map +1 -1
  293. package/dist/cli/project-settings.js +7 -1
  294. package/dist/cli/project-settings.js.map +1 -1
  295. package/dist/cli/recipe-yaml.js +8 -0
  296. package/dist/cli/recipe-yaml.js.map +1 -1
  297. package/dist/cli/recovery-command.js +3 -0
  298. package/dist/cli/recovery-command.js.map +1 -1
  299. package/dist/cli/recovery-unlock-command.d.ts +2 -0
  300. package/dist/cli/recovery-unlock-command.js +19 -0
  301. package/dist/cli/recovery-unlock-command.js.map +1 -0
  302. package/dist/cli/scaffold-files.js +4 -1
  303. package/dist/cli/scaffold-files.js.map +1 -1
  304. package/dist/cli/scaffold-validation.js +3 -0
  305. package/dist/cli/scaffold-validation.js.map +1 -1
  306. package/dist/cli/scaffold.js +2 -0
  307. package/dist/cli/scaffold.js.map +1 -1
  308. package/dist/domain/agent-hook.types.d.ts +28 -0
  309. package/dist/domain/agent-hook.types.js +2 -0
  310. package/dist/domain/agent-hook.types.js.map +1 -0
  311. package/dist/domain/agent-profile-hooks.d.ts +2 -0
  312. package/dist/domain/agent-profile-hooks.js +52 -0
  313. package/dist/domain/agent-profile-hooks.js.map +1 -0
  314. package/dist/domain/agent-profile-permissions.js +11 -1
  315. package/dist/domain/agent-profile-permissions.js.map +1 -1
  316. package/dist/domain/agent-profile.constants.d.ts +1 -0
  317. package/dist/domain/agent-profile.constants.js +9 -0
  318. package/dist/domain/agent-profile.constants.js.map +1 -1
  319. package/dist/domain/agent-profile.js +6 -1
  320. package/dist/domain/agent-profile.js.map +1 -1
  321. package/dist/domain/agent-profile.types.d.ts +11 -0
  322. package/dist/domain/agent-profile.types.js.map +1 -1
  323. package/dist/domain/agent.types.d.ts +2 -0
  324. package/dist/domain/agent.types.js.map +1 -1
  325. package/dist/domain/container-image.d.ts +1 -0
  326. package/dist/domain/container-image.js +8 -0
  327. package/dist/domain/container-image.js.map +1 -0
  328. package/dist/domain/context.constants.d.ts +1 -0
  329. package/dist/domain/context.constants.js +1 -0
  330. package/dist/domain/context.constants.js.map +1 -1
  331. package/dist/domain/context.js +6 -1
  332. package/dist/domain/context.js.map +1 -1
  333. package/dist/domain/egress.js +6 -1
  334. package/dist/domain/egress.js.map +1 -1
  335. package/dist/domain/glob.js +1 -1
  336. package/dist/domain/glob.js.map +1 -1
  337. package/dist/domain/harness.js +4 -1
  338. package/dist/domain/harness.js.map +1 -1
  339. package/dist/domain/hook-command.d.ts +2 -0
  340. package/dist/domain/hook-command.js +26 -0
  341. package/dist/domain/hook-command.js.map +1 -0
  342. package/dist/domain/hook-command.types.d.ts +7 -0
  343. package/dist/domain/hook-command.types.js +2 -0
  344. package/dist/domain/hook-command.types.js.map +1 -0
  345. package/dist/domain/hook-decision.d.ts +2 -0
  346. package/dist/domain/hook-decision.js +38 -0
  347. package/dist/domain/hook-decision.js.map +1 -0
  348. package/dist/domain/hook.constants.d.ts +3 -0
  349. package/dist/domain/hook.constants.js +31 -1
  350. package/dist/domain/hook.constants.js.map +1 -1
  351. package/dist/domain/hook.js +49 -5
  352. package/dist/domain/hook.js.map +1 -1
  353. package/dist/domain/hook.types.d.ts +82 -5
  354. package/dist/domain/hook.types.js.map +1 -1
  355. package/dist/domain/mcp-server.constants.d.ts +6 -0
  356. package/dist/domain/mcp-server.constants.js +6 -0
  357. package/dist/domain/mcp-server.constants.js.map +1 -1
  358. package/dist/domain/mcp-server.js +1 -0
  359. package/dist/domain/mcp-server.js.map +1 -1
  360. package/dist/domain/permissions.types.d.ts +1 -0
  361. package/dist/domain/permissions.types.js.map +1 -1
  362. package/dist/domain/pricing.js +4 -2
  363. package/dist/domain/pricing.js.map +1 -1
  364. package/dist/domain/redaction.js +6 -3
  365. package/dist/domain/redaction.js.map +1 -1
  366. package/dist/domain/secret-fault.d.ts +4 -0
  367. package/dist/domain/secret-fault.js +11 -0
  368. package/dist/domain/secret-fault.js.map +1 -0
  369. package/dist/domain/secrets.js +3 -2
  370. package/dist/domain/secrets.js.map +1 -1
  371. package/dist/domain/steering.js +2 -0
  372. package/dist/domain/steering.js.map +1 -1
  373. package/dist/domain/steering.types.d.ts +1 -1
  374. package/dist/domain/steering.types.js.map +1 -1
  375. package/dist/domain/transport.js +5 -1
  376. package/dist/domain/transport.js.map +1 -1
  377. package/dist/domain/trigger-payload.d.ts +4 -0
  378. package/dist/domain/trigger-payload.js +15 -0
  379. package/dist/domain/trigger-payload.js.map +1 -0
  380. package/dist/domain/workflow/checkpoint.js +19 -2
  381. package/dist/domain/workflow/checkpoint.js.map +1 -1
  382. package/dist/domain/workflow/retry.constants.d.ts +1 -0
  383. package/dist/domain/workflow/retry.constants.js +1 -0
  384. package/dist/domain/workflow/retry.constants.js.map +1 -1
  385. package/dist/domain/workflow/retry.js +6 -2
  386. package/dist/domain/workflow/retry.js.map +1 -1
  387. package/dist/domain/workflow/task-runner.js +5 -3
  388. package/dist/domain/workflow/task-runner.js.map +1 -1
  389. package/dist/domain/workflow/workflow-state.js +2 -1
  390. package/dist/domain/workflow/workflow-state.js.map +1 -1
  391. package/dist/domain/workflow.types.d.ts +1 -0
  392. package/dist/domain/workflow.types.js.map +1 -1
  393. package/dist/index.d.ts +15 -1
  394. package/dist/index.js +6 -0
  395. package/dist/index.js.map +1 -1
  396. package/dist/infrastructure/conversations/transcript-store.js +32 -5
  397. package/dist/infrastructure/conversations/transcript-store.js.map +1 -1
  398. package/dist/infrastructure/files.d.ts +1 -0
  399. package/dist/infrastructure/files.js +8 -4
  400. package/dist/infrastructure/files.js.map +1 -1
  401. package/dist/infrastructure/git/integration-candidate.js +3 -2
  402. package/dist/infrastructure/git/integration-candidate.js.map +1 -1
  403. package/dist/infrastructure/git/lock-recovery.d.ts +3 -0
  404. package/dist/infrastructure/git/lock-recovery.js +58 -0
  405. package/dist/infrastructure/git/lock-recovery.js.map +1 -0
  406. package/dist/infrastructure/git/lock-recovery.types.d.ts +11 -0
  407. package/dist/infrastructure/git/lock-recovery.types.js +2 -0
  408. package/dist/infrastructure/git/lock-recovery.types.js.map +1 -0
  409. package/dist/infrastructure/git/managed-worktree.js +5 -8
  410. package/dist/infrastructure/git/managed-worktree.js.map +1 -1
  411. package/dist/infrastructure/git/verification-command.js +35 -30
  412. package/dist/infrastructure/git/verification-command.js.map +1 -1
  413. package/dist/infrastructure/git/workspace-lease.js +12 -5
  414. package/dist/infrastructure/git/workspace-lease.js.map +1 -1
  415. package/dist/infrastructure/hook-configuration.constants.d.ts +1 -0
  416. package/dist/infrastructure/hook-configuration.constants.js +59 -0
  417. package/dist/infrastructure/hook-configuration.constants.js.map +1 -0
  418. package/dist/infrastructure/hook-configuration.d.ts +3 -0
  419. package/dist/infrastructure/hook-configuration.js +16 -0
  420. package/dist/infrastructure/hook-configuration.js.map +1 -0
  421. package/dist/infrastructure/hook-configuration.types.d.ts +14 -0
  422. package/dist/infrastructure/hook-configuration.types.js +2 -0
  423. package/dist/infrastructure/hook-configuration.types.js.map +1 -0
  424. package/dist/infrastructure/http-transport.constants.d.ts +1 -0
  425. package/dist/infrastructure/http-transport.constants.js +14 -0
  426. package/dist/infrastructure/http-transport.constants.js.map +1 -0
  427. package/dist/infrastructure/http-transport.d.ts +7 -0
  428. package/dist/infrastructure/http-transport.js +19 -0
  429. package/dist/infrastructure/http-transport.js.map +1 -0
  430. package/dist/infrastructure/input-failure.d.ts +4 -0
  431. package/dist/infrastructure/input-failure.js +14 -0
  432. package/dist/infrastructure/input-failure.js.map +1 -0
  433. package/dist/infrastructure/inspection-file.d.ts +1 -0
  434. package/dist/infrastructure/inspection-file.js +8 -2
  435. package/dist/infrastructure/inspection-file.js.map +1 -1
  436. package/dist/infrastructure/local-transport.js +16 -11
  437. package/dist/infrastructure/local-transport.js.map +1 -1
  438. package/dist/infrastructure/process.constants.d.ts +1 -0
  439. package/dist/infrastructure/process.constants.js +1 -0
  440. package/dist/infrastructure/process.constants.js.map +1 -1
  441. package/dist/infrastructure/process.d.ts +1 -0
  442. package/dist/infrastructure/process.js +48 -8
  443. package/dist/infrastructure/process.js.map +1 -1
  444. package/dist/infrastructure/reservation-ledger.d.ts +2 -0
  445. package/dist/infrastructure/reservation-ledger.js +84 -0
  446. package/dist/infrastructure/reservation-ledger.js.map +1 -0
  447. package/dist/infrastructure/reservation-recovery.d.ts +4 -0
  448. package/dist/infrastructure/reservation-recovery.js +28 -0
  449. package/dist/infrastructure/reservation-recovery.js.map +1 -0
  450. package/dist/infrastructure/s3-tombstones.d.ts +3 -0
  451. package/dist/infrastructure/s3-tombstones.js +46 -0
  452. package/dist/infrastructure/s3-tombstones.js.map +1 -0
  453. package/dist/infrastructure/s3-tombstones.types.d.ts +6 -0
  454. package/dist/infrastructure/s3-tombstones.types.js +2 -0
  455. package/dist/infrastructure/s3-tombstones.types.js.map +1 -0
  456. package/dist/infrastructure/s3-transport.d.ts +2 -0
  457. package/dist/infrastructure/s3-transport.js +4 -2
  458. package/dist/infrastructure/s3-transport.js.map +1 -1
  459. package/dist/infrastructure/sse.js +6 -3
  460. package/dist/infrastructure/sse.js.map +1 -1
  461. package/dist/infrastructure/task-queue-bullmq-connection.d.ts +3 -0
  462. package/dist/infrastructure/task-queue-bullmq-connection.js +32 -0
  463. package/dist/infrastructure/task-queue-bullmq-connection.js.map +1 -0
  464. package/dist/infrastructure/task-queue-bullmq-connection.types.d.ts +8 -0
  465. package/dist/infrastructure/task-queue-bullmq-connection.types.js +2 -0
  466. package/dist/infrastructure/task-queue-bullmq-connection.types.js.map +1 -0
  467. package/dist/infrastructure/task-queue-bullmq-resources.js +21 -15
  468. package/dist/infrastructure/task-queue-bullmq-resources.js.map +1 -1
  469. package/dist/infrastructure/task-queue-bullmq-state.d.ts +1 -0
  470. package/dist/infrastructure/task-queue-bullmq-state.js +1 -0
  471. package/dist/infrastructure/task-queue-bullmq-state.js.map +1 -1
  472. package/dist/infrastructure/task-queue-bullmq.constants.d.ts +2 -1
  473. package/dist/infrastructure/task-queue-bullmq.constants.js +2 -1
  474. package/dist/infrastructure/task-queue-bullmq.constants.js.map +1 -1
  475. package/dist/infrastructure/task-queue-bullmq.js +10 -1
  476. package/dist/infrastructure/task-queue-bullmq.js.map +1 -1
  477. package/dist/infrastructure/task-queue.js +13 -4
  478. package/dist/infrastructure/task-queue.js.map +1 -1
  479. package/dist/infrastructure/transfer-copy.d.ts +1 -0
  480. package/dist/infrastructure/transfer-copy.js +16 -1
  481. package/dist/infrastructure/transfer-copy.js.map +1 -1
  482. package/dist/infrastructure/transport-archive.d.ts +3 -3
  483. package/dist/infrastructure/transport-archive.js +71 -48
  484. package/dist/infrastructure/transport-archive.js.map +1 -1
  485. package/dist/infrastructure/transport-archive.types.d.ts +3 -0
  486. package/dist/infrastructure/transport-archive.types.js.map +1 -1
  487. package/dist/infrastructure/transport-checkpoint.js +14 -4
  488. package/dist/infrastructure/transport-checkpoint.js.map +1 -1
  489. package/dist/infrastructure/transport-envelope.d.ts +1 -0
  490. package/dist/infrastructure/transport-envelope.js +22 -5
  491. package/dist/infrastructure/transport-envelope.js.map +1 -1
  492. package/dist/infrastructure/transport-reservations.constants.d.ts +2 -0
  493. package/dist/infrastructure/transport-reservations.constants.js +3 -0
  494. package/dist/infrastructure/transport-reservations.constants.js.map +1 -0
  495. package/dist/infrastructure/transport-reservations.d.ts +1 -2
  496. package/dist/infrastructure/transport-reservations.js +18 -22
  497. package/dist/infrastructure/transport-reservations.js.map +1 -1
  498. package/dist/infrastructure/transport-reservations.types.d.ts +23 -2
  499. package/dist/infrastructure/transport-reservations.types.js.map +1 -1
  500. package/dist/infrastructure/trigger-server.js +12 -9
  501. package/dist/infrastructure/trigger-server.js.map +1 -1
  502. package/dist/infrastructure/trigger-server.types.d.ts +1 -1
  503. package/dist/infrastructure/trigger-server.types.js.map +1 -1
  504. package/dist/infrastructure/workflow-checkpoint.constants.d.ts +1 -0
  505. package/dist/infrastructure/workflow-checkpoint.constants.js +1 -0
  506. package/dist/infrastructure/workflow-checkpoint.constants.js.map +1 -1
  507. package/dist/infrastructure/workspace-lock.constants.d.ts +2 -1
  508. package/dist/infrastructure/workspace-lock.constants.js +2 -1
  509. package/dist/infrastructure/workspace-lock.constants.js.map +1 -1
  510. package/dist/infrastructure/workspace-lock.js +48 -10
  511. package/dist/infrastructure/workspace-lock.js.map +1 -1
  512. package/dist/providers/cloud-cache.js +14 -9
  513. package/dist/providers/cloud-cache.js.map +1 -1
  514. package/dist/providers/cloud-cache.types.d.ts +1 -0
  515. package/dist/providers/cloud-cache.types.js.map +1 -1
  516. package/dist/providers/cloud-files.js +3 -4
  517. package/dist/providers/cloud-files.js.map +1 -1
  518. package/dist/providers/cloud-input.constants.d.ts +2 -2
  519. package/dist/providers/cloud-input.constants.js +2 -1
  520. package/dist/providers/cloud-input.constants.js.map +1 -1
  521. package/dist/providers/cloud-input.d.ts +2 -2
  522. package/dist/providers/cloud-input.js +14 -3
  523. package/dist/providers/cloud-input.js.map +1 -1
  524. package/dist/providers/container-command.js +30 -5
  525. package/dist/providers/container-command.js.map +1 -1
  526. package/dist/providers/container-egress.constants.d.ts +7 -0
  527. package/dist/providers/container-egress.constants.js +8 -0
  528. package/dist/providers/container-egress.constants.js.map +1 -0
  529. package/dist/providers/container-egress.d.ts +7 -0
  530. package/dist/providers/container-egress.js +101 -0
  531. package/dist/providers/container-egress.js.map +1 -0
  532. package/dist/providers/container-files.js +10 -11
  533. package/dist/providers/container-files.js.map +1 -1
  534. package/dist/providers/container-mounts.js +3 -0
  535. package/dist/providers/container-mounts.js.map +1 -1
  536. package/dist/providers/container-network.js +16 -1
  537. package/dist/providers/container-network.js.map +1 -1
  538. package/dist/providers/container-plan.js +7 -10
  539. package/dist/providers/container-plan.js.map +1 -1
  540. package/dist/providers/container-recovery.js +8 -3
  541. package/dist/providers/container-recovery.js.map +1 -1
  542. package/dist/providers/container.js +36 -5
  543. package/dist/providers/container.js.map +1 -1
  544. package/dist/providers/container.types.d.ts +1 -0
  545. package/dist/providers/container.types.js.map +1 -1
  546. package/dist/providers/daytona-command.constants.js +4 -1
  547. package/dist/providers/daytona-command.constants.js.map +1 -1
  548. package/dist/providers/daytona-command.js +39 -16
  549. package/dist/providers/daytona-command.js.map +1 -1
  550. package/dist/providers/daytona-files.js +5 -5
  551. package/dist/providers/daytona-files.js.map +1 -1
  552. package/dist/providers/daytona-output.js +11 -3
  553. package/dist/providers/daytona-output.js.map +1 -1
  554. package/dist/providers/daytona.js +11 -9
  555. package/dist/providers/daytona.js.map +1 -1
  556. package/dist/providers/egress/address.d.ts +3 -0
  557. package/dist/providers/egress/address.js +26 -0
  558. package/dist/providers/egress/address.js.map +1 -0
  559. package/dist/providers/egress/egress.constants.d.ts +22 -0
  560. package/dist/providers/egress/egress.constants.js +23 -0
  561. package/dist/providers/egress/egress.constants.js.map +1 -0
  562. package/dist/providers/egress/egress.types.d.ts +11 -0
  563. package/dist/providers/egress/egress.types.js +2 -0
  564. package/dist/providers/egress/egress.types.js.map +1 -0
  565. package/dist/providers/egress/firewall.d.ts +3 -0
  566. package/dist/providers/egress/firewall.js +29 -0
  567. package/dist/providers/egress/firewall.js.map +1 -0
  568. package/dist/providers/egress/main.d.ts +1 -0
  569. package/dist/providers/egress/main.js +90 -0
  570. package/dist/providers/egress/main.js.map +1 -0
  571. package/dist/providers/egress/policy.d.ts +5 -0
  572. package/dist/providers/egress/policy.js +54 -0
  573. package/dist/providers/egress/policy.js.map +1 -0
  574. package/dist/providers/egress/proxy.d.ts +2 -0
  575. package/dist/providers/egress/proxy.js +129 -0
  576. package/dist/providers/egress/proxy.js.map +1 -0
  577. package/dist/providers/egress/tls-hello.d.ts +1 -0
  578. package/dist/providers/egress/tls-hello.js +75 -0
  579. package/dist/providers/egress/tls-hello.js.map +1 -0
  580. package/dist/providers/firecracker-command.js +12 -6
  581. package/dist/providers/firecracker-command.js.map +1 -1
  582. package/dist/providers/local-files.d.ts +2 -0
  583. package/dist/providers/local-files.js +24 -0
  584. package/dist/providers/local-files.js.map +1 -0
  585. package/dist/providers/local.js +2 -20
  586. package/dist/providers/local.js.map +1 -1
  587. package/dist/providers/vercel-command.js +33 -9
  588. package/dist/providers/vercel-command.js.map +1 -1
  589. package/dist/providers/vercel.js +11 -9
  590. package/dist/providers/vercel.js.map +1 -1
  591. package/dist/testing.d.ts +1 -1
  592. package/dist/testing.js +1 -1
  593. package/dist/testing.js.map +1 -1
  594. package/package.json +2 -2
  595. package/recipe-configuration.schema.json +2164 -35
  596. package/recipe.schema.json +2512 -404
@@ -4,4 +4,5 @@ export declare function encodeObject(key: string, bytes: Uint8Array): {
4
4
  data: Buffer<ArrayBuffer>;
5
5
  };
6
6
  export declare function decodeObject(data: Uint8Array, key: string, maxBytes: number): TransportObject;
7
+ export declare function decodeObjectEntry(data: Uint8Array, key: string, fileSize: number, maxBytes: number): TransportEntry;
7
8
  export declare function readLimit(maxBytes?: number): number;
@@ -15,8 +15,27 @@ export function encodeObject(key, bytes) {
15
15
  }
16
16
  export function decodeObject(data, key, maxBytes) {
17
17
  const buffer = Buffer.from(data);
18
- const value = JSON.parse(buffer.subarray(0, transportDefaults.headerBytes).toString());
19
18
  const bytes = buffer.subarray(transportDefaults.headerBytes);
19
+ return {
20
+ ...decodeObjectEntry(buffer, key, data.byteLength, maxBytes),
21
+ bytes,
22
+ };
23
+ }
24
+ export function decodeObjectEntry(data, key, fileSize, maxBytes) {
25
+ const size = fileSize - transportDefaults.headerBytes;
26
+ if (!Number.isSafeInteger(size) ||
27
+ size < 0 ||
28
+ size > maxBytes ||
29
+ data.byteLength < transportDefaults.headerBytes)
30
+ throw new Error("Invalid or oversized transport object");
31
+ const value = JSON.parse(Buffer.from(data.subarray(0, transportDefaults.headerBytes)).toString());
32
+ if (value &&
33
+ typeof value === "object" &&
34
+ "key" in value &&
35
+ typeof value.key === "string" &&
36
+ value.key !== key &&
37
+ value.key.toLowerCase() === key.toLowerCase())
38
+ throw new Error("Transport key casing conflicts with the stored key");
20
39
  if (!value ||
21
40
  typeof value !== "object" ||
22
41
  !("key" in value) ||
@@ -25,8 +44,7 @@ export function decodeObject(data, key, maxBytes) {
25
44
  typeof value.revision !== "string" ||
26
45
  !value.revision ||
27
46
  !("size" in value) ||
28
- value.size !== bytes.length ||
29
- bytes.length > maxBytes ||
47
+ value.size !== size ||
30
48
  !("modifiedAt" in value) ||
31
49
  typeof value.modifiedAt !== "string" ||
32
50
  !Number.isFinite(Date.parse(value.modifiedAt)))
@@ -34,9 +52,8 @@ export function decodeObject(data, key, maxBytes) {
34
52
  return {
35
53
  key,
36
54
  revision: value.revision,
37
- size: bytes.length,
55
+ size,
38
56
  modifiedAt: value.modifiedAt,
39
- bytes,
40
57
  };
41
58
  }
42
59
  export function readLimit(maxBytes = transportDefaults.maxBytes) {
@@ -1 +1 @@
1
- {"version":3,"file":"transport-envelope.js","sourceRoot":"","sources":["../../src/infrastructure/transport-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,KAAiB;IACzD,IAAI,KAAK,CAAC,UAAU,GAAG,iBAAiB,CAAC,QAAQ;QAC/C,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;IACJ,MAAM,KAAK,GAAmB;QAC5B,GAAG;QACH,QAAQ,EAAE,UAAU,EAAE;QACtB,IAAI,EAAE,KAAK,CAAC,UAAU;QACtB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAChE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAgB,EAChB,GAAW,EACX,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAC/B,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,CAC7D,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC7D,IACE,CAAC,KAAK;QACN,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC;QACjB,KAAK,CAAC,GAAG,KAAK,GAAG;QACjB,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC;QACtB,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,CAAC,KAAK,CAAC,QAAQ;QACf,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;QAClB,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM;QAC3B,KAAK,CAAC,MAAM,GAAG,QAAQ;QACvB,CAAC,CAAC,YAAY,IAAI,KAAK,CAAC;QACxB,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ;QACpC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAE9C,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,OAAO;QACL,GAAG;QACH,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,MAAM;QAClB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAQ,GAAG,iBAAiB,CAAC,QAAQ;IAC7D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n TransportEntry,\n TransportObject,\n} from \"../domain/transport.types.ts\";\nimport { transportDefaults } from \"./transport.constants.ts\";\n\nexport function encodeObject(key: string, bytes: Uint8Array) {\n if (bytes.byteLength > transportDefaults.maxBytes)\n throw new Error(\n \"Transport object exceeds 64 MiB; split large payloads into chunks\",\n );\n const entry: TransportEntry = {\n key,\n revision: randomUUID(),\n size: bytes.byteLength,\n modifiedAt: new Date().toISOString(),\n };\n const header = Buffer.alloc(transportDefaults.headerBytes, \" \");\n header.write(JSON.stringify(entry));\n return { entry, data: Buffer.concat([header, bytes]) };\n}\n\nexport function decodeObject(\n data: Uint8Array,\n key: string,\n maxBytes: number,\n): TransportObject {\n const buffer = Buffer.from(data);\n const value: unknown = JSON.parse(\n buffer.subarray(0, transportDefaults.headerBytes).toString(),\n );\n const bytes = buffer.subarray(transportDefaults.headerBytes);\n if (\n !value ||\n typeof value !== \"object\" ||\n !(\"key\" in value) ||\n value.key !== key ||\n !(\"revision\" in value) ||\n typeof value.revision !== \"string\" ||\n !value.revision ||\n !(\"size\" in value) ||\n value.size !== bytes.length ||\n bytes.length > maxBytes ||\n !(\"modifiedAt\" in value) ||\n typeof value.modifiedAt !== \"string\" ||\n !Number.isFinite(Date.parse(value.modifiedAt))\n )\n throw new Error(\"Invalid or oversized transport object\");\n return {\n key,\n revision: value.revision,\n size: bytes.length,\n modifiedAt: value.modifiedAt,\n bytes,\n };\n}\n\nexport function readLimit(maxBytes = transportDefaults.maxBytes): number {\n if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)\n throw new Error(\"Invalid transport byte limit\");\n return maxBytes;\n}\n"]}
1
+ {"version":3,"file":"transport-envelope.js","sourceRoot":"","sources":["../../src/infrastructure/transport-envelope.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n TransportEntry,\n TransportObject,\n} from \"../domain/transport.types.ts\";\nimport { transportDefaults } from \"./transport.constants.ts\";\n\nexport function encodeObject(key: string, bytes: Uint8Array) {\n if (bytes.byteLength > transportDefaults.maxBytes)\n throw new Error(\n \"Transport object exceeds 64 MiB; split large payloads into chunks\",\n );\n const entry: TransportEntry = {\n key,\n revision: randomUUID(),\n size: bytes.byteLength,\n modifiedAt: new Date().toISOString(),\n };\n const header = Buffer.alloc(transportDefaults.headerBytes, \" \");\n header.write(JSON.stringify(entry));\n return { entry, data: Buffer.concat([header, bytes]) };\n}\n\nexport function decodeObject(\n data: Uint8Array,\n key: string,\n maxBytes: number,\n): TransportObject {\n const buffer = Buffer.from(data);\n const bytes = buffer.subarray(transportDefaults.headerBytes);\n return {\n ...decodeObjectEntry(buffer, key, data.byteLength, maxBytes),\n bytes,\n };\n}\n\nexport function decodeObjectEntry(\n data: Uint8Array,\n key: string,\n fileSize: number,\n maxBytes: number,\n): TransportEntry {\n const size = fileSize - transportDefaults.headerBytes;\n if (\n !Number.isSafeInteger(size) ||\n size < 0 ||\n size > maxBytes ||\n data.byteLength < transportDefaults.headerBytes\n )\n throw new Error(\"Invalid or oversized transport object\");\n const value: unknown = JSON.parse(\n Buffer.from(data.subarray(0, transportDefaults.headerBytes)).toString(),\n );\n if (\n value &&\n typeof value === \"object\" &&\n \"key\" in value &&\n typeof value.key === \"string\" &&\n value.key !== key &&\n value.key.toLowerCase() === key.toLowerCase()\n )\n throw new Error(\"Transport key casing conflicts with the stored key\");\n if (\n !value ||\n typeof value !== \"object\" ||\n !(\"key\" in value) ||\n value.key !== key ||\n !(\"revision\" in value) ||\n typeof value.revision !== \"string\" ||\n !value.revision ||\n !(\"size\" in value) ||\n value.size !== size ||\n !(\"modifiedAt\" in value) ||\n typeof value.modifiedAt !== \"string\" ||\n !Number.isFinite(Date.parse(value.modifiedAt))\n )\n throw new Error(\"Invalid or oversized transport object\");\n return {\n key,\n revision: value.revision,\n size,\n modifiedAt: value.modifiedAt,\n };\n}\n\nexport function readLimit(maxBytes = transportDefaults.maxBytes): number {\n if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)\n throw new Error(\"Invalid transport byte limit\");\n return maxBytes;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const reservationLedgerKey = "reservations/ledger";
2
+ export declare const reservationIdPattern: RegExp;
@@ -0,0 +1,3 @@
1
+ export const reservationLedgerKey = "reservations/ledger";
2
+ export const reservationIdPattern = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
3
+ //# sourceMappingURL=transport-reservations.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-reservations.constants.js","sourceRoot":"","sources":["../../src/infrastructure/transport-reservations.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC;AAC1D,MAAM,CAAC,MAAM,oBAAoB,GAC/B,gEAAgE,CAAC","sourcesContent":["export const reservationLedgerKey = \"reservations/ledger\";\nexport const reservationIdPattern =\n /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;\n"]}
@@ -1,5 +1,4 @@
1
1
  import type { Transport } from "../domain/transport.types.ts";
2
2
  import type { StorageReservation, StorageReservationOptions } from "./storage-reservations.types.ts";
3
- import type { ReservationLedger } from "./transport-reservations.types.ts";
4
- export declare function reservationLedger(value: unknown): ReservationLedger;
3
+ export { reservationLedger } from "./reservation-ledger.ts";
5
4
  export declare function reserveTransportStorage(transporter: Transport, repository: string, options: StorageReservationOptions, measureUsage?: () => Promise<number>): Promise<StorageReservation>;
@@ -3,25 +3,12 @@ import { randomUUID } from "node:crypto";
3
3
  import { TransportConflict } from "../domain/transport.js";
4
4
  import { invariant, recordRecovery } from "../domain/errors.js";
5
5
  import { jsonBytes, jsonObject } from "./transport-json.js";
6
- const key = "reservations/ledger";
7
- export function reservationLedger(value) {
8
- if (value === undefined)
9
- return { format: 1, reservations: {} };
10
- invariant(value &&
11
- typeof value === "object" &&
12
- "format" in value &&
13
- value.format === 1 &&
14
- "reservations" in value &&
15
- value.reservations &&
16
- typeof value.reservations === "object" &&
17
- !Array.isArray(value.reservations), "Invalid reservation ledger");
18
- const entries = Object.entries(value.reservations);
19
- invariant(entries.every(([id, bytes]) => /^[a-f0-9-]{36}$/.test(id) &&
20
- typeof bytes === "number" &&
21
- Number.isSafeInteger(bytes) &&
22
- bytes >= 0), "Invalid reservation entry");
23
- return { format: 1, reservations: Object.fromEntries(entries) };
24
- }
6
+ import { reservationLedger } from "./reservation-ledger.js";
7
+ import { reservationLedgerKey as key } from "./transport-reservations.constants.js";
8
+ import { localProcessIdentity } from "./git/process-identity.js";
9
+ import { hostname } from "node:os";
10
+ export { reservationLedger } from "./reservation-ledger.js";
11
+ const ownerId = randomUUID();
25
12
  async function payloadUsage(transporter, maxEntries, signal) {
26
13
  let usage = 0, count = 0;
27
14
  for await (const entry of transporter.list("", signal ? { signal } : {})) {
@@ -37,6 +24,15 @@ export async function reserveTransportStorage(transporter, repository, options,
37
24
  Number.isSafeInteger(options.reserveBytes) &&
38
25
  options.reserveBytes >= 0, "Invalid reservation byte limit");
39
26
  const id = randomUUID();
27
+ const identity = await localProcessIdentity();
28
+ const owner = {
29
+ id: ownerId,
30
+ host: hostname(),
31
+ pid: process.pid,
32
+ createdAt: new Date().toISOString(),
33
+ repository,
34
+ ...(identity ? { identity } : {}),
35
+ };
40
36
  const maxEntries = options.maxEntries ?? transportDefaults.maxEntries;
41
37
  invariant(Number.isSafeInteger(maxEntries) && maxEntries > 0, "Invalid reservation entry limit");
42
38
  async function update(release) {
@@ -52,12 +48,12 @@ export async function reserveTransportStorage(transporter, repository, options,
52
48
  const usage = measureUsage
53
49
  ? await measureUsage()
54
50
  : await payloadUsage(transporter, maxEntries, options.signal);
55
- const reserved = Object.values(reservations).reduce((sum, value) => sum + value, 0);
51
+ const reserved = Object.values(reservations).reduce((sum, value) => sum + value.bytes, 0);
56
52
  invariant(usage + reserved + options.reserveBytes <= options.maxBytes, "Outpost storage reservation admission refused");
57
- reservations[id] = options.reserveBytes;
53
+ reservations[id] = { bytes: options.reserveBytes, owner };
58
54
  }
59
55
  try {
60
- await transporter.write(key, jsonBytes({ format: 1, reservations }), {
56
+ await transporter.write(key, jsonBytes({ format: 2, reservations }), {
61
57
  ifRevision: current?.revision ?? null,
62
58
  ...(!release && options.signal ? { signal: options.signal } : {}),
63
59
  });
@@ -1 +1 @@
1
- {"version":3,"file":"transport-reservations.js","sourceRoot":"","sources":["../../src/infrastructure/transport-reservations.ts"],"names":[],"mappings":"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","sourcesContent":["import { transportDefaults } from \"./transport.constants.ts\";\nimport { randomUUID } from \"node:crypto\";\nimport type { Transport } from \"../domain/transport.types.ts\";\nimport { TransportConflict } from \"../domain/transport.ts\";\nimport { invariant, recordRecovery } from \"../domain/errors.ts\";\nimport { jsonBytes, jsonObject } from \"./transport-json.ts\";\nimport type {\n StorageReservation,\n StorageReservationOptions,\n} from \"./storage-reservations.types.ts\";\nimport type { ReservationLedger } from \"./transport-reservations.types.ts\";\n\nconst key = \"reservations/ledger\";\n\nexport function reservationLedger(value: unknown): ReservationLedger {\n if (value === undefined) return { format: 1, reservations: {} };\n invariant(\n value &&\n typeof value === \"object\" &&\n \"format\" in value &&\n value.format === 1 &&\n \"reservations\" in value &&\n value.reservations &&\n typeof value.reservations === \"object\" &&\n !Array.isArray(value.reservations),\n \"Invalid reservation ledger\",\n );\n const entries = Object.entries(value.reservations);\n invariant(\n entries.every(\n ([id, bytes]) =>\n /^[a-f0-9-]{36}$/.test(id) &&\n typeof bytes === \"number\" &&\n Number.isSafeInteger(bytes) &&\n bytes >= 0,\n ),\n \"Invalid reservation entry\",\n );\n return { format: 1, reservations: Object.fromEntries(entries) };\n}\n\nasync function payloadUsage(\n transporter: Transport,\n maxEntries: number,\n signal?: AbortSignal,\n): Promise<number> {\n let usage = 0,\n count = 0;\n for await (const entry of transporter.list(\"\", signal ? { signal } : {})) {\n invariant(++count <= maxEntries, \"Reservation inventory is incomplete\");\n if (entry.key !== key) usage += entry.size;\n }\n return usage;\n}\n\nexport async function reserveTransportStorage(\n transporter: Transport,\n repository: string,\n options: StorageReservationOptions,\n measureUsage?: () => Promise<number>,\n): Promise<StorageReservation> {\n invariant(\n Number.isSafeInteger(options.maxBytes) &&\n options.maxBytes >= 0 &&\n Number.isSafeInteger(options.reserveBytes) &&\n options.reserveBytes >= 0,\n \"Invalid reservation byte limit\",\n );\n const id = randomUUID();\n const maxEntries = options.maxEntries ?? transportDefaults.maxEntries;\n invariant(\n Number.isSafeInteger(maxEntries) && maxEntries > 0,\n \"Invalid reservation entry limit\",\n );\n async function update(release: boolean): Promise<void> {\n for (\n let attempt = 0;\n attempt < transportDefaults.mutationAttempts;\n attempt++\n ) {\n if (!release) options.signal?.throwIfAborted();\n const current = await transporter.read(key);\n const ledger = reservationLedger(jsonObject(current));\n const reservations = { ...ledger.reservations };\n if (release) delete reservations[id];\n if (!release) {\n const usage = measureUsage\n ? await measureUsage()\n : await payloadUsage(transporter, maxEntries, options.signal);\n const reserved = Object.values(reservations).reduce(\n (sum, value) => sum + value,\n 0,\n );\n invariant(\n usage + reserved + options.reserveBytes <= options.maxBytes,\n \"Outpost storage reservation admission refused\",\n );\n reservations[id] = options.reserveBytes;\n }\n try {\n await transporter.write(key, jsonBytes({ format: 1, reservations }), {\n ifRevision: current?.revision ?? null,\n ...(!release && options.signal ? { signal: options.signal } : {}),\n });\n return;\n } catch (error) {\n if (!(error instanceof TransportConflict)) throw error;\n }\n }\n throw new Error(\"Storage reservation contention limit exceeded\");\n }\n try {\n await update(false);\n } catch (error) {\n recordRecovery(error, { reservation: id });\n throw error;\n }\n let released = false;\n const release = async () => {\n if (released) return;\n await update(true);\n released = true;\n };\n return {\n id,\n repository,\n reserveBytes: options.reserveBytes,\n release,\n [Symbol.asyncDispose]: release,\n };\n}\n"]}
1
+ {"version":3,"file":"transport-reservations.js","sourceRoot":"","sources":["../../src/infrastructure/transport-reservations.ts"],"names":[],"mappings":"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","sourcesContent":["import { transportDefaults } from \"./transport.constants.ts\";\nimport { randomUUID } from \"node:crypto\";\nimport type { Transport } from \"../domain/transport.types.ts\";\nimport { TransportConflict } from \"../domain/transport.ts\";\nimport { invariant, recordRecovery } from \"../domain/errors.ts\";\nimport { jsonBytes, jsonObject } from \"./transport-json.ts\";\nimport type {\n StorageReservation,\n StorageReservationOptions,\n} from \"./storage-reservations.types.ts\";\nimport { reservationLedger } from \"./reservation-ledger.ts\";\nimport { reservationLedgerKey as key } from \"./transport-reservations.constants.ts\";\nimport { localProcessIdentity } from \"./git/process-identity.ts\";\nimport { hostname } from \"node:os\";\nexport { reservationLedger } from \"./reservation-ledger.ts\";\n\nconst ownerId = randomUUID();\n\nasync function payloadUsage(\n transporter: Transport,\n maxEntries: number,\n signal?: AbortSignal,\n): Promise<number> {\n let usage = 0,\n count = 0;\n for await (const entry of transporter.list(\"\", signal ? { signal } : {})) {\n invariant(++count <= maxEntries, \"Reservation inventory is incomplete\");\n if (entry.key !== key) usage += entry.size;\n }\n return usage;\n}\n\nexport async function reserveTransportStorage(\n transporter: Transport,\n repository: string,\n options: StorageReservationOptions,\n measureUsage?: () => Promise<number>,\n): Promise<StorageReservation> {\n invariant(\n Number.isSafeInteger(options.maxBytes) &&\n options.maxBytes >= 0 &&\n Number.isSafeInteger(options.reserveBytes) &&\n options.reserveBytes >= 0,\n \"Invalid reservation byte limit\",\n );\n const id = randomUUID();\n const identity = await localProcessIdentity();\n const owner = {\n id: ownerId,\n host: hostname(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n repository,\n ...(identity ? { identity } : {}),\n };\n const maxEntries = options.maxEntries ?? transportDefaults.maxEntries;\n invariant(\n Number.isSafeInteger(maxEntries) && maxEntries > 0,\n \"Invalid reservation entry limit\",\n );\n async function update(release: boolean): Promise<void> {\n for (\n let attempt = 0;\n attempt < transportDefaults.mutationAttempts;\n attempt++\n ) {\n if (!release) options.signal?.throwIfAborted();\n const current = await transporter.read(key);\n const ledger = reservationLedger(jsonObject(current));\n const reservations = { ...ledger.reservations };\n if (release) delete reservations[id];\n if (!release) {\n const usage = measureUsage\n ? await measureUsage()\n : await payloadUsage(transporter, maxEntries, options.signal);\n const reserved = Object.values(reservations).reduce(\n (sum, value) => sum + value.bytes,\n 0,\n );\n invariant(\n usage + reserved + options.reserveBytes <= options.maxBytes,\n \"Outpost storage reservation admission refused\",\n );\n reservations[id] = { bytes: options.reserveBytes, owner };\n }\n try {\n await transporter.write(key, jsonBytes({ format: 2, reservations }), {\n ifRevision: current?.revision ?? null,\n ...(!release && options.signal ? { signal: options.signal } : {}),\n });\n return;\n } catch (error) {\n if (!(error instanceof TransportConflict)) throw error;\n }\n }\n throw new Error(\"Storage reservation contention limit exceeded\");\n }\n try {\n await update(false);\n } catch (error) {\n recordRecovery(error, { reservation: id });\n throw error;\n }\n let released = false;\n const release = async () => {\n if (released) return;\n await update(true);\n released = true;\n };\n return {\n id,\n repository,\n reserveBytes: options.reserveBytes,\n release,\n [Symbol.asyncDispose]: release,\n };\n}\n"]}
@@ -1,4 +1,25 @@
1
+ import type { LocalProcessIdentity } from "./git/process-identity.types.ts";
2
+ export interface StorageReservationRecord {
3
+ readonly bytes: number;
4
+ readonly owner?: {
5
+ readonly id: string;
6
+ readonly host: string;
7
+ readonly pid: number;
8
+ readonly createdAt: string;
9
+ readonly repository: string;
10
+ readonly identity?: LocalProcessIdentity;
11
+ };
12
+ }
1
13
  export interface ReservationLedger {
2
- readonly format: 1;
3
- readonly reservations: Readonly<Record<string, number>>;
14
+ readonly format: 2;
15
+ readonly reservations: Readonly<Record<string, StorageReservationRecord>>;
16
+ }
17
+ export interface StorageReservationInspection {
18
+ readonly revision?: string;
19
+ readonly reservations: Readonly<Record<string, StorageReservationRecord>>;
20
+ }
21
+ export interface StorageReservationRecoveryOptions {
22
+ readonly id: string;
23
+ readonly revision: string;
24
+ readonly processesStopped: true;
4
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transport-reservations.types.js","sourceRoot":"","sources":["../../src/infrastructure/transport-reservations.types.ts"],"names":[],"mappings":"","sourcesContent":["export interface ReservationLedger {\n readonly format: 1;\n readonly reservations: Readonly<Record<string, number>>;\n}\n"]}
1
+ {"version":3,"file":"transport-reservations.types.js","sourceRoot":"","sources":["../../src/infrastructure/transport-reservations.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { LocalProcessIdentity } from \"./git/process-identity.types.ts\";\n\nexport interface StorageReservationRecord {\n readonly bytes: number;\n readonly owner?: {\n readonly id: string;\n readonly host: string;\n readonly pid: number;\n readonly createdAt: string;\n readonly repository: string;\n readonly identity?: LocalProcessIdentity;\n };\n}\n\nexport interface ReservationLedger {\n readonly format: 2;\n readonly reservations: Readonly<Record<string, StorageReservationRecord>>;\n}\n\nexport interface StorageReservationInspection {\n readonly revision?: string;\n readonly reservations: Readonly<Record<string, StorageReservationRecord>>;\n}\n\nexport interface StorageReservationRecoveryOptions {\n readonly id: string;\n readonly revision: string;\n readonly processesStopped: true;\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { TriggerPayloadError } from "../domain/trigger-payload.js";
1
2
  import { createServer } from "node:http";
2
3
  import { triggerQueueRequest } from "../domain/trigger-job.js";
3
4
  import { triggerDefaultMaxBytes, triggerMaxBytes, triggerPathPattern, } from "../domain/trigger.constants.js";
@@ -80,8 +81,9 @@ export async function serveTriggers(options) {
80
81
  event = await route.source.verify(request, Date.now());
81
82
  }
82
83
  catch (error) {
83
- report(error, { path: route.path, stage: "verify" });
84
- return { status: 401 };
84
+ const payload = error instanceof TriggerPayloadError;
85
+ report(error, { path: route.path, stage: payload ? "decode" : "verify" });
86
+ return { status: payload ? 400 : 401 };
85
87
  }
86
88
  const failure = { path: route.path, delivery: event.delivery };
87
89
  const reply = (outcome, job) => route.source.reply?.(outcome, job) ?? defaultReply(outcome, job);
@@ -110,14 +112,14 @@ export async function serveTriggers(options) {
110
112
  headersTimeout: triggerHttpTimeoutMs,
111
113
  maxHeaderSize: triggerHttpMaxHeaderBytes,
112
114
  }, async (request, response) => {
113
- const path = new URL(request.url ?? "/", "http://trigger").pathname;
114
- const route = routes.get(path);
115
- if (!route || request.method !== "POST") {
116
- request.resume();
117
- send(response, { status: route ? 405 : 404 });
118
- return;
119
- }
120
115
  try {
116
+ const path = new URL(request.url ?? "/", "http://trigger").pathname;
117
+ const route = routes.get(path);
118
+ if (!route || request.method !== "POST") {
119
+ request.resume();
120
+ send(response, { status: route ? 405 : 404 });
121
+ return;
122
+ }
121
123
  const content = await body(request, limit);
122
124
  send(response, await handle(route, {
123
125
  method: request.method,
@@ -127,6 +129,7 @@ export async function serveTriggers(options) {
127
129
  }));
128
130
  }
129
131
  catch (error) {
132
+ request.resume();
130
133
  response.setHeader("connection", "close");
131
134
  send(response, {
132
135
  status: error instanceof RequestTooLarge ? 413 : 400,
@@ -1 +1 @@
1
- {"version":3,"file":"trigger-server.js","sourceRoot":"","sources":["../../src/infrastructure/trigger-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer } from \"node:http\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { triggerQueueRequest } from \"../domain/trigger-job.ts\";\nimport {\n triggerDefaultMaxBytes,\n triggerMaxBytes,\n triggerPathPattern,\n} from \"../domain/trigger.constants.ts\";\nimport type {\n TriggerEvent,\n TriggerHttpRequest,\n TriggerOutcome,\n TriggerReply,\n} from \"../domain/trigger.types.ts\";\nimport {\n triggerHttpMaxHeaderBytes,\n triggerHttpTimeoutMs,\n} from \"./trigger-server.constants.ts\";\nimport type {\n TriggerFailure,\n TriggerRoute,\n TriggerServer,\n TriggerServerOptions,\n} from \"./trigger-server.types.ts\";\n\nclass RequestTooLarge extends Error {}\n\nfunction routeTable(routes: readonly TriggerRoute[]) {\n if (!Array.isArray(routes) || !routes.length)\n throw new Error(\"serveTriggers() requires at least one route\");\n const table = new Map<string, TriggerRoute>();\n for (const route of routes) {\n if (!triggerPathPattern.test(route.path) || table.has(route.path))\n throw new Error(`Invalid or duplicate trigger path: ${route.path}`);\n if (\n typeof route.source?.verify !== \"function\" ||\n typeof route.on !== \"function\"\n )\n throw new Error(`Trigger route ${route.path} requires a source and on()`);\n table.set(route.path, route);\n }\n return table;\n}\n\nfunction bodyLimit(value: number | undefined): number {\n const limit = value ?? triggerDefaultMaxBytes;\n if (!Number.isSafeInteger(limit) || limit <= 0 || limit > triggerMaxBytes)\n throw new Error(\"Trigger body limit must be between 1 byte and 25 MiB\");\n return limit;\n}\n\nasync function body(request: IncomingMessage, limit: number) {\n const declared = Number(request.headers[\"content-length\"] ?? 0);\n if (declared > limit) throw new RequestTooLarge();\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of request) {\n const bytes: Buffer = chunk;\n size += bytes.byteLength;\n if (size > limit) throw new RequestTooLarge();\n chunks.push(bytes);\n }\n return Buffer.concat(chunks);\n}\n\nfunction headers(request: IncomingMessage) {\n return Object.fromEntries(\n Object.entries(request.headers).map(([name, value]) => [\n name,\n Array.isArray(value) ? value.join(\", \") : value,\n ]),\n );\n}\n\nfunction send(response: ServerResponse, reply: TriggerReply) {\n response\n .writeHead(reply.status, {\n \"cache-control\": \"no-store\",\n ...(reply.contentType ? { \"content-type\": reply.contentType } : {}),\n })\n .end(reply.body);\n}\n\nfunction defaultReply(outcome: TriggerOutcome, job?: string): TriggerReply {\n if (outcome === \"ignored\") return { status: 204 };\n return {\n status: 202,\n contentType: \"application/json\",\n body: JSON.stringify({ job }),\n };\n}\n\n/** Serve verified webhooks and publish the jobs their routes select. */\nexport async function serveTriggers(\n options: TriggerServerOptions,\n): Promise<TriggerServer> {\n const routes = routeTable(options.routes);\n const limit = bodyLimit(options.maxBytes);\n function report(error: unknown, failure: TriggerFailure) {\n try {\n options.onError?.(error, failure);\n } catch {\n /* Observer failures must not change responses. */\n }\n }\n async function handle(\n route: TriggerRoute,\n request: TriggerHttpRequest,\n ): Promise<TriggerReply> {\n let event: TriggerEvent;\n try {\n event = await route.source.verify(request, Date.now());\n } catch (error) {\n report(error, { path: route.path, stage: \"verify\" });\n return { status: 401 };\n }\n const failure = { path: route.path, delivery: event.delivery };\n const reply = (outcome: TriggerOutcome, job?: string) =>\n route.source.reply?.(outcome, job) ?? defaultReply(outcome, job);\n let queued;\n try {\n const job = await route.on(event);\n if (job === undefined) return reply(\"ignored\");\n queued = triggerQueueRequest(\n `trigger:${route.path}:${event.delivery}`,\n job,\n );\n } catch (error) {\n report(error, { ...failure, stage: \"route\" });\n return { status: 500 };\n }\n try {\n await options.queue.enqueue(queued);\n } catch (error) {\n report(error, { ...failure, stage: \"enqueue\" });\n return { status: 503 };\n }\n return reply(\"accepted\", queued.id);\n }\n const server = createServer(\n {\n requestTimeout: triggerHttpTimeoutMs,\n headersTimeout: triggerHttpTimeoutMs,\n maxHeaderSize: triggerHttpMaxHeaderBytes,\n },\n async (request, response) => {\n const path = new URL(request.url ?? \"/\", \"http://trigger\").pathname;\n const route = routes.get(path);\n if (!route || request.method !== \"POST\") {\n request.resume();\n send(response, { status: route ? 405 : 404 });\n return;\n }\n try {\n const content = await body(request, limit);\n send(\n response,\n await handle(route, {\n method: request.method,\n path,\n headers: headers(request),\n body: content,\n }),\n );\n } catch (error) {\n response.setHeader(\"connection\", \"close\");\n send(response, {\n status: error instanceof RequestTooLarge ? 413 : 400,\n });\n }\n },\n );\n server.setTimeout(triggerHttpTimeoutMs, (socket) => socket.destroy());\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.port ?? 0, options.host ?? \"127.0.0.1\", resolve);\n });\n const address = server.address();\n if (!address || typeof address === \"string\")\n throw new Error(\"Trigger server address unavailable\");\n const host =\n address.family === \"IPv6\" ? `[${address.address}]` : address.address;\n return {\n url: `http://${host}:${address.port}`,\n close: () =>\n new Promise<void>((resolve, reject) => {\n server.close((error) => (error ? reject(error) : resolve()));\n server.closeIdleConnections();\n }),\n };\n}\n"]}
1
+ {"version":3,"file":"trigger-server.js","sourceRoot":"","sources":["../../src/infrastructure/trigger-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { TriggerPayloadError } from \"../domain/trigger-payload.ts\";\nimport { createServer } from \"node:http\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { triggerQueueRequest } from \"../domain/trigger-job.ts\";\nimport {\n triggerDefaultMaxBytes,\n triggerMaxBytes,\n triggerPathPattern,\n} from \"../domain/trigger.constants.ts\";\nimport type {\n TriggerEvent,\n TriggerHttpRequest,\n TriggerOutcome,\n TriggerReply,\n} from \"../domain/trigger.types.ts\";\nimport {\n triggerHttpMaxHeaderBytes,\n triggerHttpTimeoutMs,\n} from \"./trigger-server.constants.ts\";\nimport type {\n TriggerFailure,\n TriggerRoute,\n TriggerServer,\n TriggerServerOptions,\n} from \"./trigger-server.types.ts\";\n\nclass RequestTooLarge extends Error {}\n\nfunction routeTable(routes: readonly TriggerRoute[]) {\n if (!Array.isArray(routes) || !routes.length)\n throw new Error(\"serveTriggers() requires at least one route\");\n const table = new Map<string, TriggerRoute>();\n for (const route of routes) {\n if (!triggerPathPattern.test(route.path) || table.has(route.path))\n throw new Error(`Invalid or duplicate trigger path: ${route.path}`);\n if (\n typeof route.source?.verify !== \"function\" ||\n typeof route.on !== \"function\"\n )\n throw new Error(`Trigger route ${route.path} requires a source and on()`);\n table.set(route.path, route);\n }\n return table;\n}\n\nfunction bodyLimit(value: number | undefined): number {\n const limit = value ?? triggerDefaultMaxBytes;\n if (!Number.isSafeInteger(limit) || limit <= 0 || limit > triggerMaxBytes)\n throw new Error(\"Trigger body limit must be between 1 byte and 25 MiB\");\n return limit;\n}\n\nasync function body(request: IncomingMessage, limit: number) {\n const declared = Number(request.headers[\"content-length\"] ?? 0);\n if (declared > limit) throw new RequestTooLarge();\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of request) {\n const bytes: Buffer = chunk;\n size += bytes.byteLength;\n if (size > limit) throw new RequestTooLarge();\n chunks.push(bytes);\n }\n return Buffer.concat(chunks);\n}\n\nfunction headers(request: IncomingMessage) {\n return Object.fromEntries(\n Object.entries(request.headers).map(([name, value]) => [\n name,\n Array.isArray(value) ? value.join(\", \") : value,\n ]),\n );\n}\n\nfunction send(response: ServerResponse, reply: TriggerReply) {\n response\n .writeHead(reply.status, {\n \"cache-control\": \"no-store\",\n ...(reply.contentType ? { \"content-type\": reply.contentType } : {}),\n })\n .end(reply.body);\n}\n\nfunction defaultReply(outcome: TriggerOutcome, job?: string): TriggerReply {\n if (outcome === \"ignored\") return { status: 204 };\n return {\n status: 202,\n contentType: \"application/json\",\n body: JSON.stringify({ job }),\n };\n}\n\n/** Serve verified webhooks and publish the jobs their routes select. */\nexport async function serveTriggers(\n options: TriggerServerOptions,\n): Promise<TriggerServer> {\n const routes = routeTable(options.routes);\n const limit = bodyLimit(options.maxBytes);\n function report(error: unknown, failure: TriggerFailure) {\n try {\n options.onError?.(error, failure);\n } catch {\n /* Observer failures must not change responses. */\n }\n }\n async function handle(\n route: TriggerRoute,\n request: TriggerHttpRequest,\n ): Promise<TriggerReply> {\n let event: TriggerEvent;\n try {\n event = await route.source.verify(request, Date.now());\n } catch (error) {\n const payload = error instanceof TriggerPayloadError;\n report(error, { path: route.path, stage: payload ? \"decode\" : \"verify\" });\n return { status: payload ? 400 : 401 };\n }\n const failure = { path: route.path, delivery: event.delivery };\n const reply = (outcome: TriggerOutcome, job?: string) =>\n route.source.reply?.(outcome, job) ?? defaultReply(outcome, job);\n let queued;\n try {\n const job = await route.on(event);\n if (job === undefined) return reply(\"ignored\");\n queued = triggerQueueRequest(\n `trigger:${route.path}:${event.delivery}`,\n job,\n );\n } catch (error) {\n report(error, { ...failure, stage: \"route\" });\n return { status: 500 };\n }\n try {\n await options.queue.enqueue(queued);\n } catch (error) {\n report(error, { ...failure, stage: \"enqueue\" });\n return { status: 503 };\n }\n return reply(\"accepted\", queued.id);\n }\n const server = createServer(\n {\n requestTimeout: triggerHttpTimeoutMs,\n headersTimeout: triggerHttpTimeoutMs,\n maxHeaderSize: triggerHttpMaxHeaderBytes,\n },\n async (request, response) => {\n try {\n const path = new URL(request.url ?? \"/\", \"http://trigger\").pathname;\n const route = routes.get(path);\n if (!route || request.method !== \"POST\") {\n request.resume();\n send(response, { status: route ? 405 : 404 });\n return;\n }\n const content = await body(request, limit);\n send(\n response,\n await handle(route, {\n method: request.method,\n path,\n headers: headers(request),\n body: content,\n }),\n );\n } catch (error) {\n request.resume();\n response.setHeader(\"connection\", \"close\");\n send(response, {\n status: error instanceof RequestTooLarge ? 413 : 400,\n });\n }\n },\n );\n server.setTimeout(triggerHttpTimeoutMs, (socket) => socket.destroy());\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.port ?? 0, options.host ?? \"127.0.0.1\", resolve);\n });\n const address = server.address();\n if (!address || typeof address === \"string\")\n throw new Error(\"Trigger server address unavailable\");\n const host =\n address.family === \"IPv6\" ? `[${address.address}]` : address.address;\n return {\n url: `http://${host}:${address.port}`,\n close: () =>\n new Promise<void>((resolve, reject) => {\n server.close((error) => (error ? reject(error) : resolve()));\n server.closeIdleConnections();\n }),\n };\n}\n"]}
@@ -10,7 +10,7 @@ export interface TriggerRoute {
10
10
  }
11
11
  export interface TriggerFailure {
12
12
  readonly path: string;
13
- readonly stage: "verify" | "route" | "enqueue";
13
+ readonly stage: "verify" | "decode" | "route" | "enqueue";
14
14
  readonly delivery?: string;
15
15
  }
16
16
  export interface TriggerServerOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"trigger-server.types.js","sourceRoot":"","sources":["../../src/infrastructure/trigger-server.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { TaskQueue } from \"../domain/task-queue.types.ts\";\nimport type { TriggerJob } from \"../domain/trigger-job.types.ts\";\nimport type { TriggerEvent, TriggerSource } from \"../domain/trigger.types.ts\";\n\nexport interface TriggerRoute {\n /** Exact request path, such as `/github`; part of each job identifier. */\n readonly path: string;\n readonly source: TriggerSource;\n /** Maps a verified event to a job, or `undefined` to ignore it; must return promptly. */\n on(\n event: TriggerEvent,\n ): TriggerJob | undefined | Promise<TriggerJob | undefined>;\n}\n\nexport interface TriggerFailure {\n readonly path: string;\n readonly stage: \"verify\" | \"route\" | \"enqueue\";\n readonly delivery?: string;\n}\n\nexport interface TriggerServerOptions {\n readonly queue: TaskQueue;\n readonly routes: readonly TriggerRoute[];\n /** Defaults to 127.0.0.1; expose through a TLS-terminating proxy. */\n readonly host?: string;\n readonly port?: number;\n /** Request body limit; defaults to 1 MiB, at most 25 MiB. */\n readonly maxBytes?: number;\n /** Observes rejected or failed requests; never receives secrets. */\n readonly onError?: (error: unknown, failure: TriggerFailure) => void;\n}\n\nexport interface TriggerServer {\n readonly url: string;\n close(): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"trigger-server.types.js","sourceRoot":"","sources":["../../src/infrastructure/trigger-server.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { TaskQueue } from \"../domain/task-queue.types.ts\";\nimport type { TriggerJob } from \"../domain/trigger-job.types.ts\";\nimport type { TriggerEvent, TriggerSource } from \"../domain/trigger.types.ts\";\n\nexport interface TriggerRoute {\n /** Exact request path, such as `/github`; part of each job identifier. */\n readonly path: string;\n readonly source: TriggerSource;\n /** Maps a verified event to a job, or `undefined` to ignore it; must return promptly. */\n on(\n event: TriggerEvent,\n ): TriggerJob | undefined | Promise<TriggerJob | undefined>;\n}\n\nexport interface TriggerFailure {\n readonly path: string;\n readonly stage: \"verify\" | \"decode\" | \"route\" | \"enqueue\";\n readonly delivery?: string;\n}\n\nexport interface TriggerServerOptions {\n readonly queue: TaskQueue;\n readonly routes: readonly TriggerRoute[];\n /** Defaults to 127.0.0.1; expose through a TLS-terminating proxy. */\n readonly host?: string;\n readonly port?: number;\n /** Request body limit; defaults to 1 MiB, at most 25 MiB. */\n readonly maxBytes?: number;\n /** Observes rejected or failed requests; never receives secrets. */\n readonly onError?: (error: unknown, failure: TriggerFailure) => void;\n}\n\nexport interface TriggerServer {\n readonly url: string;\n close(): Promise<void>;\n}\n"]}
@@ -1 +1,2 @@
1
1
  export declare const workflowCheckpointMaxBytes: number;
2
+ export declare const workflowCheckpointReleaseAttempts = 2;
@@ -1,2 +1,3 @@
1
1
  export const workflowCheckpointMaxBytes = 16 * 1024 * 1024;
2
+ export const workflowCheckpointReleaseAttempts = 2;
2
3
  //# sourceMappingURL=workflow-checkpoint.constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-checkpoint.constants.js","sourceRoot":"","sources":["../../src/infrastructure/workflow-checkpoint.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC","sourcesContent":["export const workflowCheckpointMaxBytes = 16 * 1024 * 1024;\n"]}
1
+ {"version":3,"file":"workflow-checkpoint.constants.js","sourceRoot":"","sources":["../../src/infrastructure/workflow-checkpoint.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC3D,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC","sourcesContent":["export const workflowCheckpointMaxBytes = 16 * 1024 * 1024;\nexport const workflowCheckpointReleaseAttempts = 2;\n"]}
@@ -1,2 +1,3 @@
1
1
  export declare const workspaceLocksDirectory: string;
2
- export declare const workspaceLockAttempts = 100;
2
+ export declare const workspaceRegistryStaleMs = 10000;
3
+ export declare const workspaceRegistryPendingDeleteAttempts = 50;
@@ -2,5 +2,6 @@ import { homedir, tmpdir, userInfo } from "node:os";
2
2
  import { createHash } from "node:crypto";
3
3
  import { join } from "node:path";
4
4
  export const workspaceLocksDirectory = join(tmpdir(), `outpost-path-locks-${createHash("sha256").update(`${userInfo().username}:${homedir()}`).digest("hex").slice(0, 24)}`);
5
- export const workspaceLockAttempts = 100;
5
+ export const workspaceRegistryStaleMs = 10_000;
6
+ export const workspaceRegistryPendingDeleteAttempts = 50;
6
7
  //# sourceMappingURL=workspace-lock.constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-lock.constants.js","sourceRoot":"","sources":["../../src/infrastructure/workspace-lock.constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CACzC,MAAM,EAAE,EACR,sBAAsB,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC,QAAQ,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CACtH,CAAC;AACF,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC","sourcesContent":["import { homedir, tmpdir, userInfo } from \"node:os\";\nimport { createHash } from \"node:crypto\";\nimport { join } from \"node:path\";\n\nexport const workspaceLocksDirectory = join(\n tmpdir(),\n `outpost-path-locks-${createHash(\"sha256\").update(`${userInfo().username}:${homedir()}`).digest(\"hex\").slice(0, 24)}`,\n);\nexport const workspaceLockAttempts = 100;\n"]}
1
+ {"version":3,"file":"workspace-lock.constants.js","sourceRoot":"","sources":["../../src/infrastructure/workspace-lock.constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CACzC,MAAM,EAAE,EACR,sBAAsB,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC,QAAQ,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CACtH,CAAC;AACF,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAC/C,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,CAAC","sourcesContent":["import { homedir, tmpdir, userInfo } from \"node:os\";\nimport { createHash } from \"node:crypto\";\nimport { join } from \"node:path\";\n\nexport const workspaceLocksDirectory = join(\n tmpdir(),\n `outpost-path-locks-${createHash(\"sha256\").update(`${userInfo().username}:${homedir()}`).digest(\"hex\").slice(0, 24)}`,\n);\nexport const workspaceRegistryStaleMs = 10_000;\nexport const workspaceRegistryPendingDeleteAttempts = 50;\n"]}
@@ -4,7 +4,7 @@ import { dirname, join, resolve } from "node:path";
4
4
  import { setTimeout } from "node:timers/promises";
5
5
  import { invariant } from "../domain/errors.js";
6
6
  import { inside } from "./files.js";
7
- import { workspaceLockAttempts, workspaceLocksDirectory, } from "./workspace-lock.constants.js";
7
+ import { workspaceLocksDirectory, workspaceRegistryPendingDeleteAttempts, workspaceRegistryStaleMs, } from "./workspace-lock.constants.js";
8
8
  import { readInspectionFile } from "./inspection-file.js";
9
9
  export async function canonicalWorkspacePath(value) {
10
10
  const path = resolve(value);
@@ -25,26 +25,57 @@ async function withRegistry(action) {
25
25
  !info.isSymbolicLink() &&
26
26
  (process.getuid === undefined || info.uid === process.getuid()), "Unsafe workspace lock registry");
27
27
  const gate = join(workspaceLocksDirectory, "gate");
28
- for (let attempt = 0; attempt < workspaceLockAttempts; attempt++) {
28
+ let observed;
29
+ let since = Date.now();
30
+ let pendingDeletes = 0;
31
+ for (;;) {
29
32
  try {
30
33
  await mkdir(gate, { mode: 0o700 });
31
34
  }
32
35
  catch (error) {
36
+ if (pendingDeletion(error) &&
37
+ ++pendingDeletes < workspaceRegistryPendingDeleteAttempts) {
38
+ await setTimeout(10);
39
+ continue;
40
+ }
33
41
  if (!(error instanceof Error &&
34
42
  "code" in error &&
35
43
  error.code === "EEXIST"))
36
44
  throw error;
37
- await setTimeout(10);
45
+ pendingDeletes = 0;
46
+ // A gate that keeps changing belongs to live holders; only an unchanged one is reported.
47
+ const current = await gateInstance(gate);
48
+ if (current !== observed)
49
+ since = Date.now();
50
+ observed = current;
51
+ if (current && Date.now() - since >= workspaceRegistryStaleMs)
52
+ throw new Error("Workspace lock registry is busy; inspect abandoned ownership before recovery");
53
+ await setTimeout(5 + Math.random() * 20);
38
54
  continue;
39
55
  }
40
56
  try {
41
57
  return await action();
42
58
  }
43
59
  finally {
44
- await rm(gate, { recursive: true });
60
+ await rm(gate, { recursive: true, maxRetries: 5 });
45
61
  }
46
62
  }
47
- throw new Error("Workspace lock registry is busy; inspect abandoned ownership before recovery");
63
+ }
64
+ async function gateInstance(gate) {
65
+ const info = await lstat(gate, { bigint: true }).catch((error) => {
66
+ if ((error instanceof Error && "code" in error && error.code === "ENOENT") ||
67
+ pendingDeletion(error))
68
+ return undefined;
69
+ throw error;
70
+ });
71
+ return info && `${info.dev}:${info.ino}:${info.birthtimeNs}:${info.ctimeNs}`;
72
+ }
73
+ // Windows reports EPERM while a just-released gate is pending deletion.
74
+ function pendingDeletion(error) {
75
+ return (process.platform === "win32" &&
76
+ error instanceof Error &&
77
+ "code" in error &&
78
+ error.code === "EPERM");
48
79
  }
49
80
  function parseLock(value) {
50
81
  invariant(value &&
@@ -144,11 +175,18 @@ async function acquireWorkspacePath(value, writable, excludedDirectories = []) {
144
175
  if (existing.excludedDirectories?.some((excluded) => inside(excluded, directory)) ||
145
176
  excludedDirectories.some((excluded) => inside(excluded, existing.directory)))
146
177
  continue;
147
- invariant(!(writable || existing.writable) ||
148
- !(inside(directory, existing.directory) ||
149
- inside(existing.directory, directory) ||
150
- containsIdentity(identities.ancestors, existing.identity) ||
151
- containsIdentity(existing.ancestors, identities.identity)), "Workspace path overlaps an active writer");
178
+ if (!(writable || existing.writable))
179
+ continue;
180
+ // Recorded inodes count only while the locked path still resolves to them; deleted ones get reused.
181
+ const current = await pathIdentities(existing.directory);
182
+ const identity = containsIdentity(current.identity ? [current.identity] : [], existing.identity)
183
+ ? existing.identity
184
+ : undefined;
185
+ const ancestors = existing.ancestors?.filter((entry) => containsIdentity(current.ancestors, entry));
186
+ invariant(!(inside(directory, existing.directory) ||
187
+ inside(existing.directory, directory) ||
188
+ containsIdentity(identities.ancestors, identity) ||
189
+ containsIdentity(ancestors, identities.identity)), "Workspace path overlaps an active writer");
152
190
  }
153
191
  await writeFile(path, JSON.stringify({
154
192
  id,