smol-symphony 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (716) hide show
  1. package/AGENTS.md +41 -22
  2. package/DESIGN.md +494 -273
  3. package/README.md +109 -57
  4. package/SPEC.md +33 -24
  5. package/WORKFLOW.minimal.yaml +34 -0
  6. package/{WORKFLOW.template.md → WORKFLOW.template.yaml} +409 -256
  7. package/WORKFLOW.yaml +487 -0
  8. package/assets/skills/symphony-issues/SKILL.md +136 -0
  9. package/assets/symphony-mise.system.toml +68 -0
  10. package/dist/src/bin/symphony.js +30 -0
  11. package/dist/src/bin/symphony.js.map +1 -0
  12. package/dist/src/core/actions/context.js +109 -0
  13. package/dist/src/core/actions/context.js.map +1 -0
  14. package/dist/{actions/parsing.js → src/core/actions/parse.js} +33 -114
  15. package/dist/src/core/actions/parse.js.map +1 -0
  16. package/dist/src/core/actions/plan.js +197 -0
  17. package/dist/src/core/actions/plan.js.map +1 -0
  18. package/dist/src/core/actions/predicates.js +111 -0
  19. package/dist/src/core/actions/predicates.js.map +1 -0
  20. package/dist/src/core/actions/run-fold.js +248 -0
  21. package/dist/src/core/actions/run-fold.js.map +1 -0
  22. package/dist/src/core/actions/template.js +118 -0
  23. package/dist/src/core/actions/template.js.map +1 -0
  24. package/dist/src/core/cli/args.js +116 -0
  25. package/dist/src/core/cli/args.js.map +1 -0
  26. package/dist/src/core/coerce.js +75 -0
  27. package/dist/src/core/coerce.js.map +1 -0
  28. package/dist/src/core/credential/account-id.js +20 -0
  29. package/dist/src/core/credential/account-id.js.map +1 -0
  30. package/dist/src/core/credential/adapter-config.js +136 -0
  31. package/dist/src/core/credential/adapter-config.js.map +1 -0
  32. package/dist/src/core/credential/availability.js +98 -0
  33. package/dist/src/core/credential/availability.js.map +1 -0
  34. package/dist/src/core/credential/extract.js +228 -0
  35. package/dist/src/core/credential/extract.js.map +1 -0
  36. package/dist/src/core/credential/fake-creds.js +171 -0
  37. package/dist/src/core/credential/fake-creds.js.map +1 -0
  38. package/dist/src/core/credential/identity.js +125 -0
  39. package/dist/src/core/credential/identity.js.map +1 -0
  40. package/dist/src/core/credential/shape.js +230 -0
  41. package/dist/src/core/credential/shape.js.map +1 -0
  42. package/dist/src/core/credential/strings.js +15 -0
  43. package/dist/src/core/credential/strings.js.map +1 -0
  44. package/dist/src/core/doctor/checks.js +303 -0
  45. package/dist/src/core/doctor/checks.js.map +1 -0
  46. package/dist/src/core/git/result.js +107 -0
  47. package/dist/src/core/git/result.js.map +1 -0
  48. package/dist/src/core/http/decisions.js +225 -0
  49. package/dist/src/core/http/decisions.js.map +1 -0
  50. package/dist/{http.js → src/core/http/render.js} +472 -738
  51. package/dist/src/core/http/render.js.map +1 -0
  52. package/dist/{http-handlers.js → src/core/http/routes.js} +52 -87
  53. package/dist/src/core/http/routes.js.map +1 -0
  54. package/dist/src/core/http/views.js +181 -0
  55. package/dist/src/core/http/views.js.map +1 -0
  56. package/dist/src/core/image/managed-image.js +95 -0
  57. package/dist/src/core/image/managed-image.js.map +1 -0
  58. package/dist/src/core/issue/file.js +149 -0
  59. package/dist/src/core/issue/file.js.map +1 -0
  60. package/dist/src/core/issue/parse.js +210 -0
  61. package/dist/src/core/issue/parse.js.map +1 -0
  62. package/dist/src/core/mcp/dispatch.js +239 -0
  63. package/dist/src/core/mcp/dispatch.js.map +1 -0
  64. package/dist/src/core/mcp/post-move.js +92 -0
  65. package/dist/src/core/mcp/post-move.js.map +1 -0
  66. package/dist/src/core/mcp/protocol.js +293 -0
  67. package/dist/src/core/mcp/protocol.js.map +1 -0
  68. package/dist/src/core/mcp/url.js +162 -0
  69. package/dist/src/core/mcp/url.js.map +1 -0
  70. package/dist/src/core/path.js +63 -0
  71. package/dist/src/core/path.js.map +1 -0
  72. package/dist/src/core/reconcile/image-decide.js +48 -0
  73. package/dist/src/core/reconcile/image-decide.js.map +1 -0
  74. package/dist/src/core/reconcile/ledger.js +142 -0
  75. package/dist/src/core/reconcile/ledger.js.map +1 -0
  76. package/dist/src/core/reconcile/pr-classify.js +62 -0
  77. package/dist/src/core/reconcile/pr-classify.js.map +1 -0
  78. package/dist/{reconciler → src/core/reconcile}/pr-decide.js +25 -12
  79. package/dist/src/core/reconcile/pr-decide.js.map +1 -0
  80. package/dist/src/core/reconcile/pr-loop.js +161 -0
  81. package/dist/src/core/reconcile/pr-loop.js.map +1 -0
  82. package/dist/src/core/reconcile/pr-notes.js +35 -0
  83. package/dist/src/core/reconcile/pr-notes.js.map +1 -0
  84. package/dist/src/core/reconcile/vm-decide.js +70 -0
  85. package/dist/src/core/reconcile/vm-decide.js.map +1 -0
  86. package/dist/src/core/reconcile/vm-reap.js +207 -0
  87. package/dist/src/core/reconcile/vm-reap.js.map +1 -0
  88. package/dist/src/core/reconcile/workspace-decide.js +162 -0
  89. package/dist/src/core/reconcile/workspace-decide.js.map +1 -0
  90. package/dist/src/core/runlog/summary.js +231 -0
  91. package/dist/src/core/runlog/summary.js.map +1 -0
  92. package/dist/src/core/runner/dispatch-config.js +95 -0
  93. package/dist/src/core/runner/dispatch-config.js.map +1 -0
  94. package/dist/src/core/runner/injection.js +61 -0
  95. package/dist/src/core/runner/injection.js.map +1 -0
  96. package/dist/src/core/runner/mise.js +210 -0
  97. package/dist/src/core/runner/mise.js.map +1 -0
  98. package/dist/src/core/runner/prompt.js +720 -0
  99. package/dist/src/core/runner/prompt.js.map +1 -0
  100. package/dist/src/core/runner/turn.js +242 -0
  101. package/dist/src/core/runner/turn.js.map +1 -0
  102. package/dist/src/core/runner/vm-plan.js +390 -0
  103. package/dist/src/core/runner/vm-plan.js.map +1 -0
  104. package/dist/src/core/schedule/admission.js +123 -0
  105. package/dist/src/core/schedule/admission.js.map +1 -0
  106. package/dist/src/core/schedule/circuit-breaker.js +111 -0
  107. package/dist/src/core/schedule/circuit-breaker.js.map +1 -0
  108. package/dist/src/core/schedule/eligibility.js +83 -0
  109. package/dist/src/core/schedule/eligibility.js.map +1 -0
  110. package/dist/src/core/schedule/reconcile-issue.js +82 -0
  111. package/dist/src/core/schedule/reconcile-issue.js.map +1 -0
  112. package/dist/src/core/schedule/retry.js +96 -0
  113. package/dist/src/core/schedule/retry.js.map +1 -0
  114. package/dist/src/core/schedule/sleep-cycle.js +133 -0
  115. package/dist/src/core/schedule/sleep-cycle.js.map +1 -0
  116. package/dist/src/core/schedule/slots.js +124 -0
  117. package/dist/src/core/schedule/slots.js.map +1 -0
  118. package/dist/src/core/schedule/tick.js +553 -0
  119. package/dist/src/core/schedule/tick.js.map +1 -0
  120. package/dist/src/core/schedule/token-fold.js +181 -0
  121. package/dist/src/core/schedule/token-fold.js.map +1 -0
  122. package/dist/src/core/state-resolve.js +86 -0
  123. package/dist/src/core/state-resolve.js.map +1 -0
  124. package/dist/src/core/vm-guards.js +278 -0
  125. package/dist/src/core/vm-guards.js.map +1 -0
  126. package/dist/src/core/workflow/derive.js +107 -0
  127. package/dist/src/core/workflow/derive.js.map +1 -0
  128. package/dist/src/core/workflow/parse.js +687 -0
  129. package/dist/src/core/workflow/parse.js.map +1 -0
  130. package/dist/src/core/workflow/prompt-probe.js +78 -0
  131. package/dist/src/core/workflow/prompt-probe.js.map +1 -0
  132. package/dist/src/core/workflow/validate.js +189 -0
  133. package/dist/src/core/workflow/validate.js.map +1 -0
  134. package/dist/src/core/workspace-key.js +19 -0
  135. package/dist/src/core/workspace-key.js.map +1 -0
  136. package/dist/src/shell/actions-runner.js +356 -0
  137. package/dist/src/shell/actions-runner.js.map +1 -0
  138. package/dist/src/shell/adapter/adapter-registry.js +45 -0
  139. package/dist/src/shell/adapter/adapter-registry.js.map +1 -0
  140. package/dist/src/shell/adapter/clock-random.js +96 -0
  141. package/dist/src/shell/adapter/clock-random.js.map +1 -0
  142. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  143. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  144. package/dist/src/shell/adapter/gondolin-dispatch.js +385 -0
  145. package/dist/src/shell/adapter/gondolin-dispatch.js.map +1 -0
  146. package/dist/src/shell/adapter/gondolin-image-converter.js +233 -0
  147. package/dist/src/shell/adapter/gondolin-image-converter.js.map +1 -0
  148. package/dist/src/shell/adapter/gondolin-image-fetch.js +180 -0
  149. package/dist/src/shell/adapter/gondolin-image-fetch.js.map +1 -0
  150. package/dist/src/shell/adapter/launcher-asset.js +57 -0
  151. package/dist/src/shell/adapter/launcher-asset.js.map +1 -0
  152. package/dist/src/shell/adapter/mise-config-asset.js +65 -0
  153. package/dist/src/shell/adapter/mise-config-asset.js.map +1 -0
  154. package/dist/src/shell/adapter/workflow-loader.js +304 -0
  155. package/dist/src/shell/adapter/workflow-loader.js.map +1 -0
  156. package/dist/src/shell/cli/doctor.js +268 -0
  157. package/dist/src/shell/cli/doctor.js.map +1 -0
  158. package/dist/src/shell/effect-interpreter-families.js +314 -0
  159. package/dist/src/shell/effect-interpreter-families.js.map +1 -0
  160. package/dist/src/shell/effect-interpreter.js +29 -0
  161. package/dist/src/shell/effect-interpreter.js.map +1 -0
  162. package/dist/src/shell/interp/acp-frame.js +137 -0
  163. package/dist/src/shell/interp/acp-frame.js.map +1 -0
  164. package/dist/src/shell/interp/acp-ws-conn.js +320 -0
  165. package/dist/src/shell/interp/acp-ws-conn.js.map +1 -0
  166. package/dist/src/shell/interp/acp-ws-frames.js +159 -0
  167. package/dist/src/shell/interp/acp-ws-frames.js.map +1 -0
  168. package/dist/src/shell/interp/acp-ws.js +197 -0
  169. package/dist/src/shell/interp/acp-ws.js.map +1 -0
  170. package/dist/src/shell/interp/acp.js +319 -0
  171. package/dist/src/shell/interp/acp.js.map +1 -0
  172. package/dist/src/shell/interp/credential-defaults.js +128 -0
  173. package/dist/src/shell/interp/credential-defaults.js.map +1 -0
  174. package/dist/src/shell/interp/credential-hooks.js +149 -0
  175. package/dist/src/shell/interp/credential-hooks.js.map +1 -0
  176. package/dist/src/shell/interp/credential-registry.js +226 -0
  177. package/dist/src/shell/interp/credential-registry.js.map +1 -0
  178. package/dist/src/shell/interp/credential.js +103 -0
  179. package/dist/src/shell/interp/credential.js.map +1 -0
  180. package/dist/src/shell/interp/gh.js +163 -0
  181. package/dist/src/shell/interp/gh.js.map +1 -0
  182. package/dist/src/shell/interp/git.js +28 -0
  183. package/dist/src/shell/interp/git.js.map +1 -0
  184. package/dist/src/shell/interp/log.js +213 -0
  185. package/dist/src/shell/interp/log.js.map +1 -0
  186. package/dist/src/shell/interp/process.js +178 -0
  187. package/dist/src/shell/interp/process.js.map +1 -0
  188. package/dist/src/shell/interp/runlog.js +193 -0
  189. package/dist/src/shell/interp/runlog.js.map +1 -0
  190. package/dist/src/shell/interp/timer.js +64 -0
  191. package/dist/src/shell/interp/timer.js.map +1 -0
  192. package/dist/src/shell/interp/tracker-disk.js +99 -0
  193. package/dist/src/shell/interp/tracker-disk.js.map +1 -0
  194. package/dist/src/shell/interp/tracker-parse.js +71 -0
  195. package/dist/src/shell/interp/tracker-parse.js.map +1 -0
  196. package/dist/src/shell/interp/tracker-scan.js +238 -0
  197. package/dist/src/shell/interp/tracker-scan.js.map +1 -0
  198. package/dist/src/shell/interp/tracker-write.js +91 -0
  199. package/dist/src/shell/interp/tracker-write.js.map +1 -0
  200. package/dist/src/shell/interp/tracker.js +41 -0
  201. package/dist/src/shell/interp/tracker.js.map +1 -0
  202. package/dist/src/shell/interp/tty.js +48 -0
  203. package/dist/src/shell/interp/tty.js.map +1 -0
  204. package/dist/src/shell/interp/vm.js +199 -0
  205. package/dist/src/shell/interp/vm.js.map +1 -0
  206. package/dist/src/shell/interp/workspace.js +310 -0
  207. package/dist/src/shell/interp/workspace.js.map +1 -0
  208. package/dist/src/shell/main-acp.js +78 -0
  209. package/dist/src/shell/main-acp.js.map +1 -0
  210. package/dist/src/shell/main-adapters.js +222 -0
  211. package/dist/src/shell/main-adapters.js.map +1 -0
  212. package/dist/src/shell/main-credential.js +122 -0
  213. package/dist/src/shell/main-credential.js.map +1 -0
  214. package/dist/src/shell/main-doctor.js +22 -0
  215. package/dist/src/shell/main-doctor.js.map +1 -0
  216. package/dist/src/shell/main-entry.js +46 -0
  217. package/dist/src/shell/main-entry.js.map +1 -0
  218. package/dist/src/shell/main-http-csrf.js +45 -0
  219. package/dist/src/shell/main-http-csrf.js.map +1 -0
  220. package/dist/src/shell/main-http-handler.js +389 -0
  221. package/dist/src/shell/main-http-handler.js.map +1 -0
  222. package/dist/src/shell/main-http-mcp.js +122 -0
  223. package/dist/src/shell/main-http-mcp.js.map +1 -0
  224. package/dist/src/shell/main-http-views.js +253 -0
  225. package/dist/src/shell/main-http-views.js.map +1 -0
  226. package/dist/src/shell/main-http.js +76 -0
  227. package/dist/src/shell/main-http.js.map +1 -0
  228. package/dist/src/shell/main-loops.js +130 -0
  229. package/dist/src/shell/main-loops.js.map +1 -0
  230. package/dist/src/shell/main-mcp.js +129 -0
  231. package/dist/src/shell/main-mcp.js.map +1 -0
  232. package/dist/src/shell/main-orchestrator.js +120 -0
  233. package/dist/src/shell/main-orchestrator.js.map +1 -0
  234. package/dist/src/shell/main-preflight.js +43 -0
  235. package/dist/src/shell/main-preflight.js.map +1 -0
  236. package/dist/src/shell/main-reconcilers-helpers.js +244 -0
  237. package/dist/src/shell/main-reconcilers-helpers.js.map +1 -0
  238. package/dist/src/shell/main-reconcilers-pr.js +148 -0
  239. package/dist/src/shell/main-reconcilers-pr.js.map +1 -0
  240. package/dist/src/shell/main-reconcilers.js +225 -0
  241. package/dist/src/shell/main-reconcilers.js.map +1 -0
  242. package/dist/src/shell/main-runner.js +355 -0
  243. package/dist/src/shell/main-runner.js.map +1 -0
  244. package/dist/src/shell/main-scaffold.js +116 -0
  245. package/dist/src/shell/main-scaffold.js.map +1 -0
  246. package/dist/src/shell/main-shutdown.js +115 -0
  247. package/dist/src/shell/main-shutdown.js.map +1 -0
  248. package/dist/src/shell/main-startup.js +48 -0
  249. package/dist/src/shell/main-startup.js.map +1 -0
  250. package/dist/src/shell/main-substrates.js +43 -0
  251. package/dist/src/shell/main-substrates.js.map +1 -0
  252. package/dist/src/shell/main.js +385 -0
  253. package/dist/src/shell/main.js.map +1 -0
  254. package/dist/src/shell/orchestrator-feedback.js +69 -0
  255. package/dist/src/shell/orchestrator-feedback.js.map +1 -0
  256. package/dist/src/shell/orchestrator-image.js +167 -0
  257. package/dist/src/shell/orchestrator-image.js.map +1 -0
  258. package/dist/src/shell/orchestrator-loop.js +468 -0
  259. package/dist/src/shell/orchestrator-loop.js.map +1 -0
  260. package/dist/src/shell/orchestrator-reconcile.js +36 -0
  261. package/dist/src/shell/orchestrator-reconcile.js.map +1 -0
  262. package/dist/src/shell/reconciler-loop.js +228 -0
  263. package/dist/src/shell/reconciler-loop.js.map +1 -0
  264. package/dist/src/shell/runner-loop-turn.js +301 -0
  265. package/dist/src/shell/runner-loop-turn.js.map +1 -0
  266. package/dist/src/shell/runner-loop.js +338 -0
  267. package/dist/src/shell/runner-loop.js.map +1 -0
  268. package/dist/src/shell/server/http.js +208 -0
  269. package/dist/src/shell/server/http.js.map +1 -0
  270. package/dist/src/shell/server/mcp-runtime-effects.js +237 -0
  271. package/dist/src/shell/server/mcp-runtime-effects.js.map +1 -0
  272. package/dist/src/shell/server/mcp-runtime.js +99 -0
  273. package/dist/src/shell/server/mcp-runtime.js.map +1 -0
  274. package/dist/src/shell/workspace-key.js +14 -0
  275. package/dist/src/shell/workspace-key.js.map +1 -0
  276. package/dist/src/types/acp.js +8 -0
  277. package/dist/src/types/acp.js.map +1 -0
  278. package/dist/src/types/actions/plan.js +6 -0
  279. package/dist/src/types/actions/plan.js.map +1 -0
  280. package/dist/src/types/actions/predicates.js +6 -0
  281. package/dist/src/types/actions/predicates.js.map +1 -0
  282. package/dist/src/types/actions/run-fold.js +8 -0
  283. package/dist/src/types/actions/run-fold.js.map +1 -0
  284. package/dist/src/types/actions.js +7 -0
  285. package/dist/src/types/actions.js.map +1 -0
  286. package/dist/src/types/adapter/clock-random.js +4 -0
  287. package/dist/src/types/adapter/clock-random.js.map +1 -0
  288. package/dist/src/types/adapter/gondolin-image-converter.js +5 -0
  289. package/dist/src/types/adapter/gondolin-image-converter.js.map +1 -0
  290. package/dist/src/types/adapter/gondolin-image-fetch.js +5 -0
  291. package/dist/src/types/adapter/gondolin-image-fetch.js.map +1 -0
  292. package/dist/src/types/adapter/workflow-loader.js +4 -0
  293. package/dist/src/types/adapter/workflow-loader.js.map +1 -0
  294. package/dist/src/types/cli/args.js +8 -0
  295. package/dist/src/types/cli/args.js.map +1 -0
  296. package/dist/src/types/config.js +8 -0
  297. package/dist/src/types/config.js.map +1 -0
  298. package/dist/src/types/credential-interp.js +6 -0
  299. package/dist/src/types/credential-interp.js.map +1 -0
  300. package/dist/src/types/credentials.js +10 -0
  301. package/dist/src/types/credentials.js.map +1 -0
  302. package/dist/src/types/doctor.js +7 -0
  303. package/dist/src/types/doctor.js.map +1 -0
  304. package/dist/src/types/domain.js +7 -0
  305. package/dist/src/types/domain.js.map +1 -0
  306. package/dist/src/types/effect.js +15 -0
  307. package/dist/src/types/effect.js.map +1 -0
  308. package/dist/src/types/errors.js +39 -0
  309. package/dist/src/types/errors.js.map +1 -0
  310. package/dist/src/types/http/decisions.js +6 -0
  311. package/dist/src/types/http/decisions.js.map +1 -0
  312. package/dist/src/types/http/render.js +10 -0
  313. package/dist/src/types/http/render.js.map +1 -0
  314. package/dist/src/types/http/views.js +6 -0
  315. package/dist/src/types/http/views.js.map +1 -0
  316. package/dist/src/types/http.js +9 -0
  317. package/dist/src/types/http.js.map +1 -0
  318. package/dist/src/types/image/managed-image.js +7 -0
  319. package/dist/src/types/image/managed-image.js.map +1 -0
  320. package/dist/src/types/interp/effect-interpreter.js +8 -0
  321. package/dist/src/types/interp/effect-interpreter.js.map +1 -0
  322. package/dist/src/types/interp/tracker.js +7 -0
  323. package/dist/src/types/interp/tracker.js.map +1 -0
  324. package/dist/src/types/issue/file.js +6 -0
  325. package/dist/src/types/issue/file.js.map +1 -0
  326. package/dist/src/types/issue/parse.js +8 -0
  327. package/dist/src/types/issue/parse.js.map +1 -0
  328. package/dist/src/types/main-acp.js +13 -0
  329. package/dist/src/types/main-acp.js.map +1 -0
  330. package/dist/src/types/main-adapters.js +5 -0
  331. package/dist/src/types/main-adapters.js.map +1 -0
  332. package/dist/src/types/main-credential.js +21 -0
  333. package/dist/src/types/main-credential.js.map +1 -0
  334. package/dist/src/types/main-doctor.js +6 -0
  335. package/dist/src/types/main-doctor.js.map +1 -0
  336. package/dist/src/types/main-http-handler.js +12 -0
  337. package/dist/src/types/main-http-handler.js.map +1 -0
  338. package/dist/src/types/main-http.js +5 -0
  339. package/dist/src/types/main-http.js.map +1 -0
  340. package/dist/src/types/main-loops.js +5 -0
  341. package/dist/src/types/main-loops.js.map +1 -0
  342. package/dist/src/types/main-mcp.js +12 -0
  343. package/dist/src/types/main-mcp.js.map +1 -0
  344. package/dist/src/types/main-orchestrator.js +5 -0
  345. package/dist/src/types/main-orchestrator.js.map +1 -0
  346. package/dist/src/types/main-reconcilers.js +11 -0
  347. package/dist/src/types/main-reconcilers.js.map +1 -0
  348. package/dist/src/types/main-runner.js +13 -0
  349. package/dist/src/types/main-runner.js.map +1 -0
  350. package/dist/src/types/main-startup.js +5 -0
  351. package/dist/src/types/main-startup.js.map +1 -0
  352. package/dist/src/types/main-substrates.js +5 -0
  353. package/dist/src/types/main-substrates.js.map +1 -0
  354. package/dist/src/types/mcp/dispatch.js +4 -0
  355. package/dist/src/types/mcp/dispatch.js.map +1 -0
  356. package/dist/src/types/mcp/post-move.js +7 -0
  357. package/dist/src/types/mcp/post-move.js.map +1 -0
  358. package/dist/src/types/mcp.js +9 -0
  359. package/dist/src/types/mcp.js.map +1 -0
  360. package/dist/src/types/ports.js +12 -0
  361. package/dist/src/types/ports.js.map +1 -0
  362. package/dist/src/types/reconcile/image-decide.js +5 -0
  363. package/dist/src/types/reconcile/image-decide.js.map +1 -0
  364. package/dist/src/types/reconcile/ledger.js +7 -0
  365. package/dist/src/types/reconcile/ledger.js.map +1 -0
  366. package/dist/src/types/reconcile/pr-loop.js +8 -0
  367. package/dist/src/types/reconcile/pr-loop.js.map +1 -0
  368. package/dist/src/types/reconcile/vm-reap.js +8 -0
  369. package/dist/src/types/reconcile/vm-reap.js.map +1 -0
  370. package/dist/src/types/reconcile/workspace-decide.js +7 -0
  371. package/dist/src/types/reconcile/workspace-decide.js.map +1 -0
  372. package/dist/src/types/reconcile.js +9 -0
  373. package/dist/src/types/reconcile.js.map +1 -0
  374. package/dist/src/types/runlog.js +7 -0
  375. package/dist/src/types/runlog.js.map +1 -0
  376. package/dist/src/types/runner/actions-runner.js +12 -0
  377. package/dist/src/types/runner/actions-runner.js.map +1 -0
  378. package/dist/src/types/runner/gondolin-dispatch.js +5 -0
  379. package/dist/src/types/runner/gondolin-dispatch.js.map +1 -0
  380. package/dist/src/types/runner/injection.js +6 -0
  381. package/dist/src/types/runner/injection.js.map +1 -0
  382. package/dist/src/types/runner/runner-loop.js +5 -0
  383. package/dist/src/types/runner/runner-loop.js.map +1 -0
  384. package/dist/src/types/runner/turn.js +4 -0
  385. package/dist/src/types/runner/turn.js.map +1 -0
  386. package/dist/src/types/runner/vm-plan.js +4 -0
  387. package/dist/src/types/runner/vm-plan.js.map +1 -0
  388. package/dist/src/types/runtime.js +9 -0
  389. package/dist/src/types/runtime.js.map +1 -0
  390. package/dist/src/types/schedule/admission.js +7 -0
  391. package/dist/src/types/schedule/admission.js.map +1 -0
  392. package/dist/src/types/schedule/circuit-breaker.js +2 -0
  393. package/dist/src/types/schedule/circuit-breaker.js.map +1 -0
  394. package/dist/src/types/schedule/eligibility.js +9 -0
  395. package/dist/src/types/schedule/eligibility.js.map +1 -0
  396. package/dist/src/types/schedule/orchestrator-loop.js +10 -0
  397. package/dist/src/types/schedule/orchestrator-loop.js.map +1 -0
  398. package/dist/src/types/schedule/sleep-cycle.js +4 -0
  399. package/dist/src/types/schedule/sleep-cycle.js.map +1 -0
  400. package/dist/src/types/schedule/slots.js +8 -0
  401. package/dist/src/types/schedule/slots.js.map +1 -0
  402. package/dist/src/types/schedule/tick.js +9 -0
  403. package/dist/src/types/schedule/tick.js.map +1 -0
  404. package/dist/src/types/server/mcp-runtime.js +8 -0
  405. package/dist/src/types/server/mcp-runtime.js.map +1 -0
  406. package/dist/src/types/workflow/parse.js +4 -0
  407. package/dist/src/types/workflow/parse.js.map +1 -0
  408. package/dist/tests/core/account-id.test.js +35 -0
  409. package/dist/tests/core/account-id.test.js.map +1 -0
  410. package/dist/tests/core/actions-parse.test.js +176 -0
  411. package/dist/tests/core/actions-parse.test.js.map +1 -0
  412. package/dist/tests/core/adapter-config.test.js +133 -0
  413. package/dist/tests/core/adapter-config.test.js.map +1 -0
  414. package/dist/tests/core/admission.test.js +215 -0
  415. package/dist/tests/core/admission.test.js.map +1 -0
  416. package/dist/tests/core/args.test.js +132 -0
  417. package/dist/tests/core/args.test.js.map +1 -0
  418. package/dist/tests/core/availability.test.js +62 -0
  419. package/dist/tests/core/availability.test.js.map +1 -0
  420. package/dist/tests/core/checks.test.js +395 -0
  421. package/dist/tests/core/checks.test.js.map +1 -0
  422. package/dist/tests/core/circuit-breaker.test.js +172 -0
  423. package/dist/tests/core/circuit-breaker.test.js.map +1 -0
  424. package/dist/tests/core/coerce.test.js +87 -0
  425. package/dist/tests/core/coerce.test.js.map +1 -0
  426. package/dist/tests/core/context.test.js +228 -0
  427. package/dist/tests/core/context.test.js.map +1 -0
  428. package/dist/tests/core/decisions.test.js +310 -0
  429. package/dist/tests/core/decisions.test.js.map +1 -0
  430. package/dist/tests/core/derive.test.js +205 -0
  431. package/dist/tests/core/derive.test.js.map +1 -0
  432. package/dist/tests/core/dispatch-config.test.js +164 -0
  433. package/dist/tests/core/dispatch-config.test.js.map +1 -0
  434. package/dist/tests/core/dispatch.test.js +302 -0
  435. package/dist/tests/core/dispatch.test.js.map +1 -0
  436. package/dist/tests/core/eligibility.test.js +163 -0
  437. package/dist/tests/core/eligibility.test.js.map +1 -0
  438. package/dist/tests/core/extract.test.js +139 -0
  439. package/dist/tests/core/extract.test.js.map +1 -0
  440. package/dist/tests/core/fake-creds.test.js +134 -0
  441. package/dist/tests/core/fake-creds.test.js.map +1 -0
  442. package/dist/tests/core/file.test.js +197 -0
  443. package/dist/tests/core/file.test.js.map +1 -0
  444. package/dist/tests/core/git-result.test.js +113 -0
  445. package/dist/tests/core/git-result.test.js.map +1 -0
  446. package/dist/tests/core/identity.test.js +180 -0
  447. package/dist/tests/core/identity.test.js.map +1 -0
  448. package/dist/tests/core/image-decide.test.js +59 -0
  449. package/dist/tests/core/image-decide.test.js.map +1 -0
  450. package/dist/tests/core/injection.test.js +163 -0
  451. package/dist/tests/core/injection.test.js.map +1 -0
  452. package/dist/tests/core/ledger.test.js +218 -0
  453. package/dist/tests/core/ledger.test.js.map +1 -0
  454. package/dist/tests/core/managed-image.test.js +68 -0
  455. package/dist/tests/core/managed-image.test.js.map +1 -0
  456. package/dist/tests/core/mise.test.js +138 -0
  457. package/dist/tests/core/mise.test.js.map +1 -0
  458. package/dist/tests/core/parse.test.js +174 -0
  459. package/dist/tests/core/parse.test.js.map +1 -0
  460. package/dist/tests/core/path.test.js +50 -0
  461. package/dist/tests/core/path.test.js.map +1 -0
  462. package/dist/tests/core/plan.test.js +218 -0
  463. package/dist/tests/core/plan.test.js.map +1 -0
  464. package/dist/tests/core/post-move.test.js +162 -0
  465. package/dist/tests/core/post-move.test.js.map +1 -0
  466. package/dist/tests/core/pr-classify.test.js +117 -0
  467. package/dist/tests/core/pr-classify.test.js.map +1 -0
  468. package/dist/tests/core/pr-decide.test.js +298 -0
  469. package/dist/tests/core/pr-decide.test.js.map +1 -0
  470. package/dist/tests/core/pr-loop.test.js +301 -0
  471. package/dist/tests/core/pr-loop.test.js.map +1 -0
  472. package/dist/tests/core/pr-notes.test.js +165 -0
  473. package/dist/tests/core/pr-notes.test.js.map +1 -0
  474. package/dist/tests/core/predicates.test.js +154 -0
  475. package/dist/tests/core/predicates.test.js.map +1 -0
  476. package/dist/tests/core/prompt.test.js +189 -0
  477. package/dist/tests/core/prompt.test.js.map +1 -0
  478. package/dist/tests/core/protocol.test.js +195 -0
  479. package/dist/tests/core/protocol.test.js.map +1 -0
  480. package/dist/tests/core/reconcile-issue.test.js +116 -0
  481. package/dist/tests/core/reconcile-issue.test.js.map +1 -0
  482. package/dist/tests/core/render.test.js +549 -0
  483. package/dist/tests/core/render.test.js.map +1 -0
  484. package/dist/tests/core/retry.test.js +186 -0
  485. package/dist/tests/core/retry.test.js.map +1 -0
  486. package/dist/tests/core/routes.test.js +247 -0
  487. package/dist/tests/core/routes.test.js.map +1 -0
  488. package/dist/tests/core/run-fold.test.js +299 -0
  489. package/dist/tests/core/run-fold.test.js.map +1 -0
  490. package/dist/tests/core/shape.test.js +185 -0
  491. package/dist/tests/core/shape.test.js.map +1 -0
  492. package/dist/tests/core/sleep-cycle.test.js +150 -0
  493. package/dist/tests/core/sleep-cycle.test.js.map +1 -0
  494. package/dist/tests/core/slots.test.js +201 -0
  495. package/dist/tests/core/slots.test.js.map +1 -0
  496. package/dist/tests/core/state-resolve.test.js +80 -0
  497. package/dist/tests/core/state-resolve.test.js.map +1 -0
  498. package/dist/tests/core/summary.test.js +200 -0
  499. package/dist/tests/core/summary.test.js.map +1 -0
  500. package/dist/tests/core/template.test.js +116 -0
  501. package/dist/tests/core/template.test.js.map +1 -0
  502. package/dist/tests/core/tick.test.js +558 -0
  503. package/dist/tests/core/tick.test.js.map +1 -0
  504. package/dist/tests/core/token-fold.test.js +176 -0
  505. package/dist/tests/core/token-fold.test.js.map +1 -0
  506. package/dist/tests/core/turn.test.js +388 -0
  507. package/dist/tests/core/turn.test.js.map +1 -0
  508. package/dist/tests/core/url.test.js +118 -0
  509. package/dist/tests/core/url.test.js.map +1 -0
  510. package/dist/tests/core/validate.test.js +247 -0
  511. package/dist/tests/core/validate.test.js.map +1 -0
  512. package/dist/tests/core/views.test.js +252 -0
  513. package/dist/tests/core/views.test.js.map +1 -0
  514. package/dist/tests/core/vm-decide.test.js +110 -0
  515. package/dist/tests/core/vm-decide.test.js.map +1 -0
  516. package/dist/tests/core/vm-guards.test.js +153 -0
  517. package/dist/tests/core/vm-guards.test.js.map +1 -0
  518. package/dist/tests/core/vm-plan.test.js +332 -0
  519. package/dist/tests/core/vm-plan.test.js.map +1 -0
  520. package/dist/tests/core/vm-reap.test.js +196 -0
  521. package/dist/tests/core/vm-reap.test.js.map +1 -0
  522. package/dist/tests/core/workflow-parse.test.js +493 -0
  523. package/dist/tests/core/workflow-parse.test.js.map +1 -0
  524. package/dist/tests/core/workspace-decide.test.js +236 -0
  525. package/dist/tests/core/workspace-decide.test.js.map +1 -0
  526. package/dist/tests/helpers/fixtures.js +167 -0
  527. package/dist/tests/helpers/fixtures.js.map +1 -0
  528. package/dist/tests/shell/acp-substrate.test.js +101 -0
  529. package/dist/tests/shell/acp-substrate.test.js.map +1 -0
  530. package/dist/tests/shell/actions-runner-push.test.js +203 -0
  531. package/dist/tests/shell/actions-runner-push.test.js.map +1 -0
  532. package/dist/tests/shell/credential-hooks.test.js +36 -0
  533. package/dist/tests/shell/credential-hooks.test.js.map +1 -0
  534. package/dist/tests/shell/credential-registry.test.js +165 -0
  535. package/dist/tests/shell/credential-registry.test.js.map +1 -0
  536. package/dist/tests/shell/credential-substrate.test.js +179 -0
  537. package/dist/tests/shell/credential-substrate.test.js.map +1 -0
  538. package/dist/tests/shell/dockerfile-mise-pin.test.js +51 -0
  539. package/dist/tests/shell/dockerfile-mise-pin.test.js.map +1 -0
  540. package/dist/tests/shell/doctor.test.js +101 -0
  541. package/dist/tests/shell/doctor.test.js.map +1 -0
  542. package/dist/tests/shell/effect-vm-create.test.js +52 -0
  543. package/dist/tests/shell/effect-vm-create.test.js.map +1 -0
  544. package/dist/tests/shell/gh-port.test.js +63 -0
  545. package/dist/tests/shell/gh-port.test.js.map +1 -0
  546. package/dist/tests/shell/gondolin-dispatch-guard.test.js +144 -0
  547. package/dist/tests/shell/gondolin-dispatch-guard.test.js.map +1 -0
  548. package/dist/tests/shell/gondolin-dispatch-shquote.test.js +168 -0
  549. package/dist/tests/shell/gondolin-dispatch-shquote.test.js.map +1 -0
  550. package/dist/tests/shell/gondolin-image-converter.test.js +208 -0
  551. package/dist/tests/shell/gondolin-image-converter.test.js.map +1 -0
  552. package/dist/tests/shell/gondolin-image-fetch.test.js +93 -0
  553. package/dist/tests/shell/gondolin-image-fetch.test.js.map +1 -0
  554. package/dist/tests/shell/http-handler.test.js +608 -0
  555. package/dist/tests/shell/http-handler.test.js.map +1 -0
  556. package/dist/tests/shell/http-server.test.js +53 -0
  557. package/dist/tests/shell/http-server.test.js.map +1 -0
  558. package/dist/tests/shell/mcp-runtime.test.js +366 -0
  559. package/dist/tests/shell/mcp-runtime.test.js.map +1 -0
  560. package/dist/tests/shell/mise-config-asset.test.js +87 -0
  561. package/dist/tests/shell/mise-config-asset.test.js.map +1 -0
  562. package/dist/tests/shell/orchestrator-loop.test.js +583 -0
  563. package/dist/tests/shell/orchestrator-loop.test.js.map +1 -0
  564. package/dist/tests/shell/reconciler-passes.test.js +314 -0
  565. package/dist/tests/shell/reconciler-passes.test.js.map +1 -0
  566. package/dist/tests/shell/runner-loop-turn.test.js +97 -0
  567. package/dist/tests/shell/runner-loop-turn.test.js.map +1 -0
  568. package/dist/tests/shell/runner-slice.test.js +536 -0
  569. package/dist/tests/shell/runner-slice.test.js.map +1 -0
  570. package/dist/tests/shell/scaffold.test.js +65 -0
  571. package/dist/tests/shell/scaffold.test.js.map +1 -0
  572. package/dist/tests/shell/tick-config.test.js +83 -0
  573. package/dist/tests/shell/tick-config.test.js.map +1 -0
  574. package/dist/tests/shell/tracker-parse-dates.test.js +44 -0
  575. package/dist/tests/shell/tracker-parse-dates.test.js.map +1 -0
  576. package/dist/tests/shell/tracker-write-issue.test.js +154 -0
  577. package/dist/tests/shell/tracker-write-issue.test.js.map +1 -0
  578. package/dist/tests/shell/workflow-prompt-split.test.js +208 -0
  579. package/dist/tests/shell/workflow-prompt-split.test.js.map +1 -0
  580. package/dist/tests/shell/workspace-live-config.test.js +140 -0
  581. package/dist/tests/shell/workspace-live-config.test.js.map +1 -0
  582. package/package.json +21 -9
  583. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  584. package/prompts/Reflect.md +91 -0
  585. package/prompts/Review.md +97 -0
  586. package/prompts/Todo.md +96 -0
  587. package/prompts/_footer.md +41 -0
  588. package/prompts/_preamble.md +42 -0
  589. package/prompts-minimal/Todo.md +26 -0
  590. package/scripts/postinstall.mjs +63 -0
  591. package/scripts/vm-agent.mjs +312 -90
  592. package/WORKFLOW.md +0 -744
  593. package/dist/acp-bridge.js +0 -324
  594. package/dist/acp-bridge.js.map +0 -1
  595. package/dist/actions/cache.js +0 -191
  596. package/dist/actions/cache.js.map +0 -1
  597. package/dist/actions/effects.js +0 -41
  598. package/dist/actions/effects.js.map +0 -1
  599. package/dist/actions/executor.js +0 -570
  600. package/dist/actions/executor.js.map +0 -1
  601. package/dist/actions/index.js +0 -13
  602. package/dist/actions/index.js.map +0 -1
  603. package/dist/actions/parsing.js.map +0 -1
  604. package/dist/actions/predicate-env.js +0 -27
  605. package/dist/actions/predicate-env.js.map +0 -1
  606. package/dist/actions/predicates.js +0 -49
  607. package/dist/actions/predicates.js.map +0 -1
  608. package/dist/actions/templating.js +0 -66
  609. package/dist/actions/templating.js.map +0 -1
  610. package/dist/actions/types.js +0 -15
  611. package/dist/actions/types.js.map +0 -1
  612. package/dist/agent/acp.js +0 -473
  613. package/dist/agent/acp.js.map +0 -1
  614. package/dist/agent/adapter-names.js +0 -159
  615. package/dist/agent/adapter-names.js.map +0 -1
  616. package/dist/agent/adapters.js +0 -511
  617. package/dist/agent/adapters.js.map +0 -1
  618. package/dist/agent/credential-extractors.js +0 -342
  619. package/dist/agent/credential-extractors.js.map +0 -1
  620. package/dist/agent/credential-secrets.js +0 -628
  621. package/dist/agent/credential-secrets.js.map +0 -1
  622. package/dist/agent/credential-ticker.js +0 -57
  623. package/dist/agent/credential-ticker.js.map +0 -1
  624. package/dist/agent/gondolin-creds-staging.js +0 -356
  625. package/dist/agent/gondolin-creds-staging.js.map +0 -1
  626. package/dist/agent/gondolin-dispatch.js +0 -375
  627. package/dist/agent/gondolin-dispatch.js.map +0 -1
  628. package/dist/agent/gondolin.js +0 -124
  629. package/dist/agent/gondolin.js.map +0 -1
  630. package/dist/agent/runner-decisions.js +0 -134
  631. package/dist/agent/runner-decisions.js.map +0 -1
  632. package/dist/agent/runner.js +0 -1456
  633. package/dist/agent/runner.js.map +0 -1
  634. package/dist/agent/tool-call-summary.js +0 -102
  635. package/dist/agent/tool-call-summary.js.map +0 -1
  636. package/dist/agent/vm-acp-mapping.js +0 -73
  637. package/dist/agent/vm-acp-mapping.js.map +0 -1
  638. package/dist/agent/vm-guards.js +0 -262
  639. package/dist/agent/vm-guards.js.map +0 -1
  640. package/dist/agent/vm-port.js +0 -22
  641. package/dist/agent/vm-port.js.map +0 -1
  642. package/dist/agent/vm-process-registry.js +0 -79
  643. package/dist/agent/vm-process-registry.js.map +0 -1
  644. package/dist/bin/cli-args.js +0 -105
  645. package/dist/bin/cli-args.js.map +0 -1
  646. package/dist/bin/symphony.js +0 -794
  647. package/dist/bin/symphony.js.map +0 -1
  648. package/dist/errors.js +0 -15
  649. package/dist/errors.js.map +0 -1
  650. package/dist/http-disk.js +0 -135
  651. package/dist/http-disk.js.map +0 -1
  652. package/dist/http-handlers.js.map +0 -1
  653. package/dist/http.js.map +0 -1
  654. package/dist/issues.js +0 -178
  655. package/dist/issues.js.map +0 -1
  656. package/dist/logging.js +0 -203
  657. package/dist/logging.js.map +0 -1
  658. package/dist/mcp.js +0 -706
  659. package/dist/mcp.js.map +0 -1
  660. package/dist/memory.js +0 -85
  661. package/dist/memory.js.map +0 -1
  662. package/dist/orchestrator-decisions.js +0 -331
  663. package/dist/orchestrator-decisions.js.map +0 -1
  664. package/dist/orchestrator.js +0 -1569
  665. package/dist/orchestrator.js.map +0 -1
  666. package/dist/prompt.js +0 -65
  667. package/dist/prompt.js.map +0 -1
  668. package/dist/reconciler/cache.js +0 -65
  669. package/dist/reconciler/cache.js.map +0 -1
  670. package/dist/reconciler/index.js +0 -448
  671. package/dist/reconciler/index.js.map +0 -1
  672. package/dist/reconciler/ledger.js +0 -131
  673. package/dist/reconciler/ledger.js.map +0 -1
  674. package/dist/reconciler/pr-adapters.js +0 -174
  675. package/dist/reconciler/pr-adapters.js.map +0 -1
  676. package/dist/reconciler/pr-decide.js.map +0 -1
  677. package/dist/reconciler/pr.js +0 -422
  678. package/dist/reconciler/pr.js.map +0 -1
  679. package/dist/reconciler/types.js +0 -12
  680. package/dist/reconciler/types.js.map +0 -1
  681. package/dist/reconciler/vm.js +0 -243
  682. package/dist/reconciler/vm.js.map +0 -1
  683. package/dist/reconciler/workspace-defaults.js +0 -83
  684. package/dist/reconciler/workspace-defaults.js.map +0 -1
  685. package/dist/reconciler/workspace.js +0 -272
  686. package/dist/reconciler/workspace.js.map +0 -1
  687. package/dist/runlog.js +0 -403
  688. package/dist/runlog.js.map +0 -1
  689. package/dist/scaffold.js +0 -165
  690. package/dist/scaffold.js.map +0 -1
  691. package/dist/trackers/local.js +0 -445
  692. package/dist/trackers/local.js.map +0 -1
  693. package/dist/trackers/types.js +0 -10
  694. package/dist/trackers/types.js.map +0 -1
  695. package/dist/types.js +0 -3
  696. package/dist/types.js.map +0 -1
  697. package/dist/util/clock.js +0 -12
  698. package/dist/util/clock.js.map +0 -1
  699. package/dist/util/crypto.js +0 -25
  700. package/dist/util/crypto.js.map +0 -1
  701. package/dist/util/frontmatter.js +0 -70
  702. package/dist/util/frontmatter.js.map +0 -1
  703. package/dist/util/fs-issues.js +0 -22
  704. package/dist/util/fs-issues.js.map +0 -1
  705. package/dist/util/process.js +0 -152
  706. package/dist/util/process.js.map +0 -1
  707. package/dist/util/workspace-key.js +0 -10
  708. package/dist/util/workspace-key.js.map +0 -1
  709. package/dist/workflow-loader.js +0 -147
  710. package/dist/workflow-loader.js.map +0 -1
  711. package/dist/workflow.js +0 -822
  712. package/dist/workflow.js.map +0 -1
  713. package/dist/workspace-types.js +0 -8
  714. package/dist/workspace-types.js.map +0 -1
  715. package/dist/workspace.js +0 -443
  716. package/dist/workspace.js.map +0 -1
