agent-hitch 0.2.6 → 0.2.7

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 (386) hide show
  1. package/README.md +14 -1
  2. package/dist/scripts/canary-buildkit-secrets.js +166 -0
  3. package/dist/scripts/canary-buildkit-secrets.js.map +1 -0
  4. package/dist/scripts/canary-collision-domains.js +77 -0
  5. package/dist/scripts/canary-collision-domains.js.map +1 -0
  6. package/dist/scripts/canary-docker-image-observation.js +181 -0
  7. package/dist/scripts/canary-docker-image-observation.js.map +1 -0
  8. package/dist/scripts/canary-gpu-hardware.js +95 -0
  9. package/dist/scripts/canary-gpu-hardware.js.map +1 -0
  10. package/dist/scripts/canary-harbor-load.js +365 -0
  11. package/dist/scripts/canary-harbor-load.js.map +1 -0
  12. package/dist/scripts/canary-packaged-harness.js +75 -0
  13. package/dist/scripts/canary-packaged-harness.js.map +1 -0
  14. package/dist/scripts/canary-resource-load.js +188 -0
  15. package/dist/scripts/canary-resource-load.js.map +1 -0
  16. package/dist/scripts/check-architecture.js +8 -4
  17. package/dist/scripts/check-architecture.js.map +1 -1
  18. package/dist/scripts/docker-reaper-crash-child.js +21 -0
  19. package/dist/scripts/docker-reaper-crash-child.js.map +1 -0
  20. package/dist/src/adapters/catalog.js +1 -0
  21. package/dist/src/adapters/catalog.js.map +1 -1
  22. package/dist/src/adapters/discovery.js +1 -1
  23. package/dist/src/adapters/discovery.js.map +1 -1
  24. package/dist/src/adapters/providers/claude.js +6 -0
  25. package/dist/src/adapters/providers/claude.js.map +1 -1
  26. package/dist/src/adapters/providers/codex.js +6 -0
  27. package/dist/src/adapters/providers/codex.js.map +1 -1
  28. package/dist/src/adapters/providers/deepseek.js +6 -0
  29. package/dist/src/adapters/providers/deepseek.js.map +1 -1
  30. package/dist/src/adapters/providers/opencode.js +6 -0
  31. package/dist/src/adapters/providers/opencode.js.map +1 -1
  32. package/dist/src/adapters/providers/pi.js +6 -0
  33. package/dist/src/adapters/providers/pi.js.map +1 -1
  34. package/dist/src/artifacts/index.js +1 -0
  35. package/dist/src/artifacts/index.js.map +1 -1
  36. package/dist/src/backends/harbor/backend.js +53 -103
  37. package/dist/src/backends/harbor/backend.js.map +1 -1
  38. package/dist/src/backends/harbor/environment-config.js +92 -0
  39. package/dist/src/backends/harbor/environment-config.js.map +1 -0
  40. package/dist/src/backends/harbor/index.js +5 -1
  41. package/dist/src/backends/harbor/index.js.map +1 -1
  42. package/dist/src/backends/harbor/model-proxy-config.js +23 -0
  43. package/dist/src/backends/harbor/model-proxy-config.js.map +1 -0
  44. package/dist/src/backends/harbor/process.js +177 -0
  45. package/dist/src/backends/harbor/process.js.map +1 -0
  46. package/dist/src/backends/harbor/runtime-toolchain.js +30 -0
  47. package/dist/src/backends/harbor/runtime-toolchain.js.map +1 -0
  48. package/dist/src/backends/harbor/supervisor.js +74 -0
  49. package/dist/src/backends/harbor/supervisor.js.map +1 -0
  50. package/dist/src/backends/harbor/task-resources.js +206 -0
  51. package/dist/src/backends/harbor/task-resources.js.map +1 -0
  52. package/dist/src/backends/harbor/tools.js +6 -29
  53. package/dist/src/backends/harbor/tools.js.map +1 -1
  54. package/dist/src/backends/index.js +4 -1
  55. package/dist/src/backends/index.js.map +1 -1
  56. package/dist/src/cli/commands/daemon.js +135 -8
  57. package/dist/src/cli/commands/daemon.js.map +1 -1
  58. package/dist/src/cli/commands/eval.js +214 -4
  59. package/dist/src/cli/commands/eval.js.map +1 -1
  60. package/dist/src/cli/commands/images.js +40 -0
  61. package/dist/src/cli/commands/images.js.map +1 -0
  62. package/dist/src/cli/commands/run.js +6 -3
  63. package/dist/src/cli/commands/run.js.map +1 -1
  64. package/dist/src/cli/commands/runs.js +25 -3
  65. package/dist/src/cli/commands/runs.js.map +1 -1
  66. package/dist/src/cli/commands/worker.js +79 -0
  67. package/dist/src/cli/commands/worker.js.map +1 -0
  68. package/dist/src/cli/main.js +4 -0
  69. package/dist/src/cli/main.js.map +1 -1
  70. package/dist/src/cli/output.js +71 -3
  71. package/dist/src/cli/output.js.map +1 -1
  72. package/dist/src/control-plane/build-admission.js +69 -0
  73. package/dist/src/control-plane/build-admission.js.map +1 -0
  74. package/dist/src/control-plane/build-records.js +6 -0
  75. package/dist/src/control-plane/build-records.js.map +1 -0
  76. package/dist/src/control-plane/collisions.js +40 -0
  77. package/dist/src/control-plane/collisions.js.map +1 -0
  78. package/dist/src/control-plane/eval-control-state.js +22 -0
  79. package/dist/src/control-plane/eval-control-state.js.map +1 -0
  80. package/dist/src/control-plane/eval-control-work.js +39 -0
  81. package/dist/src/control-plane/eval-control-work.js.map +1 -0
  82. package/dist/src/control-plane/eval-image-resolution.js +52 -0
  83. package/dist/src/control-plane/eval-image-resolution.js.map +1 -0
  84. package/dist/src/control-plane/eval-image-services.js +30 -0
  85. package/dist/src/control-plane/eval-image-services.js.map +1 -0
  86. package/dist/src/control-plane/eval-records.js +252 -0
  87. package/dist/src/control-plane/eval-records.js.map +1 -0
  88. package/dist/src/control-plane/eval-recovery.js +181 -0
  89. package/dist/src/control-plane/eval-recovery.js.map +1 -0
  90. package/dist/src/control-plane/eval-scheduler.js +447 -0
  91. package/dist/src/control-plane/eval-scheduler.js.map +1 -0
  92. package/dist/src/control-plane/index.js +20 -0
  93. package/dist/src/control-plane/index.js.map +1 -0
  94. package/dist/src/control-plane/local-worker.js +49 -0
  95. package/dist/src/control-plane/local-worker.js.map +1 -0
  96. package/dist/src/control-plane/model-capture-planning.js +12 -0
  97. package/dist/src/control-plane/model-capture-planning.js.map +1 -0
  98. package/dist/src/control-plane/remote-result-transport.js +172 -0
  99. package/dist/src/control-plane/remote-result-transport.js.map +1 -0
  100. package/dist/src/control-plane/remote-work-coordinator.js +332 -0
  101. package/dist/src/control-plane/remote-work-coordinator.js.map +1 -0
  102. package/dist/src/control-plane/remote-work-inputs.js +266 -0
  103. package/dist/src/control-plane/remote-work-inputs.js.map +1 -0
  104. package/dist/src/control-plane/remote-work-recovery.js +181 -0
  105. package/dist/src/control-plane/remote-work-recovery.js.map +1 -0
  106. package/dist/src/control-plane/remote-worker-artifacts.js +135 -0
  107. package/dist/src/control-plane/remote-worker-artifacts.js.map +1 -0
  108. package/dist/src/control-plane/remote-worker-client.js +202 -0
  109. package/dist/src/control-plane/remote-worker-client.js.map +1 -0
  110. package/dist/src/control-plane/remote-worker-configuration.js +34 -0
  111. package/dist/src/control-plane/remote-worker-configuration.js.map +1 -0
  112. package/dist/src/control-plane/remote-worker-credentials.js +58 -0
  113. package/dist/src/control-plane/remote-worker-credentials.js.map +1 -0
  114. package/dist/src/control-plane/remote-worker-protocol.js +510 -0
  115. package/dist/src/control-plane/remote-worker-protocol.js.map +1 -0
  116. package/dist/src/control-plane/remote-worker-runner.js +214 -0
  117. package/dist/src/control-plane/remote-worker-runner.js.map +1 -0
  118. package/dist/src/control-plane/remote-workers.js +377 -0
  119. package/dist/src/control-plane/remote-workers.js.map +1 -0
  120. package/dist/src/control-plane/rerun-scheduler.js +411 -0
  121. package/dist/src/control-plane/rerun-scheduler.js.map +1 -0
  122. package/dist/src/control-plane/rerun-submission.js +56 -0
  123. package/dist/src/control-plane/rerun-submission.js.map +1 -0
  124. package/dist/src/control-plane/resources.js +182 -0
  125. package/dist/src/control-plane/resources.js.map +1 -0
  126. package/dist/src/control-plane/scheduler-eval-entry.js +32 -0
  127. package/dist/src/control-plane/scheduler-eval-entry.js.map +1 -0
  128. package/dist/src/control-plane/synthetic-result.js +21 -0
  129. package/dist/src/control-plane/synthetic-result.js.map +1 -0
  130. package/dist/src/control-plane/work-admission.js +21 -0
  131. package/dist/src/control-plane/work-admission.js.map +1 -0
  132. package/dist/src/control-plane/work-dispatcher.js +229 -0
  133. package/dist/src/control-plane/work-dispatcher.js.map +1 -0
  134. package/dist/src/controller-runtime/hash.js +12 -1
  135. package/dist/src/controller-runtime/hash.js.map +1 -1
  136. package/dist/src/controller-runtime/index.js +1 -1
  137. package/dist/src/controller-runtime/index.js.map +1 -1
  138. package/dist/src/controller-runtime/store.js +15 -6
  139. package/dist/src/controller-runtime/store.js.map +1 -1
  140. package/dist/src/daemon/client.js.map +1 -1
  141. package/dist/src/daemon/event-stream.js +46 -0
  142. package/dist/src/daemon/event-stream.js.map +1 -0
  143. package/dist/src/daemon/health.js +66 -0
  144. package/dist/src/daemon/health.js.map +1 -0
  145. package/dist/src/daemon/launcher.js +28 -1
  146. package/dist/src/daemon/launcher.js.map +1 -1
  147. package/dist/src/daemon/logging.js +23 -0
  148. package/dist/src/daemon/logging.js.map +1 -0
  149. package/dist/src/daemon/scheduler.js +26 -5
  150. package/dist/src/daemon/scheduler.js.map +1 -1
  151. package/dist/src/daemon/server.js +255 -98
  152. package/dist/src/daemon/server.js.map +1 -1
  153. package/dist/src/daemon/telemetry.js +163 -0
  154. package/dist/src/daemon/telemetry.js.map +1 -0
  155. package/dist/src/daemon/worker-routes.js +219 -0
  156. package/dist/src/daemon/worker-routes.js.map +1 -0
  157. package/dist/src/domain/bundles.js +2 -0
  158. package/dist/src/domain/bundles.js.map +1 -0
  159. package/dist/src/domain/control-plane.js +2 -0
  160. package/dist/src/domain/control-plane.js.map +1 -0
  161. package/dist/src/domain/execution-evidence.js +2 -0
  162. package/dist/src/domain/execution-evidence.js.map +1 -0
  163. package/dist/src/domain/execution-plan.js +2 -0
  164. package/dist/src/domain/execution-plan.js.map +1 -0
  165. package/dist/src/domain/images.js +2 -0
  166. package/dist/src/domain/images.js.map +1 -0
  167. package/dist/src/domain/index.js +9 -0
  168. package/dist/src/domain/index.js.map +1 -1
  169. package/dist/src/domain/interactions.js +2 -0
  170. package/dist/src/domain/interactions.js.map +1 -0
  171. package/dist/src/domain/providers.js +2 -0
  172. package/dist/src/domain/providers.js.map +1 -0
  173. package/dist/src/domain/resources.js +2 -0
  174. package/dist/src/domain/resources.js.map +1 -0
  175. package/dist/src/domain/workers.js +2 -0
  176. package/dist/src/domain/workers.js.map +1 -0
  177. package/dist/src/evals/collect-only-rerun.js +182 -0
  178. package/dist/src/evals/collect-only-rerun.js.map +1 -0
  179. package/dist/src/evals/directory.js +30 -0
  180. package/dist/src/evals/directory.js.map +1 -0
  181. package/dist/src/evals/docker-engine-stats.js +89 -0
  182. package/dist/src/evals/docker-engine-stats.js.map +1 -0
  183. package/dist/src/evals/docker-ownership.js +51 -0
  184. package/dist/src/evals/docker-ownership.js.map +1 -0
  185. package/dist/src/evals/docker-reaper.js +144 -0
  186. package/dist/src/evals/docker-reaper.js.map +1 -0
  187. package/dist/src/evals/docker-resource-observer.js +207 -0
  188. package/dist/src/evals/docker-resource-observer.js.map +1 -0
  189. package/dist/src/evals/environment-image-planning.js +164 -0
  190. package/dist/src/evals/environment-image-planning.js.map +1 -0
  191. package/dist/src/evals/eval-artifact-planning.js +56 -0
  192. package/dist/src/evals/eval-artifact-planning.js.map +1 -0
  193. package/dist/src/evals/eval-finalization.js +10 -0
  194. package/dist/src/evals/eval-finalization.js.map +1 -0
  195. package/dist/src/evals/eval-lifecycle-events.js +16 -0
  196. package/dist/src/evals/eval-lifecycle-events.js.map +1 -0
  197. package/dist/src/evals/events.js +53 -5
  198. package/dist/src/evals/events.js.map +1 -1
  199. package/dist/src/evals/execution-evidence.js +116 -0
  200. package/dist/src/evals/execution-evidence.js.map +1 -0
  201. package/dist/src/evals/execution-leases.js +342 -0
  202. package/dist/src/evals/execution-leases.js.map +1 -0
  203. package/dist/src/evals/execution-plan-artifacts.js +72 -0
  204. package/dist/src/evals/execution-plan-artifacts.js.map +1 -0
  205. package/dist/src/evals/execution-plan-images.js +69 -0
  206. package/dist/src/evals/execution-plan-images.js.map +1 -0
  207. package/dist/src/evals/execution-plan-resources.js +174 -0
  208. package/dist/src/evals/execution-plan-resources.js.map +1 -0
  209. package/dist/src/evals/execution-plan.js +415 -0
  210. package/dist/src/evals/execution-plan.js.map +1 -0
  211. package/dist/src/evals/harbor-artifact-builder-image.js +99 -0
  212. package/dist/src/evals/harbor-artifact-builder-image.js.map +1 -0
  213. package/dist/src/evals/harbor-artifact-builder.js +354 -0
  214. package/dist/src/evals/harbor-artifact-builder.js.map +1 -0
  215. package/dist/src/evals/harbor-bridge-error.js +18 -3
  216. package/dist/src/evals/harbor-bridge-error.js.map +1 -1
  217. package/dist/src/evals/harbor-phase-timings.js +60 -0
  218. package/dist/src/evals/harbor-phase-timings.js.map +1 -0
  219. package/dist/src/evals/index.js +25 -2
  220. package/dist/src/evals/index.js.map +1 -1
  221. package/dist/src/evals/infrastructure-retry.js +100 -63
  222. package/dist/src/evals/infrastructure-retry.js.map +1 -1
  223. package/dist/src/evals/interaction-capture-import.js +49 -0
  224. package/dist/src/evals/interaction-capture-import.js.map +1 -0
  225. package/dist/src/evals/local-docker-provider.js +372 -0
  226. package/dist/src/evals/local-docker-provider.js.map +1 -0
  227. package/dist/src/evals/local-eval-planning.js +80 -0
  228. package/dist/src/evals/local-eval-planning.js.map +1 -0
  229. package/dist/src/evals/model-capture-plan.js +22 -0
  230. package/dist/src/evals/model-capture-plan.js.map +1 -0
  231. package/dist/src/evals/model-capture-runtime.js +114 -0
  232. package/dist/src/evals/model-capture-runtime.js.map +1 -0
  233. package/dist/src/evals/model-proxy-runtime-state.js +53 -0
  234. package/dist/src/evals/model-proxy-runtime-state.js.map +1 -0
  235. package/dist/src/evals/planned-execution-support.js +54 -0
  236. package/dist/src/evals/planned-execution-support.js.map +1 -0
  237. package/dist/src/evals/planned-execution.js +461 -0
  238. package/dist/src/evals/planned-execution.js.map +1 -0
  239. package/dist/src/evals/planned-remote-execution.js +40 -0
  240. package/dist/src/evals/planned-remote-execution.js.map +1 -0
  241. package/dist/src/evals/planned-retry-lifecycle.js +186 -0
  242. package/dist/src/evals/planned-retry-lifecycle.js.map +1 -0
  243. package/dist/src/evals/prepared-harness.js +76 -0
  244. package/dist/src/evals/prepared-harness.js.map +1 -0
  245. package/dist/src/evals/progress.js +4 -4
  246. package/dist/src/evals/progress.js.map +1 -1
  247. package/dist/src/evals/records.js +20 -3
  248. package/dist/src/evals/records.js.map +1 -1
  249. package/dist/src/evals/recovery.js +192 -0
  250. package/dist/src/evals/recovery.js.map +1 -0
  251. package/dist/src/evals/remote-infrastructure-retry.js +99 -0
  252. package/dist/src/evals/remote-infrastructure-retry.js.map +1 -0
  253. package/dist/src/evals/rerun-inputs.js +89 -0
  254. package/dist/src/evals/rerun-inputs.js.map +1 -0
  255. package/dist/src/evals/rerun-slots.js +4 -3
  256. package/dist/src/evals/rerun-slots.js.map +1 -1
  257. package/dist/src/evals/rerun-types.js +49 -0
  258. package/dist/src/evals/rerun-types.js.map +1 -0
  259. package/dist/src/evals/rerun.js +113 -75
  260. package/dist/src/evals/rerun.js.map +1 -1
  261. package/dist/src/evals/result-helpers.js +65 -0
  262. package/dist/src/evals/result-helpers.js.map +1 -0
  263. package/dist/src/evals/resume-state.js +36 -0
  264. package/dist/src/evals/resume-state.js.map +1 -0
  265. package/dist/src/evals/service-types.js +2 -0
  266. package/dist/src/evals/service-types.js.map +1 -0
  267. package/dist/src/evals/service.js +292 -257
  268. package/dist/src/evals/service.js.map +1 -1
  269. package/dist/src/evals/task-resources.js +209 -0
  270. package/dist/src/evals/task-resources.js.map +1 -0
  271. package/dist/src/evals/trial-environment-evidence.js +87 -0
  272. package/dist/src/evals/trial-environment-evidence.js.map +1 -0
  273. package/dist/src/evals/trial-execution-evidence.js +13 -0
  274. package/dist/src/evals/trial-execution-evidence.js.map +1 -0
  275. package/dist/src/evals/trial-import-identity.js +31 -0
  276. package/dist/src/evals/trial-import-identity.js.map +1 -0
  277. package/dist/src/evals/trial-import.js +143 -145
  278. package/dist/src/evals/trial-import.js.map +1 -1
  279. package/dist/src/evals/trial-publication-recovery.js +104 -0
  280. package/dist/src/evals/trial-publication-recovery.js.map +1 -0
  281. package/dist/src/evals/trial-publication.js +25 -0
  282. package/dist/src/evals/trial-publication.js.map +1 -0
  283. package/dist/src/evals/work-item-artifacts.js +23 -0
  284. package/dist/src/evals/work-item-artifacts.js.map +1 -0
  285. package/dist/src/foundation/config.js +17 -0
  286. package/dist/src/foundation/config.js.map +1 -1
  287. package/dist/src/foundation/credential-redaction.js +137 -0
  288. package/dist/src/foundation/credential-redaction.js.map +1 -0
  289. package/dist/src/foundation/environment-image-references.js +59 -0
  290. package/dist/src/foundation/environment-image-references.js.map +1 -0
  291. package/dist/src/foundation/executable.js +45 -5
  292. package/dist/src/foundation/executable.js.map +1 -1
  293. package/dist/src/foundation/index.js +5 -2
  294. package/dist/src/foundation/index.js.map +1 -1
  295. package/dist/src/foundation/process-identity.js +69 -0
  296. package/dist/src/foundation/process-identity.js.map +1 -0
  297. package/dist/src/images/context.js +47 -0
  298. package/dist/src/images/context.js.map +1 -0
  299. package/dist/src/images/docker-buildkit.js +137 -0
  300. package/dist/src/images/docker-buildkit.js.map +1 -0
  301. package/dist/src/images/dockerfile.js +67 -0
  302. package/dist/src/images/dockerfile.js.map +1 -0
  303. package/dist/src/images/gc.js +261 -0
  304. package/dist/src/images/gc.js.map +1 -0
  305. package/dist/src/images/index.js +10 -0
  306. package/dist/src/images/index.js.map +1 -0
  307. package/dist/src/images/manifest.js +109 -0
  308. package/dist/src/images/manifest.js.map +1 -0
  309. package/dist/src/images/ownership.js +5 -0
  310. package/dist/src/images/ownership.js.map +1 -0
  311. package/dist/src/images/records.js +32 -0
  312. package/dist/src/images/records.js.map +1 -0
  313. package/dist/src/images/registry.js +154 -0
  314. package/dist/src/images/registry.js.map +1 -0
  315. package/dist/src/images/service.js +224 -0
  316. package/dist/src/images/service.js.map +1 -0
  317. package/dist/src/model-access/capture.js +255 -0
  318. package/dist/src/model-access/capture.js.map +1 -0
  319. package/dist/src/model-access/index.js +5 -0
  320. package/dist/src/model-access/index.js.map +1 -0
  321. package/dist/src/model-access/policy.js +49 -0
  322. package/dist/src/model-access/policy.js.map +1 -0
  323. package/dist/src/model-access/proxy.js +336 -0
  324. package/dist/src/model-access/proxy.js.map +1 -0
  325. package/dist/src/model-access/records.js +167 -0
  326. package/dist/src/model-access/records.js.map +1 -0
  327. package/dist/src/runs/bundle.js +450 -0
  328. package/dist/src/runs/bundle.js.map +1 -0
  329. package/dist/src/runs/events.js +12 -1
  330. package/dist/src/runs/events.js.map +1 -1
  331. package/dist/src/runs/executor.js +24 -17
  332. package/dist/src/runs/executor.js.map +1 -1
  333. package/dist/src/runs/index.js +3 -1
  334. package/dist/src/runs/index.js.map +1 -1
  335. package/dist/src/runs/manifest.js +5 -5
  336. package/dist/src/runs/manifest.js.map +1 -1
  337. package/dist/src/runs/request.js +6 -1
  338. package/dist/src/runs/request.js.map +1 -1
  339. package/dist/src/runs/training-candidate.js +161 -0
  340. package/dist/src/runs/training-candidate.js.map +1 -0
  341. package/dist/src/trajectories/provider-capture.js +15 -24
  342. package/dist/src/trajectories/provider-capture.js.map +1 -1
  343. package/dist/src/trajectories/providers/deepseek.js +3 -3
  344. package/dist/src/trajectories/providers/deepseek.js.map +1 -1
  345. package/dist/src/workers/index.js +3 -0
  346. package/dist/src/workers/index.js.map +1 -0
  347. package/dist/src/workers/remote-harbor-work-spec.js +97 -0
  348. package/dist/src/workers/remote-harbor-work-spec.js.map +1 -0
  349. package/dist/src/workers/remote-harbor-worker.js +220 -0
  350. package/dist/src/workers/remote-harbor-worker.js.map +1 -0
  351. package/docs/schemas/environment-build-index.schema.json +13 -0
  352. package/docs/schemas/environment-build-record.schema.json +25 -0
  353. package/docs/schemas/environment-image-manifest.schema.json +62 -0
  354. package/docs/schemas/eval-control.schema.json +36 -0
  355. package/docs/schemas/eval-execution-plan.schema.json +216 -0
  356. package/docs/schemas/eval-rerun-result.schema.json +53 -0
  357. package/docs/schemas/eval-rerun-state.schema.json +61 -0
  358. package/docs/schemas/eval-rerun-submission.schema.json +58 -0
  359. package/docs/schemas/eval-result.schema.json +34 -1
  360. package/docs/schemas/eval-submission-input.schema.json +14 -0
  361. package/docs/schemas/eval-submission.schema.json +68 -0
  362. package/docs/schemas/eval-trial-publication.schema.json +39 -0
  363. package/docs/schemas/execution-evidence.schema.json +96 -0
  364. package/docs/schemas/execution-lease.schema.json +50 -0
  365. package/docs/schemas/execution-provider-status.schema.json +65 -0
  366. package/docs/schemas/execution-worker.schema.json +53 -0
  367. package/docs/schemas/harbor-process-exit.schema.json +14 -0
  368. package/docs/schemas/interaction-capture-ref.schema.json +45 -0
  369. package/docs/schemas/local-provider-execution.schema.json +33 -0
  370. package/docs/schemas/model-interaction.schema.json +41 -0
  371. package/docs/schemas/remote-result-envelope.schema.json +32 -0
  372. package/docs/schemas/remote-tree-envelope.schema.json +25 -0
  373. package/docs/schemas/remote-work-offer.schema.json +58 -0
  374. package/docs/schemas/remote-work-receipt.schema.json +50 -0
  375. package/docs/schemas/remote-worker-artifact.schema.json +17 -0
  376. package/docs/schemas/remote-worker-event.schema.json +18 -0
  377. package/docs/schemas/remote-worker-heartbeat.schema.json +37 -0
  378. package/docs/schemas/remote-worker-registration.schema.json +52 -0
  379. package/docs/schemas/result-bundle-index.schema.json +133 -0
  380. package/docs/schemas/run-environment-images.schema.json +39 -0
  381. package/docs/schemas/training-data-candidate.schema.json +46 -0
  382. package/integrations/harbor/hitch_harbor_agent.py +171 -539
  383. package/integrations/harbor/hitch_harbor_environment.py +331 -0
  384. package/integrations/harbor/hitch_harbor_task_resources.py +275 -0
  385. package/integrations/harbor/hitch_harbor_verifier.py +23 -0
  386. package/package.json +14 -1
