okengine 0.2.6 → 0.2.8

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 (584) hide show
  1. package/AGENTS.md +24 -23
  2. package/README.md +52 -346
  3. package/docs/spec/console.md +165 -142
  4. package/docs/spec/example.md +490 -312
  5. package/docs/spec/four-applications.md +495 -314
  6. package/docs/spec/unified-theory.md +184 -175
  7. package/package.json +45 -37
  8. package/spec/manifest.v1.schema.json +40 -9
  9. package/src/auth/api-keys.ts +2 -5
  10. package/src/auth/attenuation.ts +2 -6
  11. package/src/auth/auth.test.ts +53 -31
  12. package/src/auth/invoke-as.ts +1 -3
  13. package/src/auth/operator.ts +8 -26
  14. package/src/auth/planes.ts +2 -7
  15. package/src/auth/plugin.ts +4 -7
  16. package/src/auth/roles.ts +1 -5
  17. package/src/auth/sessions.ts +10 -39
  18. package/src/cli/args.ts +1 -5
  19. package/src/cli/ask-dev-mode.ts +40 -0
  20. package/src/cli/build.ts +1 -2
  21. package/src/cli/client-add.test.ts +8 -15
  22. package/src/cli/client-add.ts +5 -14
  23. package/src/cli/completion.test.ts +1 -5
  24. package/src/cli/completion.ts +4 -17
  25. package/src/cli/db-auto-push.test.ts +64 -0
  26. package/src/cli/db-auto-push.ts +72 -0
  27. package/src/cli/db.test.ts +189 -0
  28. package/src/cli/db.ts +386 -0
  29. package/src/cli/dev-app-runner.ts +2 -8
  30. package/src/cli/dev-db-push.test.ts +163 -0
  31. package/src/cli/dev-mode.test.ts +45 -0
  32. package/src/cli/dev-mode.ts +78 -0
  33. package/src/cli/dev.test.ts +148 -21
  34. package/src/cli/dev.ts +208 -134
  35. package/src/cli/doc-drift.test.ts +3 -2
  36. package/src/cli/doc-drift.ts +21 -50
  37. package/src/cli/doc-staleness.test.ts +69 -0
  38. package/src/cli/docker-cli.test.ts +7 -8
  39. package/src/cli/docker.ts +1 -3
  40. package/src/cli/doctor-diff-examples.ts +1 -3
  41. package/src/cli/doctor-diff.test.ts +1 -5
  42. package/src/cli/doctor-diff.ts +17 -46
  43. package/src/cli/doctor-pii.ts +2 -7
  44. package/src/cli/doctor.test.ts +26 -4
  45. package/src/cli/doctor.ts +35 -11
  46. package/src/cli/eval.ts +4 -14
  47. package/src/cli/gates-list.ts +1 -4
  48. package/src/cli/hero-meta.test.ts +17 -19
  49. package/src/cli/hero-meta.ts +20 -30
  50. package/src/cli/images.ts +3 -8
  51. package/src/cli/index.ts +11 -2
  52. package/src/cli/load-config.images.test.ts +14 -14
  53. package/src/cli/load-config.ts +41 -47
  54. package/src/cli/mcp-from-console.ts +1 -4
  55. package/src/cli/mode.ts +45 -0
  56. package/src/cli/ports.test.ts +4 -13
  57. package/src/cli/ports.ts +1 -3
  58. package/src/cli/privacy-erase.ts +1 -3
  59. package/src/cli/registry.ts +75 -25
  60. package/src/cli/schema.ts +3 -10
  61. package/src/cli/stack.ts +2 -7
  62. package/src/cli/start.ts +4 -15
  63. package/src/cli/upgrade.ts +1 -4
  64. package/src/client/budget-entry.ts +1 -6
  65. package/src/client/create.test.ts +6 -12
  66. package/src/client/create.ts +2 -8
  67. package/src/client/errors.ts +1 -4
  68. package/src/client/index.ts +1 -6
  69. package/src/client/notes-contract.test.ts +4 -13
  70. package/src/client/transport.ts +9 -30
  71. package/src/client/types.ts +8 -29
  72. package/src/compiler/aot.test.ts +2 -9
  73. package/src/compiler/aot.ts +1 -4
  74. package/src/compiler/differential.test.ts +3 -11
  75. package/src/compiler/dynamic.ts +1 -4
  76. package/src/compiler/effects-infer.ts +8 -34
  77. package/src/compiler/emit.ts +1 -3
  78. package/src/compiler/extract.test.ts +96 -30
  79. package/src/compiler/extract.ts +362 -156
  80. package/src/compiler/fixtures/skyport/oke.config.ts +1 -1
  81. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -3
  82. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -3
  83. package/src/compiler/fixtures/skyport/src/flows/support/index.ts +4 -1
  84. package/src/compiler/fixtures/skyport.expected.json +24 -83
  85. package/src/compiler/fixtures/triggers/five-triggers.ts +1 -2
  86. package/src/compiler/http-parse.ts +2 -9
  87. package/src/compiler/index.ts +1 -5
  88. package/src/compiler/response.ts +4 -7
  89. package/src/compiler/sucrose.ts +11 -31
  90. package/src/config/define-config.test.ts +57 -34
  91. package/src/config/index.ts +181 -37
  92. package/src/config/resolve-driver.test.ts +28 -17
  93. package/src/console/budget.test.ts +1 -4
  94. package/src/console/index.ts +1 -4
  95. package/src/console/server/access.test.ts +15 -43
  96. package/src/console/server/access.ts +19 -49
  97. package/src/console/server/ai.test.ts +3 -11
  98. package/src/console/server/ai.ts +23 -46
  99. package/src/console/server/app.ts +14 -45
  100. package/src/console/server/auth-rate.test.ts +4 -12
  101. package/src/console/server/bind.ts +1 -5
  102. package/src/console/server/channels.test.ts +2 -6
  103. package/src/console/server/channels.ts +10 -35
  104. package/src/console/server/claim.ts +2 -8
  105. package/src/console/server/clock.test.ts +2 -6
  106. package/src/console/server/clock.ts +8 -26
  107. package/src/console/server/console.test.ts +1 -3
  108. package/src/console/server/diff.test.ts +5 -17
  109. package/src/console/server/diff.ts +10 -31
  110. package/src/console/server/dry-run.audit.test.ts +2 -8
  111. package/src/console/server/flows.ts +64 -233
  112. package/src/console/server/gates.test.ts +14 -50
  113. package/src/console/server/gates.ts +13 -48
  114. package/src/console/server/index.ts +2 -9
  115. package/src/console/server/live.ts +3 -12
  116. package/src/console/server/operator-db.test.ts +4 -15
  117. package/src/console/server/operator-db.ts +13 -32
  118. package/src/console/server/plugin.ts +50 -53
  119. package/src/console/server/plugins.test.ts +2 -6
  120. package/src/console/server/plugins.ts +17 -56
  121. package/src/console/server/runs-pii.ts +2 -7
  122. package/src/console/server/security.gate.test.ts +18 -40
  123. package/src/console/server/serve.ts +10 -25
  124. package/src/console/server/signals.test.ts +1 -3
  125. package/src/console/server/signals.ts +17 -40
  126. package/src/console/server/state.ts +21 -59
  127. package/src/console/server/store.test.ts +20 -22
  128. package/src/console/server/store.ts +31 -76
  129. package/src/console/server/vault.test.ts +12 -29
  130. package/src/console/server/vault.ts +23 -60
  131. package/src/console/ui/access/AccessA11yView.tsx +6 -21
  132. package/src/console/ui/access/a11y.test.tsx +3 -12
  133. package/src/console/ui/access/acknowledgement.test.ts +1 -4
  134. package/src/console/ui/access/acknowledgement.ts +1 -3
  135. package/src/console/ui/access/blast-radius.ts +1 -3
  136. package/src/console/ui/access/confirmation.test.ts +1 -5
  137. package/src/console/ui/access/confirmation.ts +1 -4
  138. package/src/console/ui/access/fixture.ts +1 -5
  139. package/src/console/ui/access/grantable.test.ts +1 -3
  140. package/src/console/ui/access/grantable.ts +2 -7
  141. package/src/console/ui/access/index.ts +2 -9
  142. package/src/console/ui/access/provenance.test.ts +1 -3
  143. package/src/console/ui/access/provenance.ts +2 -6
  144. package/src/console/ui/access/search.test.ts +1 -5
  145. package/src/console/ui/access/search.ts +2 -6
  146. package/src/console/ui/ai/AiA11yView.tsx +6 -17
  147. package/src/console/ui/ai/a11y.test.tsx +2 -5
  148. package/src/console/ui/ai/fixture.ts +2 -7
  149. package/src/console/ui/ai/format.ts +1 -3
  150. package/src/console/ui/ai/group.test.ts +1 -3
  151. package/src/console/ui/ai/group.ts +4 -12
  152. package/src/console/ui/ai/index.ts +2 -9
  153. package/src/console/ui/ai/promotion.test.ts +2 -7
  154. package/src/console/ui/ai/promotion.ts +4 -13
  155. package/src/console/ui/ai/search.test.ts +2 -11
  156. package/src/console/ui/ai/search.ts +3 -13
  157. package/src/console/ui/ai/types.ts +1 -8
  158. package/src/console/ui/architecture/ArchitectureA11yView.tsx +8 -29
  159. package/src/console/ui/architecture/a11y.test.tsx +1 -2
  160. package/src/console/ui/architecture/declared.ts +1 -5
  161. package/src/console/ui/architecture/index.ts +7 -35
  162. package/src/console/ui/architecture/layout.ts +1 -3
  163. package/src/console/ui/architecture/pathologies.ts +10 -19
  164. package/src/console/ui/architecture/search.ts +6 -15
  165. package/src/console/ui/architecture/traffic.ts +4 -14
  166. package/src/console/ui/architecture/types.ts +2 -10
  167. package/src/console/ui/architecture/view.test.ts +7 -19
  168. package/src/console/ui/architecture/view.ts +9 -36
  169. package/src/console/ui/channels/ChannelsA11yView.tsx +4 -13
  170. package/src/console/ui/channels/a11y.test.tsx +1 -2
  171. package/src/console/ui/channels/confirmation.test.ts +1 -4
  172. package/src/console/ui/channels/confirmation.ts +1 -4
  173. package/src/console/ui/channels/findings.ts +1 -5
  174. package/src/console/ui/channels/index.ts +2 -8
  175. package/src/console/ui/channels/locale.test.ts +1 -3
  176. package/src/console/ui/channels/locale.ts +2 -8
  177. package/src/console/ui/channels/search.ts +4 -13
  178. package/src/console/ui/channels/taxonomy.ts +1 -3
  179. package/src/console/ui/clock/ClockA11yView.tsx +9 -26
  180. package/src/console/ui/clock/a11y.test.tsx +2 -5
  181. package/src/console/ui/clock/confirmation.test.ts +1 -3
  182. package/src/console/ui/clock/confirmation.ts +1 -4
  183. package/src/console/ui/clock/findings.ts +1 -4
  184. package/src/console/ui/clock/health.ts +2 -7
  185. package/src/console/ui/clock/index.ts +2 -10
  186. package/src/console/ui/clock/search.test.ts +1 -3
  187. package/src/console/ui/clock/search.ts +2 -6
  188. package/src/console/ui/clock/timeline.test.ts +1 -3
  189. package/src/console/ui/clock/waiting-on.test.ts +1 -5
  190. package/src/console/ui/clock/waiting-on.ts +2 -7
  191. package/src/console/ui/diff/DiffA11yView.tsx +8 -17
  192. package/src/console/ui/diff/a11y.test.tsx +2 -5
  193. package/src/console/ui/diff/group.test.ts +3 -13
  194. package/src/console/ui/diff/group.ts +2 -8
  195. package/src/console/ui/diff/index.ts +2 -8
  196. package/src/console/ui/diff/search.test.ts +1 -5
  197. package/src/console/ui/diff/search.ts +4 -16
  198. package/src/console/ui/diff/summary.ts +1 -2
  199. package/src/console/ui/dist/assets/{index-Dy4jht9P.js → index-BWo8R7NR.js} +2 -2
  200. package/src/console/ui/dist/assets/{panel-access-Dd37LU2c.js → panel-access-C0J2D-a2.js} +1 -1
  201. package/src/console/ui/dist/assets/{panel-ai-CC7LR6-J.js → panel-ai-D_m6WQI8.js} +1 -1
  202. package/src/console/ui/dist/assets/{panel-architecture-B5b3iKCz.js → panel-architecture-CKnXFyUx.js} +1 -1
  203. package/src/console/ui/dist/assets/{panel-channels-CeNjTKXp.js → panel-channels-BOmQ-onL.js} +1 -1
  204. package/src/console/ui/dist/assets/{panel-clock-DgFTLoHV.js → panel-clock-giAq0Ccv.js} +1 -1
  205. package/src/console/ui/dist/assets/{panel-diff-DxehccqB.js → panel-diff-cdonmH8c.js} +1 -1
  206. package/src/console/ui/dist/assets/{panel-flows-BtrVn-Eg.js → panel-flows-DlCU5zjA.js} +2 -2
  207. package/src/console/ui/dist/assets/{panel-gates-Z9MKRGdH.js → panel-gates-XclZxWD5.js} +1 -1
  208. package/src/console/ui/dist/assets/{panel-overview-Dt_AeXgd.js → panel-overview-BznEOTnb.js} +1 -1
  209. package/src/console/ui/dist/assets/{panel-plugins-DWd0TowH.js → panel-plugins-CcGM1g64.js} +1 -1
  210. package/src/console/ui/dist/assets/{panel-runs-DGstFHeq.js → panel-runs-CGWNHLR4.js} +1 -1
  211. package/src/console/ui/dist/assets/{panel-signals-DzEa2Fnt.js → panel-signals-CNywkdak.js} +1 -1
  212. package/src/console/ui/dist/assets/{panel-store-BJkbNgxx.js → panel-store-KmTbFHMH.js} +1 -1
  213. package/src/console/ui/dist/assets/{panel-traces-BaRVO3gM.js → panel-traces-DBLx2ilD.js} +1 -1
  214. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +1 -0
  215. package/src/console/ui/dist/index.html +1 -1
  216. package/src/console/ui/flows/FlowsA11yView.tsx +7 -22
  217. package/src/console/ui/flows/a11y.test.tsx +2 -8
  218. package/src/console/ui/flows/buffer.test.ts +3 -11
  219. package/src/console/ui/flows/buffer.ts +4 -11
  220. package/src/console/ui/flows/confirmation.ts +1 -3
  221. package/src/console/ui/flows/contract.test.ts +3 -7
  222. package/src/console/ui/flows/contract.ts +12 -36
  223. package/src/console/ui/flows/graph.test.ts +3 -11
  224. package/src/console/ui/flows/graph.ts +5 -15
  225. package/src/console/ui/flows/index.ts +1 -6
  226. package/src/console/ui/flows/save-as-test.ts +1 -3
  227. package/src/console/ui/flows/search.ts +8 -25
  228. package/src/console/ui/flows/tiers.ts +2 -9
  229. package/src/console/ui/gates/GatesA11yView.tsx +4 -13
  230. package/src/console/ui/gates/a11y.test.tsx +2 -5
  231. package/src/console/ui/gates/audit.ts +1 -3
  232. package/src/console/ui/gates/denial.test.ts +1 -3
  233. package/src/console/ui/gates/denial.ts +4 -12
  234. package/src/console/ui/gates/findings.ts +1 -3
  235. package/src/console/ui/gates/group.test.ts +3 -3
  236. package/src/console/ui/gates/group.ts +3 -11
  237. package/src/console/ui/gates/index.ts +2 -10
  238. package/src/console/ui/gates/search.ts +3 -10
  239. package/src/console/ui/overview/OverviewA11yView.tsx +9 -27
  240. package/src/console/ui/overview/a11y.test.tsx +1 -2
  241. package/src/console/ui/overview/busiest.ts +1 -3
  242. package/src/console/ui/overview/compose.ts +1 -13
  243. package/src/console/ui/overview/cost.ts +34 -38
  244. package/src/console/ui/overview/fixture.ts +21 -26
  245. package/src/console/ui/overview/golden.ts +4 -14
  246. package/src/console/ui/overview/index.ts +2 -9
  247. package/src/console/ui/overview/rank.test.ts +3 -10
  248. package/src/console/ui/overview/rank.ts +2 -7
  249. package/src/console/ui/overview/slo.test.ts +5 -9
  250. package/src/console/ui/overview/slo.ts +6 -23
  251. package/src/console/ui/overview/verdict.ts +3 -10
  252. package/src/console/ui/plugins/PluginsA11yView.tsx +8 -18
  253. package/src/console/ui/plugins/a11y.test.tsx +1 -2
  254. package/src/console/ui/plugins/fixture.ts +2 -4
  255. package/src/console/ui/plugins/group.test.ts +1 -3
  256. package/src/console/ui/plugins/group.ts +1 -6
  257. package/src/console/ui/plugins/index.ts +3 -13
  258. package/src/console/ui/plugins/search.ts +7 -19
  259. package/src/console/ui/runs/RunsA11yView.tsx +5 -20
  260. package/src/console/ui/runs/a11y.test.tsx +2 -5
  261. package/src/console/ui/runs/explain.ts +2 -6
  262. package/src/console/ui/runs/group.ts +3 -12
  263. package/src/console/ui/runs/histogram.test.ts +2 -11
  264. package/src/console/ui/runs/histogram.ts +1 -4
  265. package/src/console/ui/runs/index.ts +4 -18
  266. package/src/console/ui/runs/project.ts +2 -6
  267. package/src/console/ui/runs/query.test.ts +1 -3
  268. package/src/console/ui/runs/query.ts +12 -35
  269. package/src/console/ui/runs/search.ts +5 -19
  270. package/src/console/ui/runs/trace-link.test.ts +2 -6
  271. package/src/console/ui/runs/trace-link.ts +4 -16
  272. package/src/console/ui/runs/types.ts +1 -3
  273. package/src/console/ui/shell/App.tsx +2 -10
  274. package/src/console/ui/shell/client.ts +22 -115
  275. package/src/console/ui/shell/components/ui.tsx +1 -4
  276. package/src/console/ui/shell/layout/Shell.tsx +2 -4
  277. package/src/console/ui/shell/main.tsx +31 -96
  278. package/src/console/ui/shell/panels/access/AccessPanel.tsx +34 -98
  279. package/src/console/ui/shell/panels/ai/AiPanel.tsx +35 -106
  280. package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +18 -66
  281. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +20 -57
  282. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +22 -54
  283. package/src/console/ui/shell/panels/diff/DiffPanel.tsx +20 -61
  284. package/src/console/ui/shell/panels/flows/ContractEditor.tsx +6 -23
  285. package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +13 -28
  286. package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +9 -29
  287. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +24 -80
  288. package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +20 -58
  289. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +16 -41
  290. package/src/console/ui/shell/panels/runs/RunsPanel.tsx +31 -99
  291. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +38 -101
  292. package/src/console/ui/shell/panels/store/StorePanel.tsx +27 -66
  293. package/src/console/ui/shell/panels/traces/TracesPanel.tsx +28 -89
  294. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +22 -53
  295. package/src/console/ui/shell/setup/Wizard.tsx +3 -5
  296. package/src/console/ui/shell/styles.css +1 -2
  297. package/src/console/ui/signals/SignalsA11yView.tsx +9 -33
  298. package/src/console/ui/signals/a11y.test.tsx +2 -5
  299. package/src/console/ui/signals/confirmation.test.ts +1 -5
  300. package/src/console/ui/signals/confirmation.ts +2 -6
  301. package/src/console/ui/signals/dry-run.ts +1 -3
  302. package/src/console/ui/signals/durable.ts +1 -3
  303. package/src/console/ui/signals/findings.ts +1 -3
  304. package/src/console/ui/signals/group.test.ts +2 -8
  305. package/src/console/ui/signals/group.ts +2 -10
  306. package/src/console/ui/signals/index.ts +1 -4
  307. package/src/console/ui/signals/monitor.ts +2 -7
  308. package/src/console/ui/signals/schema-form.test.ts +3 -14
  309. package/src/console/ui/signals/schema-form.ts +1 -4
  310. package/src/console/ui/signals/search.ts +4 -13
  311. package/src/console/ui/store/StoreA11yView.tsx +3 -9
  312. package/src/console/ui/store/a11y.test.tsx +2 -5
  313. package/src/console/ui/store/confirmation.test.ts +1 -3
  314. package/src/console/ui/store/confirmation.ts +1 -4
  315. package/src/console/ui/store/dry-run.ts +2 -5
  316. package/src/console/ui/store/fixture.ts +1 -2
  317. package/src/console/ui/store/group.test.ts +1 -6
  318. package/src/console/ui/store/group.ts +1 -4
  319. package/src/console/ui/store/index.ts +1 -5
  320. package/src/console/ui/store/search.ts +2 -6
  321. package/src/console/ui/store/will-not-fire.ts +1 -2
  322. package/src/console/ui/traces/TracesA11yView.tsx +10 -29
  323. package/src/console/ui/traces/a11y.test.tsx +1 -2
  324. package/src/console/ui/traces/chain.test.ts +5 -19
  325. package/src/console/ui/traces/chain.ts +1 -3
  326. package/src/console/ui/traces/critical-path.test.ts +1 -3
  327. package/src/console/ui/traces/critical-path.ts +2 -6
  328. package/src/console/ui/traces/filter.test.ts +2 -6
  329. package/src/console/ui/traces/filter.ts +5 -13
  330. package/src/console/ui/traces/fold.test.ts +4 -12
  331. package/src/console/ui/traces/fold.ts +1 -3
  332. package/src/console/ui/traces/mini.ts +1 -3
  333. package/src/console/ui/traces/replay.test.ts +1 -3
  334. package/src/console/ui/traces/replay.ts +1 -3
  335. package/src/console/ui/traces/sampling.ts +1 -4
  336. package/src/console/ui/traces/search.ts +4 -17
  337. package/src/console/ui/traces/tier.ts +1 -3
  338. package/src/console/ui/vault/VaultA11yView.tsx +5 -17
  339. package/src/console/ui/vault/a11y.test.tsx +1 -2
  340. package/src/console/ui/vault/blast-radius.ts +1 -3
  341. package/src/console/ui/vault/confirmation.test.ts +1 -5
  342. package/src/console/ui/vault/confirmation.ts +1 -4
  343. package/src/console/ui/vault/dormant.test.ts +1 -4
  344. package/src/console/ui/vault/export-safe.test.ts +1 -5
  345. package/src/console/ui/vault/export-safe.ts +1 -4
  346. package/src/console/ui/vault/fixture.ts +3 -3
  347. package/src/console/ui/vault/group.ts +1 -2
  348. package/src/console/ui/vault/index.ts +2 -10
  349. package/src/console/ui/vault/search.ts +2 -6
  350. package/src/console/ui/vault/types.ts +1 -1
  351. package/src/console/xss.gate.test.ts +1 -3
  352. package/src/docker/compose.ts +112 -20
  353. package/src/docker/derive.ts +5 -19
  354. package/src/docker/docker.test.ts +15 -15
  355. package/src/docker/dockerfile.integration.test.ts +12 -21
  356. package/src/docker/helpers.ts +5 -4
  357. package/src/docker/index.ts +4 -11
  358. package/src/docker/pin.ts +1 -9
  359. package/src/docker/recipes/index.ts +5 -6
  360. package/src/docker/recipes/mailpit.ts +22 -0
  361. package/src/docker/recipes/postgres.ts +17 -6
  362. package/src/docker/recipes/redis.ts +6 -1
  363. package/src/docker/recipes/rustfs.ts +33 -0
  364. package/src/docker/stack-id.test.ts +33 -0
  365. package/src/docker/stack-id.ts +25 -13
  366. package/src/docker/stack.integration.test.ts +6 -21
  367. package/src/docker/types.ts +13 -4
  368. package/src/drivers/ai-anthropic.ts +5 -14
  369. package/src/drivers/ai-mock.ts +2 -5
  370. package/src/drivers/ai-openai-compatible.ts +4 -10
  371. package/src/drivers/ai-providers.test.ts +6 -13
  372. package/src/drivers/bun-native-completeness.test.ts +6 -15
  373. package/src/drivers/channel-console.ts +6 -7
  374. package/src/drivers/channel-fcm.ts +20 -30
  375. package/src/drivers/channel-resend.ts +1 -3
  376. package/src/drivers/channel-smtp.ts +2 -6
  377. package/src/drivers/channel-types.ts +2 -12
  378. package/src/drivers/channel-unifonic.ts +2 -6
  379. package/src/drivers/channel-wa-cloud.ts +2 -6
  380. package/src/drivers/channel-webpush.ts +21 -60
  381. package/src/drivers/conformance.test.ts +2 -10
  382. package/src/drivers/conformance.ts +8 -21
  383. package/src/drivers/fs.ts +2 -8
  384. package/src/drivers/index.ts +12 -56
  385. package/src/drivers/memory.ts +14 -41
  386. package/src/drivers/pgvector.ts +7 -11
  387. package/src/drivers/postgres.ts +7 -13
  388. package/src/drivers/redis.ts +13 -37
  389. package/src/drivers/s3.ts +5 -17
  390. package/src/drivers/signal-engine.ts +12 -49
  391. package/src/drivers/signal-memory.ts +2 -8
  392. package/src/drivers/signal-nats.ts +4 -9
  393. package/src/drivers/signal-postgres.ts +62 -129
  394. package/src/drivers/signal-redis.ts +7 -21
  395. package/src/drivers/signal-types.ts +1 -4
  396. package/src/drivers/sqlite.ts +3 -12
  397. package/src/drivers/types.ts +4 -23
  398. package/src/drivers/vault-env.ts +2 -6
  399. package/src/drivers/vault-infisical.ts +1 -5
  400. package/src/drivers/vault-managed.ts +1 -5
  401. package/src/drivers/vault-memory.ts +1 -5
  402. package/src/drivers/vault-openbao.ts +1 -5
  403. package/src/drivers/vault-sops.ts +10 -35
  404. package/src/drivers/vault-types.ts +1 -7
  405. package/src/elements/ai/declare.ts +4 -14
  406. package/src/elements/ai/eval.ts +2 -6
  407. package/src/elements/ai/pii.ts +1 -3
  408. package/src/elements/ai/runtime.ts +12 -50
  409. package/src/elements/ai/schema.ts +4 -15
  410. package/src/elements/ai.test.ts +13 -24
  411. package/src/elements/ai.ts +1 -5
  412. package/src/elements/channel/consent.ts +3 -12
  413. package/src/elements/channel/costs.ts +4 -15
  414. package/src/elements/channel/declare.ts +1 -4
  415. package/src/elements/channel/email-auth.ts +1 -4
  416. package/src/elements/channel/locale.ts +2 -10
  417. package/src/elements/channel/mask.ts +1 -3
  418. package/src/elements/channel/mime.ts +2 -12
  419. package/src/elements/channel/outcomes.test.ts +1 -5
  420. package/src/elements/channel/outcomes.ts +5 -15
  421. package/src/elements/channel/receipts.ts +2 -7
  422. package/src/elements/channel/runtime.ts +28 -74
  423. package/src/elements/channel/suppression.ts +8 -14
  424. package/src/elements/channel.test.ts +8 -31
  425. package/src/elements/channel.ts +3 -14
  426. package/src/elements/clock/actions.ts +6 -18
  427. package/src/elements/clock/declare.ts +1 -3
  428. package/src/elements/clock/dst.ts +1 -5
  429. package/src/elements/clock/durable.ts +4 -16
  430. package/src/elements/clock/health.ts +2 -7
  431. package/src/elements/clock/leader.ts +2 -9
  432. package/src/elements/clock/reconcile.ts +4 -15
  433. package/src/elements/clock/runtime.ts +2 -5
  434. package/src/elements/clock/schedule.ts +4 -20
  435. package/src/elements/clock.test.ts +2 -7
  436. package/src/elements/clock.ts +5 -23
  437. package/src/elements/gate/permissions.ts +1 -4
  438. package/src/elements/gate/runtime.ts +7 -28
  439. package/src/elements/gate.ts +1 -5
  440. package/src/elements/index.ts +9 -7
  441. package/src/elements/signal/declare.ts +2 -7
  442. package/src/elements/signal/reconcile.test.ts +1 -4
  443. package/src/elements/signal/runtime.ts +1 -3
  444. package/src/elements/signal.test.ts +3 -15
  445. package/src/elements/signal.ts +2 -8
  446. package/src/elements/store/cache.test.ts +46 -0
  447. package/src/elements/store/cache.ts +3 -11
  448. package/src/elements/store/classify.ts +5 -22
  449. package/src/elements/store/declare.ts +8 -17
  450. package/src/elements/store/domain-ddl.test.ts +86 -0
  451. package/src/elements/store/emit-drizzle.ts +363 -0
  452. package/src/elements/store/files-policy.test.ts +1 -5
  453. package/src/elements/store/files-policy.ts +4 -12
  454. package/src/elements/store/load-plugin-tables.ts +103 -0
  455. package/src/elements/store/missing-relation.test.ts +83 -0
  456. package/src/elements/store/missing-relation.ts +38 -0
  457. package/src/elements/store/replica.ts +1 -4
  458. package/src/elements/store/runtime.ts +25 -28
  459. package/src/elements/store/schema-decl.test.ts +227 -0
  460. package/src/elements/store/schema-decl.ts +516 -0
  461. package/src/elements/store/sql-condition.ts +11 -21
  462. package/src/elements/store/sql-session.test.ts +50 -0
  463. package/src/elements/store/sql-session.ts +60 -63
  464. package/src/elements/store/table.ts +35 -19
  465. package/src/elements/store.test.ts +17 -39
  466. package/src/elements/store.ts +37 -31
  467. package/src/elements/vault/chain.ts +34 -0
  468. package/src/elements/vault/declare.ts +22 -32
  469. package/src/elements/vault/fingerprint.ts +2 -7
  470. package/src/elements/vault/redact.ts +2 -6
  471. package/src/elements/vault/runtime.ts +18 -32
  472. package/src/elements/vault.test.ts +12 -28
  473. package/src/elements/vault.ts +8 -12
  474. package/src/index.ts +5 -2
  475. package/src/kernel/adopt-routes.ts +15 -29
  476. package/src/kernel/app.ts +34 -94
  477. package/src/kernel/auth-resolve.ts +2 -9
  478. package/src/kernel/boot-bind/ai.ts +1 -4
  479. package/src/kernel/boot-bind/channel.ts +2 -8
  480. package/src/kernel/boot-bind/clock.ts +2 -6
  481. package/src/kernel/boot-bind/gate.ts +2 -8
  482. package/src/kernel/boot-bind/runs.ts +1 -3
  483. package/src/kernel/boot-bind/signal.ts +3 -10
  484. package/src/kernel/boot-bind/store.test.ts +17 -17
  485. package/src/kernel/boot-bind/store.ts +44 -50
  486. package/src/kernel/boot-bind/vault.ts +2 -8
  487. package/src/kernel/boot.test.ts +4 -9
  488. package/src/kernel/boot.ts +29 -76
  489. package/src/kernel/budget.test.ts +1 -3
  490. package/src/kernel/capability.ts +3 -10
  491. package/src/kernel/dry-run.test.ts +1 -4
  492. package/src/kernel/dry-run.ts +3 -11
  493. package/src/kernel/effects.test.ts +1 -5
  494. package/src/kernel/effects.ts +4 -11
  495. package/src/kernel/errors.registry.test.ts +2 -8
  496. package/src/kernel/errors.ts +23 -32
  497. package/src/kernel/flow.test.ts +12 -25
  498. package/src/kernel/flow.ts +5 -25
  499. package/src/kernel/fx.test.ts +15 -44
  500. package/src/kernel/fx.ts +27 -79
  501. package/src/kernel/hook-timing.test.ts +3 -6
  502. package/src/kernel/hook-timing.ts +5 -14
  503. package/src/kernel/hooks.test.ts +10 -10
  504. package/src/kernel/hooks.ts +3 -12
  505. package/src/kernel/index.ts +4 -17
  506. package/src/kernel/journal.ts +7 -31
  507. package/src/kernel/on.ts +1 -6
  508. package/src/kernel/pipeline.test.ts +10 -19
  509. package/src/kernel/pipeline.ts +5 -17
  510. package/src/kernel/plug.ts +4 -11
  511. package/src/kernel/plugin/conflicts.test.ts +13 -25
  512. package/src/kernel/plugin/decorate.test.ts +8 -13
  513. package/src/kernel/plugin/scoping.test.ts +4 -11
  514. package/src/kernel/plugin.ts +48 -24
  515. package/src/kernel/registry.ts +80 -29
  516. package/src/kernel/router.test.ts +15 -13
  517. package/src/kernel/router.ts +9 -29
  518. package/src/kernel/routing-budget.test.ts +1 -3
  519. package/src/kernel/run-telemetry.ts +1 -3
  520. package/src/kernel/triggers.ts +4 -21
  521. package/src/manifest/diff.test.ts +26 -91
  522. package/src/manifest/diff.ts +95 -225
  523. package/src/manifest/index.ts +2 -9
  524. package/src/manifest/types.ts +29 -5
  525. package/src/manifest/undeclared.test.ts +4 -12
  526. package/src/manifest/undeclared.ts +1 -2
  527. package/src/manifest/validate.test.ts +2 -6
  528. package/src/manifest/validate.ts +4 -14
  529. package/src/mcp/authorization.ts +8 -27
  530. package/src/mcp/confirmation.ts +3 -10
  531. package/src/mcp/data.ts +2 -6
  532. package/src/mcp/docs-index.ts +4 -7
  533. package/src/mcp/docs-mcp.test.ts +3 -12
  534. package/src/mcp/docs-server.ts +7 -27
  535. package/src/mcp/docs-tools.ts +2 -6
  536. package/src/mcp/index.ts +1 -6
  537. package/src/mcp/injection.gate.test.ts +4 -18
  538. package/src/mcp/mcp.test.ts +18 -49
  539. package/src/mcp/protocol.ts +3 -10
  540. package/src/mcp/server.ts +11 -52
  541. package/src/mcp/session.ts +4 -14
  542. package/src/mcp/tools.ts +14 -50
  543. package/src/plugins/catalogue.test.ts +3 -7
  544. package/src/plugins/catalogue.ts +1 -3
  545. package/src/plugins/node-import-scan.ts +2 -5
  546. package/src/plugins/supply-chain.ts +5 -17
  547. package/src/release/exports.ts +3 -8
  548. package/src/release/measure.exports.test.ts +1 -3
  549. package/src/release/measure.ts +15 -43
  550. package/src/runs/bench.test.ts +1 -3
  551. package/src/runs/collect.ts +8 -25
  552. package/src/runs/drivers/clickhouse.ts +2 -9
  553. package/src/runs/drivers/files.ts +9 -31
  554. package/src/runs/drivers/memory.ts +5 -22
  555. package/src/runs/drivers/postgres.ts +8 -20
  556. package/src/runs/duckdb.ts +2 -8
  557. package/src/runs/index.ts +3 -14
  558. package/src/runs/outlier.ts +3 -10
  559. package/src/runs/parquet.ts +8 -26
  560. package/src/runs/privacy.ts +2 -8
  561. package/src/runs/runs.test.ts +3 -9
  562. package/src/runs/runtime.ts +7 -32
  563. package/src/runs/shred.ts +10 -29
  564. package/src/runs/types.ts +1 -4
  565. package/src/runtime/bun.ts +4 -15
  566. package/src/runtime/cold-start.bench.ts +1 -3
  567. package/src/runtime/dev-request-log.test.ts +1 -4
  568. package/src/runtime/dev-request-log.ts +1 -4
  569. package/src/runtime/index.ts +1 -6
  570. package/src/runtime/primitives.ts +5 -4
  571. package/src/runtime/security.ts +3 -13
  572. package/src/runtime/serve.test.ts +4 -7
  573. package/src/runtime/types.ts +1 -4
  574. package/src/runtime/web-standard.ts +3 -15
  575. package/src/term.test.ts +4 -6
  576. package/src/term.ts +49 -98
  577. package/src/test/create-test-app.test.ts +4 -14
  578. package/src/test/create-test-app.ts +13 -31
  579. package/src/test/provisions.integration.test.ts +3 -11
  580. package/src/upgrade/codemods.test.ts +25 -6
  581. package/src/upgrade/codemods.ts +116 -3
  582. package/src/validation/standard-schema.test.ts +2 -5
  583. package/src/validation/standard-schema.ts +13 -33
  584. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +0 -1