@@ -0,0 +1,390 @@
1
+ // FCIS rewrite — pure assembly of the per-dispatch VmPlan.
2
+ //
3
+ // Functional core: every export here is a pure, synchronous (data) -> data
4
+ // function. No IO, no async/await/Promise, no wall clock, no randomness, no
5
+ // node: imports, no process/fetch. Imports ONLY from src/types.
6
+ //
7
+ // Ports the PLAN half of the original Gondolin dispatch path (read for
8
+ // reference, never modified):
9
+ // • src/agent/runner.ts — buildVmMounts, buildForwardedEnv,
10
+ // mcpDispatchTarget, computeAcpWsUrl,
11
+ // buildDispatchOptions (the data half).
12
+ // • src/agent/vm-acp-mapping.ts — buildMcpTcpHostEntry, acpWsUrlFromExplicit,
13
+ // the synthetic-host constants + guest URLs.
14
+ // • src/agent/gondolin-dispatch.ts — buildCreateVmOptions, buildTunnelMapping,
15
+ // buildLaunchEnv, the launch argv + the staged
16
+ // guest-file set (gitconfig + launcher + fake
17
+ // creds + effort settings), the fixed guest
18
+ // PATH/gitconfig constants, sessionLabel.
19
+ //
20
+ // All of that imperative-shell code was making PURE decisions inline (which
21
+ // mounts, which env, which tunnel, which argv, which staged files). This module
22
+ // hoists those decisions out as a single (data) -> VmPlan assembly so the shell
23
+ // dispatcher only has to EXECUTE the plan (createVm + the base64-pipe writes +
24
+ // the exec) — every routing/precedence decision is testable here without a VM.
25
+ //
26
+ // mustFix (unified in-VM control plane): Gondolin blocks guest→host loopback, so
27
+ // ACP (the `/acp` WebSocket) and MCP (HTTP JSON-RPC) share ONE `tcp.hosts` entry
28
+ // (`symphony-mcp:7001`) + synthetic per-host DNS. The guest dials
29
+ // `ws://symphony-mcp:7001/acp` for ACP and `http://symphony-mcp:7001` for MCP
30
+ // over the same mapping. When MCP is reached via an explicit host URL (direct
31
+ // reachability, no tunnel) the entry is empty and the guest reaches the host
32
+ // directly. buildMcpTcpHostEntry + acpWsUrlFromExplicit are the load-bearing
33
+ // halves of that decision.
34
+ import { buildEvalModeMounts } from './dispatch-config.js';
35
+ import { buildMiseEnv, buildMiseMount, buildMisePrepCommand, buildMiseInstallCommand, miseSystemConfigFile, MISE_SHIMS_GUEST_DIR, } from './mise.js';
36
+ import { sanitizeWorkspaceKey } from '../workspace-key.js';
37
+ import { isCredentialEnvName, isCredentialTokenName, stripCredentialTokenVars, acpWsUrlFromExplicit, buildMcpTcpHostEntry, MCP_GUEST_BASE_URL, ACP_WS_GUEST_URL, } from '../vm-guards.js';
38
+ // The control-plane URL/tunnel + credential-strip primitives live canonically in
39
+ // core/vm-guards.ts; re-exported here so this file's importers (and its test) keep
40
+ // the `vm-plan.js` specifier without duplicating the definitions.
41
+ export { isCredentialTokenName, acpWsUrlFromExplicit, buildMcpTcpHostEntry, MCP_GUEST_BASE_URL, ACP_WS_GUEST_URL, } from '../vm-guards.js';
42
+ // ─── Constants ported verbatim from the reference ────────────────────────────
43
+ // The control-plane URL/host/port constants (MCP_GUEST_BASE_URL, ACP_WS_GUEST_URL,
44
+ // and the synthetic host/port/path they derive from) live canonically in
45
+ // core/vm-guards.ts; imported + re-exported above to avoid duplicating them.
46
+ /** VM session-label / name prefix (reference `vm-port.ts:SYMPHONY_VM_PREFIX`). */
47
+ export const SYMPHONY_VM_PREFIX = 'symphony-';
48
+ /**
49
+ * Default guest path the in-VM ACP launcher (`scripts/vm-agent.mjs`) is staged +
50
+ * exec'd at (reference `gondolin-dispatch.ts:DEFAULT_VM_AGENT_GUEST_PATH`).
51
+ */
52
+ export const DEFAULT_VM_AGENT_GUEST_PATH = '/opt/symphony/vm-agent.mjs';
53
+ /** Non-secret mode for the staged launcher — it is code, not a credential. */
54
+ export const VM_AGENT_LAUNCHER_MODE = 0o644;
55
+ /**
56
+ * Fixed guest agent search PATH (reference `gondolin-dispatch.ts:GUEST_AGENT_PATH`).
57
+ * `vm.exec` runs without a login shell so the profile PATH does not apply. The mise
58
+ * shims dir leads (issue 209) so the agent's CHILD processes resolve the project's
59
+ * tools; the `mise` binary itself + any baked node sit under /usr/local/bin. A
60
+ * non-existent shims dir (not yet installed) is harmlessly skipped.
61
+ * Carries no secret.
62
+ */
63
+ export const GUEST_AGENT_PATH = `${MISE_SHIMS_GUEST_DIR}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`;
64
+ /**
65
+ * Map a non-zero BYO pre-launch probe exit code to a precise, operator-facing
66
+ * contract-violation message. The enumerated codes (11/12/21/13) map to a fixed
67
+ * diagnostic; the default branch echoes the guest's own stderr token (when any)
68
+ * so an unanticipated non-zero exit still carries what the guest emitted rather
69
+ * than a bare number. Ported verbatim from the shell `probeFailureMessage` (§4a).
70
+ *
71
+ * Pure: the shell runs the probe exec (IO) and hands the captured code/stderr to
72
+ * this decider for the message it throws as a ByoContractError.
73
+ */
74
+ export function byoProbeFailureMessage(code, adapterBin, stderr, guestPath) {
75
+ const base = 'BYO image contract violation: ';
76
+ switch (code) {
77
+ case 11:
78
+ return `${base}the "node" binary is not on PATH (${guestPath}). The image must ship Node ≥ 21.`;
79
+ case 12:
80
+ return `${base}mise did not produce the "${adapterBin}" adapter shim on PATH (${guestPath}). The staged mise SYSTEM config must pin the chosen agent CLI and \`mise install\` must have succeeded.`;
81
+ case 21:
82
+ return `${base}node is present but lacks the global WebSocket (Node < 21). The in-VM launcher requires Node ≥ 21.`;
83
+ case 13:
84
+ return `${base}the in-VM launcher failed to materialize in the guest before launch.`;
85
+ default: {
86
+ const tail = stderr.trim();
87
+ const detail = tail ? ` Guest stderr: ${tail}` : '';
88
+ return `${base}the pre-launch probe failed (exit ${code}). Check that the image ships Node ≥ 21 + "${adapterBin}" + /bin/sh on PATH and a writable rootfs.${detail}`;
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Guest path of the bind-mount-trust gitconfig (reference `GUEST_GITCONFIG_PATH`).
94
+ * Lives OUTSIDE the workspace mount so it never shows in the repo's git status.
95
+ */
96
+ export const GUEST_GITCONFIG_PATH = '/opt/symphony/gitconfig';
97
+ /** Disarms git's bind-mount ownership guard for every workspace path (issue 149). */
98
+ export const GUEST_GITCONFIG_CONTENT = '[safe]\n\tdirectory = *\n';
99
+ /** Non-secret mode for the staged gitconfig. */
100
+ export const GUEST_GITCONFIG_MODE = 0o644;
101
+ // ─── Control-plane tunnel (synthetic DNS / tcp.hosts) ────────────────────────
102
+ // buildMcpTcpHostEntry + acpWsUrlFromExplicit are imported + re-exported from
103
+ // core/vm-guards.ts (their canonical home) — see the top-of-file re-export.
104
+ /**
105
+ * The host control-plane endpoint to tunnel into the guest, or null when no
106
+ * tunnel is needed/possible. Reference `runner.ts:mcpDispatchTarget`. Returns
107
+ * null when MCP is disabled, an `explicit_host_url` already points the guest at a
108
+ * directly-reachable URL, or the HTTP server has not bound a port
109
+ * (`boundMcpPort === null`). The bound port is INJECTED (it is a runtime fact the
110
+ * shell observes after the server binds) so this stays pure.
111
+ */
112
+ export function mcpDispatchTarget(cfg, boundMcpPort) {
113
+ if (!cfg.mcp.enabled || cfg.mcp.explicit_host_url)
114
+ return null;
115
+ if (boundMcpPort === null)
116
+ return null;
117
+ return { host: cfg.mcp.host, port: boundMcpPort };
118
+ }
119
+ /**
120
+ * Assemble the unified control-plane tunnel decision. Mirrors the reference's
121
+ * three coupled decisions (`buildTunnelMapping`, `computeAcpWsUrl`, the MCP base
122
+ * URL) so the ACP URL and the `tcp.hosts` entry that backs it stay consistent:
123
+ *
124
+ * • synthetic tunnel present (MCP enabled, port bound, no explicit URL):
125
+ * tcpHostEntry = { 'symphony-mcp:7001': '<host>:<port>' }
126
+ * mcpGuestBaseUrl = http://symphony-mcp:7001
127
+ * acpWsGuestUrl = ws://symphony-mcp:7001/acp
128
+ * • explicit host URL (direct reachability, no tunnel):
129
+ * tcpHostEntry = {}
130
+ * mcpGuestBaseUrl = the explicit URL verbatim
131
+ * acpWsGuestUrl = ws(s)://<explicit-host>/acp (null if unparseable)
132
+ * • neither (MCP disabled / no bound port and no explicit URL):
133
+ * tcpHostEntry = {}, mcpGuestBaseUrl = the synthetic base (unreachable),
134
+ * acpWsGuestUrl = null — the runner's validateAcpTransport fails the
135
+ * attempt fast in this case (no reachable dial-back URL).
136
+ */
137
+ export function buildControlPlaneTunnel(cfg, boundMcpPort) {
138
+ const target = mcpDispatchTarget(cfg, boundMcpPort);
139
+ if (target) {
140
+ return {
141
+ mcpGuestBaseUrl: MCP_GUEST_BASE_URL,
142
+ acpWsGuestUrl: ACP_WS_GUEST_URL,
143
+ tcpHostEntry: buildMcpTcpHostEntry(target.host, target.port),
144
+ };
145
+ }
146
+ const explicit = cfg.mcp.explicit_host_url;
147
+ if (explicit) {
148
+ return {
149
+ mcpGuestBaseUrl: explicit,
150
+ acpWsGuestUrl: acpWsUrlFromExplicit(explicit),
151
+ tcpHostEntry: {},
152
+ };
153
+ }
154
+ // No synthetic tunnel and no explicit URL: the guest has no reachable control
155
+ // plane. Surfaced as a null ACP URL so the shell fails the attempt fast
156
+ // (reference validateAcpTransport: 'acp ws url unavailable').
157
+ return {
158
+ mcpGuestBaseUrl: MCP_GUEST_BASE_URL,
159
+ acpWsGuestUrl: null,
160
+ tcpHostEntry: {},
161
+ };
162
+ }
163
+ // ─── Mounts ──────────────────────────────────────────────────────────────────
164
+ /**
165
+ * The host→guest mount set for a dispatch. Reference `runner.ts:buildVmMounts`:
166
+ * the workspace RW at its OWN host path (so `cwd` is identical inside/outside the
167
+ * VM), then the symphony-managed mise download-cache dir RW (issue 222; NOT the
168
+ * install dir — that lives on the ephemeral rootfs — and NOT a user `gondolin.volumes`
169
+ * entry; always present, mise provisioning is unconditional — issue 233),
170
+ * then every configured `gondolin.volumes` entry, then the eval-mode RO mounts
171
+ * (`tracker.root` → /symphony/issues, `logs.root` → /symphony/logs) when the
172
+ * resolved state opted in. Order is preserved exactly.
173
+ *
174
+ * SECURITY: the credential-mount denylist guard (`assertNoCredentialMounts`)
175
+ * lives in the shell (it needs `os.homedir()` + `fs.realpath`); this pure builder
176
+ * only assembles the list. The shell HARD-FAILs before createVm if any entry
177
+ * overlaps a credential directory.
178
+ */
179
+ export function buildVmMounts(cfg, resolved, workspacePath) {
180
+ const mounts = [{ host: workspacePath, guest: workspacePath, readonly: false }];
181
+ // mise provisioning is unconditional (issue 233): the download-cache mount always rides.
182
+ mounts.push(buildMiseMount(cfg));
183
+ for (const v of cfg.gondolin.volumes) {
184
+ mounts.push({ host: v.host, guest: v.guest, readonly: v.readonly });
185
+ }
186
+ for (const m of buildEvalModeMounts(cfg, resolved)) {
187
+ mounts.push({ host: m.host, guest: m.guest, readonly: m.readonly });
188
+ }
189
+ return mounts;
190
+ }
191
+ // ─── Env ──────────────────────────────────────────────────────────────────────
192
+ /**
193
+ * The stripped VM boot env. Reference `runner.ts:buildForwardedEnv`: forward the
194
+ * configured `gondolin.forward_env` vars, then STRIP every credential-bearing var
195
+ * (defense in depth). `readEnv` is INJECTED so this stays pure (no `process.env`
196
+ * in core). The credential strip is applied at compute time via the shared
197
+ * `isCredentialEnvName` policy (canonically core/vm-guards.ts) — equivalent to
198
+ * the reference's `stripCredentialEnv(computeForwardedEnv(...))` two-step, since
199
+ * folding the strip into the per-name filter already drops credential names. The
200
+ * guest holds only the placeholder bearer Gondolin substitutes at egress.
201
+ */
202
+ export function buildForwardedEnv(cfg, readEnv) {
203
+ const env = {};
204
+ for (const k of cfg.gondolin.forward_env) {
205
+ if (isCredentialEnvName(k))
206
+ continue;
207
+ const v = readEnv(k);
208
+ if (v && v.length > 0)
209
+ env[k] = v;
210
+ }
211
+ return env;
212
+ }
213
+ // ─── Staged guest files ────────────────────────────────────────────────────────
214
+ /** The bind-mount-trust gitconfig as a guest file (reference `guestGitconfigFile`). */
215
+ export function guestGitconfigFile() {
216
+ return {
217
+ guestPath: GUEST_GITCONFIG_PATH,
218
+ content: GUEST_GITCONFIG_CONTENT,
219
+ mode: GUEST_GITCONFIG_MODE,
220
+ };
221
+ }
222
+ /** The injected in-VM launcher as a guest file (reference `launcherFile`). Carries no secret → 0644. */
223
+ export function launcherFile(source, guestPath) {
224
+ return { guestPath, content: source, mode: VM_AGENT_LAUNCHER_MODE };
225
+ }
226
+ /**
227
+ * The full ordered guest-file set staged BEFORE the agent launches, mirroring the
228
+ * reference `bringUpOrTeardown` staging order:
229
+ * 1. the gitconfig (so in-guest git accepts the host-owned workspace from its
230
+ * first command — issue 149),
231
+ * 2. the mise SYSTEM config (issue 209) — always staged (issue 233),
232
+ * 3. the fake native creds (placeholders only — the guest bearer + identity),
233
+ * 4. the model/effort runtime files (e.g. claude's settings.json effortLevel),
234
+ * then (separately, written first in the reference) the launcher itself. We keep
235
+ * the launcher at the FRONT here because the shell needs its landed path before
236
+ * the probe; the relative order of the remaining files matches the reference.
237
+ *
238
+ * NOTE: the reference staged the launcher with a `/tmp` fallback when the primary
239
+ * write fails (a readonly-rootfs BYO image). That fallback is a RUNTIME decision
240
+ * (it inspects the write's exit code), so it stays in the shell — this plan
241
+ * carries only the PRIMARY launcher path; the shell may re-stage under `/tmp` and
242
+ * exec THAT path. By design the fallback path is not part of VmPlan.
243
+ */
244
+ export function buildGuestFiles(args) {
245
+ return [
246
+ launcherFile(args.launcherSource, args.guestAgentPath),
247
+ guestGitconfigFile(),
248
+ args.miseSystemConfig,
249
+ ...args.fakeCreds.files,
250
+ ...args.runtimeExtraFiles,
251
+ ];
252
+ }
253
+ // ─── Launch env + agent command ──────────────────────────────────────────────
254
+ /**
255
+ * The launch env the in-VM `vm-agent.mjs` reads to dial back + spawn the adapter.
256
+ * Reference `gondolin-dispatch.ts:buildLaunchEnv`.
257
+ *
258
+ * ORDERING (load-bearing): the placeholder bearer (`fakeCreds.env`, a FAKE value
259
+ * keyed by the credential var name itself, e.g. `ANTHROPIC_AUTH_TOKEN`) is merged
260
+ * LAST — after `stripCredentialTokenVars(runtimeEnv)` — so the strip drops a real
261
+ * token smuggled via runtimeEnv but the placeholder survives. PATH/GIT_CONFIG_GLOBAL
262
+ * lead so a runtimeEnv-supplied PATH (rare) can still override via the spread.
263
+ *
264
+ * `agentCommand` carries the absolute node + adapter paths the pre-launch probe
265
+ * resolves; those are runtime facts (the probe is IO), so the shell fills them.
266
+ * This builder fixes only the PATH-independent, secret-free env shape.
267
+ */
268
+ export function buildLaunchEnv(args) {
269
+ return {
270
+ PATH: GUEST_AGENT_PATH,
271
+ GIT_CONFIG_GLOBAL: GUEST_GITCONFIG_PATH,
272
+ ...(args.miseEnv ?? {}),
273
+ SYMPHONY_ACP_URL: args.acpWsGuestUrl,
274
+ SYMPHONY_ACP_TOKEN: args.acpToken,
275
+ SYMPHONY_ADAPTER_BIN: args.adapterBin,
276
+ SYMPHONY_ADAPTER_ARGS: JSON.stringify(args.adapterArgs),
277
+ ...(args.heartbeatIntervalMs !== undefined
278
+ ? { SYMPHONY_HEARTBEAT_INTERVAL_MS: String(args.heartbeatIntervalMs) }
279
+ : {}),
280
+ ...(args.heartbeatTimeoutMs !== undefined
281
+ ? { SYMPHONY_HEARTBEAT_TIMEOUT_MS: String(args.heartbeatTimeoutMs) }
282
+ : {}),
283
+ ...stripCredentialTokenVars(args.runtimeEnv),
284
+ // The placeholder (a fake) is ADDED after the strip — see the ordering note.
285
+ ...args.placeholderEnv,
286
+ };
287
+ }
288
+ // isCredentialTokenName + stripCredentialTokenVars (the runtime-env token strip)
289
+ // are imported + re-exported from core/vm-guards.ts — see the top-of-file
290
+ // re-export. They preserve non-secret vendor config (e.g. ANTHROPIC_MODEL) while
291
+ // dropping the named credential vars + the *_API_KEY/*_TOKEN/*_SECRET suffixes.
292
+ /**
293
+ * The agent argv exec'd inside the guest. Reference `launchAgent`:
294
+ * `[node, launcherGuestPath]`. The probe resolves the ABSOLUTE node path at
295
+ * runtime (PATH-independent), so the shell passes that as `resolvedNodeBin`; this
296
+ * builder only fixes the [node, launcher] shape.
297
+ */
298
+ export function buildAgentCommand(resolvedNodeBin, guestAgentPath) {
299
+ return [resolvedNodeBin, guestAgentPath];
300
+ }
301
+ // ─── Session label ──────────────────────────────────────────────────────────
302
+ /**
303
+ * The Gondolin session label / VM name. Reference `runner.ts:vmNameFor` +
304
+ * `gondolin-dispatch.ts:buildCreateVmOptions.sessionLabel`:
305
+ * `symphony-<sanitized-identifier>`, lowercased.
306
+ */
307
+ export function buildSessionLabel(identifier) {
308
+ return `${SYMPHONY_VM_PREFIX}${sanitizeWorkspaceKey(identifier)}`.toLowerCase();
309
+ }
310
+ /**
311
+ * Resolve the boot `imagePath` selector (issue 206). The `gondolin.image` escape
312
+ * hatch (a pre-converted asset selector) ALWAYS wins when set; otherwise fall back
313
+ * to the asset the reconcile loop auto-converted from `gondolin.oci_image`
314
+ * (`convertedImageSelector`), and finally to the empty string (no image source —
315
+ * doctor/boot surfaces it). Keeps the convert→dispatch handoff a pure decision.
316
+ */
317
+ export function resolveImagePath(image, convertedSelector) {
318
+ if (image && image.length > 0)
319
+ return image;
320
+ return convertedSelector ?? '';
321
+ }
322
+ // ─── Top-level assembly ────────────────────────────────────────────────────────
323
+ /**
324
+ * Assemble the complete VmPlan for one dispatch — the single (data) -> VmPlan
325
+ * entry point. Composes every sub-decision (session label, mounts, stripped boot
326
+ * env, staged files, agent argv, control-plane tunnel) so the shell dispatcher
327
+ * only EXECUTES the plan. Pure: no IO, no clock, no randomness.
328
+ *
329
+ * The guest path the launcher is staged/exec'd at comes from
330
+ * `cfg.gondolin.guest_agent_path` (the SINGLE source of truth — same value drives
331
+ * the staged file AND the exec arg, so they can never drift); falls back to the
332
+ * default when unset. The launch env rides on VmPlan via the assembled launch env
333
+ * — but VmPlan's shape carries only `env` (boot env), `guestFiles`, and
334
+ * `agentCommand`, so the LAUNCH env (distinct from the boot env) is returned
335
+ * alongside the plan for the shell's `vm.exec` env. By design VmPlan has no
336
+ * `launchEnv` field; we surface it next to the plan rather than widening the type
337
+ * another slice owns.
338
+ */
339
+ export function buildVmPlan(input) {
340
+ const { cfg, resolved } = input;
341
+ const guestAgentPath = cfg.gondolin.guest_agent_path && cfg.gondolin.guest_agent_path.length > 0
342
+ ? cfg.gondolin.guest_agent_path
343
+ : DEFAULT_VM_AGENT_GUEST_PATH;
344
+ const tunnel = buildControlPlaneTunnel(cfg, input.boundMcpPort);
345
+ // mise toolchain slice (issue 209): the env rides the boot env + the install/
346
+ // probe/launch execs; the system config is always staged (issue 233).
347
+ const miseEnv = buildMiseEnv();
348
+ const mise = {
349
+ env: miseEnv,
350
+ prepCommand: buildMisePrepCommand(),
351
+ installCommand: buildMiseInstallCommand(input.workspacePath),
352
+ };
353
+ const plan = {
354
+ sessionLabel: buildSessionLabel(input.identifier),
355
+ imagePath: resolveImagePath(cfg.gondolin.image, input.convertedImageSelector),
356
+ cpus: cfg.gondolin.cpus,
357
+ memMib: cfg.gondolin.mem_mib,
358
+ rootfsSize: cfg.gondolin.rootfs_size,
359
+ mounts: buildVmMounts(cfg, resolved, input.workspacePath),
360
+ env: { ...buildForwardedEnv(cfg, input.readEnv), ...miseEnv },
361
+ guestFiles: buildGuestFiles({
362
+ launcherSource: input.launcherSource,
363
+ guestAgentPath,
364
+ fakeCreds: input.fakeCreds,
365
+ runtimeExtraFiles: input.runtimeExtraFiles,
366
+ miseSystemConfig: miseSystemConfigFile(input.miseSystemConfigSource),
367
+ }),
368
+ agentCommand: buildAgentCommand(input.resolvedNodeBin, guestAgentPath),
369
+ tunnel,
370
+ allowWebSockets: input.allowWebSockets,
371
+ mise,
372
+ };
373
+ // The launch env needs the resolved ACP WS URL; when the tunnel produced none
374
+ // (no reachable control plane) the shell should already have failed the attempt
375
+ // (validateAcpTransport). We carry the empty string here so the type stays a
376
+ // plain string; the shell never reaches launch without a URL.
377
+ const launchEnv = buildLaunchEnv({
378
+ acpWsGuestUrl: tunnel.acpWsGuestUrl ?? '',
379
+ acpToken: input.acpToken,
380
+ adapterBin: input.adapterBin,
381
+ adapterArgs: input.adapterArgs,
382
+ runtimeEnv: input.runtimeEnv,
383
+ placeholderEnv: input.fakeCreds.env,
384
+ miseEnv,
385
+ heartbeatIntervalMs: cfg.acp.heartbeat_interval_ms,
386
+ heartbeatTimeoutMs: cfg.acp.heartbeat_timeout_ms,
387
+ });
388
+ return { plan, launchEnv };
389
+ }
390
+ //# sourceMappingURL=vm-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vm-plan.js","sourceRoot":"","sources":["../../../../src/core/runner/vm-plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ // FCIS rewrite — memory-aware admission decider (issue 27).
2
+ //
3
+ // Pure port of the original `computeMemoryAdmission` / `parseMemAvailableMib`
4
+ // (REF: src/memory.ts) plus the clamp-transition log decision that lived inline
5
+ // in the orchestrator's `availableGlobalSlots` (REF: src/orchestrator.ts).
6
+ //
7
+ // The /proc/meminfo READ is impure and stays in the shell (a MemProbe adapter);
8
+ // the shell hands this decider a MemorySnapshot. This is the impureCoreRisk fix:
9
+ // core never touches the wall clock, the filesystem, or /proc — it only folds the
10
+ // supplied reading + running count + config into the effective cap and a clamp
11
+ // log decision.
12
+ //
13
+ // Effective cap is `min(static_cap, running + admission_room)` where
14
+ // admission_room = max(0, floor((MemAvailable - reserve) / per_vm)).
15
+ // We add `running` because MemAvailable already reflects the memory the live VMs
16
+ // are using, so admission_room is the count of *additional* VMs that fit; adding
17
+ // the current running count yields a total cap consistent with the static one.
18
+ /**
19
+ * Parse a `/proc/meminfo` body and pull out `MemAvailable`. The kernel emits the
20
+ * value in KiB (`MemAvailable: 12345678 kB`); we return MiB for symmetry with
21
+ * `gondolin.mem_mib` and `agent.host_memory_reserve_mib`. Returns null when the
22
+ * line is missing or unparseable — the decider treats null the same as "probe
23
+ * unsupported" and skips the clamp.
24
+ *
25
+ * Pure: takes the already-read file body as data (the shell's MemProbe does the
26
+ * actual /proc read). No IO, no node builtins.
27
+ */
28
+ export function parseMemAvailableMib(meminfo) {
29
+ const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB\s*$/m);
30
+ if (!match)
31
+ return null;
32
+ const kb = parseInt(match[1], 10);
33
+ if (!Number.isFinite(kb))
34
+ return null;
35
+ return Math.floor(kb / 1024);
36
+ }
37
+ /**
38
+ * Fold the memory snapshot + running count + config into the admission summary.
39
+ *
40
+ * Skips the clamp (returns the static cap, `clampActive: false`,
41
+ * `admissionRoom: null`) when admission is disabled, the probe returned a null
42
+ * reading (unsupported host / unparseable line), or `perVmMib <= 0`. Otherwise
43
+ * computes the headroom-derived room and clamps the effective cap down to it.
44
+ */
45
+ export function computeMemoryAdmission(input) {
46
+ const { enabled, staticCap, running, probe, reserveMib, perVmMib } = input;
47
+ if (!enabled || probe.memAvailableMib === null || perVmMib <= 0) {
48
+ return {
49
+ enabled,
50
+ staticCap,
51
+ effectiveCap: staticCap,
52
+ admissionRoom: null,
53
+ memAvailableMib: probe.memAvailableMib,
54
+ reserveMib,
55
+ perVmMib,
56
+ clampActive: false,
57
+ };
58
+ }
59
+ const headroom = probe.memAvailableMib - reserveMib;
60
+ const room = headroom <= 0 ? 0 : Math.floor(headroom / perVmMib);
61
+ const memoryCap = running + room;
62
+ const effectiveCap = Math.min(staticCap, memoryCap);
63
+ return {
64
+ enabled,
65
+ staticCap,
66
+ effectiveCap,
67
+ admissionRoom: room,
68
+ memAvailableMib: probe.memAvailableMib,
69
+ reserveMib,
70
+ perVmMib,
71
+ clampActive: memoryCap < staticCap,
72
+ };
73
+ }
74
+ /**
75
+ * Compute the number of free global dispatch slots, AND the clamp-transition log
76
+ * decision, from an admission summary.
77
+ *
78
+ * The shell owns the persisted "was the clamp active last tick?" flag and passes
79
+ * it in as `previousClampActive`; this decider returns the next flag plus a
80
+ * LogEffect[] (empty unless the clamp toggled). Only logging on the transition —
81
+ * rather than every tick — keeps the log quiet while memory stays low, matching
82
+ * the original orchestrator behavior.
83
+ *
84
+ * Pending continuations hold their slot (the post-transition resume of an issue
85
+ * that just normal-exited), so the shell subtracts them here too; failure-backoff
86
+ * retries do NOT hold a slot and are excluded by the caller.
87
+ */
88
+ export function decideAvailableGlobalSlots(args) {
89
+ const { admission, running, pendingContinuations, previousClampActive } = args;
90
+ const logs = [];
91
+ if (admission.clampActive !== previousClampActive) {
92
+ if (admission.clampActive) {
93
+ logs.push({
94
+ family: 'log',
95
+ kind: 'emit',
96
+ level: 'info',
97
+ message: 'memory admission clamping concurrency',
98
+ fields: {
99
+ static_cap: admission.staticCap,
100
+ effective_cap: admission.effectiveCap,
101
+ mem_available_mib: admission.memAvailableMib,
102
+ reserve_mib: admission.reserveMib,
103
+ per_vm_mib: admission.perVmMib,
104
+ },
105
+ });
106
+ }
107
+ else {
108
+ logs.push({
109
+ family: 'log',
110
+ kind: 'emit',
111
+ level: 'info',
112
+ message: 'memory admission cleared; full static cap available',
113
+ fields: {
114
+ static_cap: admission.staticCap,
115
+ mem_available_mib: admission.memAvailableMib,
116
+ },
117
+ });
118
+ }
119
+ }
120
+ const availableSlots = Math.max(0, admission.effectiveCap - running - pendingContinuations);
121
+ return { availableSlots, nextClampActive: admission.clampActive, logs };
122
+ }
123
+ //# sourceMappingURL=admission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admission.js","sourceRoot":"","sources":["../../../../src/core/schedule/admission.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ // FCIS rewrite — pure circuit-breaker core (issue 128; hardened in issue 135).
2
+ //
3
+ // Ported faithfully from the original `src/orchestrator-decisions.ts`
4
+ // (`normalizeFailureReason`, `decideCircuitBreaker`) plus the holding-state
5
+ // trip-notes text the original `Orchestrator.routeToHolding` rendered inline.
6
+ //
7
+ // The retry path retries an abnormally-exited dispatch with exponential backoff
8
+ // and NO upper bound: a deterministically-failing dispatch loops indefinitely,
9
+ // booting + reaping a VM every backoff interval. These pure helpers bound that
10
+ // loop — count CONSECUTIVE failed dispatches and stop once an issue has made no
11
+ // progress for `threshold` attempts in a row — without the streak bookkeeping
12
+ // leaking into the shell.
13
+ //
14
+ // Issue 135 fix — count by STREAK LENGTH, NOT by same-reason. The original
15
+ // breaker only incremented when the *normalized reason matched the prior one*,
16
+ // so a dispatch that kept failing with FLAPPING reasons reset the counter every
17
+ // time the reason changed and never tripped. Issue 135 looped 8× exactly this
18
+ // way — four `ECONNRESET` turns (streak → 4), then a single `401` (different
19
+ // reason → streak reset to 1), then `ECONNRESET` again (reset to 1) — the lone
20
+ // interleaved 401 landed one short of the threshold and re-armed the breaker
21
+ // from zero. The signal we actually want is "this issue has made no progress
22
+ // for N attempts in a row", which is reason-independent: increment on every
23
+ // consecutive abnormal exit; reset only when a dispatch exits cleanly (the
24
+ // issue progressed). The normalized reason is still tracked, but only to label
25
+ // the trip — it no longer gates counting.
26
+ //
27
+ // Every function here is pure and SYNCHRONOUS: (data) -> data. The single side
28
+ // effect the original performed at the trip site — moving the issue into a
29
+ // holding state with a diagnostic note — becomes a returned `tracker.move_state`
30
+ // Effect (see `tripToHoldingEffect`), which the shell EXECUTES. No clock, no
31
+ // randomness, no IO. Imports are restricted to src/types.
32
+ /**
33
+ * Normalize a failure `reason` for the trip log / holding-state route note so
34
+ * the human-facing message is stable even when the underlying message embeds
35
+ * volatile tokens (request ids, status codes, timestamps, hashes, per-dispatch
36
+ * sentinels).
37
+ *
38
+ * Rule: lowercase, then collapse every whitespace-delimited token that contains
39
+ * a digit into `<id>` — volatile identifiers almost always carry a digit (a
40
+ * request id, an HTTP status, a timestamp fragment, a counter, a hex hash),
41
+ * whereas the human-readable words that carry a failure's *identity* (`agent
42
+ * turn refusal`, `agent turn transport_error`, `vm bring-up error`) do not.
43
+ * Used only for reporting now (counting no longer keys on it — see issue 135).
44
+ */
45
+ export function normalizeFailureReason(reason) {
46
+ return reason
47
+ .toLowerCase()
48
+ .replace(/\S*\d\S*/g, '<id>')
49
+ .replace(/\s+/g, ' ')
50
+ .trim();
51
+ }
52
+ /**
53
+ * Fold one worker exit into the circuit-breaker streak. A clean exit (`normal`)
54
+ * resets it — the issue made progress. Any abnormal exit increments the streak
55
+ * by one REGARDLESS of reason (issue 135: keying on same-reason let flapping
56
+ * failures dodge the breaker forever). When `threshold > 0` and the streak
57
+ * reaches it, the breaker trips. `threshold <= 0` disables tripping entirely
58
+ * (the streak is still tracked but never trips), so an operator can turn the
59
+ * breaker off. `reason` is normalized only to label the eventual trip.
60
+ */
61
+ export function decideCircuitBreaker(input) {
62
+ if (input.normal)
63
+ return { kind: 'reset' };
64
+ const normalizedReason = normalizeFailureReason(input.reason);
65
+ const count = input.prior ? input.prior.count + 1 : 1;
66
+ if (input.threshold > 0 && count >= input.threshold) {
67
+ return { kind: 'trip', normalizedReason, count };
68
+ }
69
+ return { kind: 'continue', normalizedReason, count };
70
+ }
71
+ /**
72
+ * Render the diagnostic note appended to the issue when the breaker trips and
73
+ * routes it to a holding state, so the operator sees why it stopped. Ported
74
+ * verbatim from the original `Orchestrator.routeToHolding`.
75
+ *
76
+ * `reason` is the human-facing failure reason (the original passed the RAW exit
77
+ * reason here, not the normalized one — the normalized form is for streak
78
+ * labeling/logging). `count` is the consecutive-failure streak length that
79
+ * tripped the breaker.
80
+ */
81
+ export function tripHoldingNotes(holdingState, reason, count) {
82
+ return [
83
+ `**Circuit breaker tripped** — routed to \`${holdingState}\` for human inspection.`,
84
+ '',
85
+ `Symphony stopped retrying after **${count} consecutive attempts failed with the same error**, to avoid an unbounded dispatch loop (issue 128).`,
86
+ '',
87
+ `**Last failure reason:** ${reason}`,
88
+ '',
89
+ `Resolve the underlying cause, then move the issue back into an active state to resume dispatch.`,
90
+ ].join('\n');
91
+ }
92
+ /**
93
+ * Fold a tripped breaker into the `tracker.move_state` Effect the shell executes
94
+ * to move the issue into its holding state with the rendered diagnostic note.
95
+ * This is the single seam where the trip decision becomes a side effect: core
96
+ * returns the Effect, the shell performs the tracker move (the original did this
97
+ * inline in `routeToHolding`).
98
+ */
99
+ export function tripToHoldingEffect(input) {
100
+ return {
101
+ family: 'tracker',
102
+ kind: 'move_state',
103
+ issueId: input.issueId,
104
+ toState: input.holdingState,
105
+ fromRoot: input.fromRoot,
106
+ fromState: input.fromState,
107
+ notes: tripHoldingNotes(input.holdingState, input.reason, input.count),
108
+ actor: input.actor,
109
+ };
110
+ }
111
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../../../src/core/schedule/circuit-breaker.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,sEAAsE;AACtE,4EAA4E;AAC5E,8EAA8E;AAC9E,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,0BAA0B;AAC1B,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,2EAA2E;AAC3E,+EAA+E;AAC/E,0CAA0C;AAC1C,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,iFAAiF;AACjF,6EAA6E;AAC7E,0DAA0D;AAU1D;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,MAAM;SACV,WAAW,EAAE;SACb,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC;SAC5B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAaD;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAgC;IACnE,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3C,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoB,EAAE,MAAc,EAAE,KAAa;IAClF,OAAO;QACL,6CAA6C,YAAY,0BAA0B;QACnF,EAAE;QACF,qCAAqC,KAAK,sGAAsG;QAChJ,EAAE;QACF,4BAA4B,MAAM,EAAE;QACpC,EAAE;QACF,iGAAiG;KAClG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAmBD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAyB;IAC3D,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,YAAY;QAC3B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;QACtE,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;AACJ,CAAC"}