@huynle/hub 0.6.1 → 0.7.2

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 (355) hide show
  1. package/CHANGELOG.md +870 -0
  2. package/Dockerfile +33 -0
  3. package/README.md +1007 -66
  4. package/package.json +47 -2
  5. package/packages/claude-code/package.json +12 -0
  6. package/packages/claude-code/src/bridge.ts +79 -0
  7. package/packages/claude-code/src/daemon.ts +339 -0
  8. package/packages/claude-code/src/hook-input.ts +44 -0
  9. package/packages/claude-code/src/ids.ts +27 -0
  10. package/packages/claude-code/src/index.ts +18 -0
  11. package/packages/claude-code/src/launcher.ts +265 -0
  12. package/packages/claude-code/src/main.ts +127 -0
  13. package/packages/claude-code/src/mcp.ts +109 -0
  14. package/packages/claude-code/src/paths.ts +34 -0
  15. package/packages/claude-code/src/plugin.ts +99 -0
  16. package/packages/claude-code/src/references.ts +51 -0
  17. package/packages/claude-code/src/sessions.ts +600 -0
  18. package/packages/claude-code/src/tools.ts +66 -0
  19. package/packages/claude-code/src/wire.ts +71 -0
  20. package/packages/claude-code/src/wrapper.ts +156 -0
  21. package/packages/cli/dist/hub-cli.js +21647 -0
  22. package/packages/cli/package.json +11 -0
  23. package/packages/cli/src/acting.ts +70 -0
  24. package/packages/cli/src/args.ts +91 -0
  25. package/packages/cli/src/bootstrap/adapters/archive-stream.ts +276 -0
  26. package/packages/cli/src/bootstrap/adapters/claude/README.md +63 -0
  27. package/packages/cli/src/bootstrap/adapters/claude/adapter.ts +250 -0
  28. package/packages/cli/src/bootstrap/adapters/claude/index.ts +42 -0
  29. package/packages/cli/src/bootstrap/adapters/claude/node.ts +99 -0
  30. package/packages/cli/src/bootstrap/adapters/claude/openpgp.ts +221 -0
  31. package/packages/cli/src/bootstrap/adapters/claude/ports.ts +44 -0
  32. package/packages/cli/src/bootstrap/adapters/claude/source.ts +160 -0
  33. package/packages/cli/src/bootstrap/adapters/claude/verify.ts +165 -0
  34. package/packages/cli/src/bootstrap/adapters/codex/archive.ts +103 -0
  35. package/packages/cli/src/bootstrap/adapters/codex/index.ts +60 -0
  36. package/packages/cli/src/bootstrap/adapters/codex/plan.ts +129 -0
  37. package/packages/cli/src/bootstrap/adapters/codex/sigstore.ts +298 -0
  38. package/packages/cli/src/bootstrap/adapters/codex/source.ts +102 -0
  39. package/packages/cli/src/bootstrap/adapters/codex/verify.ts +298 -0
  40. package/packages/cli/src/bootstrap/adapters/inflate.ts +433 -0
  41. package/packages/cli/src/bootstrap/adapters/node-services/control.ts +392 -0
  42. package/packages/cli/src/bootstrap/adapters/node-services/index.ts +23 -0
  43. package/packages/cli/src/bootstrap/adapters/node-services/launch.ts +223 -0
  44. package/packages/cli/src/bootstrap/adapters/node-services/plan.ts +222 -0
  45. package/packages/cli/src/bootstrap/adapters/node-services/render.ts +183 -0
  46. package/packages/cli/src/bootstrap/adapters/node-services/validate.ts +197 -0
  47. package/packages/cli/src/bootstrap/adapters/opencode/README.md +204 -0
  48. package/packages/cli/src/bootstrap/adapters/opencode/apply.ts +205 -0
  49. package/packages/cli/src/bootstrap/adapters/opencode/archive.ts +91 -0
  50. package/packages/cli/src/bootstrap/adapters/opencode/config.ts +212 -0
  51. package/packages/cli/src/bootstrap/adapters/opencode/index.ts +81 -0
  52. package/packages/cli/src/bootstrap/adapters/opencode/plan.ts +266 -0
  53. package/packages/cli/src/bootstrap/adapters/opencode/verify.ts +208 -0
  54. package/packages/cli/src/bootstrap/adapters/readiness.ts +19 -0
  55. package/packages/cli/src/bootstrap/ansible/index.ts +2 -0
  56. package/packages/cli/src/bootstrap/ansible/render.ts +279 -0
  57. package/packages/cli/src/bootstrap/contracts/actions.ts +470 -0
  58. package/packages/cli/src/bootstrap/contracts/artifact.ts +323 -0
  59. package/packages/cli/src/bootstrap/contracts/canonical.ts +54 -0
  60. package/packages/cli/src/bootstrap/contracts/credentials.ts +108 -0
  61. package/packages/cli/src/bootstrap/contracts/discovery.ts +215 -0
  62. package/packages/cli/src/bootstrap/contracts/envelope.ts +143 -0
  63. package/packages/cli/src/bootstrap/contracts/exec.ts +74 -0
  64. package/packages/cli/src/bootstrap/contracts/harness.ts +202 -0
  65. package/packages/cli/src/bootstrap/contracts/index.ts +28 -0
  66. package/packages/cli/src/bootstrap/contracts/journal.ts +198 -0
  67. package/packages/cli/src/bootstrap/contracts/paths.ts +91 -0
  68. package/packages/cli/src/bootstrap/contracts/plan.ts +155 -0
  69. package/packages/cli/src/bootstrap/contracts/platform.ts +62 -0
  70. package/packages/cli/src/bootstrap/contracts/preview.ts +190 -0
  71. package/packages/cli/src/bootstrap/contracts/profile.ts +105 -0
  72. package/packages/cli/src/bootstrap/contracts/secrets.ts +52 -0
  73. package/packages/cli/src/bootstrap/contracts/validate.ts +212 -0
  74. package/packages/cli/src/bootstrap/discovery/discover.ts +146 -0
  75. package/packages/cli/src/bootstrap/discovery/executables.ts +134 -0
  76. package/packages/cli/src/bootstrap/discovery/host.ts +96 -0
  77. package/packages/cli/src/bootstrap/discovery/index.ts +6 -0
  78. package/packages/cli/src/bootstrap/discovery/node-probe.ts +97 -0
  79. package/packages/cli/src/bootstrap/discovery/probe.ts +41 -0
  80. package/packages/cli/src/bootstrap/discovery/util.ts +13 -0
  81. package/packages/cli/src/bootstrap/enrollment/enroll.ts +226 -0
  82. package/packages/cli/src/bootstrap/enrollment/index.ts +7 -0
  83. package/packages/cli/src/bootstrap/enrollment/policy.ts +54 -0
  84. package/packages/cli/src/bootstrap/enrollment/result.ts +63 -0
  85. package/packages/cli/src/bootstrap/enrollment/status.ts +119 -0
  86. package/packages/cli/src/bootstrap/enrollment/store.ts +173 -0
  87. package/packages/cli/src/bootstrap/preview/index.ts +5 -0
  88. package/packages/cli/src/bootstrap/preview/links.ts +31 -0
  89. package/packages/cli/src/bootstrap/preview/plan.ts +242 -0
  90. package/packages/cli/src/bootstrap/preview/preview.ts +104 -0
  91. package/packages/cli/src/bootstrap/preview/render.ts +30 -0
  92. package/packages/cli/src/bootstrap/profiles/audit.ts +87 -0
  93. package/packages/cli/src/bootstrap/profiles/catalog.ts +49 -0
  94. package/packages/cli/src/bootstrap/profiles/desired.ts +96 -0
  95. package/packages/cli/src/bootstrap/profiles/drift.ts +286 -0
  96. package/packages/cli/src/bootstrap/profiles/history.ts +263 -0
  97. package/packages/cli/src/bootstrap/profiles/index.ts +11 -0
  98. package/packages/cli/src/bootstrap/profiles/upgrade.ts +276 -0
  99. package/packages/cli/src/bootstrap/profiles/versions.ts +50 -0
  100. package/packages/cli/src/bootstrap/release/README.md +115 -0
  101. package/packages/cli/src/bootstrap/release/assets.ts +39 -0
  102. package/packages/cli/src/bootstrap/release/cli.ts +332 -0
  103. package/packages/cli/src/bootstrap/release/harness-pins.json +94 -0
  104. package/packages/cli/src/bootstrap/release/manifest.ts +118 -0
  105. package/packages/cli/src/bootstrap/release/pins.ts +192 -0
  106. package/packages/cli/src/bootstrap/release/text-assets.d.ts +9 -0
  107. package/packages/cli/src/bootstrap/release/trust-root.json +13 -0
  108. package/packages/cli/src/bootstrap/release/trust-root.ts +39 -0
  109. package/packages/cli/src/bootstrap/release/verify.ts +88 -0
  110. package/packages/cli/src/bootstrap/transaction/builtin.ts +312 -0
  111. package/packages/cli/src/bootstrap/transaction/engine.ts +994 -0
  112. package/packages/cli/src/bootstrap/transaction/executor.ts +112 -0
  113. package/packages/cli/src/bootstrap/transaction/fs.ts +194 -0
  114. package/packages/cli/src/bootstrap/transaction/index.ts +12 -0
  115. package/packages/cli/src/bootstrap/transaction/json-merge.ts +403 -0
  116. package/packages/cli/src/bootstrap/transaction/lock.ts +132 -0
  117. package/packages/cli/src/bootstrap/transaction/resolve.ts +48 -0
  118. package/packages/cli/src/bootstrap/transaction/result.ts +60 -0
  119. package/packages/cli/src/bootstrap/transaction/state.ts +88 -0
  120. package/packages/cli/src/bootstrap/transaction/store.ts +189 -0
  121. package/packages/cli/src/bootstrap/transaction/stream.ts +254 -0
  122. package/packages/cli/src/cli.ts +814 -0
  123. package/packages/cli/src/credentials.ts +75 -0
  124. package/packages/cli/src/decisions.ts +106 -0
  125. package/packages/cli/src/format.ts +185 -0
  126. package/packages/cli/src/grants.ts +105 -0
  127. package/packages/cli/src/http.ts +86 -0
  128. package/packages/cli/src/launches.ts +157 -0
  129. package/packages/cli/src/login.ts +160 -0
  130. package/packages/cli/src/main.ts +63 -0
  131. package/packages/cli/src/mcp.ts +879 -0
  132. package/packages/cli/src/messaging.ts +108 -0
  133. package/packages/cli/src/node.ts +279 -0
  134. package/packages/cli/src/openshell.ts +166 -0
  135. package/packages/cli/src/setup/commands.ts +809 -0
  136. package/packages/cli/src/setup/contributors.ts +171 -0
  137. package/packages/cli/src/setup/dispatch.ts +74 -0
  138. package/packages/cli/src/setup/launch-driver.ts +109 -0
  139. package/packages/cli/src/setup/runtime.ts +456 -0
  140. package/packages/cli/src/setup/spoke-probe.ts +120 -0
  141. package/packages/cli/src/setup/state.ts +244 -0
  142. package/packages/cli/src/setup/user-bin.ts +39 -0
  143. package/packages/cli/src/spoke-services.ts +109 -0
  144. package/packages/cli/src/watch.ts +28 -0
  145. package/packages/codex/package.json +12 -0
  146. package/packages/codex/src/app-server.ts +44 -0
  147. package/packages/codex/src/ids.ts +28 -0
  148. package/packages/codex/src/index.ts +11 -0
  149. package/packages/codex/src/jsonrpc.ts +168 -0
  150. package/packages/codex/src/main.ts +123 -0
  151. package/packages/codex/src/paths.ts +65 -0
  152. package/packages/codex/src/sessions.ts +578 -0
  153. package/packages/codex/src/spoke.ts +252 -0
  154. package/packages/codex/src/stdio.ts +101 -0
  155. package/packages/codex/src/websocket.ts +206 -0
  156. package/packages/codex/src/wrapper.ts +104 -0
  157. package/packages/node/package.json +12 -0
  158. package/packages/node/src/config.ts +184 -0
  159. package/packages/node/src/connection.ts +283 -0
  160. package/packages/node/src/daemon.ts +281 -0
  161. package/packages/node/src/git.ts +107 -0
  162. package/packages/node/src/harnesses.ts +95 -0
  163. package/packages/node/src/host.ts +319 -0
  164. package/packages/node/src/index.ts +17 -0
  165. package/packages/node/src/main.ts +11 -0
  166. package/packages/node/src/openshell.ts +73 -0
  167. package/packages/node/src/run.ts +106 -0
  168. package/packages/preset/package.json +12 -0
  169. package/packages/preset/presets/aider.json +8 -0
  170. package/packages/preset/presets/goose.json +8 -0
  171. package/packages/preset/src/index.ts +2 -0
  172. package/packages/preset/src/main.ts +195 -0
  173. package/packages/preset/src/presets.ts +87 -0
  174. package/packages/preset/src/spoke.ts +251 -0
  175. package/packages/preset/src/tmux.ts +160 -0
  176. package/packages/protocol/package.json +12 -0
  177. package/packages/protocol/src/harness-events.ts +207 -0
  178. package/packages/protocol/src/index.ts +1215 -0
  179. package/packages/protocol/src/launch.ts +172 -0
  180. package/packages/protocol/src/node.ts +409 -0
  181. package/packages/protocol/src/preset.ts +144 -0
  182. package/packages/protocol/src/webhook.ts +62 -0
  183. package/packages/protocol/src/wire.ts +54 -0
  184. package/packages/runtime-openshell/image/Dockerfile +36 -0
  185. package/packages/runtime-openshell/image/README.md +78 -0
  186. package/packages/runtime-openshell/image/build.ts +203 -0
  187. package/packages/runtime-openshell/image/entry.sh +163 -0
  188. package/packages/runtime-openshell/image/image.ts +119 -0
  189. package/packages/runtime-openshell/image/start.sh +24 -0
  190. package/packages/runtime-openshell/package.json +12 -0
  191. package/packages/runtime-openshell/src/cli.ts +584 -0
  192. package/packages/runtime-openshell/src/index.ts +6 -0
  193. package/packages/runtime-openshell/src/policy.ts +155 -0
  194. package/packages/runtime-openshell/src/profiles.ts +181 -0
  195. package/packages/runtime-openshell/src/runtime.ts +475 -0
  196. package/packages/runtime-sdk/package.json +12 -0
  197. package/packages/runtime-sdk/src/index.ts +87 -0
  198. package/packages/sdk/package.json +12 -0
  199. package/packages/sdk/src/address.ts +25 -0
  200. package/packages/sdk/src/client.ts +553 -0
  201. package/packages/sdk/src/envelope.ts +108 -0
  202. package/packages/sdk/src/errors.ts +107 -0
  203. package/packages/sdk/src/index.ts +10 -0
  204. package/packages/sdk/src/sse.ts +70 -0
  205. package/packages/sdk/src/types.ts +517 -0
  206. package/packages/sdk/src/webhook.ts +102 -0
  207. package/packages/spoke-sdk/package.json +12 -0
  208. package/packages/spoke-sdk/src/activity.ts +3 -0
  209. package/packages/spoke-sdk/src/connection.ts +295 -0
  210. package/packages/spoke-sdk/src/delivery.ts +119 -0
  211. package/packages/spoke-sdk/src/enroll.ts +101 -0
  212. package/packages/spoke-sdk/src/harness-hello.ts +79 -0
  213. package/packages/spoke-sdk/src/harness-spoke.ts +263 -0
  214. package/packages/spoke-sdk/src/index.ts +12 -0
  215. package/packages/spoke-sdk/src/prompt-refs.ts +154 -0
  216. package/packages/spoke-sdk/src/prompt-source.ts +8 -0
  217. package/packages/spoke-sdk/src/session-ids.ts +49 -0
  218. package/packages/spoke-sdk/src/session-tokens.ts +78 -0
  219. package/scripts/hub-claude-code.service +34 -0
  220. package/scripts/hub-codex-spoke.service +34 -0
  221. package/scripts/hub-node.service +35 -0
  222. package/scripts/io.github.huynle.hub-claude-code.plist +53 -0
  223. package/scripts/io.github.huynle.hub-codex-spoke.plist +53 -0
  224. package/scripts/io.github.huynle.hub-node.plist +52 -0
  225. package/src/central/actions.ts +233 -0
  226. package/src/central/agent/acting-routes.ts +133 -0
  227. package/src/central/agent/acting-switch.ts +93 -0
  228. package/src/central/agent/acting.ts +599 -0
  229. package/src/central/agent/actor.ts +193 -0
  230. package/src/central/agent/app-tokens.ts +152 -0
  231. package/src/central/agent/archive-inactive.ts +66 -0
  232. package/src/central/agent/audit.ts +185 -0
  233. package/src/central/agent/decisions.ts +530 -0
  234. package/src/central/agent/delivery.ts +678 -0
  235. package/src/central/agent/directory-routes.ts +328 -0
  236. package/src/central/agent/directory.ts +473 -0
  237. package/src/central/agent/envelope.ts +51 -0
  238. package/src/central/agent/errors.ts +74 -0
  239. package/src/central/agent/escalation.ts +219 -0
  240. package/src/central/agent/event-cursor.ts +49 -0
  241. package/src/central/agent/event-writers.ts +181 -0
  242. package/src/central/agent/events-routes.ts +148 -0
  243. package/src/central/agent/events.ts +277 -0
  244. package/src/central/agent/grant-requests.ts +221 -0
  245. package/src/central/agent/grant-routes.ts +70 -0
  246. package/src/central/agent/grants.ts +235 -0
  247. package/src/central/agent/launch-engine.ts +1025 -0
  248. package/src/central/agent/launch-routes.ts +252 -0
  249. package/src/central/agent/launch-spec.ts +289 -0
  250. package/src/central/agent/launch-store.ts +264 -0
  251. package/src/central/agent/launch-tokens.ts +206 -0
  252. package/src/central/agent/message-limits.ts +123 -0
  253. package/src/central/agent/message-routes.ts +385 -0
  254. package/src/central/agent/messages.ts +442 -0
  255. package/src/central/agent/names.ts +86 -0
  256. package/src/central/agent/placement.ts +419 -0
  257. package/src/central/agent/policy.ts +378 -0
  258. package/src/central/agent/read-routes.ts +385 -0
  259. package/src/central/agent/routes.ts +281 -0
  260. package/src/central/agent/session-tokens.ts +227 -0
  261. package/src/central/agent/versioned.ts +26 -0
  262. package/src/central/agent/watch.ts +112 -0
  263. package/src/central/agent/webhook-routes.ts +86 -0
  264. package/src/central/agent/webhooks.ts +493 -0
  265. package/src/central/agent-data.ts +177 -0
  266. package/src/central/auth.ts +350 -121
  267. package/src/central/backend/file-state.ts +240 -0
  268. package/src/central/backend/index.ts +116 -0
  269. package/src/central/backend/memory-bus.ts +93 -0
  270. package/src/central/backend/memory-catalog.ts +90 -0
  271. package/src/central/backend/memory-counters.ts +102 -0
  272. package/src/central/backend/memory-events.ts +45 -0
  273. package/src/central/backend/memory-kv.ts +38 -0
  274. package/src/central/backend/memory-state.ts +77 -0
  275. package/src/central/backend/postgres-migrate.ts +100 -0
  276. package/src/central/backend/postgres-state.ts +197 -0
  277. package/src/central/backend/types.ts +246 -0
  278. package/src/central/backend/valkey-bus.ts +425 -0
  279. package/src/central/backend/valkey-catalog.ts +190 -0
  280. package/src/central/backend/valkey-client.ts +36 -0
  281. package/src/central/backend/valkey-counters.ts +38 -0
  282. package/src/central/backend/valkey-events.ts +168 -0
  283. package/src/central/backend/valkey-kv.ts +54 -0
  284. package/src/central/backend/valkey-locks.ts +92 -0
  285. package/src/central/cluster/directory.ts +141 -0
  286. package/src/central/cluster/feed.ts +84 -0
  287. package/src/central/cluster/leader.ts +232 -0
  288. package/src/central/cluster/live.ts +151 -0
  289. package/src/central/cluster/recommend.ts +59 -0
  290. package/src/central/cluster/rpc.ts +150 -0
  291. package/src/central/cluster/view.ts +232 -0
  292. package/src/central/config.ts +197 -3
  293. package/src/central/done.ts +96 -0
  294. package/src/central/events-log.ts +100 -0
  295. package/src/central/http.ts +1149 -170
  296. package/src/central/import.ts +318 -0
  297. package/src/central/main.ts +211 -12
  298. package/src/central/mcp-http.ts +116 -0
  299. package/src/central/nodes.ts +314 -0
  300. package/src/central/oidc.ts +34 -14
  301. package/src/central/push-target.ts +118 -18
  302. package/src/central/push.ts +256 -82
  303. package/src/central/registry.ts +902 -92
  304. package/src/central/say-stream.ts +128 -0
  305. package/src/central/session-token-push.ts +233 -0
  306. package/src/central/speech/openai.ts +21 -14
  307. package/src/central/speech/openrouter.ts +27 -10
  308. package/src/central/speech/provider.ts +54 -0
  309. package/src/central/speech/service.ts +22 -7
  310. package/src/central/spoke-ws.ts +522 -44
  311. package/src/central/store-shared.ts +518 -0
  312. package/src/central/store.ts +625 -189
  313. package/src/central/token-keys.ts +165 -0
  314. package/src/config.ts +118 -7
  315. package/src/index.ts +195 -43
  316. package/src/protocol.ts +3 -650
  317. package/src/rpc.ts +27 -2
  318. package/src/spoke/client.ts +3 -272
  319. package/src/spoke/conversation.ts +16 -12
  320. package/src/spoke/deliver.ts +75 -0
  321. package/src/spoke/enroll.ts +2 -79
  322. package/src/spoke/env-inject.ts +160 -0
  323. package/src/spoke/hub-rpc.ts +115 -0
  324. package/src/spoke/hub-tool.ts +409 -0
  325. package/src/spoke/launch-source.ts +22 -0
  326. package/src/spoke/launchers.ts +174 -0
  327. package/src/spoke/locations.ts +3 -2
  328. package/src/spoke/node-roots.ts +61 -0
  329. package/src/spoke/prompt-refs.ts +116 -0
  330. package/src/spoke/session-tokens.ts +2 -0
  331. package/src/spoke/spoke.ts +465 -28
  332. package/src/spoke/taskboard.ts +243 -0
  333. package/src/spoke/timeline.ts +184 -6
  334. package/src/spoke/tracker.ts +79 -4
  335. package/src/status.ts +203 -10
  336. package/src/tui-commands.ts +13 -6
  337. package/src/tui-messaging.ts +93 -0
  338. package/src/tui.ts +15 -0
  339. package/web/dist/apple-touch-icon.png +0 -0
  340. package/web/dist/assets/highlight-y4rJE6Tt.js +5 -0
  341. package/web/dist/assets/index-DWnMhXk9.js +20 -0
  342. package/web/dist/assets/index-DmUXEnw9.css +1 -0
  343. package/web/dist/assets/ort-wasm-simd-threaded-CxTQ5xH-.wasm +0 -0
  344. package/web/dist/assets/silero-worker-C8-8vLVS.js +11 -0
  345. package/web/dist/assets/silero_vad-Dt9-bTDn.onnx +0 -0
  346. package/web/dist/favicon.ico +0 -0
  347. package/web/dist/icon-192.png +0 -0
  348. package/web/dist/icon-512.png +0 -0
  349. package/web/dist/icon-maskable-512.png +0 -0
  350. package/web/dist/icon.svg +5 -5
  351. package/web/dist/index.html +17 -4
  352. package/web/dist/manifest.webmanifest +2 -2
  353. package/web/dist/sw.js +79 -15
  354. package/web/dist/assets/index-DClBvfLL.css +0 -1
  355. package/web/dist/assets/index-Djo_yF0a.js +0 -12