@@ -0,0 +1,34 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { atomicWriteJSON, invalidInput } from "../foundation/index.js";
4
+ import { parseRemoteWorkerCredential } from "./remote-worker-client.js";
5
+ import { parseRemoteWorkerRegistration } from "./remote-workers.js";
6
+ export async function loadRemoteWorkerRegistration(file) {
7
+ return parseRemoteWorkerRegistration(await jsonFile(file, "worker registration"));
8
+ }
9
+ export async function loadRemoteWorkerCredential(file) {
10
+ return parseRemoteWorkerCredential(await jsonFile(file, "worker credential"));
11
+ }
12
+ export async function loadRemoteWorkerToken(file) {
13
+ try {
14
+ return (await readFile(path.resolve(file), "utf8")).trim();
15
+ }
16
+ catch (error) {
17
+ throw invalidInput(`worker admin credential is not readable: ${safeMessage(error)}`);
18
+ }
19
+ }
20
+ export function writeRemoteWorkerCredential(file, credential) {
21
+ return atomicWriteJSON(path.resolve(file), parseRemoteWorkerCredential(credential), 0o600);
22
+ }
23
+ async function jsonFile(file, label) {
24
+ try {
25
+ return JSON.parse(await readFile(path.resolve(file), "utf8"));
26
+ }
27
+ catch (error) {
28
+ throw invalidInput(`${label} is not readable JSON: ${safeMessage(error)}`);
29
+ }
30
+ }
31
+ function safeMessage(error) {
32
+ return (error?.message || String(error)).replace(/[\0\r\n]/g, " ").slice(0, 1_024);
33
+ }
34
+ //# sourceMappingURL=remote-worker-configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-worker-configuration.js","sourceRoot":"","sources":["../../../src/control-plane/remote-worker-configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAExE,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAEpE,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,IAAY;IAC7D,OAAO,6BAA6B,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,IAAY;IAC3D,OAAO,2BAA2B,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAY;IACtD,IAAI,CAAC;QAAC,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAAC,CAAC;IACnE,OAAO,KAAK,EAAE,CAAC;QAAC,MAAM,YAAY,CAAC,4CAA4C,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAAC,CAAC;AACzG,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,IAAY,EAAE,UAAoC;IAC5F,OAAO,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,2BAA2B,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC;AAC7F,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAa;IACjD,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAY,CAAC;IAAC,CAAC;IACjF,OAAO,KAAK,EAAE,CAAC;QAAC,MAAM,YAAY,CAAC,GAAG,KAAK,0BAA0B,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAAC,CAAC;AAC/F,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,CAAE,KAAe,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAChG,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { HitchError, PROVIDER_ENVIRONMENT_NAMES } from "../foundation/index.js";
2
+ export const DEFAULT_CREDENTIAL_ENVELOPE_TTL_MS = 60_000;
3
+ export class RemoteCredentialEnvelopeIssuer {
4
+ env;
5
+ ttlMs;
6
+ constructor(input = {}) {
7
+ const ttl = input.ttlMs ?? DEFAULT_CREDENTIAL_ENVELOPE_TTL_MS;
8
+ if (!Number.isSafeInteger(ttl) || ttl < 1_000 || ttl > 5 * 60_000) {
9
+ throw new TypeError("remote credential envelope TTL is invalid");
10
+ }
11
+ this.env = input.env ?? process.env;
12
+ this.ttlMs = ttl;
13
+ }
14
+ namesFor(explicitNames) {
15
+ const names = new Set(PROVIDER_ENVIRONMENT_NAMES.filter((name) => this.env[name] !== undefined));
16
+ for (const name of canonicalRemoteCredentialNames(explicitNames))
17
+ names.add(name);
18
+ return this.requireAvailable([...names]);
19
+ }
20
+ requireAvailable(value) {
21
+ const names = canonicalRemoteCredentialNames(value);
22
+ for (const name of names) {
23
+ if (this.env[name] === undefined) {
24
+ throw new HitchError(`remote credential is unavailable: ${name}`, { code: "credential_unavailable", exitCode: 10 });
25
+ }
26
+ }
27
+ return names;
28
+ }
29
+ issue(offer) {
30
+ if (offer.state !== "accepted")
31
+ throw credentialError("remote credential envelope requires an accepted active lease");
32
+ const credentials = {};
33
+ for (const name of offer.credential_names ?? []) {
34
+ const value = this.env[name];
35
+ if (value === undefined) {
36
+ throw new HitchError(`remote credential is unavailable: ${name}`, { code: "credential_unavailable", exitCode: 10 });
37
+ }
38
+ credentials[name] = value;
39
+ }
40
+ const issued = new Date();
41
+ return {
42
+ schema_version: "1", worker_id: offer.worker_id, generation: offer.generation, offer_id: offer.offer_id,
43
+ lease_id: offer.lease.lease_id, epoch: offer.lease.epoch, issued_at: issued.toISOString(),
44
+ expires_at: new Date(issued.getTime() + this.ttlMs).toISOString(), credentials,
45
+ };
46
+ }
47
+ }
48
+ export function canonicalRemoteCredentialNames(value) {
49
+ if (!Array.isArray(value) || value.length > 256
50
+ || value.some((name) => typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
51
+ || new Set(value).size !== value.length)
52
+ throw credentialError("remote credential names are invalid");
53
+ return [...value].sort();
54
+ }
55
+ function credentialError(message) {
56
+ return new HitchError(message, { code: "worker_protocol_invalid", exitCode: 12 });
57
+ }
58
+ //# sourceMappingURL=remote-worker-credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-worker-credentials.js","sourceRoot":"","sources":["../../../src/control-plane/remote-worker-credentials.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEhF,MAAM,CAAC,MAAM,kCAAkC,GAAG,MAAM,CAAC;AAEzD,MAAM,OAAO,8BAA8B;IACxB,GAAG,CAAoB;IACvB,KAAK,CAAS;IAE/B,YAAY,QAAqD,EAAE;QACjE,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,IAAI,kCAAkC,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC;YAClE,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;IACnB,CAAC;IAED,QAAQ,CAAC,aAAgC;QACvC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,0BAA0B,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;QACzG,KAAK,MAAM,IAAI,IAAI,8BAA8B,CAAC,aAAa,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClF,OAAO,IAAI,CAAC,gBAAgB,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,gBAAgB,CAAC,KAAwB;QACvC,MAAM,KAAK,GAAG,8BAA8B,CAAC,KAAK,CAAC,CAAC;QACpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;gBACjC,MAAM,IAAI,UAAU,CAAC,qCAAqC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;YACtH,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,KAAwB;QAC5B,IAAI,KAAK,CAAC,KAAK,KAAK,UAAU;YAAE,MAAM,eAAe,CAAC,8DAA8D,CAAC,CAAC;QACtH,MAAM,WAAW,GAA2B,EAAE,CAAC;QAC/C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,gBAAgB,IAAI,EAAE,EAAE,CAAC;YAChD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,MAAM,IAAI,UAAU,CAAC,qCAAqC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;YACtH,CAAC;YACD,WAAW,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QAC5B,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QAC1B,OAAO;YACL,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACvG,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE;YACzF,UAAU,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE,WAAW;SAC/E,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,8BAA8B,CAAC,KAAwB;IACrE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG;WAC1C,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;WACxF,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM;QAAE,MAAM,eAAe,CAAC,qCAAqC,CAAC,CAAC;IACxG,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,SAAS,eAAe,CAAC,OAAe;IACtC,OAAO,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;AACpF,CAAC"}
@@ -0,0 +1,510 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { readdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { HitchError, atomicWriteJSON, ensureDir, readJSON, sha256JSON, statePaths, withFileLock } from "../foundation/index.js";
5
+ import { parseExecutionLease } from "../evals/index.js";
6
+ import { maxResourceVectors, resourceValue, subtractResourceVectors, sumResourceVectors, validateResourceVector } from "./resources.js";
7
+ import { RemoteWorkerArtifactStore } from "./remote-worker-artifacts.js";
8
+ import { RemoteWorkInputStore } from "./remote-work-inputs.js";
9
+ import { RemoteCredentialEnvelopeIssuer, canonicalRemoteCredentialNames } from "./remote-worker-credentials.js";
10
+ const OFFER_ID = /^offer_[a-f0-9]{32}$/;
11
+ const LEASE_ID = /^lease_[a-f0-9]{32}$/;
12
+ const SHA256 = /^sha256:[a-f0-9]{64}$/;
13
+ const DEFAULT_OFFER_TTL_MS = 30_000;
14
+ export class RemoteWorkerProtocol {
15
+ registry;
16
+ directory;
17
+ locks;
18
+ offerTtlMs;
19
+ credentials;
20
+ artifacts;
21
+ inputs;
22
+ constructor(input) {
23
+ const ttl = input.offerTtlMs ?? DEFAULT_OFFER_TTL_MS;
24
+ if (!input.root || !Number.isSafeInteger(ttl) || ttl < 1_000 || ttl > 5 * 60_000)
25
+ throw new TypeError("remote worker protocol configuration is invalid");
26
+ const paths = statePaths(input.root);
27
+ this.registry = input.registry;
28
+ this.directory = paths.workerProtocol;
29
+ this.locks = paths.workerProtocolLocks;
30
+ this.offerTtlMs = ttl;
31
+ this.credentials = new RemoteCredentialEnvelopeIssuer({
32
+ ...(input.credentialEnv ? { env: input.credentialEnv } : {}),
33
+ ...(input.credentialEnvelopeTtlMs === undefined ? {} : { ttlMs: input.credentialEnvelopeTtlMs }),
34
+ });
35
+ this.artifacts = new RemoteWorkerArtifactStore({ root: input.root });
36
+ this.inputs = new RemoteWorkInputStore(input.root);
37
+ }
38
+ async initialize() {
39
+ await Promise.all([ensureDir(this.directory), ensureDir(this.locks), this.artifacts.initialize(), this.inputs.initialize()]);
40
+ }
41
+ credentialNamesFor(explicitNames) {
42
+ return this.credentials.namesFor(explicitNames);
43
+ }
44
+ async createOffer(workerId, leaseValue, workValue, inputRefs = [], credentialNames = []) {
45
+ return withFileLock(this.locks, `offers-${workerId}`, async () => {
46
+ const worker = await this.requireReadyWorker(workerId);
47
+ const lease = parseExecutionLease(leaseValue);
48
+ const work = parseRemoteWorkItem(workValue);
49
+ const inputs = inputRefs.map(parseInputRef);
50
+ const credentials = this.credentials.requireAvailable(credentialNames);
51
+ if (new Set(inputs.map((entry) => entry.kind)).size !== inputs.length)
52
+ throw protocolError("remote work inputs are duplicated");
53
+ if (lease.state !== "offered" || lease.worker_id !== workerId || lease.provider !== worker.worker.provider
54
+ || lease.collision_domain_id !== worker.worker.collision_domain_id || lease.work_id !== work.work_id
55
+ || lease.eval_id !== work.eval_id || work.provider !== worker.worker.provider
56
+ || JSON.stringify(lease.reservation) !== JSON.stringify(work.reservation))
57
+ throw protocolError("remote work offer identity is invalid");
58
+ const outstanding = sumReservations(await this.listOffers(workerId, worker.generation));
59
+ const used = maxResources(worker.worker.capacity.allocated, outstanding);
60
+ assertFits(lease.reservation, available(worker.worker.capacity.allocatable, used));
61
+ const now = new Date();
62
+ const offer = {
63
+ schema_version: "1",
64
+ offer_id: `offer_${randomUUID().replaceAll("-", "")}`,
65
+ nonce: randomBytes(32).toString("hex"),
66
+ generation: worker.generation,
67
+ worker_id: workerId,
68
+ lease,
69
+ work,
70
+ ...(inputs.length > 0 ? { inputs } : {}),
71
+ ...(credentials.length > 0 ? { credential_names: credentials } : {}),
72
+ state: "offered",
73
+ issued_at: now.toISOString(),
74
+ expires_at: new Date(now.getTime() + this.offerTtlMs).toISOString(),
75
+ };
76
+ await atomicWriteJSON(this.offerPath(workerId, offer.offer_id), offer);
77
+ return offer;
78
+ }, { timeoutCode: "worker_offer_selection_locked", timeoutExitCode: 12 });
79
+ }
80
+ async listOffers(workerId, generation) {
81
+ await this.requireWorkerGeneration(workerId, generation);
82
+ const directory = this.offersDirectory(workerId);
83
+ await ensureDir(directory);
84
+ const entries = await readdir(directory, { withFileTypes: true });
85
+ const offers = [];
86
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
87
+ if (!entry.isFile() || !/^offer_[a-f0-9]{32}\.json$/.test(entry.name))
88
+ continue;
89
+ let offer = parseRemoteWorkOffer(await readJSON(path.join(directory, entry.name)));
90
+ if (offer.state === "offered" && Date.parse(offer.expires_at) <= Date.now())
91
+ offer = await this.expireOffer(offer);
92
+ if (offer.state === "offered" || offer.state === "accepted" || offer.state === "cancel-requested" || offer.state === "completed" || offer.state === "release-requested")
93
+ offers.push(offer);
94
+ }
95
+ return offers;
96
+ }
97
+ async acceptOffer(workerId, value) {
98
+ const receipt = parseAcceptReceipt(value);
99
+ await this.requireWorkerGeneration(workerId, receipt.generation);
100
+ return this.updateOffer(workerId, receipt.offer_id, async (offer) => {
101
+ assertOfferReceipt(offer, workerId, receipt.generation, receipt.nonce);
102
+ const digest = sha256JSON(receipt);
103
+ if (offer.accept_receipt_digest) {
104
+ if (offer.accept_receipt_digest !== digest)
105
+ throw replayError("remote work offer acceptance conflicts with its receipt");
106
+ return offer;
107
+ }
108
+ if (offer.state !== "offered")
109
+ throw replayError(`remote work offer cannot be accepted from ${offer.state}`);
110
+ if (Date.parse(offer.expires_at) <= Date.now())
111
+ return { ...offer, state: "expired" };
112
+ const now = new Date().toISOString();
113
+ if (!receipt.accepted)
114
+ return {
115
+ ...offer,
116
+ state: "rejected",
117
+ rejection_code: receipt.rejection_code,
118
+ accept_receipt_digest: digest,
119
+ completed_at: now,
120
+ };
121
+ await atomicWriteJSON(this.leaseIndexPath(offer.lease.lease_id), {
122
+ schema_version: "1", worker_id: workerId, offer_id: offer.offer_id,
123
+ lease_id: offer.lease.lease_id, epoch: offer.lease.epoch,
124
+ });
125
+ return { ...offer, state: "accepted", accepted_at: now, accept_receipt_digest: digest };
126
+ });
127
+ }
128
+ async recordEvent(workerId, value) {
129
+ const event = parseRemoteWorkerEvent(value);
130
+ await this.requireWorkerGeneration(workerId, event.generation);
131
+ const offer = await this.offerForLease(event.lease_id);
132
+ if (offer.worker_id !== workerId || offer.generation !== event.generation || offer.lease.epoch !== event.epoch
133
+ || !new Set(["accepted", "cancel-requested", "completed", "release-requested"]).has(offer.state))
134
+ throw protocolError("remote worker event lease is not active");
135
+ return withFileLock(this.locks, `event-${event.lease_id}`, async () => {
136
+ const statePath = this.eventStatePath(event.lease_id);
137
+ const state = await readJSON(statePath, null);
138
+ const sequence = state === null ? 0 : validSequence(state.sequence, true);
139
+ const file = this.eventPath(event.lease_id, event.sequence);
140
+ const existing = await readJSON(file, null);
141
+ if (existing !== null) {
142
+ const parsed = parseRemoteWorkerEvent(existing);
143
+ if (sha256JSON(parsed) !== sha256JSON(event))
144
+ throw replayError("remote worker event sequence was replayed with different content");
145
+ if (event.sequence === sequence + 1)
146
+ await atomicWriteJSON(statePath, { schema_version: "1", sequence: event.sequence });
147
+ else if (event.sequence > sequence)
148
+ throw protocolError("remote worker event sequence has a gap");
149
+ return { event: parsed, duplicate: true };
150
+ }
151
+ if (event.sequence !== sequence + 1)
152
+ throw protocolError("remote worker event sequence is not contiguous");
153
+ await atomicWriteJSON(file, event);
154
+ await atomicWriteJSON(statePath, { schema_version: "1", sequence: event.sequence });
155
+ return { event, duplicate: false };
156
+ }, { timeoutCode: "worker_event_locked", timeoutExitCode: 12 });
157
+ }
158
+ async completeOffer(workerId, value) {
159
+ const receipt = parseTerminalReceipt(value);
160
+ await this.requireWorkerGeneration(workerId, receipt.generation);
161
+ return this.updateOffer(workerId, receipt.offer_id, async (offer) => {
162
+ assertOfferReceipt(offer, workerId, receipt.generation, receipt.nonce);
163
+ if (offer.lease.lease_id !== receipt.lease_id || offer.lease.epoch !== receipt.epoch)
164
+ throw protocolError("remote work terminal receipt lease is invalid");
165
+ const digest = sha256JSON(receipt);
166
+ if (offer.terminal_receipt_digest) {
167
+ if (offer.terminal_receipt_digest !== digest)
168
+ throw replayError("remote work terminal receipt conflicts with the persisted receipt");
169
+ return offer;
170
+ }
171
+ if (offer.state !== "accepted" && offer.state !== "cancel-requested")
172
+ throw replayError(`remote work offer cannot complete from ${offer.state}`);
173
+ for (const artifact of receipt.artifacts)
174
+ await this.artifacts.verify(workerId, receipt.lease_id, artifact, receipt.epoch);
175
+ return {
176
+ ...offer,
177
+ state: "completed",
178
+ completed_at: receipt.sent_at,
179
+ terminal: { status: receipt.status, artifacts: receipt.artifacts, sent_at: receipt.sent_at },
180
+ terminal_receipt_digest: digest,
181
+ };
182
+ });
183
+ }
184
+ async requestCancel(workerId, offerId) {
185
+ return this.updateOffer(workerId, offerId, async (offer) => {
186
+ if (offer.state === "cancel-requested" || offer.state === "completed" || offer.state === "released")
187
+ return offer;
188
+ if (offer.state !== "offered" && offer.state !== "accepted")
189
+ throw protocolError(`remote work offer cannot be cancelled from ${offer.state}`);
190
+ return { ...offer, state: "cancel-requested" };
191
+ });
192
+ }
193
+ async withdrawUnacceptedOffer(workerId, offerId) {
194
+ return this.updateOffer(workerId, offerId, async (offer) => {
195
+ if (offer.state !== "offered")
196
+ return offer;
197
+ return { ...offer, state: "expired", completed_at: new Date().toISOString() };
198
+ });
199
+ }
200
+ async requestRelease(workerId, offerId) {
201
+ return this.updateOffer(workerId, offerId, async (offer) => {
202
+ if (offer.state === "release-requested" || offer.state === "released")
203
+ return offer;
204
+ if (offer.state !== "completed")
205
+ throw protocolError(`remote work offer cannot request release from ${offer.state}`);
206
+ return { ...offer, state: "release-requested" };
207
+ });
208
+ }
209
+ async releaseOffer(workerId, value) {
210
+ const receipt = parseReleaseReceipt(value);
211
+ await this.requireWorkerGeneration(workerId, receipt.generation);
212
+ return this.updateOffer(workerId, receipt.offer_id, async (offer) => {
213
+ assertOfferReceipt(offer, workerId, receipt.generation, receipt.nonce);
214
+ if (offer.lease.lease_id !== receipt.lease_id || offer.lease.epoch !== receipt.epoch)
215
+ throw protocolError("remote work release receipt lease is invalid");
216
+ const digest = sha256JSON(receipt);
217
+ if (offer.release_receipt_digest) {
218
+ if (offer.release_receipt_digest !== digest)
219
+ throw replayError("remote work release receipt conflicts with the persisted receipt");
220
+ return offer;
221
+ }
222
+ if (offer.state !== "completed" && offer.state !== "release-requested" && offer.state !== "cancel-requested")
223
+ throw protocolError(`remote work offer cannot be released from ${offer.state}`);
224
+ return {
225
+ ...offer,
226
+ state: "released",
227
+ ...(offer.terminal ? {} : {
228
+ completed_at: receipt.sent_at,
229
+ terminal: { status: "cancelled", artifacts: [], sent_at: receipt.sent_at },
230
+ }),
231
+ released_at: receipt.sent_at,
232
+ release_receipt_digest: digest,
233
+ };
234
+ });
235
+ }
236
+ async getOffer(workerId, offerId) {
237
+ validateOfferIdentity(workerId, offerId);
238
+ const value = await readJSON(this.offerPath(workerId, offerId), null);
239
+ return value === null ? null : parseRemoteWorkOffer(value);
240
+ }
241
+ async getOfferForLease(leaseId) {
242
+ if (!LEASE_ID.test(leaseId))
243
+ throw protocolError("remote work lease id is invalid");
244
+ try {
245
+ return await this.offerForLease(leaseId);
246
+ }
247
+ catch (error) {
248
+ if (error.code === "worker_offer_not_found")
249
+ return null;
250
+ throw error;
251
+ }
252
+ }
253
+ async findOfferForLease(workerId, leaseId) {
254
+ if (!/^worker_[a-z0-9][a-z0-9_-]{0,62}$/.test(workerId) || !LEASE_ID.test(leaseId))
255
+ throw protocolError("remote work lease identity is invalid");
256
+ const directory = this.offersDirectory(workerId);
257
+ const entries = await readdir(directory, { withFileTypes: true }).catch((error) => {
258
+ if (error.code === "ENOENT")
259
+ return [];
260
+ throw error;
261
+ });
262
+ const matches = [];
263
+ for (const entry of entries) {
264
+ if (!entry.isFile() || !/^offer_[a-f0-9]{32}\.json$/.test(entry.name))
265
+ continue;
266
+ const offer = parseRemoteWorkOffer(await readJSON(path.join(directory, entry.name)));
267
+ if (offer.lease.lease_id === leaseId)
268
+ matches.push(offer);
269
+ }
270
+ if (matches.length > 1)
271
+ throw protocolError(`remote worker lease has multiple offers: ${leaseId}`);
272
+ return matches[0] ?? null;
273
+ }
274
+ async uploadArtifact(input) {
275
+ const offer = await this.activeOfferForLease(input.workerId, input.leaseId, input.generation, input.epoch);
276
+ if (offer.state !== "accepted" && offer.state !== "cancel-requested")
277
+ throw protocolError("remote worker artifact lease is not collecting evidence");
278
+ return this.artifacts.upload(input);
279
+ }
280
+ async activeOfferForLease(workerId, leaseId, generation, epoch) {
281
+ await this.requireWorkerGeneration(workerId, generation);
282
+ const offer = await this.offerForLease(leaseId);
283
+ if (offer.worker_id !== workerId || offer.generation !== generation || offer.lease.epoch !== epoch)
284
+ throw protocolError("remote worker lease identity is invalid");
285
+ return offer;
286
+ }
287
+ async issueCredentialEnvelope(workerId, leaseId, generation, epoch) {
288
+ const offer = await this.activeOfferForLease(workerId, leaseId, generation, epoch);
289
+ return this.credentials.issue(offer);
290
+ }
291
+ async validateHeartbeatLeases(workerId, heartbeat) {
292
+ await this.registry.validateHeartbeatGeneration(workerId, heartbeat.generation);
293
+ for (const lease of heartbeat.active_leases) {
294
+ const offer = await this.offerForLease(lease.lease_id).catch(() => null);
295
+ if (!offer || offer.worker_id !== workerId || offer.generation !== heartbeat.generation
296
+ || offer.lease.epoch !== lease.epoch || !acceptedOrCollecting(offer.state)) {
297
+ throw protocolError(`remote worker heartbeat reports an unauthorized lease: ${lease.lease_id}`);
298
+ }
299
+ }
300
+ }
301
+ async resolveInput(workerId, leaseId, generation, digest) {
302
+ await this.requireWorkerGeneration(workerId, generation);
303
+ const offer = await this.findOfferForLease(workerId, leaseId);
304
+ if (!offer || offer.generation !== generation || !new Set(["offered", "accepted", "cancel-requested"]).has(offer.state)) {
305
+ throw protocolError("remote work input lease is not active");
306
+ }
307
+ const ref = offer.inputs?.find((entry) => entry.digest === digest);
308
+ if (!ref)
309
+ throw protocolError("remote work input is not authorized for this lease");
310
+ return this.inputs.verify(ref);
311
+ }
312
+ artifactPath(workerId, leaseId, digest) {
313
+ return this.artifacts.pathFor(workerId, leaseId, digest);
314
+ }
315
+ async updateOffer(workerId, offerId, update) {
316
+ validateOfferIdentity(workerId, offerId);
317
+ return withFileLock(this.locks, offerId, async () => {
318
+ const current = await this.getOffer(workerId, offerId);
319
+ if (!current)
320
+ throw new HitchError(`remote work offer not found: ${offerId}`, { code: "worker_offer_not_found", exitCode: 3 });
321
+ const next = parseRemoteWorkOffer(await update(current));
322
+ if (next !== current)
323
+ await atomicWriteJSON(this.offerPath(workerId, offerId), next);
324
+ return next;
325
+ }, { timeoutCode: "worker_offer_locked", timeoutExitCode: 12 });
326
+ }
327
+ async expireOffer(offer) {
328
+ return this.updateOffer(offer.worker_id, offer.offer_id, async (current) => current.state === "offered" && Date.parse(current.expires_at) <= Date.now()
329
+ ? { ...current, state: "expired" }
330
+ : current);
331
+ }
332
+ async offerForLease(leaseId) {
333
+ if (!LEASE_ID.test(leaseId))
334
+ throw protocolError("remote worker lease id is invalid");
335
+ const index = await readJSON(this.leaseIndexPath(leaseId), null);
336
+ if (!index || typeof index.worker_id !== "string" || typeof index.offer_id !== "string")
337
+ throw protocolError("remote worker lease has no accepted offer");
338
+ const offer = await this.getOffer(index.worker_id, index.offer_id);
339
+ if (!offer || index.lease_id !== leaseId || index.epoch !== offer.lease.epoch)
340
+ throw protocolError("remote worker lease index is invalid");
341
+ return offer;
342
+ }
343
+ async requireReadyWorker(workerId) {
344
+ const worker = await this.registry.get(workerId);
345
+ if (!worker || worker.worker.status !== "ready" || worker.revoked_at)
346
+ throw new HitchError("remote worker is unavailable", { code: "worker_unavailable", exitCode: 10 });
347
+ return worker;
348
+ }
349
+ async requireWorkerGeneration(workerId, generation) {
350
+ const worker = await this.requireReadyWorker(workerId);
351
+ if (worker.generation !== generation)
352
+ throw new HitchError("remote worker generation is stale", { code: "worker_generation_mismatch", exitCode: 12 });
353
+ }
354
+ offersDirectory(workerId) { return path.join(this.directory, "workers", workerId, "offers"); }
355
+ offerPath(workerId, offerId) { return path.join(this.offersDirectory(workerId), `${offerId}.json`); }
356
+ leaseIndexPath(leaseId) { return path.join(this.directory, "leases", `${leaseId}.json`); }
357
+ eventStatePath(leaseId) { return path.join(this.directory, "events", leaseId, "state.json"); }
358
+ eventPath(leaseId, sequence) { return path.join(this.directory, "events", leaseId, `${String(sequence).padStart(12, "0")}.json`); }
359
+ }
360
+ export function parseRemoteWorkOffer(value) {
361
+ const record = exact(value, [
362
+ "schema_version", "offer_id", "nonce", "generation", "worker_id", "lease", "work", "inputs", "credential_names", "state", "issued_at", "expires_at",
363
+ "accepted_at", "completed_at", "released_at", "rejection_code", "terminal",
364
+ "accept_receipt_digest", "terminal_receipt_digest", "release_receipt_digest",
365
+ ], "remote work offer");
366
+ const states = new Set(["offered", "accepted", "rejected", "cancel-requested", "completed", "release-requested", "released", "expired"]);
367
+ if (record.schema_version !== "1" || typeof record.offer_id !== "string" || !OFFER_ID.test(record.offer_id)
368
+ || typeof record.nonce !== "string" || !/^[a-f0-9]{64}$/.test(record.nonce)
369
+ || !Number.isSafeInteger(record.generation) || record.generation < 1
370
+ || typeof record.worker_id !== "string" || !/^worker_[a-z0-9][a-z0-9_-]{0,62}$/.test(record.worker_id)
371
+ || !states.has(String(record.state)) || !timestamp(record.issued_at) || !timestamp(record.expires_at))
372
+ throw protocolError("remote work offer identity is invalid");
373
+ for (const field of ["accepted_at", "completed_at", "released_at"])
374
+ if (record[field] !== undefined && !timestamp(record[field]))
375
+ throw protocolError(`remote work offer ${field} is invalid`);
376
+ for (const field of ["accept_receipt_digest", "terminal_receipt_digest", "release_receipt_digest"])
377
+ if (record[field] !== undefined && (typeof record[field] !== "string" || !SHA256.test(record[field])))
378
+ throw protocolError(`remote work offer ${field} is invalid`);
379
+ const lease = parseExecutionLease(record.lease);
380
+ const work = parseRemoteWorkItem(record.work);
381
+ const inputs = record.inputs === undefined ? undefined : Array.isArray(record.inputs) ? record.inputs.map(parseInputRef) : (() => { throw protocolError("remote work inputs are invalid"); })();
382
+ if (inputs && new Set(inputs.map((entry) => entry.kind)).size !== inputs.length)
383
+ throw protocolError("remote work inputs are duplicated");
384
+ const credentialNames = record.credential_names === undefined ? undefined : canonicalRemoteCredentialNames(record.credential_names);
385
+ const terminal = record.terminal === undefined ? undefined : parseTerminal(record.terminal);
386
+ if (lease.worker_id !== record.worker_id || lease.work_id !== work.work_id || lease.eval_id !== work.eval_id
387
+ || (record.state === "completed" || record.state === "release-requested" || record.state === "released") !== (terminal !== undefined))
388
+ throw protocolError("remote work offer evidence is inconsistent");
389
+ return { ...record, ...(inputs ? { inputs } : {}), ...(credentialNames?.length ? { credential_names: credentialNames } : {}) };
390
+ }
391
+ function parseRemoteWorkItem(value) {
392
+ const record = exact(value, ["schema_version", "work_id", "eval_id", "backend", "logical_attempt", "task_ids", "slots", "opaque_membership", "requested_parallelism", "reservation", "provider", "image_refs", "artifact_id", "runtime_contract"], "remote work item");
393
+ if (record.schema_version !== "1" || typeof record.work_id !== "string" || !/^work_[a-f0-9]{32}$/.test(record.work_id)
394
+ || typeof record.eval_id !== "string" || !/^eval_[a-f0-9]{32}$/.test(record.eval_id) || record.backend !== "harbor"
395
+ || record.logical_attempt !== null && (!Number.isSafeInteger(record.logical_attempt) || record.logical_attempt < 1)
396
+ || !stringArray(record.task_ids) || !stringArray(record.slots) || typeof record.opaque_membership !== "boolean"
397
+ || !Number.isSafeInteger(record.requested_parallelism) || record.requested_parallelism < 1
398
+ || typeof record.provider !== "string" || !record.provider || record.image_refs !== undefined && !Array.isArray(record.image_refs)
399
+ || record.artifact_id !== undefined && (typeof record.artifact_id !== "string" || !SHA256.test(record.artifact_id))
400
+ || (record.artifact_id === undefined) !== (record.runtime_contract === undefined))
401
+ throw protocolError("remote work item is invalid");
402
+ if (record.runtime_contract !== undefined) {
403
+ const runtime = exact(record.runtime_contract, ["docker_platform", "artifact_platform", "node_version"], "remote work runtime contract");
404
+ if ((runtime.docker_platform !== "linux/amd64" && runtime.docker_platform !== "linux/arm64")
405
+ || (runtime.artifact_platform !== "linux-x64" && runtime.artifact_platform !== "linux-arm64")
406
+ || (runtime.docker_platform === "linux/amd64") !== (runtime.artifact_platform === "linux-x64")
407
+ || typeof runtime.node_version !== "string" || !/^v\d+\.\d+\.\d+$/.test(runtime.node_version))
408
+ throw protocolError("remote work runtime contract is invalid");
409
+ }
410
+ return { ...record, reservation: validateResourceVector(record.reservation, "remote work reservation") };
411
+ }
412
+ function parseAcceptReceipt(value) {
413
+ const record = exact(value, ["schema_version", "offer_id", "nonce", "generation", "accepted", "rejection_code", "sent_at"], "remote work accept receipt");
414
+ if (record.schema_version !== "1" || typeof record.offer_id !== "string" || !OFFER_ID.test(record.offer_id)
415
+ || typeof record.nonce !== "string" || !/^[a-f0-9]{64}$/.test(record.nonce) || !Number.isSafeInteger(record.generation) || record.generation < 1
416
+ || typeof record.accepted !== "boolean" || !timestamp(record.sent_at)
417
+ || record.accepted === false !== (typeof record.rejection_code === "string" && Boolean(record.rejection_code)))
418
+ throw protocolError("remote work accept receipt is invalid");
419
+ return record;
420
+ }
421
+ function parseTerminalReceipt(value) {
422
+ const record = exact(value, ["schema_version", "offer_id", "nonce", "generation", "lease_id", "epoch", "status", "artifacts", "sent_at"], "remote work terminal receipt");
423
+ if (record.schema_version !== "1" || typeof record.offer_id !== "string" || !OFFER_ID.test(record.offer_id)
424
+ || typeof record.nonce !== "string" || !/^[a-f0-9]{64}$/.test(record.nonce) || !Number.isSafeInteger(record.generation) || record.generation < 1
425
+ || typeof record.lease_id !== "string" || !LEASE_ID.test(record.lease_id) || !Number.isSafeInteger(record.epoch) || record.epoch < 1
426
+ || !new Set(["succeeded", "failed", "cancelled"]).has(String(record.status)) || !Array.isArray(record.artifacts) || !timestamp(record.sent_at))
427
+ throw protocolError("remote work terminal receipt is invalid");
428
+ const artifacts = record.artifacts.map(parseArtifactRef).sort((left, right) => left.digest.localeCompare(right.digest));
429
+ if (new Set(artifacts.map((entry) => `${entry.kind}:${entry.digest}`)).size !== artifacts.length)
430
+ throw protocolError("remote work terminal artifacts are duplicated");
431
+ return { ...record, artifacts };
432
+ }
433
+ function parseReleaseReceipt(value) {
434
+ const record = exact(value, ["schema_version", "offer_id", "nonce", "generation", "lease_id", "epoch", "sent_at"], "remote work release receipt");
435
+ if (record.schema_version !== "1" || typeof record.offer_id !== "string" || !OFFER_ID.test(record.offer_id)
436
+ || typeof record.nonce !== "string" || !/^[a-f0-9]{64}$/.test(record.nonce) || !Number.isSafeInteger(record.generation) || record.generation < 1
437
+ || typeof record.lease_id !== "string" || !LEASE_ID.test(record.lease_id) || !Number.isSafeInteger(record.epoch) || record.epoch < 1 || !timestamp(record.sent_at))
438
+ throw protocolError("remote work release receipt is invalid");
439
+ return record;
440
+ }
441
+ export function parseRemoteWorkerEvent(value) {
442
+ const record = exact(value, ["schema_version", "generation", "lease_id", "epoch", "sequence", "type", "payload", "sent_at"], "remote worker event");
443
+ if (record.schema_version !== "1" || !Number.isSafeInteger(record.generation) || record.generation < 1
444
+ || typeof record.lease_id !== "string" || !LEASE_ID.test(record.lease_id) || !Number.isSafeInteger(record.epoch) || record.epoch < 1
445
+ || !Number.isSafeInteger(record.sequence) || record.sequence < 1 || typeof record.type !== "string" || !record.type || record.type.length > 256
446
+ || record.payload !== undefined && (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload)) || !timestamp(record.sent_at))
447
+ throw protocolError("remote worker event is invalid");
448
+ return record;
449
+ }
450
+ function parseTerminal(value) {
451
+ const record = exact(value, ["status", "artifacts", "sent_at"], "remote work terminal evidence");
452
+ if (!new Set(["succeeded", "failed", "cancelled"]).has(String(record.status)) || !Array.isArray(record.artifacts) || !timestamp(record.sent_at))
453
+ throw protocolError("remote work terminal evidence is invalid");
454
+ return { status: record.status, artifacts: record.artifacts.map(parseArtifactRef), sent_at: record.sent_at };
455
+ }
456
+ function parseArtifactRef(value) {
457
+ const record = exact(value, ["kind", "digest", "size"], "remote work artifact ref");
458
+ if (record.kind !== "result-bundle" && record.kind !== "diagnostic" || typeof record.digest !== "string" || !SHA256.test(record.digest)
459
+ || !Number.isSafeInteger(record.size) || record.size < 0 || record.size > 512 * 1024 * 1024)
460
+ throw protocolError("remote work artifact ref is invalid");
461
+ return { kind: record.kind, digest: record.digest, size: record.size };
462
+ }
463
+ function parseInputRef(value) {
464
+ const record = exact(value, ["kind", "format", "digest", "size"], "remote work input ref");
465
+ if (!new Set(["work-spec", "harness-artifact", "controller-runtime", "task-input"]).has(String(record.kind))
466
+ || !new Set(["json", "hitch-tree-v1"]).has(String(record.format)) || typeof record.digest !== "string" || !SHA256.test(record.digest)
467
+ || !Number.isSafeInteger(record.size) || record.size < 1 || record.size > 256 * 1024 * 1024)
468
+ throw protocolError("remote work input ref is invalid");
469
+ return record;
470
+ }
471
+ function assertOfferReceipt(offer, workerId, generation, nonce) {
472
+ if (offer.worker_id !== workerId || offer.generation !== generation || offer.nonce !== nonce)
473
+ throw replayError("remote work receipt identity does not match the offer");
474
+ }
475
+ function validateOfferIdentity(workerId, offerId) {
476
+ if (!/^worker_[a-z0-9][a-z0-9_-]{0,62}$/.test(workerId) || !OFFER_ID.test(offerId))
477
+ throw protocolError("remote work offer lookup identity is invalid");
478
+ }
479
+ function available(allocatable, allocated) {
480
+ return subtractResourceVectors(allocatable, allocated);
481
+ }
482
+ function sumReservations(offers) {
483
+ return sumResourceVectors(offers.map((offer) => offer.lease.reservation));
484
+ }
485
+ function maxResources(left, right) {
486
+ return maxResourceVectors(left, right);
487
+ }
488
+ function assertFits(requested, capacity) {
489
+ if (fields().some((field) => resourceValue(requested, field) > resourceValue(capacity, field)))
490
+ throw new HitchError("remote worker rejected work capacity", { code: "worker_rejected", exitCode: 10 });
491
+ }
492
+ function acceptedOrCollecting(state) {
493
+ return state === "accepted" || state === "cancel-requested" || state === "completed" || state === "release-requested";
494
+ }
495
+ function fields() { return ["cpu_millis", "memory_bytes", "container_slots", "build_slots", "gpu_count", "ephemeral_disk_bytes"]; }
496
+ function timestamp(value) { return typeof value === "string" && Number.isFinite(Date.parse(value)); }
497
+ function stringArray(value) { return Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.length > 0 && !/[\0\r\n]/.test(entry)) && new Set(value).size === value.length; }
498
+ function validSequence(value, allowZero) { if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1))
499
+ throw protocolError("remote worker event sequence state is invalid"); return value; }
500
+ function exact(value, keys, label) {
501
+ if (!value || typeof value !== "object" || Array.isArray(value))
502
+ throw protocolError(`${label} must be an object`);
503
+ const record = value;
504
+ if (Object.keys(record).some((key) => !keys.includes(key)))
505
+ throw protocolError(`${label} has unknown fields`);
506
+ return record;
507
+ }
508
+ function protocolError(message) { return new HitchError(message, { code: "worker_protocol_invalid", exitCode: 2 }); }
509
+ function replayError(message) { return new HitchError(message, { code: "worker_protocol_replay", exitCode: 12 }); }
510
+ //# sourceMappingURL=remote-worker-protocol.js.map