@treeseed/sdk 0.10.28 → 0.12.4

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 (509) hide show
  1. package/README.md +213 -7
  2. package/dist/agent-capacity.d.ts +916 -0
  3. package/dist/agent-capacity.js +1600 -0
  4. package/dist/agent-tools.d.ts +37 -0
  5. package/dist/agent-tools.js +301 -0
  6. package/dist/api/app.d.ts +1 -1
  7. package/dist/api/auth/d1-database.d.ts +2 -2
  8. package/dist/api/auth/d1-provider.d.ts +4 -4
  9. package/dist/api/auth/d1-store.d.ts +2 -2
  10. package/dist/api/auth/memory-provider.d.ts +1 -1
  11. package/dist/api/auth/tokens.d.ts +1 -1
  12. package/dist/api/config.d.ts +3 -1
  13. package/dist/api/config.js +34 -10
  14. package/dist/api/http.d.ts +2 -2
  15. package/dist/api/index.d.ts +10 -10
  16. package/dist/api/index.js +4 -2
  17. package/dist/api/operations-routes.d.ts +3 -3
  18. package/dist/api/operations.d.ts +1 -1
  19. package/dist/api/providers.d.ts +1 -1
  20. package/dist/api/providers.js +21 -4
  21. package/dist/api/railway.d.ts +3 -2
  22. package/dist/api/sdk-dispatch.d.ts +4 -4
  23. package/dist/api/sdk-routes.d.ts +2 -2
  24. package/dist/api/templates.d.ts +2 -2
  25. package/dist/api/types.d.ts +3 -2
  26. package/dist/capacity-provider.d.ts +118 -59
  27. package/dist/capacity-provider.js +235 -43
  28. package/dist/capacity.d.ts +2 -2
  29. package/dist/cli-tools.d.ts +1 -1
  30. package/dist/content-operations.d.ts +97 -0
  31. package/dist/content-operations.js +250 -0
  32. package/dist/content-store.d.ts +4 -4
  33. package/dist/control-plane-client.d.ts +13 -18
  34. package/dist/control-plane-client.js +8 -86
  35. package/dist/control-plane.d.ts +3 -2
  36. package/dist/control-plane.js +38 -13
  37. package/dist/copilot.d.ts +3 -0
  38. package/dist/copilot.js +16 -1
  39. package/dist/d1-http.d.ts +1 -1
  40. package/dist/d1-store.d.ts +32 -57
  41. package/dist/d1-store.js +115 -318
  42. package/dist/db/d1.d.ts +1 -232
  43. package/dist/db/index.d.ts +3 -3
  44. package/dist/db/market-schema.d.ts +50900 -11018
  45. package/dist/db/market-schema.js +2313 -467
  46. package/dist/db/node-sqlite.d.ts +1 -232
  47. package/dist/db/node-sqlite.js +7 -2
  48. package/dist/db/schema.d.ts +0 -462
  49. package/dist/db/schema.js +2 -22
  50. package/dist/dispatch.d.ts +1 -1
  51. package/dist/dispatch.js +0 -11
  52. package/dist/fixture-support.js +52 -4
  53. package/dist/governance.d.ts +133 -0
  54. package/dist/governance.js +285 -0
  55. package/dist/graph/build.d.ts +2 -2
  56. package/dist/graph/context-query-contracts.d.ts +1 -1
  57. package/dist/graph/dsl.d.ts +1 -1
  58. package/dist/graph/query.d.ts +4 -4
  59. package/dist/graph/ranking.d.ts +1 -1
  60. package/dist/graph/schema.d.ts +1 -1
  61. package/dist/graph.d.ts +11 -11
  62. package/dist/guarantees/index.d.ts +425 -0
  63. package/dist/guarantees/index.js +1146 -0
  64. package/dist/hosting/apps.d.ts +12 -0
  65. package/dist/hosting/apps.js +107 -0
  66. package/dist/hosting/builtins.d.ts +25 -0
  67. package/dist/hosting/builtins.js +602 -0
  68. package/dist/hosting/contracts.d.ts +208 -0
  69. package/dist/hosting/contracts.js +0 -0
  70. package/dist/hosting/graph.d.ts +195 -0
  71. package/dist/hosting/graph.js +1092 -0
  72. package/dist/hosting/index.d.ts +4 -0
  73. package/dist/hosting/index.js +4 -0
  74. package/dist/index.d.ts +88 -67
  75. package/dist/index.js +142 -14
  76. package/dist/local-dev/managed-dev.d.ts +85 -0
  77. package/dist/local-dev/managed-dev.js +368 -0
  78. package/dist/managed-dependencies.d.ts +9 -0
  79. package/dist/managed-dependencies.js +10 -9
  80. package/dist/market-client.d.ts +327 -9
  81. package/dist/market-client.js +464 -20
  82. package/dist/model-registry.d.ts +1 -1
  83. package/dist/model-registry.js +19 -19
  84. package/dist/operations/agent-tools.d.ts +2 -11
  85. package/dist/operations/agent-tools.js +2 -14
  86. package/dist/operations/providers/default.d.ts +1 -1
  87. package/dist/operations/providers/default.js +20 -37
  88. package/dist/operations/repository-operations.d.ts +26 -2
  89. package/dist/operations/repository-operations.js +159 -1
  90. package/dist/operations/runtime.d.ts +3 -3
  91. package/dist/operations/services/bootstrap-runner.d.ts +4 -2
  92. package/dist/operations/services/bootstrap-runner.js +22 -2
  93. package/dist/operations/services/build-warning-policy.d.ts +34 -0
  94. package/dist/operations/services/build-warning-policy.js +81 -82
  95. package/dist/operations/services/commit-message-provider.js +4 -4
  96. package/dist/operations/services/config-runtime.d.ts +83 -24
  97. package/dist/operations/services/config-runtime.js +449 -130
  98. package/dist/operations/services/deploy.d.ts +106 -624
  99. package/dist/operations/services/deploy.js +746 -263
  100. package/dist/operations/services/deployment-readiness.d.ts +30 -0
  101. package/dist/operations/services/deployment-readiness.js +206 -0
  102. package/dist/operations/services/git-remote-policy.js +12 -4
  103. package/dist/operations/services/git-runner.d.ts +66 -0
  104. package/dist/operations/services/git-runner.js +479 -0
  105. package/dist/operations/services/git-workflow.d.ts +45 -6
  106. package/dist/operations/services/git-workflow.js +251 -23
  107. package/dist/operations/services/github-actions-verification.d.ts +4 -1
  108. package/dist/operations/services/github-actions-verification.js +12 -3
  109. package/dist/operations/services/github-api.js +22 -6
  110. package/dist/operations/services/github-automation.d.ts +4 -4
  111. package/dist/operations/services/github-automation.js +19 -21
  112. package/dist/operations/services/github-credentials.d.ts +13 -0
  113. package/dist/operations/services/github-credentials.js +59 -0
  114. package/dist/operations/services/hosted-service-checks.d.ts +81 -0
  115. package/dist/operations/services/hosted-service-checks.js +478 -0
  116. package/dist/operations/services/hosting-audit.d.ts +2 -2
  117. package/dist/operations/services/hosting-audit.js +7 -7
  118. package/dist/operations/services/hub-launch.d.ts +1 -1
  119. package/dist/operations/services/hub-provider-launch.d.ts +5 -5
  120. package/dist/operations/services/hub-provider-launch.js +22 -17
  121. package/dist/operations/services/live-hosted-service-checks.d.ts +26 -0
  122. package/dist/operations/services/live-hosted-service-checks.js +463 -0
  123. package/dist/operations/services/local-dev.js +0 -1
  124. package/dist/operations/services/managed-host-security.d.ts +1 -1
  125. package/dist/operations/services/managed-host-security.js +1 -1
  126. package/dist/operations/services/managed-repositories.d.ts +37 -0
  127. package/dist/operations/services/managed-repositories.js +177 -0
  128. package/dist/operations/services/operations-runner-smoke.d.ts +31 -0
  129. package/dist/operations/services/operations-runner-smoke.js +198 -0
  130. package/dist/operations/services/package-adapters.d.ts +170 -0
  131. package/dist/operations/services/package-adapters.js +859 -0
  132. package/dist/operations/services/package-reference-policy.d.ts +13 -88
  133. package/dist/operations/services/package-reference-policy.js +96 -215
  134. package/dist/operations/services/project-host-operations.d.ts +4 -4
  135. package/dist/operations/services/project-platform.d.ts +57 -173
  136. package/dist/operations/services/project-platform.js +80 -111
  137. package/dist/operations/services/project-web-monitor.d.ts +2 -2
  138. package/dist/operations/services/project-web-monitor.js +122 -5
  139. package/dist/operations/services/railway-api.d.ts +117 -5
  140. package/dist/operations/services/railway-api.js +915 -93
  141. package/dist/operations/services/railway-deploy.d.ts +30 -112
  142. package/dist/operations/services/railway-deploy.js +521 -961
  143. package/dist/operations/services/release-history.js +3 -4
  144. package/dist/operations/services/release-proof-ledger.d.ts +13 -0
  145. package/dist/operations/services/release-proof-ledger.js +81 -0
  146. package/dist/operations/services/release-proof-planner.d.ts +48 -0
  147. package/dist/operations/services/release-proof-planner.js +195 -0
  148. package/dist/operations/services/release-proof-runner.d.ts +16 -0
  149. package/dist/operations/services/release-proof-runner.js +169 -0
  150. package/dist/operations/services/release-proof.d.ts +81 -0
  151. package/dist/operations/services/release-proof.js +79 -0
  152. package/dist/operations/services/repository-save-orchestrator.d.ts +8 -7
  153. package/dist/operations/services/repository-save-orchestrator.js +604 -212
  154. package/dist/operations/services/runtime-paths.d.ts +1 -3
  155. package/dist/operations/services/runtime-paths.js +0 -4
  156. package/dist/operations/services/runtime-tools.d.ts +1 -191
  157. package/dist/operations/services/runtime-tools.js +4 -443
  158. package/dist/operations/services/save-deploy-preflight.js +9 -1
  159. package/dist/operations/services/template-host-bindings.d.ts +2 -2
  160. package/dist/operations/services/template-host-bindings.js +3 -2
  161. package/dist/operations/services/template-registry.d.ts +6 -6
  162. package/dist/operations/services/template-registry.js +33 -14
  163. package/dist/operations/services/template-secret-sync.d.ts +3 -3
  164. package/dist/operations/services/verification-cache.d.ts +25 -0
  165. package/dist/operations/services/verification-cache.js +71 -0
  166. package/dist/operations/services/workflow-timing.d.ts +25 -0
  167. package/dist/operations/services/workflow-timing.js +79 -0
  168. package/dist/operations/services/workspace-dependency-mode.js +50 -5
  169. package/dist/operations/services/workspace-preflight.js +8 -8
  170. package/dist/operations/services/workspace-save-script.d.ts +4 -4
  171. package/dist/operations/services/workspace-save.d.ts +1 -0
  172. package/dist/operations/services/workspace-save.js +42 -19
  173. package/dist/operations/services/workspace-tools.js +42 -7
  174. package/dist/operations-registry.d.ts +1 -1
  175. package/dist/operations-registry.js +5 -6
  176. package/dist/operations-types.d.ts +1 -1
  177. package/dist/operations.d.ts +17 -11
  178. package/dist/operations.js +57 -3
  179. package/dist/platform/book-export.js +2 -0
  180. package/dist/platform/books-data.d.ts +1 -1
  181. package/dist/platform/config.d.ts +27 -0
  182. package/dist/platform/config.js +100 -0
  183. package/dist/platform/content-runtime-source.d.ts +54 -0
  184. package/dist/platform/content-runtime-source.js +169 -0
  185. package/dist/platform/contracts.d.ts +38 -2
  186. package/dist/platform/deploy-config.d.ts +1 -1
  187. package/dist/platform/deploy-config.js +80 -9
  188. package/dist/platform/deploy-runtime.d.ts +1 -1
  189. package/dist/platform/desired-state.d.ts +63 -0
  190. package/dist/platform/desired-state.js +1021 -0
  191. package/dist/platform/env.yaml +568 -45
  192. package/dist/platform/environment.d.ts +3 -2
  193. package/dist/platform/environment.js +122 -21
  194. package/dist/platform/local-content-materialization.d.ts +9 -0
  195. package/dist/platform/local-content-materialization.js +212 -0
  196. package/dist/platform/plugin.d.ts +13 -4
  197. package/dist/platform/plugins/runtime.d.ts +3 -3
  198. package/dist/platform/plugins/runtime.js +62 -1
  199. package/dist/platform/plugins.d.ts +6 -6
  200. package/dist/platform/published-content-pipeline.d.ts +3 -3
  201. package/dist/platform/published-content-pipeline.js +5 -1
  202. package/dist/platform/published-content.d.ts +4 -3
  203. package/dist/platform/site-config-schema.d.ts +1 -0
  204. package/dist/platform/site-config-schema.js +4 -1
  205. package/dist/platform/tenant/runtime-config.d.ts +152 -2
  206. package/dist/platform/tenant-config.d.ts +1 -1
  207. package/dist/platform/utils/site-config-schema.d.ts +154 -0
  208. package/dist/platform/utils/site-config-schema.js +400 -461
  209. package/dist/platform-operation-store.d.ts +1 -1
  210. package/dist/platform-operation-store.js +2 -2
  211. package/dist/platform-operations.js +1 -1
  212. package/dist/plugin-default.d.ts +1 -1
  213. package/dist/plugin-default.js +27 -12
  214. package/dist/project-import.d.ts +91 -0
  215. package/dist/project-import.js +257 -0
  216. package/dist/project-workflow.d.ts +1 -1
  217. package/dist/reconcile/bootstrap-systems.d.ts +2 -2
  218. package/dist/reconcile/bootstrap-systems.js +5 -5
  219. package/dist/reconcile/builtin-adapters.d.ts +7 -1
  220. package/dist/reconcile/builtin-adapters.js +3190 -429
  221. package/dist/reconcile/contracts.d.ts +23 -6
  222. package/dist/reconcile/desired-state.d.ts +2 -192
  223. package/dist/reconcile/desired-state.js +29 -38
  224. package/dist/reconcile/engine.d.ts +61 -395
  225. package/dist/reconcile/engine.js +229 -44
  226. package/dist/reconcile/index.d.ts +10 -8
  227. package/dist/reconcile/index.js +2 -0
  228. package/dist/reconcile/live-acceptance.d.ts +100 -0
  229. package/dist/reconcile/live-acceptance.js +2027 -0
  230. package/dist/reconcile/platform.d.ts +104 -0
  231. package/dist/reconcile/platform.js +100 -0
  232. package/dist/reconcile/providers/docker-private.d.ts +54 -0
  233. package/dist/reconcile/providers/docker-private.js +101 -0
  234. package/dist/reconcile/providers/github-private.d.ts +65 -0
  235. package/dist/reconcile/providers/github-private.js +119 -0
  236. package/dist/reconcile/providers/local-private.d.ts +28 -0
  237. package/dist/reconcile/providers/local-private.js +75 -0
  238. package/dist/reconcile/providers/railway-iac.d.ts +66 -0
  239. package/dist/reconcile/providers/railway-iac.js +275 -0
  240. package/dist/reconcile/providers/release-private.d.ts +92 -0
  241. package/dist/reconcile/providers/release-private.js +152 -0
  242. package/dist/reconcile/registry.d.ts +2 -2
  243. package/dist/reconcile/registry.js +16 -1
  244. package/dist/reconcile/state.d.ts +1 -1
  245. package/dist/reconcile/state.js +7 -44
  246. package/dist/reconcile/units.d.ts +1 -1
  247. package/dist/reconcile/units.js +9 -3
  248. package/dist/remote.d.ts +6 -6
  249. package/dist/scenes/artifacts.d.ts +16 -0
  250. package/dist/scenes/artifacts.js +61 -0
  251. package/dist/scenes/auth.d.ts +2 -0
  252. package/dist/scenes/auth.js +28 -0
  253. package/dist/scenes/base-url.d.ts +12 -0
  254. package/dist/scenes/base-url.js +123 -0
  255. package/dist/scenes/builtin-plugins.d.ts +2 -0
  256. package/dist/scenes/builtin-plugins.js +447 -0
  257. package/dist/scenes/checkpoints.d.ts +14 -0
  258. package/dist/scenes/checkpoints.js +33 -0
  259. package/dist/scenes/device-matrix.d.ts +2 -0
  260. package/dist/scenes/device-matrix.js +97 -0
  261. package/dist/scenes/devices.d.ts +11 -0
  262. package/dist/scenes/devices.js +22 -0
  263. package/dist/scenes/diagnostics.d.ts +5 -0
  264. package/dist/scenes/diagnostics.js +22 -0
  265. package/dist/scenes/diagram-providers.d.ts +2 -0
  266. package/dist/scenes/diagram-providers.js +181 -0
  267. package/dist/scenes/diagram-validation.d.ts +22 -0
  268. package/dist/scenes/diagram-validation.js +74 -0
  269. package/dist/scenes/environment.d.ts +2 -0
  270. package/dist/scenes/environment.js +65 -0
  271. package/dist/scenes/evidence-bundle.d.ts +5 -0
  272. package/dist/scenes/evidence-bundle.js +42 -0
  273. package/dist/scenes/evidence-report.d.ts +10 -0
  274. package/dist/scenes/evidence-report.js +73 -0
  275. package/dist/scenes/evidence.d.ts +16 -0
  276. package/dist/scenes/evidence.js +277 -0
  277. package/dist/scenes/index.d.ts +48 -0
  278. package/dist/scenes/index.js +312 -0
  279. package/dist/scenes/inspect.d.ts +9 -0
  280. package/dist/scenes/inspect.js +64 -0
  281. package/dist/scenes/loader.d.ts +3 -0
  282. package/dist/scenes/loader.js +38 -0
  283. package/dist/scenes/logs.d.ts +2 -0
  284. package/dist/scenes/logs.js +47 -0
  285. package/dist/scenes/operations.d.ts +3 -0
  286. package/dist/scenes/operations.js +128 -0
  287. package/dist/scenes/phase0.d.ts +9 -0
  288. package/dist/scenes/phase0.js +637 -0
  289. package/dist/scenes/planner.d.ts +12 -0
  290. package/dist/scenes/planner.js +166 -0
  291. package/dist/scenes/playwright-adapter.d.ts +2 -0
  292. package/dist/scenes/playwright-adapter.js +63 -0
  293. package/dist/scenes/plugins.d.ts +5 -0
  294. package/dist/scenes/plugins.js +115 -0
  295. package/dist/scenes/progress.d.ts +17 -0
  296. package/dist/scenes/progress.js +24 -0
  297. package/dist/scenes/publish-destinations.d.ts +12 -0
  298. package/dist/scenes/publish-destinations.js +99 -0
  299. package/dist/scenes/publish-plan-report.d.ts +10 -0
  300. package/dist/scenes/publish-plan-report.js +70 -0
  301. package/dist/scenes/publish-plan.d.ts +15 -0
  302. package/dist/scenes/publish-plan.js +248 -0
  303. package/dist/scenes/publish-redaction.d.ts +16 -0
  304. package/dist/scenes/publish-redaction.js +148 -0
  305. package/dist/scenes/publish-release-record.d.ts +13 -0
  306. package/dist/scenes/publish-release-record.js +34 -0
  307. package/dist/scenes/publish-report.d.ts +10 -0
  308. package/dist/scenes/publish-report.js +67 -0
  309. package/dist/scenes/publish.d.ts +16 -0
  310. package/dist/scenes/publish.js +298 -0
  311. package/dist/scenes/registry.d.ts +12 -0
  312. package/dist/scenes/registry.js +65 -0
  313. package/dist/scenes/remotion-adapter.d.ts +3 -0
  314. package/dist/scenes/remotion-adapter.js +94 -0
  315. package/dist/scenes/remotion-composition-registry.d.ts +2 -0
  316. package/dist/scenes/remotion-composition-registry.js +12 -0
  317. package/dist/scenes/remotion-compositions.d.ts +15 -0
  318. package/dist/scenes/remotion-compositions.js +464 -0
  319. package/dist/scenes/remotion-diagrams.d.ts +9 -0
  320. package/dist/scenes/remotion-diagrams.js +188 -0
  321. package/dist/scenes/remotion-input.d.ts +13 -0
  322. package/dist/scenes/remotion-input.js +291 -0
  323. package/dist/scenes/remotion-visuals.d.ts +29 -0
  324. package/dist/scenes/remotion-visuals.js +198 -0
  325. package/dist/scenes/render-media-assets.d.ts +9 -0
  326. package/dist/scenes/render-media-assets.js +117 -0
  327. package/dist/scenes/render-report.d.ts +10 -0
  328. package/dist/scenes/render-report.js +27 -0
  329. package/dist/scenes/render.d.ts +2 -0
  330. package/dist/scenes/render.js +218 -0
  331. package/dist/scenes/reporter.d.ts +3 -0
  332. package/dist/scenes/reporter.js +101 -0
  333. package/dist/scenes/resume.d.ts +2 -0
  334. package/dist/scenes/resume.js +79 -0
  335. package/dist/scenes/runner.d.ts +2 -0
  336. package/dist/scenes/runner.js +674 -0
  337. package/dist/scenes/schema.d.ts +6 -0
  338. package/dist/scenes/schema.js +994 -0
  339. package/dist/scenes/seed.d.ts +2 -0
  340. package/dist/scenes/seed.js +83 -0
  341. package/dist/scenes/segments.d.ts +15 -0
  342. package/dist/scenes/segments.js +79 -0
  343. package/dist/scenes/selectors.d.ts +3 -0
  344. package/dist/scenes/selectors.js +21 -0
  345. package/dist/scenes/timeline.d.ts +11 -0
  346. package/dist/scenes/timeline.js +26 -0
  347. package/dist/scenes/timeouts.d.ts +6 -0
  348. package/dist/scenes/timeouts.js +17 -0
  349. package/dist/scenes/training-captions.d.ts +14 -0
  350. package/dist/scenes/training-captions.js +98 -0
  351. package/dist/scenes/training-report.d.ts +10 -0
  352. package/dist/scenes/training-report.js +78 -0
  353. package/dist/scenes/training-transcript.d.ts +24 -0
  354. package/dist/scenes/training-transcript.js +204 -0
  355. package/dist/scenes/training.d.ts +10 -0
  356. package/dist/scenes/training.js +134 -0
  357. package/dist/scenes/types.d.ts +1872 -0
  358. package/dist/scenes/types.js +8 -0
  359. package/dist/scenes/visual-audit-contact-sheets.d.ts +6 -0
  360. package/dist/scenes/visual-audit-contact-sheets.js +119 -0
  361. package/dist/scenes/visual-audit-fixtures.d.ts +25 -0
  362. package/dist/scenes/visual-audit-fixtures.js +210 -0
  363. package/dist/scenes/visual-audit-report.d.ts +11 -0
  364. package/dist/scenes/visual-audit-report.js +85 -0
  365. package/dist/scenes/visual-audit-review.d.ts +23 -0
  366. package/dist/scenes/visual-audit-review.js +829 -0
  367. package/dist/scenes/visual-audit-routes.d.ts +13 -0
  368. package/dist/scenes/visual-audit-routes.js +308 -0
  369. package/dist/scenes/visual-audit.d.ts +3 -0
  370. package/dist/scenes/visual-audit.js +596 -0
  371. package/dist/scripts/build-dist.js +154 -40
  372. package/dist/scripts/check-build-warnings.js +42 -53
  373. package/dist/scripts/cleanup-markdown.js +1 -1
  374. package/dist/scripts/cli-test-fixtures.js +24 -28
  375. package/dist/scripts/deployment-readiness.js +20 -0
  376. package/dist/scripts/generate-treedx-openapi-types.js +186 -0
  377. package/dist/scripts/migrate-journey-csv-to-guarantees.js +140 -0
  378. package/dist/scripts/operations-runner-smoke.js +16 -0
  379. package/dist/scripts/package-tools.js +12 -1
  380. package/dist/scripts/prepare.js +3 -7
  381. package/dist/scripts/release-verify.js +6 -6
  382. package/dist/scripts/run-fixture-astro-command.js +1 -1
  383. package/dist/scripts/template-catalog.test.js +68 -82
  384. package/dist/scripts/tenant-dev.js +0 -10
  385. package/dist/scripts/tenant-workflow-action.js +10 -1
  386. package/dist/scripts/test-cloudflare-local.js +1 -1
  387. package/dist/scripts/test-scaffold.js +0 -1
  388. package/dist/scripts/test-smoke.js +3 -3
  389. package/dist/scripts/treeseed-build-dist.js +4 -7
  390. package/dist/scripts/treeseed-release-verify.js +1 -1
  391. package/dist/scripts/verify-driver.js +30 -21
  392. package/dist/scripts/workflow-commands.test.js +18 -27
  393. package/dist/scripts/workspace-command-e2e.js +1 -1
  394. package/dist/scripts/workspace-lint.js +6 -0
  395. package/dist/scripts/workspace-release-test.js +90 -1
  396. package/dist/scripts/workspace-test.js +6 -0
  397. package/dist/sdk-dispatch.d.ts +1 -1
  398. package/dist/sdk-dispatch.js +0 -16
  399. package/dist/sdk-fields.d.ts +1 -1
  400. package/dist/sdk-filters.d.ts +1 -1
  401. package/dist/sdk-types.d.ts +1352 -116
  402. package/dist/sdk-types.js +413 -2
  403. package/dist/sdk.d.ts +106 -100
  404. package/dist/sdk.js +211 -51
  405. package/dist/secrets-capability.d.ts +421 -0
  406. package/dist/secrets-capability.js +530 -0
  407. package/dist/seeds/normalize.js +1 -0
  408. package/dist/seeds/planner.js +38 -0
  409. package/dist/seeds/schema.js +293 -4
  410. package/dist/seeds/types.d.ts +60 -0
  411. package/dist/seeds/types.js +9 -1
  412. package/dist/service-credentials.d.ts +54 -0
  413. package/dist/service-credentials.js +112 -0
  414. package/dist/stores/cursor-store.d.ts +2 -2
  415. package/dist/stores/envelopes.d.ts +1 -1
  416. package/dist/stores/helpers.d.ts +1 -1
  417. package/dist/stores/lease-store.d.ts +2 -2
  418. package/dist/stores/message-store.d.ts +2 -2
  419. package/dist/stores/operational-store.d.ts +3 -26
  420. package/dist/stores/operational-store.js +0 -225
  421. package/dist/stores/project-workflow-store.d.ts +2 -2
  422. package/dist/stores/run-store.d.ts +2 -3
  423. package/dist/stores/run-store.js +0 -98
  424. package/dist/stores/subscription-store.d.ts +2 -3
  425. package/dist/stores/subscription-store.js +0 -100
  426. package/dist/template-catalog.d.ts +1 -1
  427. package/dist/template-launch-requirements.d.ts +2 -2
  428. package/dist/template-launch-requirements.js +9 -0
  429. package/dist/template-launch-ui.d.ts +2 -2
  430. package/dist/treedx/adapters.d.ts +6 -0
  431. package/dist/treedx/adapters.js +36 -0
  432. package/dist/treedx/client.d.ts +222 -0
  433. package/dist/treedx/client.js +871 -0
  434. package/dist/treedx/errors.d.ts +13 -0
  435. package/dist/treedx/errors.js +17 -0
  436. package/dist/treedx/federated-client.d.ts +27 -0
  437. package/dist/treedx/federated-client.js +158 -0
  438. package/dist/treedx/generated/openapi-types.d.ts +3558 -0
  439. package/dist/treedx/generated/openapi-types.js +0 -0
  440. package/dist/treedx/graph-adapter.d.ts +33 -0
  441. package/dist/treedx/graph-adapter.js +156 -0
  442. package/dist/treedx/index.d.ts +14 -0
  443. package/dist/treedx/index.js +48 -0
  444. package/dist/treedx/market-integration.d.ts +27 -0
  445. package/dist/treedx/market-integration.js +131 -0
  446. package/dist/treedx/ports.d.ts +166 -0
  447. package/dist/treedx/ports.js +231 -0
  448. package/dist/treedx/query-adapter.d.ts +19 -0
  449. package/dist/treedx/query-adapter.js +62 -0
  450. package/dist/treedx/registry-client.d.ts +11 -0
  451. package/dist/treedx/registry-client.js +19 -0
  452. package/dist/treedx/repository-adapter.d.ts +45 -0
  453. package/dist/treedx/repository-adapter.js +319 -0
  454. package/dist/treedx/sdk-integration.d.ts +27 -0
  455. package/dist/treedx/sdk-integration.js +63 -0
  456. package/dist/treedx/types.d.ts +1084 -0
  457. package/dist/treedx/types.js +8 -0
  458. package/dist/treedx/workspace-adapter.d.ts +27 -0
  459. package/dist/treedx/workspace-adapter.js +65 -0
  460. package/dist/treedx-backends.d.ts +220 -0
  461. package/dist/treedx-backends.js +634 -0
  462. package/dist/treedx-client.d.ts +89 -0
  463. package/dist/treedx-client.js +184 -0
  464. package/dist/treeseed/services/compose.yml +2 -3
  465. package/dist/treeseed/template-catalog/catalog.fixture.json +27 -27
  466. package/dist/types/agents.d.ts +256 -0
  467. package/dist/types/agents.js +42 -1
  468. package/dist/verification.d.ts +1 -0
  469. package/dist/verification.js +143 -14
  470. package/dist/workflow/operations.d.ts +580 -523
  471. package/dist/workflow/operations.js +2084 -1279
  472. package/dist/workflow/policy.js +19 -7
  473. package/dist/workflow/runs.d.ts +9 -3
  474. package/dist/workflow/runs.js +66 -26
  475. package/dist/workflow/session.d.ts +4 -1
  476. package/dist/workflow/session.js +50 -10
  477. package/dist/workflow/worktrees.d.ts +6 -2
  478. package/dist/workflow/worktrees.js +98 -15
  479. package/dist/workflow-state.d.ts +17 -4
  480. package/dist/workflow-state.js +68 -41
  481. package/dist/workflow-support.d.ts +26 -17
  482. package/dist/workflow-support.js +84 -24
  483. package/dist/workflow.d.ts +51 -16
  484. package/dist/workflow.js +19 -7
  485. package/drizzle/d1/0000_treeseed_d1.sql +0 -18
  486. package/drizzle/market/0000_market_control_plane.sql +1793 -1499
  487. package/drizzle/market/0004_treedx_market_integration.sql +99 -0
  488. package/drizzle/market/0005_agent_capacity_coordination.sql +145 -0
  489. package/drizzle/market/0006_provider_assignment_lifecycle.sql +9 -0
  490. package/drizzle/market/0007_agent_architecture_gap_closure.sql +157 -0
  491. package/drizzle/market/0008_agent_capacity_operational_closure.sql +27 -0
  492. package/drizzle/market/0009_agent_capacity_runtime_hardening.sql +29 -0
  493. package/drizzle/market/0010_secrets_capability_persistence.sql +130 -0
  494. package/drizzle/market/0011_github_app_adapter.sql +47 -0
  495. package/drizzle/market/0012_workday_test_runs.sql +44 -0
  496. package/drizzle/market/0013_runtime_task_lifecycle.sql +39 -0
  497. package/drizzle/market/0014_governance_pipeline.sql +189 -0
  498. package/package.json +106 -18
  499. package/scripts/{verify-driver.mjs → verify-driver.ts} +19 -4
  500. package/templates/github/deploy-web.workflow.yml +49 -12
  501. package/dist/operations/services/mailpit-runtime.d.ts +0 -20
  502. package/dist/operations/services/mailpit-runtime.js +0 -67
  503. package/dist/operations/services/release-candidate.d.ts +0 -41
  504. package/dist/operations/services/release-candidate.js +0 -616
  505. package/dist/scripts/ensure-mailpit.js +0 -48
  506. package/dist/scripts/logs-mailpit.js +0 -2
  507. package/dist/scripts/run-ts.js +0 -45
  508. package/dist/scripts/stop-mailpit.js +0 -5
  509. package/dist/scripts/treeseed-run-ts.js +0 -45