@@ -0,0 +1,112 @@
1
+ // The per-action executor interface. The engine (engine.ts) owns everything transactional: the lock, preview
2
+ // revalidation, consent, path resolution and symlink refusal, the journal, backups, staging, atomic replacement,
3
+ // rollback and resume. An executor only says, for one action kind:
4
+ //
5
+ // plan which journal class the step is and which path it changes (pure, from the action alone);
6
+ // check whether the step may run on this host now (read-only: preconditions, pins, versions);
7
+ // change what the target should become (read-only: bytes for a file, a symlink target, a directory tree,
8
+ // or an `effect` for steps with no file of their own to stage: enrollment, verification, activation,
9
+ // launch). Large content (a harness, hubctl) is never passed as bytes: it is streamed into the step's
10
+ // StagingArea (stream.ts) and named as a StagedFile, which the engine moves or copies into place;
11
+ // undo how to reverse a done effect that has no backup (clear a stored credential, unload a service).
12
+ //
13
+ // Executors never write files themselves (except an effect, through its own typed client such as the
14
+ // CredentialStore), never receive a shell, and never see the journal's internals. Adapter tasks supply the
15
+ // harness, MCP-registration, service-activation, enrollment and verification executors through this interface.
16
+ import type { Action, ActionKind, EntryClass, EvidenceResult, ExecutionContext, FileState, PathRef, RootMapping } from "../contracts/index.ts"
17
+ import type { TxFs } from "./fs.ts"
18
+ import type { StagedFile, StagingArea } from "./stream.ts"
19
+ import type { ResolveOptions } from "./resolve.ts"
20
+ import type { TxResult } from "./result.ts"
21
+
22
+ export type ActionOf<K extends ActionKind> = Extract<Action, { kind: K }>
23
+
24
+ /** The host apply runs on (computed fresh by the caller: hostFingerprint() and targetOf()). */
25
+ export interface HostInfo {
26
+ readonly fingerprint: string
27
+ readonly target: string
28
+ }
29
+
30
+ /**
31
+ * One file of a directory tree an executor wants staged (a harness unpacked from its verified archive): small
32
+ * content as bytes, large content as a file of the step's staging area.
33
+ */
34
+ export type TreeFile = {
35
+ /** Relative, "/"-separated, inside the tree (isSafeRel). */
36
+ readonly rel: string
37
+ readonly mode: number
38
+ } & ({ readonly bytes: Uint8Array } | { readonly staged: StagedFile })
39
+
40
+ export type Change =
41
+ /**
42
+ * Write these bytes (or this staged file: one the step's staging area wrote is moved, any other is copied, its
43
+ * digest checked while it is copied). `refuse`: an existing different file is a conflict. `backup`: it is backed
44
+ * up and replaced.
45
+ */
46
+ | ({ readonly type: "file"; readonly mode: number; readonly replace: "refuse" | "backup" } & ({ readonly bytes: Uint8Array } | { readonly staged: StagedFile }))
47
+ | { readonly type: "directory"; readonly mode: number }
48
+ | { readonly type: "symlink"; readonly target: string; readonly replace: "refuse" | "backup" }
49
+ /** A fresh, immutable tree (a versioned install root). An existing different tree is a conflict. */
50
+ | { readonly type: "tree"; readonly files: readonly TreeFile[]; readonly mode: number }
51
+ /**
52
+ * v1.1: remove the target file (a service unit of a removed component). The engine backs it up, journals it and
53
+ * unlinks it; a rollback restores the backup. A missing target is skipped.
54
+ */
55
+ | { readonly type: "remove" }
56
+ /** A step without a file to stage. The engine journals the target's state before and after `perform`. */
57
+ | { readonly type: "effect"; readonly perform: () => Promise<TxResult<void>> }
58
+ /** Already exactly as wanted: journaled as skipped. */
59
+ | { readonly type: "none"; readonly reason: string }
60
+
61
+ export interface StepPlan {
62
+ readonly class: EntryClass
63
+ /** The path the step changes; required for every class except external. */
64
+ readonly path?: PathRef
65
+ readonly resolve?: ResolveOptions
66
+ }
67
+
68
+ export interface StepContext {
69
+ readonly fs: TxFs
70
+ readonly roots: readonly RootMapping[]
71
+ /** v1.1: where apply runs; paths resolve for it. */
72
+ readonly context: ExecutionContext
73
+ readonly host: HostInfo
74
+ readonly now: () => number
75
+ /** The resolved, symlink-checked absolute path of the step's target (when it has one). */
76
+ readonly target?: string
77
+ /** The target's state now. */
78
+ readonly current: FileState
79
+ /**
80
+ * The target's state before this transaction touched it: `current`, unless an earlier step of this transaction
81
+ * changed the same path and left it exactly so. Preconditions are checked against this.
82
+ */
83
+ readonly original: FileState
84
+ /** Resolves another PathRef with the same symlink rules. */
85
+ readonly resolve: (ref: PathRef, options?: ResolveOptions) => TxResult<string>
86
+ /** Records upstream evidence for the apply report (never into the journal). */
87
+ readonly recordEvidence: (results: readonly EvidenceResult[]) => void
88
+ /** The step's private scratch space for large downloads and what they unpack to (removed after the step). */
89
+ readonly staging: StagingArea
90
+ }
91
+
92
+ export interface UndoContext {
93
+ readonly fs: TxFs
94
+ readonly roots: readonly RootMapping[]
95
+ readonly context: ExecutionContext
96
+ readonly host: HostInfo
97
+ readonly now: () => number
98
+ readonly target?: string
99
+ /** Why the engine undoes: a credential to clear, or a done external step to reverse. */
100
+ readonly reason: "clear-credential" | "revert-external"
101
+ }
102
+
103
+ export interface ActionExecutor<K extends ActionKind = ActionKind> {
104
+ readonly kind: K
105
+ plan(action: ActionOf<K>): StepPlan
106
+ check?(action: ActionOf<K>, ctx: StepContext): Promise<TxResult<void>>
107
+ change(action: ActionOf<K>, ctx: StepContext): Promise<TxResult<Change>>
108
+ /** Required for class credential (clear it; a stale credential is never restored). Optional for external. */
109
+ undo?(action: ActionOf<K>, ctx: UndoContext): Promise<TxResult<void>>
110
+ }
111
+
112
+ export type ExecutorMap = { readonly [K in ActionKind]?: ActionExecutor<K> }
@@ -0,0 +1,194 @@
1
+ // The filesystem operations the applier uses, and nothing more: no exec, no spawn, no network. Everything the
2
+ // transaction layer does to disk goes through a TxFs, so tests can inject faults (ENOSPC, a kill at any step
3
+ // boundary) and the engine stays a pure function of what this interface reports.
4
+ //
5
+ // Writes are only ever "create a new file exclusively" (O_CREAT|O_EXCL|O_NOFOLLOW, then fsync); replacing a file is
6
+ // always stage-then-rename, so a reader (or a crash) sees the old file or the new one, never a torn mix. Large files
7
+ // (a harness binary, hubctl itself) are written and read in chunks (createExclusive, readRange), never whole.
8
+ import {
9
+ chmodSync,
10
+ closeSync,
11
+ constants,
12
+ fchmodSync,
13
+ fsyncSync,
14
+ linkSync,
15
+ lstatSync,
16
+ mkdirSync,
17
+ openSync,
18
+ readdirSync,
19
+ readFileSync,
20
+ readlinkSync,
21
+ readSync,
22
+ renameSync,
23
+ rmdirSync,
24
+ symlinkSync,
25
+ unlinkSync,
26
+ writeSync,
27
+ } from "node:fs"
28
+ import type { EntryKind } from "../contracts/index.ts"
29
+
30
+ export interface EntryInfo {
31
+ readonly kind: EntryKind
32
+ /** Permission bits (0..0o7777). */
33
+ readonly mode: number
34
+ readonly size: number
35
+ }
36
+
37
+ /** A file being created exclusively, written in chunks (createExclusive). */
38
+ export interface TxWriter {
39
+ write(chunk: Uint8Array): void
40
+ /** Sets the mode, fsyncs and closes: the file is complete. */
41
+ close(): void
42
+ /** Closes and removes the incomplete file. */
43
+ abort(): void
44
+ }
45
+
46
+ export interface TxFs {
47
+ /** Does not follow symlinks; undefined when nothing is there. */
48
+ lstat(path: string): EntryInfo | undefined
49
+ readFile(path: string): Uint8Array
50
+ /** At most `length` bytes from `offset` (fewer at the end of the file). Does not follow a final symlink. */
51
+ readRange(path: string, offset: number, length: number): Uint8Array
52
+ readlink(path: string): string
53
+ readdir(path: string): string[]
54
+ /** Creates a new file (fails if anything exists there, a symlink included), writes it fully, sets `mode`, fsyncs. */
55
+ writeExclusive(path: string, data: Uint8Array | string, mode: number): void
56
+ /** Like writeExclusive, for content that arrives in chunks: the file is created now and completed by close(). */
57
+ createExclusive(path: string, mode: number): TxWriter
58
+ /** Atomic replace of `to`'s directory entry. */
59
+ rename(from: string, to: string): void
60
+ /** Hard link; fails if `to` exists (an atomic create-if-absent of complete content). */
61
+ link(from: string, to: string): void
62
+ unlink(path: string): void
63
+ /** One level; fails if it exists. */
64
+ mkdir(path: string, mode: number): void
65
+ /** Creates every missing level with `mode`. */
66
+ mkdirp(path: string, mode: number): void
67
+ /** Fails if not empty. */
68
+ rmdir(path: string): void
69
+ symlink(target: string, path: string): void
70
+ chmod(path: string, mode: number): void
71
+ /** Best effort: makes a rename in `path` durable. */
72
+ fsyncDir(path: string): void
73
+ }
74
+
75
+ function kindOf(mode: number): EntryKind {
76
+ switch (mode & constants.S_IFMT) {
77
+ case constants.S_IFREG:
78
+ return "file"
79
+ case constants.S_IFDIR:
80
+ return "directory"
81
+ case constants.S_IFLNK:
82
+ return "symlink"
83
+ default:
84
+ return "other"
85
+ }
86
+ }
87
+
88
+ const errno = (error: unknown) => (error as NodeJS.ErrnoException | undefined)?.code
89
+
90
+ export const nodeFs: TxFs = {
91
+ lstat(path) {
92
+ try {
93
+ const s = lstatSync(path)
94
+ return { kind: kindOf(s.mode), mode: s.mode & 0o7777, size: s.size }
95
+ } catch (error) {
96
+ if (errno(error) === "ENOENT" || errno(error) === "ENOTDIR") return undefined
97
+ throw error
98
+ }
99
+ },
100
+ readFile: (path) => new Uint8Array(readFileSync(path)),
101
+ readRange(path, offset, length) {
102
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW)
103
+ try {
104
+ const out = new Uint8Array(length)
105
+ let got = 0
106
+ while (got < length) {
107
+ const n = readSync(fd, out, got, length - got, offset + got)
108
+ if (n === 0) break
109
+ got += n
110
+ }
111
+ return got === length ? out : out.subarray(0, got)
112
+ } finally {
113
+ closeSync(fd)
114
+ }
115
+ },
116
+ readlink: (path) => readlinkSync(path),
117
+ readdir: (path) => readdirSync(path),
118
+ writeExclusive(path, data, mode) {
119
+ const fd = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, mode)
120
+ let ok = false
121
+ try {
122
+ const bytes = typeof data === "string" ? Buffer.from(data, "utf8") : data
123
+ let offset = 0
124
+ while (offset < bytes.byteLength) offset += writeSync(fd, bytes, offset, bytes.byteLength - offset)
125
+ fchmodSync(fd, mode)
126
+ fsyncSync(fd)
127
+ ok = true
128
+ } finally {
129
+ closeSync(fd)
130
+ if (!ok) {
131
+ try {
132
+ unlinkSync(path)
133
+ } catch {}
134
+ }
135
+ }
136
+ },
137
+ createExclusive(path, mode) {
138
+ const fd = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, mode)
139
+ let open = true
140
+ const finish = (complete: boolean) => {
141
+ if (!open) return
142
+ open = false
143
+ try {
144
+ if (complete) {
145
+ fchmodSync(fd, mode)
146
+ fsyncSync(fd)
147
+ }
148
+ } finally {
149
+ closeSync(fd)
150
+ if (!complete) {
151
+ try {
152
+ unlinkSync(path)
153
+ } catch {}
154
+ }
155
+ }
156
+ }
157
+ return {
158
+ write(chunk) {
159
+ if (!open) throw new Error(`${path} is already closed`)
160
+ let offset = 0
161
+ while (offset < chunk.byteLength) offset += writeSync(fd, chunk, offset, chunk.byteLength - offset)
162
+ },
163
+ close() {
164
+ try {
165
+ finish(true)
166
+ } catch (error) {
167
+ try {
168
+ unlinkSync(path)
169
+ } catch {}
170
+ throw error
171
+ }
172
+ },
173
+ abort: () => finish(false),
174
+ }
175
+ },
176
+ rename: (from, to) => renameSync(from, to),
177
+ link: (from, to) => linkSync(from, to),
178
+ unlink: (path) => unlinkSync(path),
179
+ mkdir: (path, mode) => mkdirSync(path, { mode }),
180
+ mkdirp: (path, mode) => void mkdirSync(path, { recursive: true, mode }),
181
+ rmdir: (path) => rmdirSync(path),
182
+ symlink: (target, path) => symlinkSync(target, path),
183
+ chmod: (path, mode) => chmodSync(path, mode),
184
+ fsyncDir(path) {
185
+ try {
186
+ const fd = openSync(path, constants.O_RDONLY)
187
+ try {
188
+ fsyncSync(fd)
189
+ } finally {
190
+ closeSync(fd)
191
+ }
192
+ } catch {}
193
+ },
194
+ }
@@ -0,0 +1,12 @@
1
+ // The transactional local applier for bootstrap setup plans (see engine.ts for the guarantees and executor.ts for
2
+ // the per-action executor interface adapters implement). Built on the frozen v1 contracts in ../contracts.
3
+ export * from "./result.ts"
4
+ export * from "./fs.ts"
5
+ export * from "./state.ts"
6
+ export * from "./resolve.ts"
7
+ export * from "./lock.ts"
8
+ export * from "./store.ts"
9
+ export * from "./json-merge.ts"
10
+ export * from "./executor.ts"
11
+ export * from "./builtin.ts"
12
+ export * from "./engine.ts"