@@ -7,12 +7,7 @@
7
7
  * 4. `compose.override.yml` — user-owned; oke never writes it
8
8
  */
9
9
 
10
- import {
11
- defaultHostPort,
12
- envPrefix,
13
- serviceNameFor,
14
- toYaml,
15
- } from "./helpers.ts";
10
+ import { defaultHostPort, envPrefix, serviceNameFor, toYaml } from "./helpers.ts";
16
11
  import { recipeFor } from "./recipes/index.ts";
17
12
  import type {
18
13
  DeriveOptions,
@@ -23,7 +18,7 @@ import type {
23
18
  } from "./types.ts";
24
19
  import { DEFAULT_DOCKER_DIR } from "./types.ts";
25
20
  import { generateCredentials } from "./credentials.ts";
26
- import { hostPortForInstance } from "./stack-id.ts";
21
+ import { hostPortForInstance, instancePortOffset } from "./stack-id.ts";
27
22
  import { APP_PORT } from "../runtime/types.ts";
28
23
 
29
24
  /** Canonical layer-4 filename — never written by derivation. */
@@ -41,7 +36,7 @@ export function composePathRefs(composeDir: string = DEFAULT_DOCKER_DIR): {
41
36
  } {
42
37
  const flat = composeDir === "." || composeDir === "";
43
38
  return {
44
- envFile: flat ? ".env.stack" : "../.env.stack",
39
+ envFile: ".env.docker",
45
40
  buildContext: flat ? "." : "..",
46
41
  dockerfile: "Dockerfile",
47
42
  };
@@ -57,8 +52,7 @@ export function buildSpecs(options: DeriveOptions): ServiceSpec[] {
57
52
  const specs: ServiceSpec[] = [];
58
53
  for (const [role, image] of Object.entries(options.images)) {
59
54
  const recipe = recipeFor(image, recipes);
60
- const creds =
61
- options.credentials?.[role] ?? generateCredentials(role);
55
+ const creds = options.credentials?.[role] ?? generateCredentials(role);
62
56
  const port = recipe.port;
63
57
  const hostPort = options.instanceId
64
58
  ? hostPortForInstance(role, port, options.instanceId)
@@ -116,12 +110,17 @@ export function emitComposeLayers(
116
110
  files.push({ path: "compose.yml", content: `${toYaml(base)}\n` });
117
111
 
118
112
  // Layer 2 — per-role
113
+ const extraHostOffset = options.instanceId ? instancePortOffset(options.instanceId) : 0;
119
114
  for (const spec of specs) {
120
115
  const recipe = recipeFor(spec.image, recipes);
121
116
  const applied = recipe.apply(spec);
117
+ const ports = [
118
+ `${spec.hostPort}:${spec.port}`,
119
+ ...(applied.extraPorts ?? []).map((p) => `${p.host + extraHostOffset}:${p.container}`),
120
+ ];
122
121
  const service: Record<string, unknown> = {
123
122
  image: spec.image,
124
- ports: [`${spec.hostPort}:${spec.port}`],
123
+ ports,
125
124
  networks: ["oke"],
126
125
  env_file: [paths.envFile],
127
126
  };
@@ -131,10 +130,14 @@ export function emitComposeLayers(
131
130
  if (applied.volumes) service.volumes = applied.volumes;
132
131
  if (applied.user) service.user = applied.user;
133
132
 
134
- const doc = {
133
+ const namedVolumes = namedVolumeDecls(applied.volumes);
134
+ const doc: Record<string, unknown> = {
135
135
  services: { [spec.serviceName]: service },
136
136
  networks: { oke: { external: false } },
137
137
  };
138
+ if (Object.keys(namedVolumes).length > 0) {
139
+ doc.volumes = namedVolumes;
140
+ }
138
141
  const path = `compose.${spec.role}.yml`;
139
142
  files.push({ path, content: `${toYaml(doc)}\n` });
140
143
  }
@@ -181,7 +184,7 @@ export function emitComposeLayers(
181
184
  }
182
185
 
183
186
  /**
184
- * Build `.env.stack` key/value map (credentials + connection URLs).
187
+ * Build docker env key/value map (credentials + connection URLs).
185
188
  *
186
189
  * @param specs - Services
187
190
  * @param recipes - Extra recipes
@@ -214,17 +217,89 @@ export function buildStackEnv(
214
217
  return env;
215
218
  }
216
219
 
220
+ /** Friendly section titles for known compose roles. */
221
+ const ROLE_SECTION_TITLE: Readonly<Record<string, string>> = {
222
+ "store.sql": "store.sql — Postgres",
223
+ "store.kv": "store.kv — Redis",
224
+ "store.files": "store.files — object storage (S3)",
225
+ "store.index": "store.index — search index",
226
+ "channel.email": "channel.email — Mailpit (SMTP + UI)",
227
+ signal: "signal — message bus",
228
+ };
229
+
230
+ /** Friendly aliases emitted beside their role block. */
231
+ const ROLE_ALIASES: Readonly<Record<string, readonly string[]>> = {
232
+ "store.sql": ["DATABASE_URL"],
233
+ "store.kv": ["REDIS_URL"],
234
+ };
235
+
236
+ /**
237
+ * Infer role from an `OKE_<ROLE>_*` key (`OKE_STORE_SQL_URL` → `store.sql`).
238
+ *
239
+ * @param key - Env key
240
+ */
241
+ function roleFromEnvKey(key: string): string | undefined {
242
+ const m = /^OKE_(.+)_(USER|PASSWORD|DB|URL)$/.exec(key);
243
+ if (!m) return undefined;
244
+ return m[1]!.toLowerCase().replaceAll("_", ".");
245
+ }
246
+
217
247
  /**
218
- * Serialise stack env to dotenv text.
248
+ * Serialise stack env to commented, role-grouped dotenv text.
219
249
  *
220
- * @param env - Key/value map
250
+ * @param env - Key/value map from {@link buildStackEnv}
221
251
  */
222
252
  export function formatStackEnv(env: Readonly<Record<string, string>>): string {
223
- return (
224
- Object.entries(env)
225
- .map(([k, v]) => `${k}=${escapeEnv(v)}`)
226
- .join("\n") + "\n"
227
- );
253
+ const roles = new Set<string>();
254
+ for (const key of Object.keys(env)) {
255
+ const role = roleFromEnvKey(key);
256
+ if (role) roles.add(role);
257
+ }
258
+ const ordered = [...roles].sort((a, b) => a.localeCompare(b));
259
+ const used = new Set<string>();
260
+ const lines: string[] = [
261
+ "# docker/.env.docker — generated by `oke dev --docker`",
262
+ "# Credentials + connection URLs for the local compose stack.",
263
+ "# Host ports are unique per project (instance offset). Do not commit.",
264
+ "# Vault resolution: process.env → .env.local → docker/.env.docker → driver",
265
+ "#",
266
+ "# Re-run `oke dev --docker` to regenerate. Put compose tweaks in",
267
+ "# compose.override.yml — never put secrets in YAML.",
268
+ "",
269
+ ];
270
+
271
+ for (const role of ordered) {
272
+ const prefix = envPrefix(role);
273
+ const title = ROLE_SECTION_TITLE[role] ?? role;
274
+ lines.push(`# ── ${title} ${"─".repeat(Math.max(4, 56 - title.length))}`);
275
+ for (const field of ["USER", "PASSWORD", "DB", "URL"] as const) {
276
+ const key = `${prefix}_${field}`;
277
+ const value = env[key];
278
+ if (value === undefined) continue;
279
+ lines.push(`${key}=${escapeEnv(value)}`);
280
+ used.add(key);
281
+ }
282
+ for (const alias of ROLE_ALIASES[role] ?? []) {
283
+ const value = env[alias];
284
+ if (value === undefined) continue;
285
+ lines.push(`${alias}=${escapeEnv(value)}`);
286
+ used.add(alias);
287
+ }
288
+ lines.push("");
289
+ }
290
+
291
+ const leftover = Object.keys(env)
292
+ .filter((k) => !used.has(k))
293
+ .sort((a, b) => a.localeCompare(b));
294
+ if (leftover.length > 0) {
295
+ lines.push(`# ── other ${"─".repeat(50)}`);
296
+ for (const key of leftover) {
297
+ lines.push(`${key}=${escapeEnv(env[key]!)}`);
298
+ }
299
+ lines.push("");
300
+ }
301
+
302
+ return `${lines.join("\n")}\n`;
228
303
  }
229
304
 
230
305
  /**
@@ -249,6 +324,23 @@ function secretNames(spec: ServiceSpec): string[] {
249
324
  return [`${p}_PASSWORD`];
250
325
  }
251
326
 
327
+ /**
328
+ * Collect named volume declarations from recipe volume mounts (`name:/path`).
329
+ *
330
+ * @param volumes - Compose volume strings
331
+ */
332
+ function namedVolumeDecls(
333
+ volumes: readonly string[] | undefined,
334
+ ): Record<string, Record<string, never>> {
335
+ const out: Record<string, Record<string, never>> = {};
336
+ for (const v of volumes ?? []) {
337
+ if (v.startsWith(".") || v.startsWith("/") || v.startsWith("~")) continue;
338
+ const name = v.split(":")[0];
339
+ if (name) out[name] = {};
340
+ }
341
+ return out;
342
+ }
343
+
252
344
  function escapeEnv(value: string): string {
253
345
  if (/[\s#"'$\\]/.test(value)) {
254
346
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
@@ -17,7 +17,7 @@ import { DEFAULT_DOCKER_DIR } from "./types.ts";
17
17
  /**
18
18
  * Derive infrastructure files from normalised image pins.
19
19
  *
20
- * Credentials land only in the returned `stackEnv` (for `.env.stack`);
20
+ * Credentials land only in the returned `stackEnv` (for `.env.docker`);
21
21
  * generated YAML never contains cleartext secrets. Layer 4
22
22
  * (`compose.override.yml`) is listed in `composeFiles` but never written.
23
23
  *
@@ -37,10 +37,7 @@ export function deriveInfrastructure(options: DeriveOptions): DeriveResult {
37
37
  };
38
38
 
39
39
  const specs = buildSpecs(normalised);
40
- const { files: composeFilesContent, composeFiles } = emitComposeLayers(
41
- specs,
42
- normalised,
43
- );
40
+ const { files: composeFilesContent, composeFiles } = emitComposeLayers(specs, normalised);
44
41
  const dockerfile: GeneratedFile = {
45
42
  path: "Dockerfile",
46
43
  content: emitDockerfile({ appPort: normalised.appPort }),
@@ -57,18 +54,14 @@ export function deriveInfrastructure(options: DeriveOptions): DeriveResult {
57
54
  }
58
55
  }
59
56
 
60
- const stackEnv = buildStackEnv(
61
- specs,
62
- normalised.recipes ?? [],
63
- normalised.host ?? "127.0.0.1",
64
- );
57
+ const stackEnv = buildStackEnv(specs, normalised.recipes ?? [], normalised.host ?? "127.0.0.1");
65
58
 
66
59
  return { specs, files, stackEnv, composeFiles };
67
60
  }
68
61
 
69
62
  /**
70
63
  * Write derived files to disk. Never writes `compose.override.yml` or
71
- * credential values into YAML. Optionally writes `.env.stack`.
64
+ * credential values into YAML. Optionally writes `docker/.env.docker`.
72
65
  *
73
66
  * @param result - Derive result
74
67
  * @param outDir - Destination for Dockerfile / compose (usually `docker/`)
@@ -79,11 +72,6 @@ export async function writeDerivedFiles(
79
72
  outDir: string,
80
73
  options: {
81
74
  readonly writeStackEnv?: boolean;
82
- /**
83
- * Directory for `.env.stack` (project root). Defaults to `outDir`.
84
- * Prefer the project cwd when compose lives under `docker/`.
85
- */
86
- readonly stackEnvDir?: string;
87
75
  } = {},
88
76
  ): Promise<readonly string[]> {
89
77
  const written: string[] = [];
@@ -95,9 +83,7 @@ export async function writeDerivedFiles(
95
83
  written.push(path);
96
84
  }
97
85
  if (options.writeStackEnv) {
98
- const envRoot = (options.stackEnvDir ?? outDir).replace(/\/$/, "");
99
- mkdirSync(envRoot, { recursive: true });
100
- const envPath = `${envRoot}/.env.stack`;
86
+ const envPath = `${root}/.env.docker`;
101
87
  await Bun.write(envPath, formatStackEnv(result.stackEnv));
102
88
  written.push(envPath);
103
89
  }
@@ -78,7 +78,12 @@ describe("image recipes", () => {
78
78
  match: (i) => /nats/i.test(i),
79
79
  apply: () => ({
80
80
  command: ["--js"],
81
- healthcheck: { test: ["CMD", "nats-server", "--signal", "ldm"], interval: "5s", timeout: "3s", retries: 5 },
81
+ healthcheck: {
82
+ test: ["CMD", "nats-server", "--signal", "ldm"],
83
+ interval: "5s",
84
+ timeout: "3s",
85
+ retries: 5,
86
+ },
82
87
  }),
83
88
  url: (_s, c) => `nats://${c.host}:${c.port}`,
84
89
  };
@@ -120,29 +125,23 @@ describe("deriveInfrastructure", () => {
120
125
  COMPOSE_OVERRIDE,
121
126
  ]);
122
127
 
123
- const sqlYml = result.files.find((f) => f.path === "compose.store.sql.yml")!
124
- .content;
128
+ const sqlYml = result.files.find((f) => f.path === "compose.store.sql.yml")!.content;
125
129
  expect(sqlYml).toContain("pgvector/pgvector:pg17");
126
130
  expect(sqlYml).toContain("POSTGRES_PASSWORD");
127
131
  expect(sqlYml).toContain("${OKE_STORE_SQL_PASSWORD}");
128
- expect(sqlYml).toContain("../.env.stack");
132
+ expect(sqlYml).toContain(".env.docker");
129
133
  expect(sqlYml).not.toContain(fixedCreds["store.sql"].password);
130
134
 
131
135
  const baseYml = result.files.find((f) => f.path === "compose.yml")!.content;
132
- expect(baseYml).toContain("context: \"..\"");
136
+ expect(baseYml).toContain('context: ".."');
133
137
  expect(baseYml).toContain("app:");
134
138
 
135
139
  for (const f of result.files) {
136
- assertNoCredentialsInYaml(
137
- f.content,
138
- Object.values(fixedCreds),
139
- );
140
+ assertNoCredentialsInYaml(f.content, Object.values(fixedCreds));
140
141
  }
141
142
 
142
143
  expect(result.stackEnv.DATABASE_URL).toContain("postgres://");
143
- expect(result.stackEnv.OKE_STORE_SQL_PASSWORD).toBe(
144
- fixedCreds["store.sql"].password,
145
- );
144
+ expect(result.stackEnv.OKE_STORE_SQL_PASSWORD).toBe(fixedCreds["store.sql"].password);
146
145
  });
147
146
 
148
147
  test("includeApp false omits app service (infra-only stack)", () => {
@@ -187,13 +186,14 @@ describe("deriveInfrastructure", () => {
187
186
  });
188
187
  const written = await writeDerivedFiles(result, dockerDir, {
189
188
  writeStackEnv: true,
190
- stackEnvDir: root,
191
189
  });
192
190
  expect(written.some((p) => p.endsWith(COMPOSE_OVERRIDE))).toBe(false);
193
- expect(await Bun.file(join(root, ".env.stack")).exists()).toBe(true);
191
+ expect(await Bun.file(join(dockerDir, ".env.docker")).exists()).toBe(true);
194
192
  expect(await Bun.file(join(dockerDir, "compose.yml")).exists()).toBe(true);
195
- const envText = await Bun.file(join(root, ".env.stack")).text();
193
+ const envText = await Bun.file(join(dockerDir, ".env.docker")).text();
196
194
  expect(envText).toContain("DATABASE_URL=");
195
+ expect(envText).toContain("# docker/.env.docker — generated by");
196
+ expect(envText).toContain("# ── store.sql — Postgres");
197
197
  expect(formatStackEnv(result.stackEnv)).toContain("OKE_STORE_SQL_PASSWORD=");
198
198
  });
199
199
 
@@ -96,10 +96,10 @@ console.log("listening", server.port);
96
96
  await Bun.write(join(dir, "Dockerfile"), df);
97
97
 
98
98
  const tag = `oke-docker-fixture:${Date.now()}`;
99
- const build = Bun.spawn(
100
- ["docker", "build", "-t", tag, dir],
101
- { stdout: "pipe", stderr: "pipe" },
102
- );
99
+ const build = Bun.spawn(["docker", "build", "-t", tag, dir], {
100
+ stdout: "pipe",
101
+ stderr: "pipe",
102
+ });
103
103
  const [buildOut, buildErr, buildCode] = await Promise.all([
104
104
  new Response(build.stdout).text(),
105
105
  new Response(build.stderr).text(),
@@ -111,19 +111,10 @@ console.log("listening", server.port);
111
111
  }
112
112
 
113
113
  const name = `oke-fixture-run-${Date.now()}`;
114
- const run = Bun.spawn(
115
- [
116
- "docker",
117
- "run",
118
- "-d",
119
- "--name",
120
- name,
121
- "-p",
122
- "0:6530",
123
- tag,
124
- ],
125
- { stdout: "pipe", stderr: "pipe" },
126
- );
114
+ const run = Bun.spawn(["docker", "run", "-d", "--name", name, "-p", "0:6530", tag], {
115
+ stdout: "pipe",
116
+ stderr: "pipe",
117
+ });
127
118
  const [runOut, runErr, runCode] = await Promise.all([
128
119
  new Response(run.stdout).text(),
129
120
  new Response(run.stderr).text(),
@@ -133,10 +124,10 @@ console.log("listening", server.port);
133
124
  if (runCode !== 0) console.error(runOut, runErr);
134
125
 
135
126
  // Resolve published port
136
- const portProc = Bun.spawn(
137
- ["docker", "port", name, "6530"],
138
- { stdout: "pipe", stderr: "pipe" },
139
- );
127
+ const portProc = Bun.spawn(["docker", "port", name, "6530"], {
128
+ stdout: "pipe",
129
+ stderr: "pipe",
130
+ });
140
131
  const portOut = await new Response(portProc.stdout).text();
141
132
  await portProc.exited;
142
133
  const m = portOut.match(/:(\d+)/);
@@ -74,10 +74,11 @@ export function toYaml(value: unknown, indent = 0): string {
74
74
  const body = toYaml(item, indent + 1);
75
75
  const lines = body.split("\n");
76
76
  const first = lines[0] ?? "";
77
- const rest = lines.slice(1).map((l) => `${pad} ${l}`).join("\n");
78
- return rest
79
- ? `${pad}- ${first}\n${rest}`
80
- : `${pad}- ${first}`;
77
+ const rest = lines
78
+ .slice(1)
79
+ .map((l) => `${pad} ${l}`)
80
+ .join("\n");
81
+ return rest ? `${pad}- ${first}\n${rest}` : `${pad}- ${first}`;
81
82
  }
82
83
  return `${pad}- ${toYaml(item, 0)}`;
83
84
  })
@@ -14,6 +14,7 @@ export type {
14
14
  GeneratedFile,
15
15
  ImageRecipe,
16
16
  RecipeApplyResult,
17
+ RecipeExtraPort,
17
18
  ServiceCredentials,
18
19
  ServiceEndpoint,
19
20
  ServiceSpec,
@@ -35,19 +36,11 @@ export { deriveInfrastructure, writeDerivedFiles } from "./derive.ts";
35
36
  export { emitDockerfile } from "./dockerfile.ts";
36
37
  export { credEnv, envPrefix, serviceNameFor } from "./helpers.ts";
37
38
  export { generateCredentials } from "./credentials.ts";
38
- export {
39
- builtinRecipes,
40
- postgres,
41
- recipeFor,
42
- redis,
43
- } from "./recipes/index.ts";
44
- export {
45
- formatStackPreview,
46
- resolveStack,
47
- type StackRow,
48
- } from "./stack.ts";
39
+ export { builtinRecipes, mailpit, postgres, recipeFor, redis, rustfs } from "./recipes/index.ts";
40
+ export { formatStackPreview, resolveStack, type StackRow } from "./stack.ts";
49
41
  export {
50
42
  hostPortForInstance,
43
+ instancePortOffset,
51
44
  loadExistingStackCredentials,
52
45
  parseStackCredentials,
53
46
  stackAppSlug,
package/src/docker/pin.ts CHANGED
@@ -61,15 +61,7 @@ export function formatImagesLock(lock: ImagesLock): string {
61
61
  */
62
62
  async function dockerDigest(image: string): Promise<string> {
63
63
  const proc = Bun.spawn(
64
- [
65
- "docker",
66
- "buildx",
67
- "imagetools",
68
- "inspect",
69
- "--format",
70
- "{{.Manifest.Digest}}",
71
- image,
72
- ],
64
+ ["docker", "buildx", "imagetools", "inspect", "--format", "{{.Manifest.Digest}}", image],
73
65
  { stdout: "pipe", stderr: "pipe" },
74
66
  );
75
67
  const [stdout, stderr, code] = await Promise.all([
@@ -3,13 +3,15 @@
3
3
  */
4
4
 
5
5
  import type { ImageRecipe } from "../types.ts";
6
+ import { mailpit } from "./mailpit.ts";
6
7
  import { postgres } from "./postgres.ts";
7
8
  import { redis } from "./redis.ts";
9
+ import { rustfs } from "./rustfs.ts";
8
10
 
9
11
  /** Default recipe catalogue. */
10
- export const builtinRecipes: readonly ImageRecipe[] = [postgres, redis];
12
+ export const builtinRecipes: readonly ImageRecipe[] = [postgres, redis, mailpit, rustfs];
11
13
 
12
- export { postgres, redis };
14
+ export { mailpit, postgres, redis, rustfs };
13
15
 
14
16
  /**
15
17
  * Resolve the recipe for an image reference.
@@ -17,10 +19,7 @@ export { postgres, redis };
17
19
  * @param image - Image pin
18
20
  * @param extra - Plugin / test recipes (tried first)
19
21
  */
20
- export function recipeFor(
21
- image: string,
22
- extra: readonly ImageRecipe[] = [],
23
- ): ImageRecipe {
22
+ export function recipeFor(image: string, extra: readonly ImageRecipe[] = []): ImageRecipe {
24
23
  for (const r of extra) {
25
24
  if (r.match(image)) return r;
26
25
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Mailpit image recipe — local SMTP catcher for docker ≈ prod email.
3
+ */
4
+
5
+ import type { ImageRecipe } from "../types.ts";
6
+
7
+ /** SMTP catcher (Mailpit) — UI on 8025. */
8
+ export const mailpit: ImageRecipe = {
9
+ id: "mailpit",
10
+ port: 1025,
11
+ match: (i) => /mailpit/i.test(i),
12
+ apply: () => ({
13
+ extraPorts: [{ host: 8025, container: 8025 }],
14
+ healthcheck: {
15
+ test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8025/api/v1/info || exit 1"],
16
+ interval: "5s",
17
+ timeout: "3s",
18
+ retries: 10,
19
+ },
20
+ }),
21
+ url: (_s, c) => `smtp://${c.host}:${c.port}`,
22
+ };
@@ -3,16 +3,27 @@
3
3
  */
4
4
 
5
5
  import { credEnv } from "../helpers.ts";
6
- import type { ImageRecipe } from "../types.ts";
6
+ import type { ImageRecipe, ServiceSpec } from "../types.ts";
7
+
8
+ const postgresEnv = (s: ServiceSpec) => ({
9
+ POSTGRES_USER: credEnv(s, "USER"),
10
+ POSTGRES_PASSWORD: credEnv(s, "PASSWORD"),
11
+ POSTGRES_DB: credEnv(s, "DB"),
12
+ });
13
+
14
+ const postgresHealth = {
15
+ test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"],
16
+ interval: "5s",
17
+ timeout: "3s",
18
+ retries: 10,
19
+ };
7
20
 
8
21
  /** Postgres-protocol servers (Postgres · Neon · Supabase · pgvector · Timescale). */
9
22
  export const postgres: ImageRecipe = {
10
23
  id: "postgres",
11
24
  port: 5432,
12
25
  match: (i) => /postgres|pgvector|timescale/i.test(i),
13
- apply: (s) => ({
14
- environment: { POSTGRES_USER: credEnv(s, "USER"), POSTGRES_PASSWORD: credEnv(s, "PASSWORD"), POSTGRES_DB: credEnv(s, "DB") },
15
- healthcheck: { test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"], interval: "5s", timeout: "3s", retries: 10 },
16
- }),
17
- url: (_s, c) => `postgres://${c.user}:${encodeURIComponent(c.password)}@${c.host}:${c.port}/${c.database}`,
26
+ apply: (s) => ({ environment: postgresEnv(s), healthcheck: postgresHealth }),
27
+ url: (_s, c) =>
28
+ `postgres://${c.user}:${encodeURIComponent(c.password)}@${c.host}:${c.port}/${c.database}`,
18
29
  };
@@ -12,7 +12,12 @@ export const redis: ImageRecipe = {
12
12
  match: (i) => /redis|valkey|dragonfly|keydb/i.test(i),
13
13
  apply: (s) => ({
14
14
  command: ["redis-server", "--requirepass", credEnv(s, "PASSWORD")],
15
- healthcheck: { test: ["CMD", "redis-cli", "-a", credEnv(s, "PASSWORD"), "ping"], interval: "5s", timeout: "3s", retries: 10 },
15
+ healthcheck: {
16
+ test: ["CMD", "redis-cli", "-a", credEnv(s, "PASSWORD"), "ping"],
17
+ interval: "5s",
18
+ timeout: "3s",
19
+ retries: 10,
20
+ },
16
21
  }),
17
22
  url: (_s, c) => `redis://:${encodeURIComponent(c.password)}@${c.host}:${c.port}`,
18
23
  };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * RustFS image recipe — S3-compatible object store (Apache 2.0 MinIO alternative).
3
+ */
4
+
5
+ import { credEnv } from "../helpers.ts";
6
+ import type { ImageRecipe } from "../types.ts";
7
+
8
+ /** S3-protocol object storage (RustFS). Console on 9001. */
9
+ export const rustfs: ImageRecipe = {
10
+ id: "rustfs",
11
+ port: 9000,
12
+ match: (i) => /rustfs/i.test(i),
13
+ apply: (s) => ({
14
+ environment: {
15
+ RUSTFS_ACCESS_KEY: credEnv(s, "USER"),
16
+ RUSTFS_SECRET_KEY: credEnv(s, "PASSWORD"),
17
+ RUSTFS_CONSOLE_ENABLE: "true",
18
+ RUSTFS_ADDRESS: ":9000",
19
+ },
20
+ command: ["/data"],
21
+ volumes: [`${s.serviceName}-data:/data`],
22
+ extraPorts: [{ host: 9001, container: 9001 }],
23
+ healthcheck: {
24
+ test: ["CMD-SHELL", "curl -f http://127.0.0.1:9000/health || exit 1"],
25
+ interval: "5s",
26
+ timeout: "5s",
27
+ retries: 12,
28
+ start_period: "10s",
29
+ },
30
+ }),
31
+ url: (_s, c) =>
32
+ `http://${encodeURIComponent(c.user)}:${encodeURIComponent(c.password)}@${c.host}:${c.port}/${c.database}`,
33
+ };
@@ -5,6 +5,7 @@
5
5
  import { describe, expect, test } from "bun:test";
6
6
  import {
7
7
  hostPortForInstance,
8
+ instancePortOffset,
8
9
  parseStackCredentials,
9
10
  stackAppSlug,
10
11
  stackInstanceId,
@@ -83,4 +84,36 @@ describe("deriveInfrastructure instanceId", () => {
83
84
  expect(result.stackEnv.DATABASE_URL).toMatch(/:15\d{3}\//);
84
85
  expect(result.stackEnv.REDIS_URL).toMatch(/:16\d{3}/);
85
86
  });
87
+
88
+ test("offsets Mailpit UI and RustFS console so stacks do not share 8025/9001", () => {
89
+ const id = "abcd12";
90
+ const n = instancePortOffset(id);
91
+ const result = deriveInfrastructure({
92
+ images: {
93
+ "channel.email": "axllent/mailpit:v1.22.3",
94
+ "store.files": "rustfs/rustfs:1.0.0-beta.11",
95
+ },
96
+ app: `dev-${id}`,
97
+ instanceId: id,
98
+ includeApp: false,
99
+ credentials: {
100
+ "channel.email": {
101
+ user: "oke",
102
+ password: "stack-id-test-mail-password",
103
+ database: "oke",
104
+ },
105
+ "store.files": {
106
+ user: "oke",
107
+ password: "stack-id-test-files-password",
108
+ database: "oke",
109
+ },
110
+ },
111
+ });
112
+ const mailYml = result.files.find((f) => f.path === "compose.channel.email.yml")!.content;
113
+ const filesYml = result.files.find((f) => f.path === "compose.store.files.yml")!.content;
114
+ expect(mailYml).toContain(`${8025 + n}:8025`);
115
+ expect(mailYml).not.toContain("8025:8025");
116
+ expect(filesYml).toContain(`${9001 + n}:9001`);
117
+ expect(filesYml).not.toContain("9001:9001");
118
+ });
86
119
  });