@@ -1,16 +1,19 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, relative, resolve } from "node:path";
3
- import { spawnSync } from "node:child_process";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { relative, resolve } from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
4
  import { loadCliDeployConfig } from "./runtime-tools.js";
5
+ import { resolveTreeseedMachineEnvironmentValues } from "./config-runtime.js";
5
6
  import { createPersistentDeployTarget, resolveTreeseedResourceIdentity } from "./deploy.js";
7
+ import { classifyTreeseedGitMode, runTreeseedGitText } from "./git-runner.js";
8
+ import { discoverTreeseedApplications } from "../../hosting/apps.js";
6
9
  import { runPrefixedCommand, sleep } from "./bootstrap-runner.js";
7
- import { resolveTreeseedToolCommand } from "../../managed-dependencies.js";
8
10
  import {
9
11
  ensureRailwayEnvironment,
10
12
  ensureRailwayProject,
11
13
  ensureRailwayService,
12
14
  ensureRailwayServiceInstanceConfiguration,
13
15
  ensureRailwayServiceVolume,
16
+ deployRailwayServiceInstance,
14
17
  getRailwayServiceInstance,
15
18
  listRailwayEnvironments,
16
19
  listRailwayProjects,
@@ -20,7 +23,6 @@ import {
20
23
  railwayGraphqlRequest,
21
24
  resolveRailwayApiToken,
22
25
  resolveRailwayApiUrl,
23
- resolveRailwayWorkspace,
24
26
  resolveRailwayWorkspaceContext,
25
27
  upsertRailwayVariables
26
28
  } from "./railway-api.js";
@@ -31,37 +33,87 @@ function normalizeScope(scope) {
31
33
  function resolveRailwayEnvironmentForScope(scope, configuredEnvironment) {
32
34
  return normalizeRailwayEnvironmentName(configuredEnvironment || normalizeScope(scope));
33
35
  }
34
- const RAILWAY_SERVICE_KEYS = ["api", "marketOperationsRunner"];
36
+ const RAILWAY_SERVICE_KEYS = ["api", "operationsRunner", "capacityProviderManager", "capacityProviderRunner"];
35
37
  const HOSTED_PROJECT_SERVICE_KEYS = ["api"];
36
38
  const WORKER_RUNNER_BOOTSTRAP_INDEX = 1;
37
39
  const WORKER_RUNNER_VOLUME_MOUNT_PATH = "/data";
38
- const MARKET_OPERATIONS_RUNNER_BOOTSTRAP_COUNT = 2;
40
+ const OPERATIONS_RUNNER_BOOTSTRAP_COUNT = 2;
41
+ const PUBLIC_TREEDX_NODE_SERVICE_KEY_PREFIX = "public-treedx-node-";
42
+ function isTreeseedOperationsRunnerResourceName(value) {
43
+ const normalized = String(value ?? "").trim().toLowerCase();
44
+ if (!normalized) {
45
+ return false;
46
+ }
47
+ if (normalized.startsWith("market-ops")) {
48
+ return true;
49
+ }
50
+ return normalized.includes("operations-runner");
51
+ }
52
+ function findStaleTreeseedOperationsRunnerResources(resources, desiredNames) {
53
+ const desired = new Set([...desiredNames].map((value) => String(value ?? "").trim()).filter(Boolean));
54
+ return resources.filter((resource) => {
55
+ const name = String(resource?.name ?? "").trim();
56
+ return name && isTreeseedOperationsRunnerResourceName(name) && !desired.has(name);
57
+ });
58
+ }
39
59
  function shouldManageRailwaySchedules(scope, phase = "deploy") {
40
60
  const environment = normalizeRailwayEnvironmentName(scope);
41
61
  return phase === "deploy" && (environment === "staging" || environment === "production");
42
62
  }
43
63
  function railwayServiceNameSuffix(serviceKey) {
44
- return serviceKey === "workdayManager" ? "workday-manager" : serviceKey === "workerRunner" ? "worker-runner" : serviceKey === "marketOperationsRunner" ? "market-operations-runner" : serviceKey;
64
+ return serviceKey === "capacityProviderManager" ? "capacity-provider-manager" : serviceKey === "capacityProviderRunner" ? "capacity-provider-runner" : serviceKey === "workdayManager" ? "workday-manager" : serviceKey === "workerRunner" ? "worker-runner" : serviceKey === "operationsRunner" ? "operations-runner" : serviceKey;
45
65
  }
46
66
  function deriveRailwayWorkerRunnerServiceName(projectSlug, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
47
67
  const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
48
68
  return `${projectSlug}-worker-runner-${String(normalizedIndex).padStart(2, "0")}`;
49
69
  }
50
- function deriveRailwayMarketOperationsRunnerServiceName(baseServiceName, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
70
+ function deriveRailwayOperationsRunnerServiceName(baseServiceName, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
51
71
  const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
52
- const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "") || "treeseed-market-operations-runner";
72
+ const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "").replace(/-\d{2}$/u, "") || "treeseed-api-operations-runner";
53
73
  return `${base}-${String(normalizedIndex).padStart(2, "0")}`;
54
74
  }
75
+ function railwayImageRefEnvForService(serviceKey) {
76
+ if (serviceKey === "api") return "TREESEED_API_IMAGE_REF";
77
+ if (serviceKey === "operationsRunner") return "TREESEED_OPERATIONS_RUNNER_IMAGE_REF";
78
+ if (serviceKey === "capacityProviderManager") return "TREESEED_AGENT_MANAGER_IMAGE_REF";
79
+ if (serviceKey === "capacityProviderRunner") return "TREESEED_AGENT_RUNNER_IMAGE_REF";
80
+ if (isPublicTreeDxNodeServiceKey(serviceKey)) return "TREESEED_PUBLIC_TREEDX_IMAGE_REF";
81
+ return null;
82
+ }
83
+ function defaultRailwayImageRef(serviceKey, scope = "staging", env = process.env) {
84
+ if (serviceKey === "api") {
85
+ return envValue("TREESEED_API_IMAGE_REF", env) || null;
86
+ }
87
+ if (serviceKey === "operationsRunner") {
88
+ return envValue("TREESEED_OPERATIONS_RUNNER_IMAGE_REF", env) || null;
89
+ }
90
+ if (serviceKey === "capacityProviderManager") {
91
+ return envValue("TREESEED_AGENT_MANAGER_IMAGE_REF", env) || null;
92
+ }
93
+ if (serviceKey === "capacityProviderRunner") {
94
+ return envValue("TREESEED_AGENT_RUNNER_IMAGE_REF", env) || null;
95
+ }
96
+ if (isPublicTreeDxNodeServiceKey(serviceKey)) {
97
+ return envValue("TREESEED_PUBLIC_TREEDX_IMAGE_REF", env) || null;
98
+ }
99
+ return null;
100
+ }
101
+ function isPublicTreeDxNodeServiceKey(serviceKey) {
102
+ return String(serviceKey ?? "").startsWith(PUBLIC_TREEDX_NODE_SERVICE_KEY_PREFIX);
103
+ }
55
104
  function deriveRailwayWorkerRunnerVolumeName(serviceName, environmentName = "") {
56
- const environment = normalizeRailwayEnvironmentName(environmentName);
57
- const environmentSuffix = environment === "production" ? "-prod" : environment ? `-${environment}` : "";
58
- return `${serviceName}${environmentSuffix}-data`;
105
+ return `${serviceName}-volume`;
106
+ }
107
+ function deriveRailwayOperationsRunnerVolumeName(serviceName, environmentName = "") {
108
+ return `${serviceName}-volume`;
59
109
  }
60
- function deriveRailwayMarketOperationsRunnerVolumeName(serviceName, environmentName = "") {
61
- const environment = normalizeRailwayEnvironmentName(environmentName);
62
- const environmentSuffix = environment === "production" ? "-prod" : environment ? `-${environment}` : "";
63
- const index = String(serviceName ?? "").match(/-(\d+)$/u)?.[1] ?? "01";
64
- return `market-ops-runner-${index}${environmentSuffix}-data`;
110
+ function deriveRailwayCapacityProviderRunnerServiceName(baseServiceName, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
111
+ const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
112
+ const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "").replace(/-\d{2}$/u, "") || "treeseed-capacity-provider-runner";
113
+ return `${base}-${String(normalizedIndex).padStart(2, "0")}`;
114
+ }
115
+ function deriveRailwayCapacityProviderRunnerVolumeName(serviceName, environmentName = "") {
116
+ return `${serviceName}-volume`;
65
117
  }
66
118
  function railwayServiceRuntimeStartCommand(service) {
67
119
  return service.startCommand;
@@ -75,8 +127,8 @@ function normalizeScheduleExpressions(value) {
75
127
  }
76
128
  return [];
77
129
  }
78
- function envValue(name) {
79
- const value = process.env[name];
130
+ function envValue(name, env = process.env) {
131
+ const value = env?.[name];
80
132
  return typeof value === "string" && value.trim() ? value.trim() : "";
81
133
  }
82
134
  function relativeRailwayRootDir(tenantRoot, serviceRoot) {
@@ -87,6 +139,10 @@ function configuredEnvValue(env, name) {
87
139
  const value = env?.[name];
88
140
  return typeof value === "string" && value.trim() ? value.trim() : "";
89
141
  }
142
+ function railwayDeployTransport(env) {
143
+ const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_TRANSPORT").toLowerCase();
144
+ return configured === "cli-fallback" ? "cli-fallback" : "api";
145
+ }
90
146
  async function timedRailwayPhase(timings, name, run, metadata) {
91
147
  const startMs = performance.now();
92
148
  try {
@@ -149,6 +205,10 @@ function railwayStatusDeploymentSettled(status) {
149
205
  const normalized = String(status ?? "").trim().toUpperCase();
150
206
  return normalized === "SUCCESS" || normalized === "SLEEPING";
151
207
  }
208
+ function railwayStatusDeploymentTerminalFailure(status) {
209
+ const normalized = String(status ?? "").trim().toUpperCase();
210
+ return ["FAILED", "CRASHED", "REMOVED"].includes(normalized);
211
+ }
152
212
  function formatRailwayDeploymentStatusSummary(scope, checks) {
153
213
  const aliases = {
154
214
  api: "api",
@@ -219,12 +279,14 @@ function collectRailwayDeploymentStatusChecks(statusPayload, scope, services) {
219
279
  const status = String(deployment?.status ?? "").trim().toUpperCase();
220
280
  const instanceStatuses = Array.isArray(deployment?.instances) ? deployment.instances.map((entry) => String(entry?.status ?? "").trim()).filter(Boolean) : [];
221
281
  const ok = railwayStatusDeploymentSettled(status);
282
+ const terminalFailure = railwayStatusDeploymentTerminalFailure(status);
222
283
  return {
223
284
  type: "deployment-status",
224
285
  service: service.key,
225
286
  serviceName: service.serviceName,
226
287
  environment: normalizeRailwayEnvironmentName(environment.name),
227
288
  ok,
289
+ terminalFailure,
228
290
  status: status || "missing_deployment",
229
291
  observed: {
230
292
  status: status || null,
@@ -234,74 +296,10 @@ function collectRailwayDeploymentStatusChecks(statusPayload, scope, services) {
234
296
  instanceStatuses,
235
297
  volumeMounts: Array.isArray(deployment?.meta?.volumeMounts) ? deployment.meta.volumeMounts : []
236
298
  },
237
- message: ok ? void 0 : `Railway deployment for ${service.serviceName} is not settled yet; observed ${status || "missing deployment status"}.`
299
+ message: ok ? void 0 : terminalFailure ? `Railway deployment for ${service.serviceName} failed with terminal status ${status}.` : `Railway deployment for ${service.serviceName} is not settled yet; observed ${status || "missing deployment status"}.`
238
300
  };
239
301
  });
240
302
  }
241
- function normalizeRailwayCliVolume(value, { serviceId, serviceName, environmentId, fallbackName, fallbackMountPath }) {
242
- if (!value || typeof value !== "object") {
243
- return null;
244
- }
245
- const record = value;
246
- const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
247
- if (!id) {
248
- return null;
249
- }
250
- const listedServiceName = typeof record.serviceName === "string" && record.serviceName.trim() ? record.serviceName.trim() : "";
251
- if (listedServiceName && serviceName && listedServiceName !== serviceName) {
252
- return null;
253
- }
254
- const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : fallbackName;
255
- const mountPath = typeof record.mountPath === "string" && record.mountPath.trim() ? record.mountPath.trim() : fallbackMountPath;
256
- const sizeMb = typeof record.sizeMB === "number" ? record.sizeMB : null;
257
- const currentSizeMb = typeof record.currentSizeMB === "number" ? record.currentSizeMB : null;
258
- return {
259
- id,
260
- name,
261
- projectId: null,
262
- instances: [{
263
- id,
264
- serviceId,
265
- environmentId,
266
- mountPath,
267
- state: "READY",
268
- sizeGb: sizeMb === null ? null : sizeMb / 1e3,
269
- usedGb: currentSizeMb === null ? null : currentSizeMb / 1e3
270
- }]
271
- };
272
- }
273
- function normalizeRailwayCliVolumeList(value, options) {
274
- if (!value || typeof value !== "object" || !Array.isArray(value.volumes)) {
275
- return [];
276
- }
277
- return value.volumes.map((entry) => normalizeRailwayCliVolume(entry, options)).filter(Boolean);
278
- }
279
- function listRailwayServiceVolumesWithCli({
280
- cwd,
281
- serviceId,
282
- serviceName,
283
- environmentId,
284
- name,
285
- mountPath,
286
- env = process.env
287
- }) {
288
- const listResult = runRailway(["volume", "--service", serviceId, "--environment", environmentId, "list", "--json"], {
289
- cwd,
290
- capture: true,
291
- allowFailure: true,
292
- env
293
- });
294
- if ((listResult.status ?? 1) !== 0) {
295
- return [];
296
- }
297
- return normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
298
- serviceId,
299
- serviceName,
300
- environmentId,
301
- fallbackName: name,
302
- fallbackMountPath: mountPath
303
- });
304
- }
305
303
  function isUsableRailwayToken(value) {
306
304
  return typeof value === "string" && value.trim().length >= 8;
307
305
  }
@@ -311,7 +309,7 @@ function resolveRailwayAuthToken(env = process.env) {
311
309
  function buildRailwayCommandEnv(env = process.env) {
312
310
  const merged = { ...env };
313
311
  const token = resolveRailwayAuthToken(merged);
314
- const projectToken = configuredEnvValue(merged, "RAILWAY_TOKEN");
312
+ const projectToken = configuredEnvValue(merged, "TREESEED_RAILWAY_TOKEN");
315
313
  if (token) {
316
314
  merged.RAILWAY_API_TOKEN = token;
317
315
  } else {
@@ -320,13 +318,6 @@ function buildRailwayCommandEnv(env = process.env) {
320
318
  merged.RAILWAY_TOKEN = projectToken || void 0;
321
319
  return merged;
322
320
  }
323
- function buildRailwayDeployCommandEnv(env = process.env) {
324
- const merged = buildRailwayCommandEnv(env);
325
- if (shouldAttachRailwayDeployLogs(merged)) {
326
- merged.CI = "true";
327
- }
328
- return merged;
329
- }
330
321
  function normalizeRailwaySchedule(schedule) {
331
322
  if (!schedule || typeof schedule !== "object") {
332
323
  return null;
@@ -376,47 +367,6 @@ function collectRailwaySchedules(value, seen = /* @__PURE__ */ new Set()) {
376
367
  visit(value);
377
368
  return matches;
378
369
  }
379
- function normalizeRailwayProjectList(payload) {
380
- try {
381
- const parsed = JSON.parse(payload);
382
- if (!Array.isArray(parsed)) {
383
- return [];
384
- }
385
- return parsed.map((entry) => {
386
- if (!entry || typeof entry !== "object") {
387
- return null;
388
- }
389
- const id = typeof entry.id === "string" ? entry.id.trim() : "";
390
- const name = typeof entry.name === "string" ? entry.name.trim() : "";
391
- if (!id && !name) {
392
- return null;
393
- }
394
- return { id, name };
395
- }).filter(Boolean);
396
- } catch {
397
- return [];
398
- }
399
- }
400
- function railwayMessage(result) {
401
- return `${result?.stderr ?? ""}
402
- ${result?.stdout ?? ""}`.trim();
403
- }
404
- function isRailwayAlreadyExistsMessage(result) {
405
- return /already exists|already taken|duplicate|has already been taken/iu.test(railwayMessage(result));
406
- }
407
- function isRailwayTransientFailure(result) {
408
- const message = railwayMessage(result);
409
- if (!message.trim() && result?.status === 1) {
410
- return true;
411
- }
412
- return /timed out|failed to fetch|error decoding response body|expected value at line 1 column 1|temporarily unavailable|econnreset|etimedout|failed to stream build logs|failed to retrieve build log/iu.test(message);
413
- }
414
- function sleepSync(milliseconds) {
415
- if (!Number.isFinite(milliseconds) || milliseconds <= 0) {
416
- return;
417
- }
418
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
419
- }
420
370
  function isRailwayScheduleCapabilityError(error) {
421
371
  const message = error instanceof Error ? error.message : String(error ?? "");
422
372
  return /cronTriggers|cronTriggerCreate|cronTriggerUpdate/iu.test(message);
@@ -489,33 +439,6 @@ mutation TreeseedScheduleUpdate($id: String!, $name: String!, $schedule: String!
489
439
  `.trim()
490
440
  };
491
441
  }
492
- function runRailway(args, { cwd, capture = false, allowFailure = false, input, env, retryTransient = true, retryAttempts = 3, retryDelayMs = 2e3 } = {}) {
493
- const effectiveEnv = buildRailwayCommandEnv({ ...process.env, ...env ?? {} });
494
- const railway = resolveTreeseedToolCommand("railway", { env: effectiveEnv });
495
- if (!railway) {
496
- throw new Error("Railway CLI is unavailable.");
497
- }
498
- const runWithEnv = (spawnEnv) => spawnSync(railway.command, [...railway.argsPrefix, ...args], {
499
- cwd,
500
- stdio: input !== void 0 ? ["pipe", capture ? "pipe" : "inherit", capture ? "pipe" : "inherit"] : capture ? "pipe" : "inherit",
501
- encoding: "utf8",
502
- env: spawnEnv,
503
- input
504
- });
505
- let result = null;
506
- const maxAttempts = retryTransient && !allowFailure ? Math.max(1, Number(retryAttempts) || 1) : 1;
507
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
508
- result = runWithEnv(effectiveEnv);
509
- if (result.status === 0 || allowFailure || !isRailwayTransientFailure(result) || attempt === maxAttempts) {
510
- break;
511
- }
512
- sleepSync((Number(retryDelayMs) || 0) * attempt);
513
- }
514
- if (result?.status !== 0 && !allowFailure) {
515
- throw new Error(result.stderr?.trim() || result.stdout?.trim() || `railway ${args.join(" ")} failed`);
516
- }
517
- return result;
518
- }
519
442
  async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
520
443
  services = configuredRailwayServices(tenantRoot, scope),
521
444
  env = process.env,
@@ -602,6 +525,26 @@ async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
602
525
  }
603
526
  };
604
527
  }
528
+ if (checks.some((entry) => entry.terminalFailure === true)) {
529
+ return {
530
+ ok: false,
531
+ checks: checks.map((check) => ({
532
+ ...check,
533
+ settle: {
534
+ durationMs: elapsedMs(startMs),
535
+ pollCount,
536
+ finalStatus: check.status,
537
+ terminalFailure: check.terminalFailure === true
538
+ }
539
+ })),
540
+ settle: {
541
+ durationMs: elapsedMs(startMs),
542
+ pollCount,
543
+ status: "failed"
544
+ },
545
+ message: "Railway deployment reached a terminal failed state."
546
+ };
547
+ }
605
548
  if (Date.now() >= deadline) {
606
549
  return {
607
550
  ok: false,
@@ -668,264 +611,21 @@ query TreeseedRailwayDeploymentStatus($projectId: String!) {
668
611
  });
669
612
  return payload.data?.project ?? null;
670
613
  }
671
- function setRailwaySecretVariable({ cwd, service, environment, key, value, env = process.env, capture = false, allowFailure = false }) {
672
- const effectiveEnv = buildRailwayCommandEnv({
673
- ...process.env,
674
- ...env ?? {}
675
- });
676
- let result = null;
677
- for (let attempt = 0; attempt < 3; attempt += 1) {
678
- const railway = resolveTreeseedToolCommand("railway", { env: effectiveEnv });
679
- if (!railway) {
680
- throw new Error("Railway CLI is unavailable.");
681
- }
682
- result = spawnSync(railway.command, [
683
- ...railway.argsPrefix,
684
- "variable",
685
- "set",
686
- "--service",
687
- service,
688
- "--environment",
689
- environment,
690
- "--stdin",
691
- "--skip-deploys",
692
- key
693
- ], {
694
- cwd,
695
- stdio: ["pipe", capture ? "pipe" : "inherit", capture ? "pipe" : "inherit"],
696
- encoding: "utf8",
697
- env: effectiveEnv,
698
- input: `${value}
699
- `
700
- });
701
- if (result.status === 0) {
702
- return result;
703
- }
704
- if (!isRailwayTransientFailure(result)) {
705
- break;
706
- }
707
- }
708
- if (result?.status !== 0 && !allowFailure) {
709
- throw new Error(result?.stderr?.trim() || result?.stdout?.trim() || `railway variable set --stdin ${key} failed`);
710
- }
711
- return result;
712
- }
713
- function ensureRailwayProjectExists(service, { env = process.env } = {}) {
714
- const projectName = typeof service?.projectName === "string" ? service.projectName.trim() : "";
715
- const projectId = typeof service?.projectId === "string" ? service.projectId.trim() : "";
716
- const listed = runRailway(["list", "--json"], {
717
- cwd: service.rootDir,
718
- capture: true,
719
- allowFailure: true,
720
- env
721
- });
722
- if (listed.status === 0) {
723
- const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === projectName || entry.id === projectName || entry.id === projectId);
724
- if (match) {
725
- return match;
726
- }
727
- }
728
- if (!projectName) {
729
- if (projectId) {
730
- return { id: projectId, name: "" };
731
- }
732
- throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a projectName.`);
733
- }
734
- const args = ["init", "--name", projectName, "--json"];
735
- const workspace = resolveRailwayWorkspace(env);
736
- if (workspace) {
737
- args.push("--workspace", workspace);
738
- }
739
- const created = runRailway(args, {
740
- cwd: service.rootDir,
741
- capture: true,
742
- allowFailure: true,
743
- env
744
- });
745
- if (created.status !== 0 && !isRailwayAlreadyExistsMessage(created)) {
746
- throw new Error(railwayMessage(created) || `railway ${args.join(" ")} failed`);
747
- }
748
- return null;
749
- }
750
- function ensureRailwayEnvironmentExists(service, { env = process.env } = {}) {
751
- const environmentName = normalizeRailwayEnvironmentName(service?.railwayEnvironment);
752
- if (!environmentName) {
753
- return null;
754
- }
755
- const linkResult = runRailway(["environment", "link", environmentName, "--json"], {
756
- cwd: service.rootDir,
757
- capture: true,
758
- allowFailure: true,
759
- env
760
- });
761
- if (linkResult.status === 0) {
762
- return linkResult;
763
- }
764
- const createResult = runRailway(["environment", "new", environmentName, "--json"], {
765
- cwd: service.rootDir,
766
- capture: true,
767
- allowFailure: true,
768
- env
769
- });
770
- if (createResult.status !== 0 && !isRailwayAlreadyExistsMessage(createResult)) {
771
- throw new Error(railwayMessage(createResult) || `railway environment new ${environmentName} failed`);
772
- }
773
- const relinkResult = runRailway(["environment", "link", environmentName, "--json"], {
774
- cwd: service.rootDir,
775
- capture: true,
776
- allowFailure: true,
777
- env
778
- });
779
- if (relinkResult.status !== 0) {
780
- throw new Error(railwayMessage(relinkResult) || `railway environment link ${environmentName} failed`);
781
- }
782
- return relinkResult;
783
- }
784
- function ensureRailwayServiceExists(service, { env = process.env } = {}) {
785
- const serviceSelector = typeof (service?.serviceName ?? service?.serviceId) === "string" ? String(service.serviceName ?? service.serviceId).trim() : "";
786
- if (!serviceSelector) {
787
- throw new Error(`Railway service ${service?.key ?? "(unknown)"} is missing a service selector.`);
788
- }
789
- const statusArgs = ["service", "status", "--service", serviceSelector, "--environment", service.railwayEnvironment, "--json"];
790
- const statusResult = runRailway(statusArgs, {
791
- cwd: service.rootDir,
792
- capture: true,
793
- allowFailure: true,
794
- env
795
- });
796
- if (statusResult.status === 0) {
797
- return statusResult;
798
- }
799
- const createResult = runRailway(["add", "--service", serviceSelector, "--json"], {
800
- cwd: service.rootDir,
801
- capture: true,
802
- allowFailure: true,
803
- env
804
- });
805
- if (createResult.status !== 0 && !isRailwayAlreadyExistsMessage(createResult)) {
806
- throw new Error(railwayMessage(createResult) || `railway add --service ${serviceSelector} failed`);
807
- }
808
- const refreshed = runRailway(statusArgs, {
809
- cwd: service.rootDir,
810
- capture: true,
811
- allowFailure: true,
812
- env
813
- });
814
- if (refreshed.status !== 0) {
815
- throw new Error(railwayMessage(refreshed) || `railway service status --service ${serviceSelector} failed`);
816
- }
817
- return refreshed;
818
- }
819
- function ensureRailwayDatabaseServiceExists(service, {
820
- database = "postgres",
821
- env = process.env
822
- } = {}) {
823
- const serviceSelector = typeof (service?.serviceName ?? service?.serviceId) === "string" ? String(service.serviceName ?? service.serviceId).trim() : "";
824
- if (!serviceSelector) {
825
- throw new Error(`Railway database service ${service?.key ?? "(unknown)"} is missing a service selector.`);
826
- }
827
- const projectSelector = typeof service?.projectId === "string" && service.projectId.trim() ? service.projectId.trim() : typeof service?.projectName === "string" && service.projectName.trim() ? service.projectName.trim() : "";
828
- if (!projectSelector) {
829
- throw new Error(`Railway database service ${service?.key ?? serviceSelector} is missing a project selector.`);
830
- }
831
- const linkArgs = ["link", "--project", projectSelector];
832
- const workspace = resolveRailwayWorkspace(env);
833
- if (workspace) {
834
- linkArgs.push("--workspace", workspace);
835
- }
836
- const environmentName = normalizeRailwayEnvironmentName(service?.railwayEnvironment);
837
- if (environmentName) {
838
- linkArgs.push("--environment", environmentName);
839
- }
840
- const linkResult = runRailway(linkArgs, {
841
- cwd: service.rootDir,
842
- capture: true,
843
- allowFailure: true,
844
- env
845
- });
846
- if ((linkResult.status ?? 1) !== 0) {
847
- throw new Error(railwayMessage(linkResult) || `railway ${linkArgs.join(" ")} failed`);
848
- }
849
- const statusArgs = ["service", "status", "--service", serviceSelector, "--environment", service.railwayEnvironment, "--json"];
850
- const statusResult = runRailway(statusArgs, {
851
- cwd: service.rootDir,
852
- capture: true,
853
- allowFailure: true,
854
- env
855
- });
856
- if (statusResult.status === 0) {
857
- return statusResult;
858
- }
859
- const addResult = runRailway(["add", "--database", database, "--service", serviceSelector, "--json"], {
860
- cwd: service.rootDir,
861
- capture: true,
862
- allowFailure: true,
863
- env
864
- });
865
- if (addResult.status !== 0 && !isRailwayAlreadyExistsMessage(addResult)) {
866
- throw new Error(railwayMessage(addResult) || `railway add --database ${database} --service ${serviceSelector} failed`);
867
- }
868
- const refreshed = runRailway(statusArgs, {
869
- cwd: service.rootDir,
870
- capture: true,
871
- allowFailure: true,
872
- env
873
- });
874
- if (refreshed.status !== 0) {
875
- throw new Error(railwayMessage(refreshed) || `railway service status --service ${serviceSelector} failed`);
876
- }
877
- return refreshed;
878
- }
879
- function ensureRailwayProjectContext(service, { env = process.env, allowFailure = false, capture = false } = {}) {
880
- ensureRailwayProjectExists(service, { env });
881
- let projectSelector = service?.projectId ?? "";
882
- if ((!projectSelector || !String(projectSelector).trim()) && service?.projectName) {
883
- const listed = runRailway(["list", "--json"], {
884
- cwd: service.rootDir,
885
- capture: true,
886
- allowFailure: true,
887
- env
888
- });
889
- if (listed.status === 0) {
890
- const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === service.projectName || entry.id === service.projectName);
891
- projectSelector = match?.id || match?.name || service.projectName;
892
- } else {
893
- projectSelector = service.projectName;
894
- }
895
- }
896
- projectSelector = typeof projectSelector === "string" ? projectSelector.trim() : "";
897
- if (typeof projectSelector !== "string" || projectSelector.trim().length === 0) {
898
- throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a project selector.`);
899
- }
900
- const args = ["link", "--project", projectSelector];
901
- const workspace = resolveRailwayWorkspace(env);
902
- if (workspace) {
903
- args.push("--workspace", workspace);
904
- }
905
- const environmentName = normalizeRailwayEnvironmentName(service?.railwayEnvironment);
906
- if (environmentName) {
907
- args.push("--environment", environmentName);
908
- }
909
- runRailway(args, {
910
- cwd: service.rootDir,
911
- capture,
912
- allowFailure,
913
- env
914
- });
915
- if (environmentName) {
916
- ensureRailwayEnvironmentExists(service, { env });
917
- return runRailway(["environment", "link", environmentName, "--json"], {
918
- cwd: service.rootDir,
919
- capture,
920
- allowFailure,
921
- env
922
- });
923
- }
924
- return null;
925
- }
926
- function configuredRailwayServices(tenantRoot, scope) {
927
- const deployConfig = loadCliDeployConfig(tenantRoot);
614
+ function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, application = null, machineConfigRoot = tenantRoot) {
928
615
  const normalizedScope = normalizeScope(scope);
616
+ const imageRefKeys = [
617
+ "TREESEED_API_IMAGE_REF",
618
+ "TREESEED_OPERATIONS_RUNNER_IMAGE_REF",
619
+ "TREESEED_AGENT_MANAGER_IMAGE_REF",
620
+ "TREESEED_AGENT_RUNNER_IMAGE_REF"
621
+ ];
622
+ let machineEnv = {};
623
+ try {
624
+ machineEnv = resolveTreeseedMachineEnvironmentValues(machineConfigRoot, normalizedScope, imageRefKeys);
625
+ } catch {
626
+ machineEnv = {};
627
+ }
628
+ const imageRefEnv = { ...process.env, ...machineEnv };
929
629
  let identity;
930
630
  try {
931
631
  identity = resolveTreeseedResourceIdentity(deployConfig, createPersistentDeployTarget(normalizedScope));
@@ -939,13 +639,14 @@ function configuredRailwayServices(tenantRoot, scope) {
939
639
  }
940
640
  const configuredOptionalServiceKeys = Object.keys(deployConfig.services ?? {}).filter((serviceKey) => RAILWAY_SERVICE_KEYS.includes(serviceKey));
941
641
  const serviceKeys = hostingKind === "hosted_project" ? [.../* @__PURE__ */ new Set([...HOSTED_PROJECT_SERVICE_KEYS, ...configuredOptionalServiceKeys])] : RAILWAY_SERVICE_KEYS;
942
- return serviceKeys.flatMap((serviceKey) => {
642
+ const configuredServices = serviceKeys.flatMap((serviceKey) => {
943
643
  const service = deployConfig.services?.[serviceKey];
944
644
  if (!service || service.enabled === false || (service.provider ?? "railway") !== "railway") {
945
645
  return [];
946
646
  }
947
- const defaultRootDir = ["api", "marketOperationsRunner"].includes(serviceKey) ? "." : "packages/core";
948
- const serviceRoot = resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir);
647
+ const isCapacityProviderService = String(serviceKey).startsWith("capacityProvider");
648
+ const defaultRootDir = ["api", "operationsRunner"].includes(serviceKey) ? "." : isCapacityProviderService ? "packages/agent" : "packages/core";
649
+ const serviceRoot = isCapacityProviderService ? resolveRailwayCapacityProviderRoot(tenantRoot, service) : resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir);
949
650
  const railwayEnvironment = resolveRailwayEnvironmentForScope(
950
651
  normalizedScope,
951
652
  service.environments?.[normalizedScope]?.railwayEnvironment
@@ -953,45 +654,249 @@ function configuredRailwayServices(tenantRoot, scope) {
953
654
  const publicBaseUrl = service.environments?.[normalizedScope]?.baseUrl ?? service.publicBaseUrl ?? null;
954
655
  const configuredServiceName = service.railway?.serviceName ?? (serviceKey === "workerRunner" ? deriveRailwayWorkerRunnerServiceName(identity.deploymentKey) : `${identity.deploymentKey}-${railwayServiceNameSuffix(serviceKey)}`);
955
656
  const configuredRunnerPool = service.railway?.runnerPool && typeof service.railway.runnerPool === "object" ? service.railway.runnerPool : null;
956
- const runnerPool = serviceKey === "marketOperationsRunner" ? {
957
- bootstrapCount: Math.max(1, Number.parseInt(String(configuredRunnerPool?.bootstrapCount ?? MARKET_OPERATIONS_RUNNER_BOOTSTRAP_COUNT), 10) || MARKET_OPERATIONS_RUNNER_BOOTSTRAP_COUNT),
958
- maxRunners: Math.max(1, Number.parseInt(String(configuredRunnerPool?.maxRunners ?? configuredRunnerPool?.bootstrapCount ?? MARKET_OPERATIONS_RUNNER_BOOTSTRAP_COUNT), 10) || MARKET_OPERATIONS_RUNNER_BOOTSTRAP_COUNT),
657
+ const runnerPool = serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? {
658
+ bootstrapCount: Math.max(1, Number.parseInt(String(configuredRunnerPool?.bootstrapCount ?? (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)), 10) || (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)),
659
+ maxRunners: Math.max(1, Number.parseInt(String(configuredRunnerPool?.maxRunners ?? configuredRunnerPool?.bootstrapCount ?? (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)), 10) || (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)),
959
660
  volumeMountPath: service.railway?.volumeMountPath ?? configuredRunnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH
960
661
  } : serviceKey === "workerRunner" ? {
961
662
  bootstrapIndex: WORKER_RUNNER_BOOTSTRAP_INDEX,
962
663
  volumeMountPath: WORKER_RUNNER_VOLUME_MOUNT_PATH
963
664
  } : null;
964
- const instanceCount = serviceKey === "marketOperationsRunner" ? runnerPool.bootstrapCount : 1;
665
+ const instanceCount = serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? runnerPool.bootstrapCount : 1;
965
666
  return Array.from({ length: instanceCount }, (_, offset) => {
966
667
  const runnerIndex = offset + 1;
967
- const serviceName = serviceKey === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerServiceName(configuredServiceName, runnerIndex) : configuredServiceName;
668
+ const serviceName = serviceKey === "operationsRunner" ? deriveRailwayOperationsRunnerServiceName(configuredServiceName, runnerIndex) : serviceKey === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerServiceName(configuredServiceName, runnerIndex) : configuredServiceName;
669
+ const configuredImageRefEnv = service.railway?.imageRefEnv ?? railwayImageRefEnvForService(serviceKey);
670
+ const imageRef = service.railway?.imageRef ?? (configuredImageRefEnv ? envValue(configuredImageRefEnv, imageRefEnv) || null : null) ?? defaultRailwayImageRef(serviceKey, normalizedScope, imageRefEnv);
671
+ const sourcePolicy = resolveRailwayServiceSourcePolicy({
672
+ tenantRoot,
673
+ scope: normalizedScope,
674
+ serviceKey,
675
+ service,
676
+ serviceRoot,
677
+ imageRef
678
+ });
679
+ const resolvedImageRef = sourcePolicy.sourceMode === "image" ? imageRef : null;
968
680
  return {
969
681
  key: serviceKey,
970
- instanceKey: serviceKey === "marketOperationsRunner" ? `${serviceKey}:${runnerIndex}` : serviceKey,
971
- runnerIndex: serviceKey === "marketOperationsRunner" ? runnerIndex : null,
682
+ instanceKey: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? `${serviceKey}:${runnerIndex}` : serviceKey,
683
+ runnerIndex: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? runnerIndex : null,
684
+ serviceConfig: service,
972
685
  scope: normalizedScope,
973
686
  projectId: service.railway?.projectId ?? null,
974
687
  projectName: service.railway?.projectName ?? identity.deploymentKey,
975
688
  serviceId: service.railway?.serviceId ?? null,
976
689
  serviceName,
977
- runnerId: serviceKey === "marketOperationsRunner" ? serviceName : null,
690
+ runnerId: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? serviceName : null,
978
691
  rootDir: serviceRoot,
979
692
  publicBaseUrl,
980
693
  railwayEnvironment,
981
- buildCommand: service.railway?.buildCommand ?? null,
982
- startCommand: service.railway?.startCommand ?? null,
694
+ buildCommand: resolvedImageRef ? null : service.railway?.buildCommand ?? null,
695
+ startCommand: isCapacityProviderService ? null : resolvedImageRef ? null : service.railway?.startCommand ?? null,
696
+ imageRef: resolvedImageRef,
697
+ sourceMode: sourcePolicy.sourceMode,
698
+ sourceRepo: sourcePolicy.sourceRepo,
699
+ sourceBranch: sourcePolicy.sourceBranch,
700
+ sourceCommit: sourcePolicy.sourceCommit,
701
+ sourceRootDirectory: sourcePolicy.sourceRootDirectory,
702
+ dockerfilePath: sourcePolicy.sourceMode === "git" ? service.railway?.dockerfilePath ?? null : null,
983
703
  healthcheckPath: service.railway?.healthcheckPath ?? null,
984
704
  healthcheckTimeoutSeconds: service.railway?.healthcheckTimeoutSeconds ?? null,
985
705
  healthcheckIntervalSeconds: service.railway?.healthcheckIntervalSeconds ?? null,
986
706
  restartPolicy: service.railway?.restartPolicy ?? null,
987
707
  runtimeMode: service.railway?.runtimeMode ?? null,
988
- volumeMountPath: serviceKey === "marketOperationsRunner" ? runnerPool.volumeMountPath : service.railway?.volumeMountPath ?? null,
708
+ volumeMountPath: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? runnerPool.volumeMountPath : service.railway?.volumeMountPath ?? null,
989
709
  schedule: normalizeScheduleExpressions(service.railway?.schedule),
990
710
  hostingKind,
991
- runnerPool
711
+ runnerPool,
712
+ application,
713
+ secretRefs: Array.isArray(service.secretRefs) ? service.secretRefs : [],
714
+ variableRefs: Array.isArray(service.variableRefs) ? service.variableRefs : []
992
715
  };
993
716
  });
994
717
  }).filter(Boolean);
718
+ return [
719
+ ...configuredServices,
720
+ ...configuredPublicTreeDxRailwayServices({
721
+ tenantRoot,
722
+ scope: normalizedScope,
723
+ deployConfig,
724
+ identity,
725
+ hostingKind,
726
+ application,
727
+ imageRefEnv,
728
+ workspaceRoot: machineConfigRoot
729
+ })
730
+ ];
731
+ }
732
+ function configuredPublicTreeDxRailwayServices({ tenantRoot, scope, deployConfig, identity, hostingKind, application, imageRefEnv, workspaceRoot }) {
733
+ if (deployConfig.hosting?.kind !== "treeseed_control_plane") {
734
+ return [];
735
+ }
736
+ const railway = deployConfig.publicTreeDxFederation?.railway ?? {};
737
+ const nodePool = railway.nodePool && typeof railway.nodePool === "object" && !Array.isArray(railway.nodePool) ? railway.nodePool : {};
738
+ const bootstrapCount = Math.max(0, Number.parseInt(String(nodePool.bootstrapCount ?? 1), 10) || 0);
739
+ if (bootstrapCount <= 0) {
740
+ return [];
741
+ }
742
+ const configuredSource = railway.source && typeof railway.source === "object" && !Array.isArray(railway.source) ? railway.source : {};
743
+ const treeDxRoot = resolve(workspaceRoot ?? tenantRoot, "packages", "treedx");
744
+ const configuredMode = typeof railway.sourceMode === "string" ? railway.sourceMode : null;
745
+ const sourceMode = scope === "prod" ? "image" : configuredMode === "git" || configuredMode === "image" ? configuredMode : "git";
746
+ const repository = typeof railway.sourceRepo === "string" ? railway.sourceRepo : typeof configuredSource.repository === "string" ? configuredSource.repository : typeof configuredSource.repo === "string" ? configuredSource.repo : readTreeseedPackageRepository(treeDxRoot) ?? "treeseed-ai/treedx";
747
+ const sourceBranch = typeof railway.sourceBranch === "string" ? railway.sourceBranch : typeof configuredSource.branch === "string" ? configuredSource.branch : scope === "staging" ? "staging" : null;
748
+ const sourceRootDirectory = typeof railway.sourceRootDirectory === "string" ? railway.sourceRootDirectory : typeof configuredSource.rootDirectory === "string" ? configuredSource.rootDirectory : ".";
749
+ const projectName = typeof railway.projectName === "string" && railway.projectName.trim() ? railway.projectName.trim() : typeof imageRefEnv.TREESEED_PUBLIC_TREEDX_RAILWAY_PROJECT_NAME === "string" && imageRefEnv.TREESEED_PUBLIC_TREEDX_RAILWAY_PROJECT_NAME.trim() ? imageRefEnv.TREESEED_PUBLIC_TREEDX_RAILWAY_PROJECT_NAME.trim() : identity.deploymentKey;
750
+ const railwayEnvironment = resolveRailwayEnvironmentForScope(scope, railway.environmentName);
751
+ const baseImageRef = envValue("TREESEED_PUBLIC_TREEDX_IMAGE_REF", imageRefEnv) || "treeseed/treedx";
752
+ return Array.from({ length: bootstrapCount }, (_, offset) => {
753
+ const index = offset + 1;
754
+ const serviceName = `${PUBLIC_TREEDX_NODE_SERVICE_KEY_PREFIX}${String(index).padStart(2, "0")}`;
755
+ return {
756
+ key: serviceName,
757
+ instanceKey: serviceName,
758
+ runnerIndex: null,
759
+ serviceConfig: null,
760
+ scope,
761
+ projectId: typeof railway.projectId === "string" ? railway.projectId : null,
762
+ projectName,
763
+ serviceId: typeof railway.serviceId === "string" && bootstrapCount === 1 ? railway.serviceId : null,
764
+ serviceName,
765
+ runnerId: null,
766
+ rootDir: treeDxRoot,
767
+ publicBaseUrl: null,
768
+ railwayEnvironment,
769
+ buildCommand: sourceMode === "git" ? railway.buildCommand ?? null : null,
770
+ startCommand: sourceMode === "git" ? railway.startCommand ?? null : null,
771
+ imageRef: sourceMode === "image" ? baseImageRef : null,
772
+ sourceMode,
773
+ sourceRepo: sourceMode === "git" ? repository : null,
774
+ sourceBranch: sourceMode === "git" ? sourceBranch : null,
775
+ sourceCommit: sourceMode === "git" ? headCommitSafe(treeDxRoot) ?? headCommitSafe(tenantRoot) : null,
776
+ sourceRootDirectory: sourceMode === "git" ? sourceRootDirectory : null,
777
+ dockerfilePath: sourceMode === "git" ? railway.dockerfilePath ?? "/Dockerfile" : null,
778
+ healthcheckPath: railway.healthcheckPath ?? null,
779
+ healthcheckTimeoutSeconds: railway.healthcheckTimeoutSeconds ?? null,
780
+ healthcheckIntervalSeconds: railway.healthcheckIntervalSeconds ?? null,
781
+ restartPolicy: railway.restartPolicy ?? null,
782
+ runtimeMode: railway.runtimeMode ?? "replicated",
783
+ volumeMountPath: railway.volumeMountPath ?? "/data",
784
+ schedule: [],
785
+ hostingKind,
786
+ runnerPool: null,
787
+ application,
788
+ environmentVariables: {
789
+ PORT: "4000",
790
+ TREEDX_DATA_DIR: railway.volumeMountPath ?? "/data",
791
+ TREEDX_AUTH_MODE: "connected",
792
+ TREEDX_AUTH_VERIFIER: "hs256_dev",
793
+ TREEDX_ALLOW_DEV_VERIFIER_IN_PROD: "true",
794
+ TREEDX_EXEC_BACKEND: "container_sandbox",
795
+ TREEDX_FEDERATION_MODE: "connected_library",
796
+ TREEDX_JWT_AUDIENCE: "treedx-public-federation",
797
+ TREEDX_JWT_ISSUER: "https://api.treeseed.local/treedx",
798
+ TREEDX_BOOTSTRAP_TRUST_ACTOR_ID: "treeseed-api",
799
+ TREEDX_BOOTSTRAP_TRUST_TENANT_ID: "treeseed-control-plane",
800
+ TREEDX_BOOTSTRAP_TRUST_REPO_IDS: "*",
801
+ TREEDX_BOOTSTRAP_TRUST_REFS: "*",
802
+ TREEDX_BOOTSTRAP_TRUST_PATHS: "**",
803
+ TREEDX_SCOPE: "public_federation"
804
+ },
805
+ secretRefs: ["TREEDX_SECRET_KEY_BASE", "TREEDX_ADMIN_TOKEN", "TREEDX_JWT_HS256_SECRET"],
806
+ variableRefs: [
807
+ "PORT",
808
+ "TREEDX_DATA_DIR",
809
+ "TREEDX_AUTH_MODE",
810
+ "TREEDX_AUTH_VERIFIER",
811
+ "TREEDX_ALLOW_DEV_VERIFIER_IN_PROD",
812
+ "TREEDX_EXEC_BACKEND",
813
+ "TREEDX_FEDERATION_MODE",
814
+ "TREEDX_JWT_AUDIENCE",
815
+ "TREEDX_JWT_ISSUER",
816
+ "TREEDX_BOOTSTRAP_TRUST_ACTOR_ID",
817
+ "TREEDX_BOOTSTRAP_TRUST_TENANT_ID",
818
+ "TREEDX_BOOTSTRAP_TRUST_REPO_IDS",
819
+ "TREEDX_BOOTSTRAP_TRUST_REFS",
820
+ "TREEDX_BOOTSTRAP_TRUST_PATHS",
821
+ "TREEDX_SCOPE"
822
+ ]
823
+ };
824
+ });
825
+ }
826
+ function readTreeseedPackageRepository(packageRoot) {
827
+ const manifestPath = resolve(packageRoot, "treeseed.package.yaml");
828
+ if (!existsSync(manifestPath)) return null;
829
+ try {
830
+ const manifest = parseYaml(readFileSync(manifestPath, "utf8"));
831
+ const repository = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.repository : null;
832
+ return typeof repository === "string" && /^[^/\s]+\/[^/\s]+$/u.test(repository.trim()) ? repository.trim() : null;
833
+ } catch {
834
+ return null;
835
+ }
836
+ }
837
+ function headCommitSafe(cwd) {
838
+ try {
839
+ return runTreeseedGitText(["rev-parse", "HEAD"], {
840
+ cwd,
841
+ mode: classifyTreeseedGitMode(["rev-parse", "HEAD"])
842
+ }).trim();
843
+ } catch {
844
+ return null;
845
+ }
846
+ }
847
+ function resolveRailwayServiceSourcePolicy({ tenantRoot, scope, serviceKey, service, serviceRoot, imageRef }) {
848
+ const configuredMode = typeof service.railway?.sourceMode === "string" ? service.railway.sourceMode : null;
849
+ const configuredSource = service.railway?.source && typeof service.railway.source === "object" && !Array.isArray(service.railway.source) ? service.railway.source : {};
850
+ const configuredRepo = typeof service.railway?.sourceRepo === "string" ? service.railway.sourceRepo : typeof configuredSource.repository === "string" ? configuredSource.repository : typeof configuredSource.repo === "string" ? configuredSource.repo : null;
851
+ const packageRepository = configuredRepo ?? readTreeseedPackageRepository(serviceRoot) ?? readTreeseedPackageRepository(tenantRoot);
852
+ const sourceEligible = ["api", "operationsRunner", "capacityProviderManager", "capacityProviderRunner"].includes(serviceKey);
853
+ const sourceMode = scope === "prod" ? "image" : configuredMode === "git" || configuredMode === "image" ? configuredMode : imageRef ? "image" : scope === "staging" && sourceEligible && packageRepository ? "git" : "git";
854
+ if (sourceMode !== "git") {
855
+ return {
856
+ sourceMode: "image",
857
+ sourceRepo: null,
858
+ sourceBranch: null,
859
+ sourceCommit: null,
860
+ sourceRootDirectory: null
861
+ };
862
+ }
863
+ return {
864
+ sourceMode: "git",
865
+ sourceRepo: packageRepository,
866
+ sourceBranch: typeof service.railway?.sourceBranch === "string" ? service.railway.sourceBranch : typeof configuredSource.branch === "string" ? configuredSource.branch : scope === "staging" ? "staging" : null,
867
+ sourceCommit: headCommitSafe(serviceRoot),
868
+ sourceRootDirectory: typeof service.railway?.sourceRootDirectory === "string" ? service.railway.sourceRootDirectory : typeof configuredSource.rootDirectory === "string" ? configuredSource.rootDirectory : "."
869
+ };
870
+ }
871
+ function resolveRailwayCapacityProviderRoot(tenantRoot, service) {
872
+ if (service.railway?.rootDir) {
873
+ return resolve(tenantRoot, service.railway.rootDir);
874
+ }
875
+ const candidates = [
876
+ resolve(tenantRoot, "..", "agent"),
877
+ resolve(tenantRoot, "packages", "agent"),
878
+ resolve(tenantRoot, "..", "..", "packages", "agent")
879
+ ];
880
+ const found = candidates.find(
881
+ (candidate) => existsSync(resolve(candidate, "treeseed.package.yaml")) || existsSync(resolve(candidate, "package.json"))
882
+ );
883
+ return found ?? resolve(tenantRoot, "packages", "agent");
884
+ }
885
+ function configuredRailwayServices(tenantRoot, scope) {
886
+ const deployConfig = loadCliDeployConfig(tenantRoot);
887
+ const direct = configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig);
888
+ const nested = discoverTreeseedApplications(tenantRoot).filter((application) => application.root !== resolve(tenantRoot)).flatMap((application) => configuredRailwayServicesForConfig(
889
+ application.root,
890
+ scope,
891
+ application.config,
892
+ {
893
+ id: application.id,
894
+ root: application.root,
895
+ relativeRoot: application.relativeRoot
896
+ },
897
+ tenantRoot
898
+ ));
899
+ return [...direct, ...nested];
995
900
  }
996
901
  function configuredRailwayScheduledJobs(tenantRoot, scope, { phase = "deploy" } = {}) {
997
902
  if (!shouldManageRailwaySchedules(scope, phase)) {
@@ -1087,7 +992,11 @@ function validateRailwayServiceConfiguration(tenantRoot, scope) {
1087
992
  if (!service.projectName && !service.projectId) {
1088
993
  issues.push(`${service.key}: set railway.projectName or railway.projectId in treeseed.site.yaml.`);
1089
994
  }
1090
- if (!existsSync(service.rootDir)) {
995
+ if (service.sourceMode === "git" && !service.sourceRepo) {
996
+ issues.push(`${service.key}: staging source builds require railway.source.repository or package repository metadata.`);
997
+ }
998
+ const usesExternalGitSource = service.sourceMode === "git" && Boolean(service.sourceRepo);
999
+ if (!service.imageRef && !usesExternalGitSource && !existsSync(service.rootDir)) {
1091
1000
  issues.push(`${service.key}: service root ${service.rootDir} does not exist.`);
1092
1001
  }
1093
1002
  if (service.schedule?.length && !service.startCommand) {
@@ -1109,7 +1018,7 @@ function validateRailwayDeployPrerequisites(tenantRoot, scope, { env = process.e
1109
1018
  const validation = validateRailwayServiceConfiguration(tenantRoot, scope);
1110
1019
  const token = resolveRailwayAuthToken(env);
1111
1020
  if (typeof token !== "string" || token.trim().length === 0) {
1112
- throw new Error("Configure RAILWAY_API_TOKEN before deploying Railway-managed services.");
1021
+ throw new Error("Configure TREESEED_RAILWAY_API_TOKEN before deploying Railway-managed services.");
1113
1022
  }
1114
1023
  return validation;
1115
1024
  }
@@ -1121,7 +1030,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
1121
1030
  const effectiveApiToken = apiToken || resolveRailwayAuthToken(env);
1122
1031
  const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
1123
1032
  if (typeof effectiveApiToken !== "string" || effectiveApiToken.trim().length === 0) {
1124
- throw new Error("Configure RAILWAY_API_TOKEN before deploying Railway-managed services.");
1033
+ throw new Error("Configure TREESEED_RAILWAY_API_TOKEN before deploying Railway-managed services.");
1125
1034
  }
1126
1035
  const results = [];
1127
1036
  try {
@@ -1147,7 +1056,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
1147
1056
  const current = await getRailwayServiceInstance({
1148
1057
  serviceId: target.service.id,
1149
1058
  environmentId: target.environment.id,
1150
- env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
1059
+ env: { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
1151
1060
  fetchImpl
1152
1061
  });
1153
1062
  const desired = {
@@ -1177,7 +1086,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
1177
1086
  environmentId: target.environment.id,
1178
1087
  startCommand: desired.command,
1179
1088
  cronSchedule: desired.schedule,
1180
- env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
1089
+ env: { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
1181
1090
  fetchImpl
1182
1091
  });
1183
1092
  results.push({
@@ -1207,7 +1116,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
1207
1116
  return results;
1208
1117
  }
1209
1118
  async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch, apiToken, apiUrl, env = process.env } = {}) {
1210
- const effectiveApiToken = apiToken || env?.RAILWAY_API_TOKEN;
1119
+ const effectiveApiToken = apiToken || resolveRailwayAuthToken(env);
1211
1120
  const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
1212
1121
  const configured = configuredRailwayScheduledJobs(tenantRoot, scope);
1213
1122
  const checks = [];
@@ -1231,7 +1140,7 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
1231
1140
  const existing = await getRailwayServiceInstance({
1232
1141
  serviceId: target.service.id,
1233
1142
  environmentId: target.environment.id,
1234
- env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
1143
+ env: { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
1235
1144
  fetchImpl
1236
1145
  });
1237
1146
  checks.push({
@@ -1286,7 +1195,7 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
1286
1195
  } = {}) {
1287
1196
  const effectiveApiToken = apiToken || resolveRailwayAuthToken(env);
1288
1197
  const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
1289
- const effectiveEnv = { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl };
1198
+ const effectiveEnv = { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl };
1290
1199
  const services = configuredRailwayServices(tenantRoot, scope);
1291
1200
  const checks = [];
1292
1201
  const deploymentStatusServices = [];
@@ -1346,18 +1255,19 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
1346
1255
  } : null,
1347
1256
  message: instance.id ? void 0 : `Railway service instance for ${target.service.name} is missing in ${target.environment.name}.`
1348
1257
  });
1349
- if (service.key === "workerRunner") {
1350
- const expectedVolumeName = deriveRailwayWorkerRunnerVolumeName(target.service.name, target.environment.name);
1258
+ const expectedVolumeMountPath = service.volumeMountPath ?? service.runnerPool?.volumeMountPath ?? null;
1259
+ if (expectedVolumeMountPath) {
1260
+ const expectedVolumeName = service.key === "operationsRunner" ? deriveRailwayOperationsRunnerVolumeName(target.service.name, target.environment.name) : service.key === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerVolumeName(target.service.name, target.environment.name) : deriveRailwayWorkerRunnerVolumeName(target.service.name, target.environment.name);
1351
1261
  const volumes = await listRailwayVolumes({
1352
1262
  projectId: target.project.id,
1353
1263
  env: effectiveEnv,
1354
1264
  fetchImpl
1355
1265
  });
1356
1266
  const volume = volumes.find(
1357
- (candidate) => candidate.name === expectedVolumeName && candidate.instances.some((entry) => entry.serviceId === target.service.id && entry.environmentId === target.environment.id && entry.mountPath === WORKER_RUNNER_VOLUME_MOUNT_PATH)
1267
+ (candidate) => candidate.name === expectedVolumeName && candidate.instances.some((entry) => entry.serviceId === target.service.id && entry.environmentId === target.environment.id && entry.mountPath === expectedVolumeMountPath)
1358
1268
  ) ?? null;
1359
1269
  checks.push({
1360
- type: "worker-runner-volume",
1270
+ type: "service-volume",
1361
1271
  service: service.key,
1362
1272
  serviceName: target.service.name,
1363
1273
  serviceId: target.service.id,
@@ -1365,7 +1275,7 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
1365
1275
  environment: target.environment.name,
1366
1276
  environmentId: target.environment.id,
1367
1277
  volumeName: expectedVolumeName,
1368
- mountPath: WORKER_RUNNER_VOLUME_MOUNT_PATH,
1278
+ mountPath: expectedVolumeMountPath,
1369
1279
  ok: Boolean(volume),
1370
1280
  status: volume ? "checked" : "missing",
1371
1281
  observed: volume ? {
@@ -1373,7 +1283,7 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
1373
1283
  name: volume.name,
1374
1284
  instances: volume.instances
1375
1285
  } : null,
1376
- message: volume ? void 0 : `Railway worker-runner volume ${expectedVolumeName} is missing or is not mounted at ${WORKER_RUNNER_VOLUME_MOUNT_PATH}.`
1286
+ message: volume ? void 0 : `Railway volume ${expectedVolumeName} is missing or is not mounted on ${target.service.name} at ${expectedVolumeMountPath}.`
1377
1287
  });
1378
1288
  }
1379
1289
  }
@@ -1407,29 +1317,34 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
1407
1317
  checks
1408
1318
  };
1409
1319
  }
1410
- function shouldAttachRailwayDeployLogs(env = process.env) {
1411
- const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_ATTACH_LOGS");
1412
- if (configured === "1" || configured === "true") {
1413
- return true;
1414
- }
1415
- if (configured === "0" || configured === "false") {
1416
- return false;
1320
+ function railwayPhaseTimeoutMs(env = process.env, phase = "default") {
1321
+ const configured = Number.parseInt(configuredEnvValue(env, `TREESEED_RAILWAY_${String(phase).toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}_TIMEOUT_MS`), 10);
1322
+ if (Number.isFinite(configured) && configured > 0) {
1323
+ return configured;
1417
1324
  }
1418
- return false;
1419
- }
1420
- function shouldUseVerboseRailwayDeploy(env = process.env) {
1421
- const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_VERBOSE");
1422
- if (configured === "1" || configured === "true") {
1423
- return true;
1325
+ const defaultConfigured = Number.parseInt(configuredEnvValue(env, "TREESEED_RAILWAY_PHASE_TIMEOUT_MS"), 10);
1326
+ if (Number.isFinite(defaultConfigured) && defaultConfigured > 0) {
1327
+ return defaultConfigured;
1424
1328
  }
1425
- if (configured === "0" || configured === "false") {
1426
- return false;
1329
+ if (phase === "sync_runtime_config") {
1330
+ return 6e5;
1427
1331
  }
1428
- return shouldAttachRailwayDeployLogs(env);
1332
+ return phase === "deploy" ? 3e5 : 18e4;
1429
1333
  }
1430
- function shouldIncludeRailwayIgnoredFiles(env = process.env) {
1431
- const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_INCLUDE_IGNORED");
1432
- return configured === "1" || configured === "true";
1334
+ async function withRailwayPhaseTimeout(run, timeoutMs, message) {
1335
+ let timer = null;
1336
+ try {
1337
+ return await Promise.race([
1338
+ Promise.resolve().then(run),
1339
+ new Promise((_, reject) => {
1340
+ timer = setTimeout(() => reject(new Error(message)), timeoutMs);
1341
+ })
1342
+ ]);
1343
+ } finally {
1344
+ if (timer) {
1345
+ clearTimeout(timer);
1346
+ }
1347
+ }
1433
1348
  }
1434
1349
  function shouldRunRailwayPredeployBuild(env = process.env) {
1435
1350
  const configured = configuredEnvValue(env, "TREESEED_RAILWAY_PREDEPLOY_BUILD");
@@ -1441,56 +1356,6 @@ function shouldRunRailwayPredeployBuild(env = process.env) {
1441
1356
  }
1442
1357
  return configuredEnvValue(env, "CI") !== "true";
1443
1358
  }
1444
- function planRailwayServiceDeploy(service, { env = process.env, projectTokenMode = false } = {}) {
1445
- const serviceSelector = service.serviceName ?? service.serviceId;
1446
- const args = ["up"];
1447
- if (serviceSelector) {
1448
- args.push("--service", serviceSelector);
1449
- }
1450
- if (shouldIncludeRailwayIgnoredFiles(env)) {
1451
- args.push("--no-gitignore");
1452
- }
1453
- args.push(shouldAttachRailwayDeployLogs(env) ? "--ci" : "--detach");
1454
- if (shouldUseVerboseRailwayDeploy(env)) {
1455
- args.push("--verbose");
1456
- }
1457
- if (!projectTokenMode && service.projectId) {
1458
- args.push("--project", service.projectId);
1459
- }
1460
- const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
1461
- if (!projectTokenMode && environmentName) {
1462
- args.push("--environment", environmentName);
1463
- }
1464
- return {
1465
- command: "railway",
1466
- args,
1467
- cwd: service.rootDir
1468
- };
1469
- }
1470
- function planRailwayProjectEnvironmentLink(service) {
1471
- const args = ["link"];
1472
- if (service.projectId) {
1473
- args.push("--project", service.projectId);
1474
- }
1475
- const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
1476
- if (environmentName) {
1477
- args.push("--environment", environmentName);
1478
- }
1479
- args.push("--json");
1480
- return {
1481
- command: "railway",
1482
- args,
1483
- cwd: service.rootDir
1484
- };
1485
- }
1486
- function buildRailwayCliContextEnv(env, service) {
1487
- return {
1488
- ...env,
1489
- RAILWAY_PROJECT_ID: configuredEnvValue(service, "projectId") || configuredEnvValue(env, "RAILWAY_PROJECT_ID") || void 0,
1490
- RAILWAY_ENVIRONMENT_ID: configuredEnvValue(service, "environmentId") || configuredEnvValue(env, "RAILWAY_ENVIRONMENT_ID") || void 0,
1491
- RAILWAY_SERVICE_ID: configuredEnvValue(service, "serviceId") || configuredEnvValue(env, "RAILWAY_SERVICE_ID") || void 0
1492
- };
1493
- }
1494
1359
  async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
1495
1360
  if (service.key !== "api") {
1496
1361
  return null;
@@ -1505,7 +1370,7 @@ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
1505
1370
  [
1506
1371
  "TREESEED_API_AUTH_APPROVAL_BASE_URL",
1507
1372
  "TREESEED_SITE_URL",
1508
- "BETTER_AUTH_URL"
1373
+ "TREESEED_BETTER_AUTH_URL"
1509
1374
  ].map((key) => [key, configuredEnvValue(env, key)]).filter(([, value]) => value)
1510
1375
  );
1511
1376
  if (Object.keys(variables).length === 0) {
@@ -1521,166 +1386,6 @@ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
1521
1386
  write ? write(`[${prefix.scope}][${prefix.system}][${prefix.task}][vars] Synced device login approval URL variables for ${service.serviceName ?? serviceId}.`, "stdout") : null;
1522
1387
  return { variables: Object.keys(variables) };
1523
1388
  }
1524
- function buildRailwayLinkCommandEnv(env = process.env, service = {}) {
1525
- return buildRailwayCliContextEnv({
1526
- ...buildRailwayDeployCommandEnv({ ...env, RAILWAY_TOKEN: void 0 }),
1527
- RAILWAY_TOKEN: void 0
1528
- }, service);
1529
- }
1530
- function railwayCliConfigPath(env = process.env) {
1531
- const railwayHome = configuredEnvValue(env, "RAILWAY_HOME");
1532
- if (railwayHome) {
1533
- return resolve(railwayHome, "config.json");
1534
- }
1535
- const home = configuredEnvValue(env, "HOME");
1536
- if (!home) {
1537
- return "";
1538
- }
1539
- return resolve(home, ".railway", "config.json");
1540
- }
1541
- function readRailwayCliConfig(configPath) {
1542
- if (!configPath || !existsSync(configPath)) {
1543
- return {};
1544
- }
1545
- try {
1546
- const parsed = JSON.parse(readFileSync(configPath, "utf8"));
1547
- return parsed && typeof parsed === "object" ? parsed : {};
1548
- } catch {
1549
- return {};
1550
- }
1551
- }
1552
- function writeRailwayCliProjectConfig(service, { env = process.env, cwd = service.rootDir } = {}) {
1553
- const projectId = configuredEnvValue(service, "projectId");
1554
- const environmentId = configuredEnvValue(service, "environmentId");
1555
- const serviceId = configuredEnvValue(service, "serviceId");
1556
- const projectPath = cwd ? resolve(cwd) : "";
1557
- const configPath = railwayCliConfigPath(env);
1558
- if (!configPath || !projectPath || !projectId || !environmentId || !serviceId) {
1559
- return null;
1560
- }
1561
- const existing = readRailwayCliConfig(configPath);
1562
- const projects = existing.projects && typeof existing.projects === "object" ? existing.projects : {};
1563
- const next = {
1564
- ...existing,
1565
- projects: {
1566
- ...projects,
1567
- [projectPath]: {
1568
- projectPath,
1569
- name: configuredEnvValue(service, "projectName") || configuredEnvValue(service, "serviceName") || projectId,
1570
- project: projectId,
1571
- environment: environmentId,
1572
- environmentName: normalizeRailwayEnvironmentName(service.railwayEnvironment) || configuredEnvValue(service, "environmentName") || environmentId,
1573
- service: serviceId
1574
- }
1575
- },
1576
- user: existing.user && typeof existing.user === "object" ? existing.user : {
1577
- token: null,
1578
- accessToken: null,
1579
- refreshToken: null,
1580
- tokenExpiresAt: null
1581
- },
1582
- linkedFunctions: existing.linkedFunctions ?? null
1583
- };
1584
- mkdirSync(dirname(configPath), { recursive: true });
1585
- writeFileSync(configPath, `${JSON.stringify(next, null, 2)}
1586
- `, "utf8");
1587
- return {
1588
- configPath,
1589
- projectPath,
1590
- projectId,
1591
- environmentId,
1592
- serviceId
1593
- };
1594
- }
1595
- function planRailwayServiceLink(service, { env = process.env } = {}) {
1596
- const args = ["link"];
1597
- if (service.projectId) {
1598
- args.push("--project", service.projectId);
1599
- }
1600
- const workspace = resolveRailwayWorkspace(env);
1601
- if (workspace) {
1602
- args.push("--workspace", workspace);
1603
- }
1604
- const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
1605
- if (environmentName) {
1606
- args.push("--environment", environmentName);
1607
- }
1608
- const serviceSelector = service.serviceName ?? service.serviceId;
1609
- if (serviceSelector) {
1610
- args.push("--service", serviceSelector);
1611
- }
1612
- args.push("--json");
1613
- return {
1614
- command: "railway",
1615
- args,
1616
- cwd: service.rootDir
1617
- };
1618
- }
1619
- function railwayProjectTokenName(service) {
1620
- const environment = normalizeRailwayEnvironmentName(service.railwayEnvironment) || "environment";
1621
- const serviceName = String(service.serviceName ?? service.serviceId ?? service.key ?? "service").toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 48) || "service";
1622
- return `treeseed-ci-${environment}-${serviceName}`;
1623
- }
1624
- async function createRailwayCliProjectToken(service, { env = process.env } = {}) {
1625
- const projectId = configuredEnvValue(service, "projectId");
1626
- const environmentId = configuredEnvValue(service, "environmentId");
1627
- if (!projectId || !environmentId) {
1628
- return "";
1629
- }
1630
- const name = railwayProjectTokenName(service);
1631
- try {
1632
- const existingPayload = await railwayGraphqlRequest({
1633
- query: `
1634
- query TreeseedProjectTokens($projectId: String!) {
1635
- projectTokens(projectId: $projectId, first: 100) {
1636
- edges {
1637
- node {
1638
- id
1639
- name
1640
- projectId
1641
- environmentId
1642
- }
1643
- }
1644
- }
1645
- }
1646
- `.trim(),
1647
- variables: { projectId },
1648
- env
1649
- });
1650
- const existingTokens = railwayEdgeNodes(existingPayload.data?.projectTokens);
1651
- for (const token2 of existingTokens) {
1652
- if (token2?.name === name && token2?.projectId === projectId && token2?.environmentId === environmentId && token2?.id) {
1653
- await railwayGraphqlRequest({
1654
- query: `
1655
- mutation TreeseedProjectTokenDelete($id: String!) {
1656
- projectTokenDelete(id: $id)
1657
- }
1658
- `.trim(),
1659
- variables: { id: token2.id },
1660
- env
1661
- });
1662
- }
1663
- }
1664
- } catch {
1665
- }
1666
- const payload = await railwayGraphqlRequest({
1667
- query: `
1668
- mutation TreeseedProjectTokenCreate($input: ProjectTokenCreateInput!) {
1669
- projectTokenCreate(input: $input)
1670
- }
1671
- `.trim(),
1672
- variables: {
1673
- input: {
1674
- projectId,
1675
- environmentId,
1676
- name
1677
- }
1678
- },
1679
- env
1680
- });
1681
- const token = payload.data?.projectTokenCreate;
1682
- return typeof token === "string" && token.trim() ? token.trim() : "";
1683
- }
1684
1389
  async function resolveRailwayDeployProjectContext(service, { env = process.env } = {}) {
1685
1390
  if (service.projectId) {
1686
1391
  return service;
@@ -1699,15 +1404,23 @@ async function resolveRailwayDeployProjectContext(service, { env = process.env }
1699
1404
  projectName: project.name ?? service.projectName
1700
1405
  };
1701
1406
  }
1702
- async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env } = {}) {
1703
- const wantsInstanceConfig = service.buildCommand || service.startCommand || service.rootDir || service.healthcheckPath || service.healthcheckTimeoutSeconds !== null || service.healthcheckTimeoutSeconds !== void 0 || service.healthcheckIntervalSeconds !== null || service.healthcheckIntervalSeconds !== void 0 || service.restartPolicy || service.runtimeMode;
1704
- const wantsRunnerVolume = service.key === "workerRunner" || Boolean(service.volumeMountPath);
1407
+ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env, writePhase = null } = {}) {
1408
+ const writeSyncPhase = (stage, message) => {
1409
+ if (typeof writePhase === "function") {
1410
+ writePhase(`sync-runtime-config:${stage}`, message);
1411
+ }
1412
+ };
1413
+ const wantsInstanceConfig = service.buildCommand || service.startCommand || !(service.imageRef || service.sourceMode === "image") && service.rootDir || service.healthcheckPath || service.healthcheckTimeoutSeconds !== null || service.healthcheckTimeoutSeconds !== void 0 || service.healthcheckIntervalSeconds !== null || service.healthcheckIntervalSeconds !== void 0 || service.restartPolicy || service.runtimeMode;
1414
+ const wantsRunnerVolume = service.key === "workerRunner" || service.key === "operationsRunner" || service.key === "capacityProviderRunner";
1705
1415
  if (!wantsInstanceConfig && !wantsRunnerVolume) {
1416
+ writeSyncPhase("skip", "No runtime configuration changes requested.");
1706
1417
  return null;
1707
1418
  }
1419
+ writeSyncPhase("workspace", "Resolving Railway workspace.");
1708
1420
  const workspace = await resolveRailwayWorkspaceContext({ env });
1709
1421
  let project = null;
1710
1422
  if (service.projectId) {
1423
+ writeSyncPhase("project", `Resolving Railway project ${service.projectName ?? service.projectId}.`);
1711
1424
  project = await ensureRailwayProject({
1712
1425
  projectId: service.projectId,
1713
1426
  projectName: service.projectName,
@@ -1716,9 +1429,11 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1716
1429
  workspace: workspace.id
1717
1430
  }).then((result) => result.project);
1718
1431
  } else {
1432
+ writeSyncPhase("project", `Looking up Railway project ${service.projectName}.`);
1719
1433
  const projects = await listRailwayProjects({ env, workspaceId: workspace.id });
1720
1434
  project = projects.find((entry) => entry.name === service.projectName) ?? null;
1721
1435
  if (!project) {
1436
+ writeSyncPhase("project", `Creating Railway project ${service.projectName}.`);
1722
1437
  project = await ensureRailwayProject({
1723
1438
  projectName: service.projectName,
1724
1439
  defaultEnvironmentName: service.railwayEnvironment,
@@ -1730,6 +1445,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1730
1445
  const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
1731
1446
  let environment = project.environments.find((entry) => entry.name === environmentName || entry.id === environmentName) ?? null;
1732
1447
  if (!environment) {
1448
+ writeSyncPhase("environment", `Creating Railway environment ${environmentName}.`);
1733
1449
  environment = await ensureRailwayEnvironment({
1734
1450
  projectId: project.id,
1735
1451
  environmentName,
@@ -1738,63 +1454,107 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1738
1454
  }
1739
1455
  let railwayService = project.services.find((entry) => entry.id === service.serviceId || entry.name === service.serviceName) ?? null;
1740
1456
  if (!railwayService) {
1457
+ writeSyncPhase("service", `Creating Railway service ${service.serviceName ?? service.key}.`);
1741
1458
  railwayService = await ensureRailwayService({
1742
1459
  projectId: project.id,
1743
1460
  serviceId: service.serviceId,
1744
1461
  serviceName: service.serviceName,
1462
+ environmentId: environment.id,
1463
+ imageRef: service.imageRef,
1464
+ sourceRepo: service.sourceRepo,
1465
+ sourceBranch: service.sourceBranch,
1466
+ env
1467
+ }).then((result) => result.service);
1468
+ } else if (service.imageRef) {
1469
+ railwayService = await ensureRailwayService({
1470
+ projectId: project.id,
1471
+ serviceId: service.serviceId,
1472
+ serviceName: service.serviceName,
1473
+ environmentId: environment.id,
1474
+ imageRef: service.imageRef,
1475
+ sourceRepo: service.sourceRepo,
1476
+ sourceBranch: service.sourceBranch,
1745
1477
  env
1746
1478
  }).then((result) => result.service);
1747
1479
  }
1480
+ if (wantsInstanceConfig) {
1481
+ writeSyncPhase("instance", "Ensuring Railway service instance configuration.");
1482
+ }
1748
1483
  const runtimeConfiguration = wantsInstanceConfig ? await ensureRailwayServiceInstanceConfiguration({
1749
1484
  serviceId: railwayService.id,
1750
1485
  environmentId: environment.id,
1751
1486
  buildCommand: service.buildCommand,
1752
1487
  startCommand: railwayServiceRuntimeStartCommand(service),
1753
1488
  cronSchedule: service.schedule?.[0] ?? null,
1754
- rootDirectory: relativeRailwayRootDir(tenantRoot, service.rootDir),
1489
+ rootDirectory: service.imageRef || service.sourceMode === "image" ? null : service.sourceRootDirectory ?? ".",
1755
1490
  healthcheckPath: service.healthcheckPath,
1756
1491
  healthcheckTimeoutSeconds: service.healthcheckTimeoutSeconds,
1757
1492
  healthcheckIntervalSeconds: service.healthcheckIntervalSeconds,
1758
1493
  restartPolicy: service.restartPolicy,
1759
1494
  runtimeMode: service.runtimeMode,
1495
+ deploymentRegion: wantsRunnerVolume ? configuredEnvValue(env, "TREESEED_RAILWAY_STATEFUL_REGION") || "us-west2" : null,
1760
1496
  env
1761
1497
  }) : null;
1498
+ writeSyncPhase("variables", "Upserting Railway runtime variables.");
1762
1499
  await upsertRailwayVariables({
1763
1500
  projectId: project.id,
1764
1501
  environmentId: environment.id,
1765
1502
  serviceId: railwayService.id,
1766
1503
  variables: {
1767
1504
  TREESEED_SKIP_PACKAGE_PREPARE: "1",
1768
- ...service.key === "marketOperationsRunner" ? {
1505
+ ...service.sourceMode === "git" ? {
1506
+ TREESEED_DEPLOY_SOURCE_MODE: "git",
1507
+ ...service.sourceRepo ? { TREESEED_DEPLOY_SOURCE_REPOSITORY: service.sourceRepo } : {},
1508
+ ...service.sourceBranch ? { TREESEED_DEPLOY_SOURCE_BRANCH: service.sourceBranch } : {},
1509
+ ...service.sourceCommit ? { TREESEED_DEPLOY_SOURCE_COMMIT: service.sourceCommit } : {}
1510
+ } : {
1511
+ TREESEED_DEPLOY_SOURCE_MODE: "image"
1512
+ },
1513
+ ...service.key === "operationsRunner" ? {
1769
1514
  NIXPACKS_APT_PKGS: "git",
1770
1515
  NIXPACKS_PKGS: "git",
1771
1516
  TREESEED_PLATFORM_RUNNER_ID: service.runnerId ?? railwayService.name,
1772
1517
  TREESEED_PLATFORM_RUNNER_DATA_DIR: service.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH,
1773
1518
  TREESEED_PLATFORM_RUNNER_ENVIRONMENT: normalizeScope(service.scope) === "prod" ? "production" : normalizeScope(service.scope),
1774
- TREESEED_MARKET_ID: normalizeScope(service.scope),
1519
+ TREESEED_MANAGER_ID: normalizeScope(service.scope),
1520
+ ...configuredEnvValue(env, "TREESEED_RAILWAY_API_TOKEN") ? { TREESEED_RAILWAY_API_TOKEN: configuredEnvValue(env, "TREESEED_RAILWAY_API_TOKEN") } : {},
1521
+ ...configuredEnvValue(env, "TREESEED_RAILWAY_WORKSPACE") ? { TREESEED_RAILWAY_WORKSPACE: configuredEnvValue(env, "TREESEED_RAILWAY_WORKSPACE") } : {},
1775
1522
  ...configuredEnvValue(env, "TREESEED_PLATFORM_RUNNER_SECRET") ? { TREESEED_PLATFORM_RUNNER_SECRET: configuredEnvValue(env, "TREESEED_PLATFORM_RUNNER_SECRET") } : {},
1776
- ...configuredEnvValue(env, "TREESEED_MARKET_API_BASE_URL") || configuredEnvValue(env, "TREESEED_MARKET_URL") ? {
1777
- TREESEED_MARKET_API_BASE_URL: configuredEnvValue(env, "TREESEED_MARKET_API_BASE_URL") || configuredEnvValue(env, "TREESEED_MARKET_URL")
1523
+ ...configuredEnvValue(env, "TREESEED_API_BASE_URL") || configuredEnvValue(env, "TREESEED_URL") ? {
1524
+ TREESEED_API_BASE_URL: configuredEnvValue(env, "TREESEED_API_BASE_URL") || configuredEnvValue(env, "TREESEED_URL")
1778
1525
  } : {}
1526
+ } : {},
1527
+ ...String(service.key).startsWith("capacityProvider") ? {
1528
+ TREESEED_PROVIDER_ENVIRONMENT: normalizeScope(service.scope) === "prod" ? "production" : normalizeScope(service.scope),
1529
+ TREESEED_MANAGER_ID: normalizeScope(service.scope),
1530
+ TREESEED_MARKET_ID: normalizeScope(service.scope),
1531
+ TREESEED_PROVIDER_ROLE: service.key === "capacityProviderManager" ? "manager" : "runner",
1532
+ ...service.key === "capacityProviderRunner" ? {
1533
+ TREESEED_PROVIDER_DATA_DIR: service.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH,
1534
+ TREESEED_PROVIDER_RUNNER_ID: service.runnerId ?? railwayService.name
1535
+ } : {},
1536
+ ...configuredEnvValue(env, "TREESEED_MARKET_URL") ? { TREESEED_MARKET_URL: configuredEnvValue(env, "TREESEED_MARKET_URL") } : {},
1537
+ ...configuredEnvValue(env, "TREESEED_CAPACITY_PROVIDER_API_KEY") ? { TREESEED_CAPACITY_PROVIDER_API_KEY: configuredEnvValue(env, "TREESEED_CAPACITY_PROVIDER_API_KEY") } : {},
1538
+ ...configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") ? { TREESEED_CODEX_AUTH_JSON_B64: configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") } : {}
1779
1539
  } : {}
1780
1540
  },
1781
1541
  env
1782
1542
  });
1783
1543
  const volumeMountPath = service.volumeMountPath ?? service.runnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH;
1784
- const volumeConfiguration = wantsRunnerVolume ? await ensureRailwayServiceVolumeWithCliFallback({
1785
- tenantRoot,
1544
+ if (wantsRunnerVolume) {
1545
+ writeSyncPhase("volume", `Ensuring Railway volume mounted at ${volumeMountPath}.`);
1546
+ }
1547
+ const volumeConfiguration = wantsRunnerVolume ? await ensureRailwayServiceVolume({
1786
1548
  projectId: project.id,
1787
1549
  environmentId: environment.id,
1788
- environmentName: environment.name,
1789
1550
  serviceId: railwayService.id,
1790
- serviceName: railwayService.name,
1791
- name: service.key === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
1551
+ name: service.key === "operationsRunner" ? deriveRailwayOperationsRunnerVolumeName(railwayService.name, environment.name) : service.key === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
1792
1552
  mountPath: volumeMountPath,
1793
- preferCli: service.key === "marketOperationsRunner",
1794
1553
  env
1795
1554
  }) : null;
1796
1555
  if (wantsRunnerVolume) {
1797
1556
  if (service.key === "workerRunner") {
1557
+ writeSyncPhase("volume-vars", "Upserting Railway worker volume variables.");
1798
1558
  await upsertRailwayVariables({
1799
1559
  projectId: project.id,
1800
1560
  environmentId: environment.id,
@@ -1810,6 +1570,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1810
1570
  });
1811
1571
  }
1812
1572
  }
1573
+ writeSyncPhase("done", "Runtime configuration is synchronized.");
1813
1574
  return {
1814
1575
  projectId: project.id,
1815
1576
  projectName: project.name ?? service.projectName ?? null,
@@ -1828,163 +1589,6 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1828
1589
  } : null
1829
1590
  };
1830
1591
  }
1831
- async function ensureRailwayServiceVolumeWithCliFallback({
1832
- tenantRoot,
1833
- projectId,
1834
- environmentId,
1835
- environmentName,
1836
- serviceId,
1837
- serviceName,
1838
- name,
1839
- mountPath,
1840
- preferCli = false,
1841
- env = process.env
1842
- }) {
1843
- if (!preferCli) {
1844
- try {
1845
- return await ensureRailwayServiceVolume({
1846
- projectId,
1847
- environmentId,
1848
- serviceId,
1849
- name,
1850
- mountPath,
1851
- env
1852
- });
1853
- } catch (error) {
1854
- const message = error instanceof Error ? error.message : String(error);
1855
- if (!message.includes("Problem processing request")) {
1856
- throw error;
1857
- }
1858
- }
1859
- }
1860
- const cliOptions = {
1861
- cwd: tenantRoot,
1862
- capture: true,
1863
- env
1864
- };
1865
- ensureRailwayProjectContext({
1866
- key: serviceName,
1867
- projectId,
1868
- serviceName,
1869
- rootDir: tenantRoot,
1870
- railwayEnvironment: environmentName
1871
- }, {
1872
- env,
1873
- capture: true
1874
- });
1875
- const volumeArgs = ["volume", "--service", serviceId, "--environment", environmentId];
1876
- const listResult = runRailway([...volumeArgs, "list", "--json"], cliOptions);
1877
- const existingVolumes = normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
1878
- serviceId,
1879
- serviceName,
1880
- environmentId,
1881
- fallbackName: name,
1882
- fallbackMountPath: mountPath
1883
- });
1884
- let volume = existingVolumes.find((entry) => entry.name === name) ?? existingVolumes.find((entry) => entry.instances.some((instance2) => instance2.mountPath === mountPath)) ?? existingVolumes[0] ?? null;
1885
- let created = false;
1886
- let updated = false;
1887
- if (!volume) {
1888
- const createResult = runRailway([...volumeArgs, "add", "--mount-path", mountPath, "--json"], cliOptions);
1889
- volume = normalizeRailwayCliVolume(parseRailwayJsonOutput(createResult.stdout ?? ""), {
1890
- serviceId,
1891
- serviceName,
1892
- environmentId,
1893
- fallbackName: name,
1894
- fallbackMountPath: mountPath
1895
- });
1896
- if (!volume) {
1897
- throw new Error(`Railway CLI volume add did not return a usable volume for ${serviceName} in ${environmentName}.`);
1898
- }
1899
- created = true;
1900
- }
1901
- let instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
1902
- if (!instance || instance.mountPath !== mountPath) {
1903
- const attachResult = runRailway([...volumeArgs, "attach", "--volume", volume.id, "--yes", "--json"], {
1904
- ...cliOptions,
1905
- allowFailure: true
1906
- });
1907
- if ((attachResult.status ?? 1) !== 0) {
1908
- const attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
1909
- if (!/already mounted/iu.test(attachMessage)) {
1910
- throw new Error(attachMessage || `Railway volume attach failed for ${serviceName} in ${environmentName}.`);
1911
- }
1912
- }
1913
- const attachedVolume = (attachResult.status ?? 1) === 0 ? normalizeRailwayCliVolume(parseRailwayJsonOutput(attachResult.stdout ?? ""), {
1914
- serviceId,
1915
- serviceName,
1916
- environmentId,
1917
- fallbackName: name,
1918
- fallbackMountPath: mountPath
1919
- }) : null;
1920
- volume = attachedVolume ?? {
1921
- ...volume,
1922
- instances: [{
1923
- ...instance ?? {
1924
- id: volume.id,
1925
- serviceId,
1926
- environmentId,
1927
- state: "READY",
1928
- sizeGb: null,
1929
- usedGb: null
1930
- },
1931
- serviceId,
1932
- environmentId,
1933
- mountPath
1934
- }]
1935
- };
1936
- instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
1937
- updated = true;
1938
- }
1939
- const apiVolume = await waitForRailwayServiceVolumeMount({
1940
- projectId,
1941
- volumeId: volume.id,
1942
- volumeName: name,
1943
- serviceId,
1944
- environmentId,
1945
- mountPath,
1946
- env
1947
- });
1948
- if (apiVolume) {
1949
- volume = apiVolume;
1950
- } else {
1951
- throw new Error(`Railway volume ${name} was not attached to ${serviceName} at ${mountPath}.`);
1952
- }
1953
- return {
1954
- volume,
1955
- instance: volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null,
1956
- created,
1957
- updated
1958
- };
1959
- }
1960
- async function waitForRailwayServiceVolumeMount({
1961
- projectId,
1962
- volumeId,
1963
- volumeName,
1964
- serviceId,
1965
- environmentId,
1966
- mountPath,
1967
- env
1968
- }) {
1969
- for (let attempt = 0; attempt <= 24; attempt += 1) {
1970
- const volumes = await listRailwayVolumes({ projectId, env });
1971
- const mounted = volumes.find(
1972
- (entry) => entry.instances.some(
1973
- (instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
1974
- )
1975
- ) ?? null;
1976
- const match = mounted ?? volumes.find((entry) => entry.id === volumeId) ?? volumes.find((entry) => entry.name === volumeName) ?? null;
1977
- if (match?.instances.some(
1978
- (instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
1979
- )) {
1980
- return match;
1981
- }
1982
- if (attempt < 24) {
1983
- await sleep(5e3);
1984
- }
1985
- }
1986
- return null;
1987
- }
1988
1592
  async function deployRailwayService(tenantRoot, service, {
1989
1593
  dryRun = false,
1990
1594
  write,
@@ -1993,34 +1597,42 @@ async function deployRailwayService(tenantRoot, service, {
1993
1597
  } = {}) {
1994
1598
  const timings = [];
1995
1599
  if (dryRun) {
1996
- const plan2 = planRailwayServiceDeploy(service, { env });
1997
1600
  return {
1998
1601
  service: service.key,
1999
1602
  status: "planned",
2000
- command: [plan2.command, ...plan2.args].join(" "),
2001
- cwd: plan2.cwd,
1603
+ command: "railway-api serviceInstanceDeployV2",
1604
+ cwd: service.rootDir,
2002
1605
  publicBaseUrl: service.publicBaseUrl,
2003
- timings
1606
+ timings,
1607
+ transport: {
1608
+ railway: {
1609
+ reconcile: "api",
1610
+ deploy: railwayDeployTransport(env)
1611
+ }
1612
+ }
2004
1613
  };
2005
1614
  }
2006
1615
  const deployService = await timedRailwayPhase(timings, "railway:resolve-context", () => resolveRailwayDeployProjectContext(service, { env }), {
2007
1616
  service: service.key
2008
1617
  });
2009
1618
  const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
2010
- let railwayDeployEnv = buildRailwayDeployCommandEnv(commandEnv);
2011
- const railway = resolveTreeseedToolCommand("railway", { env: commandEnv });
2012
- if (!railway) {
2013
- throw new Error("Railway CLI is unavailable.");
2014
- }
1619
+ const deployTransport = railwayDeployTransport(commandEnv);
2015
1620
  const taskPrefix = prefix ?? {
2016
1621
  scope: normalizeScope(deployService.scope ?? deployService.railwayEnvironment ?? "railway"),
2017
1622
  system: deployService.key === "api" ? "api" : "agents",
2018
1623
  task: `${deployService.key}-railway-deploy`,
2019
1624
  stage: "deploy"
2020
1625
  };
2021
- const runtimeConfiguration = await timedRailwayPhase(timings, "railway:sync-runtime-config", () => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, {
2022
- env: commandEnv
2023
- }), { service: deployService.key });
1626
+ const writePhase = (stage, message) => {
1627
+ write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][${stage}] ${message}`, "stdout") : null;
1628
+ };
1629
+ writePhase("resolve-context", `Resolved Railway service ${deployService.serviceName ?? deployService.serviceId ?? deployService.key}.`);
1630
+ writePhase("sync-runtime-config", "Syncing Railway runtime configuration.");
1631
+ const runtimeConfiguration = await timedRailwayPhase(timings, "railway:sync-runtime-config", () => withRailwayPhaseTimeout(
1632
+ () => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, { env: commandEnv, writePhase }),
1633
+ railwayPhaseTimeoutMs(commandEnv, "sync_runtime_config"),
1634
+ `Railway runtime configuration sync timed out for ${deployService.serviceName ?? deployService.key}.`
1635
+ ), { service: deployService.key });
2024
1636
  const cliDeployService = {
2025
1637
  ...deployService,
2026
1638
  projectId: runtimeConfiguration?.projectId ?? deployService.projectId,
@@ -2030,31 +1642,15 @@ async function deployRailwayService(tenantRoot, service, {
2030
1642
  serviceName: runtimeConfiguration?.serviceName ?? deployService.serviceName,
2031
1643
  railwayEnvironment: runtimeConfiguration?.environmentName ?? runtimeConfiguration?.environmentId ?? deployService.railwayEnvironment
2032
1644
  };
2033
- await timedRailwayPhase(timings, "railway:device-login-vars", () => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix), {
1645
+ writePhase("device-login-vars", "Syncing Railway device-login variables.");
1646
+ await timedRailwayPhase(timings, "railway:device-login-vars", () => withRailwayPhaseTimeout(
1647
+ () => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix),
1648
+ railwayPhaseTimeoutMs(commandEnv, "device_login_vars"),
1649
+ `Railway device-login variable sync timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
1650
+ ), {
2034
1651
  service: cliDeployService.key
2035
1652
  });
2036
- railwayDeployEnv = buildRailwayCliContextEnv(railwayDeployEnv, cliDeployService);
2037
- const hasCommandApiToken = Boolean(configuredEnvValue(commandEnv, "RAILWAY_API_TOKEN"));
2038
- let usesProjectToken = Boolean(configuredEnvValue(railwayDeployEnv, "RAILWAY_TOKEN"));
2039
- if (usesProjectToken) {
2040
- railwayDeployEnv = { ...railwayDeployEnv, RAILWAY_API_TOKEN: void 0 };
2041
- }
2042
- await timedRailwayPhase(timings, "railway:project-token", async () => {
2043
- if (usesProjectToken || hasCommandApiToken) {
2044
- return null;
2045
- }
2046
- const projectToken = await createRailwayCliProjectToken(cliDeployService, { env: commandEnv });
2047
- if (projectToken) {
2048
- railwayDeployEnv = buildRailwayCliContextEnv({ ...railwayDeployEnv, RAILWAY_API_TOKEN: void 0, RAILWAY_TOKEN: projectToken }, cliDeployService);
2049
- usesProjectToken = true;
2050
- } else if (configuredEnvValue(commandEnv, "CI") === "true") {
2051
- throw new Error(`Railway CI deploy requires a project token for ${cliDeployService.serviceName ?? cliDeployService.key}. Automatic project token creation did not return a token.`);
2052
- }
2053
- return null;
2054
- }, { service: cliDeployService.key });
2055
- const linkPlan = planRailwayServiceLink(cliDeployService, { env: commandEnv });
2056
- const plan = planRailwayServiceDeploy(cliDeployService, { env, projectTokenMode: usesProjectToken });
2057
- if (deployService.buildCommand && shouldRunRailwayPredeployBuild(commandEnv)) {
1653
+ if (deployService.buildCommand && !deployService.imageRef && shouldRunRailwayPredeployBuild(commandEnv)) {
2058
1654
  const buildResult = await timedRailwayPhase(timings, "railway:predeploy-build", () => runPrefixedCommand("bash", ["-lc", deployService.buildCommand], {
2059
1655
  cwd: deployService.rootDir,
2060
1656
  env: commandEnv,
@@ -2065,102 +1661,66 @@ async function deployRailwayService(tenantRoot, service, {
2065
1661
  throw new Error(`Railway ${deployService.key} build command failed.`);
2066
1662
  }
2067
1663
  }
2068
- const hasRailwayApiToken = Boolean(configuredEnvValue(commandEnv, "RAILWAY_API_TOKEN"));
2069
- const cliConfig = configuredEnvValue(commandEnv, "CI") === "true" ? writeRailwayCliProjectConfig(cliDeployService, { env: railwayDeployEnv, cwd: plan.cwd }) : null;
2070
- const effectiveLinkPlan = hasRailwayApiToken ? linkPlan : usesProjectToken ? planRailwayProjectEnvironmentLink(cliDeployService) : linkPlan;
2071
- const railwayLinkEnv = hasRailwayApiToken ? buildRailwayLinkCommandEnv(commandEnv, cliDeployService) : railwayDeployEnv;
2072
- await timedRailwayPhase(timings, "railway:link", async () => {
2073
- if (cliConfig) {
2074
- write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][link] Wrote Railway CLI project context for ${cliConfig.projectPath}.`, "stdout") : null;
2075
- return;
2076
- }
2077
- const linkResult = await runPrefixedCommand(railway.command, [...railway.argsPrefix, ...effectiveLinkPlan.args], {
2078
- cwd: effectiveLinkPlan.cwd,
2079
- env: railwayLinkEnv,
2080
- write,
2081
- prefix: { ...taskPrefix, stage: "link" }
2082
- });
2083
- if (linkResult.status !== 0) {
2084
- throw new Error(linkResult.stderr?.trim() || linkResult.stdout?.trim() || `railway ${effectiveLinkPlan.args.join(" ")} failed with exit code ${linkResult.status ?? "unknown"} in ${effectiveLinkPlan.cwd}`);
2085
- }
2086
- }, { service: cliDeployService.key });
2087
- await timedRailwayPhase(timings, "railway:deploy", async () => {
2088
- let lastFailure = null;
2089
- for (let attempt = 1; attempt <= 5; attempt += 1) {
2090
- const result = await runPrefixedCommand(railway.command, [...railway.argsPrefix, ...plan.args], {
2091
- cwd: plan.cwd,
2092
- env: railwayDeployEnv,
2093
- write,
2094
- prefix: taskPrefix
2095
- });
2096
- if (result.status === 0) {
2097
- lastFailure = null;
2098
- break;
2099
- }
2100
- lastFailure = result;
2101
- if (!isRailwayTransientFailure(result) || attempt === 5) {
2102
- throw new Error(result.stderr?.trim() || result.stdout?.trim() || `railway ${plan.args.join(" ")} failed with exit code ${result.status ?? "unknown"} in ${plan.cwd}`);
2103
- }
2104
- const backoffMs = 5e3 * attempt;
2105
- const warning = `Railway deploy for ${deployService.serviceName ?? deployService.serviceId ?? deployService.key} hit a transient failure; retrying in ${Math.round(backoffMs / 1e3)}s...`;
2106
- write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][retry] ${warning}`, "stderr") : console.warn(warning);
2107
- await sleep(backoffMs);
2108
- }
2109
- if (lastFailure) {
2110
- throw new Error(lastFailure.stderr?.trim() || lastFailure.stdout?.trim() || `railway ${plan.args.join(" ")} failed`);
2111
- }
2112
- }, { service: cliDeployService.key });
2113
- return {
2114
- service: deployService.key,
2115
- status: "deployed",
2116
- command: [plan.command, ...plan.args].join(" "),
2117
- cwd: plan.cwd,
2118
- publicBaseUrl: deployService.publicBaseUrl,
2119
- timings,
2120
- runtimeConfiguration: runtimeConfiguration ? {
2121
- updated: runtimeConfiguration.updated,
2122
- healthcheckPath: runtimeConfiguration.instance?.healthcheckPath ?? null,
2123
- healthcheckTimeoutSeconds: runtimeConfiguration.instance?.healthcheckTimeoutSeconds ?? null,
2124
- runtimeMode: runtimeConfiguration.instance?.runtimeMode ?? null,
2125
- volume: runtimeConfiguration.volume ?? null
2126
- } : null
2127
- };
1664
+ if (deployTransport !== "cli-fallback") {
1665
+ writePhase("deploy", `Deploying Railway service ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key} through the Railway API.`);
1666
+ const apiDeploy = await timedRailwayPhase(timings, "railway:api-deploy", () => withRailwayPhaseTimeout(
1667
+ () => deployRailwayServiceInstance({
1668
+ serviceId: cliDeployService.serviceId,
1669
+ environmentId: cliDeployService.environmentId,
1670
+ env: commandEnv
1671
+ }),
1672
+ railwayPhaseTimeoutMs(commandEnv, "deploy"),
1673
+ `Railway API deploy phase timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
1674
+ ), { service: cliDeployService.key });
1675
+ return {
1676
+ service: deployService.key,
1677
+ status: "deployed",
1678
+ command: "railway-api serviceInstanceDeployV2",
1679
+ cwd: deployService.rootDir,
1680
+ publicBaseUrl: deployService.publicBaseUrl,
1681
+ timings,
1682
+ deploymentId: apiDeploy.deploymentId,
1683
+ transport: {
1684
+ railway: {
1685
+ reconcile: "api",
1686
+ deploy: "api"
1687
+ }
1688
+ },
1689
+ runtimeConfiguration: runtimeConfiguration ? {
1690
+ updated: runtimeConfiguration.updated,
1691
+ healthcheckPath: runtimeConfiguration.instance?.healthcheckPath ?? null,
1692
+ healthcheckTimeoutSeconds: runtimeConfiguration.instance?.healthcheckTimeoutSeconds ?? null,
1693
+ runtimeMode: runtimeConfiguration.instance?.runtimeMode ?? null,
1694
+ volume: runtimeConfiguration.volume ?? null
1695
+ } : null
1696
+ };
1697
+ }
1698
+ throw new Error(`Railway deployment for ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key} requires Railway API deployment support. CLI deploy fallback has been removed.`);
2128
1699
  }
2129
1700
  export {
2130
1701
  buildRailwayCommandEnv,
2131
- buildRailwayDeployCommandEnv,
2132
- buildRailwayLinkCommandEnv,
2133
1702
  collectRailwayDeploymentStatusChecks,
2134
1703
  configuredRailwayScheduledJobs,
2135
1704
  configuredRailwayServices,
2136
1705
  deployRailwayService,
2137
- deriveRailwayMarketOperationsRunnerServiceName,
2138
- deriveRailwayMarketOperationsRunnerVolumeName,
1706
+ deriveRailwayCapacityProviderRunnerServiceName,
1707
+ deriveRailwayCapacityProviderRunnerVolumeName,
1708
+ deriveRailwayOperationsRunnerServiceName,
1709
+ deriveRailwayOperationsRunnerVolumeName,
2139
1710
  deriveRailwayWorkerRunnerServiceName,
2140
1711
  deriveRailwayWorkerRunnerVolumeName,
2141
- ensureRailwayDatabaseServiceExists,
2142
- ensureRailwayEnvironmentExists,
2143
- ensureRailwayProjectContext,
2144
- ensureRailwayProjectExists,
2145
1712
  ensureRailwayScheduledJobs,
2146
- ensureRailwayServiceExists,
2147
- ensureRailwayServiceVolumeWithCliFallback,
2148
- isRailwayTransientFailure,
1713
+ findStaleTreeseedOperationsRunnerResources,
1714
+ isTreeseedOperationsRunnerResourceName,
2149
1715
  isUsableRailwayToken,
2150
- listRailwayServiceVolumesWithCli,
2151
1716
  parseRailwayJsonOutput,
2152
- planRailwayServiceDeploy,
2153
- planRailwayServiceLink,
2154
1717
  railwayServiceRuntimeStartCommand,
2155
1718
  resolveRailwayAuthToken,
2156
1719
  resolveRailwayDeploymentProfile,
2157
- runRailway,
2158
- setRailwaySecretVariable,
2159
1720
  shouldRunRailwayPredeployBuild,
2160
1721
  validateRailwayDeployPrerequisites,
2161
1722
  validateRailwayServiceConfiguration,
2162
1723
  verifyRailwayManagedResources,
2163
1724
  verifyRailwayScheduledJobs,
2164
- waitForRailwayManagedDeploymentsSettled,
2165
- writeRailwayCliProjectConfig
1725
+ waitForRailwayManagedDeploymentsSettled
2166
1726
  };