okengine 0.2.6 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (681) hide show
  1. package/AGENTS.md +33 -30
  2. package/README.md +74 -340
  3. package/{spec/manifest.v1.schema.json → manifest.v1.schema.json} +40 -9
  4. package/package.json +47 -44
  5. package/site/content/docs/ai/llms-txt.mdx +54 -0
  6. package/site/content/docs/ai/mcp.mdx +123 -0
  7. package/site/content/docs/ai/meta.json +5 -0
  8. package/site/content/docs/ai/skills.mdx +53 -0
  9. package/site/content/docs/console/access.mdx +29 -0
  10. package/site/content/docs/console/ai.mdx +35 -0
  11. package/site/content/docs/console/architecture.mdx +35 -0
  12. package/site/content/docs/console/channels.mdx +37 -0
  13. package/site/content/docs/console/clock.mdx +31 -0
  14. package/site/content/docs/console/flows.mdx +31 -0
  15. package/site/content/docs/console/gates.mdx +35 -0
  16. package/site/content/docs/console/manifest-diff.mdx +34 -0
  17. package/site/content/docs/console/meta.json +23 -0
  18. package/site/content/docs/console/overview.mdx +40 -0
  19. package/site/content/docs/console/plugins.mdx +41 -0
  20. package/site/content/docs/console/privacy.mdx +32 -0
  21. package/site/content/docs/console/runs.mdx +40 -0
  22. package/site/content/docs/console/signals.mdx +31 -0
  23. package/site/content/docs/console/store.mdx +32 -0
  24. package/site/content/docs/console/tenancy.mdx +32 -0
  25. package/site/content/docs/console/traces.mdx +34 -0
  26. package/site/content/docs/console/vault.mdx +37 -0
  27. package/site/content/docs/elements/ai.mdx +180 -0
  28. package/site/content/docs/elements/channel.mdx +167 -0
  29. package/site/content/docs/elements/clock.mdx +182 -0
  30. package/site/content/docs/elements/flow.mdx +288 -0
  31. package/site/content/docs/elements/gate.mdx +171 -0
  32. package/site/content/docs/elements/meta.json +5 -0
  33. package/site/content/docs/elements/signal.mdx +171 -0
  34. package/site/content/docs/elements/store.mdx +320 -0
  35. package/site/content/docs/elements/vault.mdx +263 -0
  36. package/site/content/docs/get-started/basic-usage.mdx +124 -0
  37. package/site/content/docs/get-started/comparison.mdx +65 -0
  38. package/site/content/docs/get-started/installation.mdx +113 -0
  39. package/site/content/docs/get-started/introduction.mdx +123 -0
  40. package/site/content/docs/get-started/meta.json +5 -0
  41. package/site/content/docs/index.mdx +63 -0
  42. package/site/content/docs/meta.json +5 -0
  43. package/site/content/docs/plugins/compression.mdx +60 -0
  44. package/site/content/docs/plugins/cors.mdx +92 -0
  45. package/site/content/docs/plugins/csrf.mdx +96 -0
  46. package/site/content/docs/plugins/ip-allowlist.mdx +92 -0
  47. package/site/content/docs/plugins/maintenance-mode.mdx +101 -0
  48. package/site/content/docs/plugins/meta.json +15 -0
  49. package/site/content/docs/plugins/security-headers.mdx +136 -0
  50. package/site/content/docs/reference/cli.md +101 -0
  51. package/site/content/docs/reference/configuration.mdx +159 -0
  52. package/site/content/docs/reference/environment-variables.mdx +87 -0
  53. package/site/content/docs/reference/errors.mdx +80 -0
  54. package/site/content/docs/reference/fx.mdx +117 -0
  55. package/site/content/docs/reference/meta.json +5 -0
  56. package/site/content/docs/reference/plugins.mdx +249 -0
  57. package/site/content/docs/reference/security.md +63 -0
  58. package/src/auth/api-keys.ts +2 -5
  59. package/src/auth/attenuation.ts +2 -6
  60. package/src/auth/auth.test.ts +56 -31
  61. package/src/auth/invoke-as.ts +1 -3
  62. package/src/auth/operator.ts +8 -26
  63. package/src/auth/planes.ts +2 -7
  64. package/src/auth/plugin.ts +4 -7
  65. package/src/auth/roles.ts +1 -5
  66. package/src/auth/sessions.ts +10 -39
  67. package/src/cli/args.ts +1 -5
  68. package/src/cli/ask-dev-mode.ts +40 -0
  69. package/src/cli/build.ts +1 -2
  70. package/src/cli/client-add.test.ts +8 -15
  71. package/src/cli/client-add.ts +5 -14
  72. package/src/cli/completion.test.ts +1 -5
  73. package/src/cli/completion.ts +4 -17
  74. package/src/cli/db-auto-push.test.ts +64 -0
  75. package/src/cli/db-auto-push.ts +72 -0
  76. package/src/cli/db.test.ts +189 -0
  77. package/src/cli/db.ts +456 -0
  78. package/src/cli/dev-app-runner.ts +2 -8
  79. package/src/cli/dev-db-push.test.ts +193 -0
  80. package/src/cli/dev-mode.test.ts +45 -0
  81. package/src/cli/dev-mode.ts +78 -0
  82. package/src/cli/dev-schema-sync.test.ts +66 -0
  83. package/src/cli/dev-schema-sync.ts +139 -0
  84. package/src/cli/dev.test.ts +264 -15
  85. package/src/cli/dev.ts +355 -139
  86. package/src/cli/doc-staleness.test.ts +69 -0
  87. package/src/cli/docker-cli.test.ts +27 -8
  88. package/src/cli/docker.ts +11 -3
  89. package/src/cli/doctor-diff.test.ts +1 -5
  90. package/src/cli/doctor-diff.ts +17 -46
  91. package/src/cli/doctor-pii.ts +2 -7
  92. package/src/cli/doctor.test.ts +26 -4
  93. package/src/cli/doctor.ts +35 -11
  94. package/src/cli/drizzle-env.test.ts +67 -0
  95. package/src/cli/drizzle-env.ts +78 -0
  96. package/src/cli/ensure-drizzle-config.ts +50 -0
  97. package/src/cli/eval.ts +4 -14
  98. package/src/cli/gates-list.ts +1 -4
  99. package/src/cli/hero-meta.test.ts +17 -19
  100. package/src/cli/hero-meta.ts +20 -30
  101. package/src/cli/images.ts +3 -8
  102. package/src/cli/index.ts +11 -2
  103. package/src/cli/load-config.images.test.ts +14 -14
  104. package/src/cli/load-config.ts +47 -47
  105. package/src/cli/mcp-from-console.ts +1 -4
  106. package/src/cli/mode.ts +65 -0
  107. package/src/cli/openbao-bootstrap.test.ts +147 -0
  108. package/src/cli/openbao-bootstrap.ts +280 -0
  109. package/src/cli/openbao-restart.integration.test.ts +136 -0
  110. package/src/cli/ports.test.ts +7 -14
  111. package/src/cli/ports.ts +7 -5
  112. package/src/cli/privacy-erase.ts +1 -3
  113. package/src/cli/registry.ts +75 -25
  114. package/src/cli/resolve-dev-sql-env.test.ts +48 -0
  115. package/src/cli/resolve-dev-sql-env.ts +42 -0
  116. package/src/cli/schema.ts +3 -10
  117. package/src/cli/stack.ts +8 -10
  118. package/src/cli/start.ts +4 -15
  119. package/src/cli/upgrade.ts +1 -4
  120. package/src/cli/vault-cmd.ts +63 -0
  121. package/src/client/budget-entry.ts +1 -6
  122. package/src/client/create.test.ts +6 -12
  123. package/src/client/create.ts +2 -8
  124. package/src/client/errors.ts +1 -4
  125. package/src/client/index.ts +1 -6
  126. package/src/client/notes-contract.test.ts +4 -13
  127. package/src/client/transport.ts +9 -30
  128. package/src/client/types.ts +15 -30
  129. package/src/compiler/aot.test.ts +2 -9
  130. package/src/compiler/aot.ts +1 -4
  131. package/src/compiler/differential.test.ts +3 -11
  132. package/src/compiler/dynamic.ts +1 -4
  133. package/src/compiler/effects-infer.ts +8 -34
  134. package/src/compiler/emit.ts +1 -3
  135. package/src/compiler/extract.test.ts +136 -30
  136. package/src/compiler/extract.ts +485 -157
  137. package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
  138. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -3
  139. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -3
  140. package/src/compiler/fixtures/skyport/src/flows/support/index.ts +4 -1
  141. package/src/compiler/fixtures/skyport.expected.json +24 -83
  142. package/src/compiler/fixtures/triggers/five-triggers.ts +1 -2
  143. package/src/compiler/http-parse.ts +2 -9
  144. package/src/compiler/index.ts +1 -5
  145. package/src/compiler/response.ts +16 -7
  146. package/src/compiler/sucrose.ts +11 -31
  147. package/src/config/define-config.test.ts +55 -34
  148. package/src/config/index.ts +177 -46
  149. package/src/config/resolve-driver.test.ts +28 -17
  150. package/src/console/budget.test.ts +1 -4
  151. package/src/console/index.ts +1 -4
  152. package/src/console/server/access.test.ts +15 -43
  153. package/src/console/server/access.ts +19 -49
  154. package/src/console/server/ai.test.ts +3 -11
  155. package/src/console/server/ai.ts +23 -46
  156. package/src/console/server/app.ts +16 -45
  157. package/src/console/server/auth-rate.test.ts +4 -12
  158. package/src/console/server/bind.ts +1 -5
  159. package/src/console/server/channels.test.ts +2 -6
  160. package/src/console/server/channels.ts +10 -35
  161. package/src/console/server/claim.ts +2 -8
  162. package/src/console/server/clock.test.ts +2 -6
  163. package/src/console/server/clock.ts +8 -26
  164. package/src/console/server/console.test.ts +1 -3
  165. package/src/console/server/diff.test.ts +5 -17
  166. package/src/console/server/diff.ts +10 -31
  167. package/src/console/server/dry-run.audit.test.ts +2 -8
  168. package/src/console/server/flows.ts +64 -233
  169. package/src/console/server/gates.test.ts +14 -50
  170. package/src/console/server/gates.ts +13 -48
  171. package/src/console/server/index.ts +2 -9
  172. package/src/console/server/live.ts +3 -12
  173. package/src/console/server/operator-db.test.ts +4 -15
  174. package/src/console/server/operator-db.ts +13 -32
  175. package/src/console/server/plugin.ts +50 -53
  176. package/src/console/server/plugins.test.ts +2 -6
  177. package/src/console/server/plugins.ts +17 -56
  178. package/src/console/server/runs-pii.ts +2 -7
  179. package/src/console/server/security.gate.test.ts +18 -40
  180. package/src/console/server/serve.ts +10 -25
  181. package/src/console/server/signals.test.ts +1 -3
  182. package/src/console/server/signals.ts +17 -40
  183. package/src/console/server/state.ts +21 -59
  184. package/src/console/server/store.test.ts +20 -22
  185. package/src/console/server/store.ts +31 -76
  186. package/src/console/server/vault.test.ts +12 -29
  187. package/src/console/server/vault.ts +44 -66
  188. package/src/console/ui/access/AccessA11yView.tsx +6 -21
  189. package/src/console/ui/access/a11y.test.tsx +3 -12
  190. package/src/console/ui/access/acknowledgement.test.ts +1 -4
  191. package/src/console/ui/access/acknowledgement.ts +1 -3
  192. package/src/console/ui/access/blast-radius.ts +1 -3
  193. package/src/console/ui/access/confirmation.test.ts +1 -5
  194. package/src/console/ui/access/confirmation.ts +1 -4
  195. package/src/console/ui/access/fixture.ts +1 -5
  196. package/src/console/ui/access/grantable.test.ts +1 -3
  197. package/src/console/ui/access/grantable.ts +2 -7
  198. package/src/console/ui/access/index.ts +2 -9
  199. package/src/console/ui/access/provenance.test.ts +1 -3
  200. package/src/console/ui/access/provenance.ts +2 -6
  201. package/src/console/ui/access/search.test.ts +1 -5
  202. package/src/console/ui/access/search.ts +2 -6
  203. package/src/console/ui/ai/AiA11yView.tsx +6 -17
  204. package/src/console/ui/ai/a11y.test.tsx +2 -5
  205. package/src/console/ui/ai/fixture.ts +2 -7
  206. package/src/console/ui/ai/format.ts +1 -3
  207. package/src/console/ui/ai/group.test.ts +1 -3
  208. package/src/console/ui/ai/group.ts +4 -12
  209. package/src/console/ui/ai/index.ts +2 -9
  210. package/src/console/ui/ai/promotion.test.ts +2 -7
  211. package/src/console/ui/ai/promotion.ts +4 -13
  212. package/src/console/ui/ai/search.test.ts +2 -11
  213. package/src/console/ui/ai/search.ts +3 -13
  214. package/src/console/ui/ai/types.ts +1 -8
  215. package/src/console/ui/architecture/ArchitectureA11yView.tsx +8 -29
  216. package/src/console/ui/architecture/a11y.test.tsx +1 -2
  217. package/src/console/ui/architecture/declared.ts +1 -5
  218. package/src/console/ui/architecture/index.ts +7 -35
  219. package/src/console/ui/architecture/layout.ts +1 -3
  220. package/src/console/ui/architecture/pathologies.ts +10 -19
  221. package/src/console/ui/architecture/search.ts +6 -15
  222. package/src/console/ui/architecture/traffic.ts +4 -14
  223. package/src/console/ui/architecture/types.ts +2 -10
  224. package/src/console/ui/architecture/view.test.ts +7 -19
  225. package/src/console/ui/architecture/view.ts +9 -36
  226. package/src/console/ui/channels/ChannelsA11yView.tsx +4 -13
  227. package/src/console/ui/channels/a11y.test.tsx +1 -2
  228. package/src/console/ui/channels/confirmation.test.ts +1 -4
  229. package/src/console/ui/channels/confirmation.ts +1 -4
  230. package/src/console/ui/channels/findings.ts +1 -5
  231. package/src/console/ui/channels/index.ts +2 -8
  232. package/src/console/ui/channels/locale.test.ts +1 -3
  233. package/src/console/ui/channels/locale.ts +2 -8
  234. package/src/console/ui/channels/search.ts +4 -13
  235. package/src/console/ui/channels/taxonomy.ts +1 -3
  236. package/src/console/ui/clock/ClockA11yView.tsx +9 -26
  237. package/src/console/ui/clock/a11y.test.tsx +2 -5
  238. package/src/console/ui/clock/confirmation.test.ts +1 -3
  239. package/src/console/ui/clock/confirmation.ts +1 -4
  240. package/src/console/ui/clock/findings.ts +1 -4
  241. package/src/console/ui/clock/health.ts +2 -7
  242. package/src/console/ui/clock/index.ts +2 -10
  243. package/src/console/ui/clock/search.test.ts +1 -3
  244. package/src/console/ui/clock/search.ts +2 -6
  245. package/src/console/ui/clock/timeline.test.ts +1 -3
  246. package/src/console/ui/clock/waiting-on.test.ts +1 -5
  247. package/src/console/ui/clock/waiting-on.ts +2 -7
  248. package/src/console/ui/diff/DiffA11yView.tsx +8 -17
  249. package/src/console/ui/diff/a11y.test.tsx +2 -5
  250. package/src/console/ui/diff/group.test.ts +3 -13
  251. package/src/console/ui/diff/group.ts +2 -8
  252. package/src/console/ui/diff/index.ts +2 -8
  253. package/src/console/ui/diff/search.test.ts +1 -5
  254. package/src/console/ui/diff/search.ts +4 -16
  255. package/src/console/ui/diff/summary.ts +1 -2
  256. package/src/console/ui/dist/assets/{index-Dy4jht9P.js → index-BWo8R7NR.js} +2 -2
  257. package/src/console/ui/dist/assets/{panel-access-Dd37LU2c.js → panel-access-C0J2D-a2.js} +1 -1
  258. package/src/console/ui/dist/assets/{panel-ai-CC7LR6-J.js → panel-ai-D_m6WQI8.js} +1 -1
  259. package/src/console/ui/dist/assets/{panel-architecture-B5b3iKCz.js → panel-architecture-CKnXFyUx.js} +1 -1
  260. package/src/console/ui/dist/assets/{panel-channels-CeNjTKXp.js → panel-channels-BOmQ-onL.js} +1 -1
  261. package/src/console/ui/dist/assets/{panel-clock-DgFTLoHV.js → panel-clock-giAq0Ccv.js} +1 -1
  262. package/src/console/ui/dist/assets/{panel-diff-DxehccqB.js → panel-diff-cdonmH8c.js} +1 -1
  263. package/src/console/ui/dist/assets/{panel-flows-BtrVn-Eg.js → panel-flows-DlCU5zjA.js} +2 -2
  264. package/src/console/ui/dist/assets/{panel-gates-Z9MKRGdH.js → panel-gates-XclZxWD5.js} +1 -1
  265. package/src/console/ui/dist/assets/{panel-overview-Dt_AeXgd.js → panel-overview-BznEOTnb.js} +1 -1
  266. package/src/console/ui/dist/assets/{panel-plugins-DWd0TowH.js → panel-plugins-CcGM1g64.js} +1 -1
  267. package/src/console/ui/dist/assets/{panel-runs-DGstFHeq.js → panel-runs-CGWNHLR4.js} +1 -1
  268. package/src/console/ui/dist/assets/{panel-signals-DzEa2Fnt.js → panel-signals-CNywkdak.js} +1 -1
  269. package/src/console/ui/dist/assets/{panel-store-BJkbNgxx.js → panel-store-KmTbFHMH.js} +1 -1
  270. package/src/console/ui/dist/assets/{panel-traces-BaRVO3gM.js → panel-traces-DBLx2ilD.js} +1 -1
  271. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +1 -0
  272. package/src/console/ui/dist/index.html +1 -1
  273. package/src/console/ui/flows/FlowsA11yView.tsx +7 -22
  274. package/src/console/ui/flows/a11y.test.tsx +2 -8
  275. package/src/console/ui/flows/buffer.test.ts +3 -11
  276. package/src/console/ui/flows/buffer.ts +4 -11
  277. package/src/console/ui/flows/confirmation.ts +1 -3
  278. package/src/console/ui/flows/contract.test.ts +3 -7
  279. package/src/console/ui/flows/contract.ts +12 -36
  280. package/src/console/ui/flows/graph.test.ts +3 -11
  281. package/src/console/ui/flows/graph.ts +5 -15
  282. package/src/console/ui/flows/index.ts +1 -6
  283. package/src/console/ui/flows/save-as-test.ts +1 -3
  284. package/src/console/ui/flows/search.ts +8 -25
  285. package/src/console/ui/flows/tiers.ts +2 -9
  286. package/src/console/ui/gates/GatesA11yView.tsx +4 -13
  287. package/src/console/ui/gates/a11y.test.tsx +2 -5
  288. package/src/console/ui/gates/audit.ts +1 -3
  289. package/src/console/ui/gates/denial.test.ts +1 -3
  290. package/src/console/ui/gates/denial.ts +4 -12
  291. package/src/console/ui/gates/findings.ts +1 -3
  292. package/src/console/ui/gates/group.test.ts +3 -3
  293. package/src/console/ui/gates/group.ts +3 -11
  294. package/src/console/ui/gates/index.ts +2 -10
  295. package/src/console/ui/gates/search.ts +3 -10
  296. package/src/console/ui/overview/OverviewA11yView.tsx +9 -27
  297. package/src/console/ui/overview/a11y.test.tsx +1 -2
  298. package/src/console/ui/overview/busiest.ts +1 -3
  299. package/src/console/ui/overview/compose.ts +1 -13
  300. package/src/console/ui/overview/cost.ts +34 -38
  301. package/src/console/ui/overview/fixture.ts +21 -26
  302. package/src/console/ui/overview/golden.ts +4 -14
  303. package/src/console/ui/overview/index.ts +2 -9
  304. package/src/console/ui/overview/rank.test.ts +3 -10
  305. package/src/console/ui/overview/rank.ts +2 -7
  306. package/src/console/ui/overview/slo.test.ts +5 -9
  307. package/src/console/ui/overview/slo.ts +6 -23
  308. package/src/console/ui/overview/verdict.ts +3 -10
  309. package/src/console/ui/plugins/PluginsA11yView.tsx +8 -18
  310. package/src/console/ui/plugins/a11y.test.tsx +1 -2
  311. package/src/console/ui/plugins/fixture.ts +2 -4
  312. package/src/console/ui/plugins/group.test.ts +1 -3
  313. package/src/console/ui/plugins/group.ts +1 -6
  314. package/src/console/ui/plugins/index.ts +3 -13
  315. package/src/console/ui/plugins/search.ts +7 -19
  316. package/src/console/ui/runs/RunsA11yView.tsx +5 -20
  317. package/src/console/ui/runs/a11y.test.tsx +2 -5
  318. package/src/console/ui/runs/explain.ts +2 -6
  319. package/src/console/ui/runs/group.ts +3 -12
  320. package/src/console/ui/runs/histogram.test.ts +2 -11
  321. package/src/console/ui/runs/histogram.ts +1 -4
  322. package/src/console/ui/runs/index.ts +4 -18
  323. package/src/console/ui/runs/project.ts +2 -6
  324. package/src/console/ui/runs/query.test.ts +1 -3
  325. package/src/console/ui/runs/query.ts +12 -35
  326. package/src/console/ui/runs/search.ts +5 -19
  327. package/src/console/ui/runs/trace-link.test.ts +2 -6
  328. package/src/console/ui/runs/trace-link.ts +4 -16
  329. package/src/console/ui/runs/types.ts +1 -3
  330. package/src/console/ui/shell/App.tsx +2 -10
  331. package/src/console/ui/shell/client.ts +22 -115
  332. package/src/console/ui/shell/components/ui.tsx +1 -4
  333. package/src/console/ui/shell/layout/Shell.tsx +2 -4
  334. package/src/console/ui/shell/main.tsx +31 -96
  335. package/src/console/ui/shell/panels/access/AccessPanel.tsx +34 -98
  336. package/src/console/ui/shell/panels/ai/AiPanel.tsx +35 -106
  337. package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +18 -66
  338. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +20 -57
  339. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +22 -54
  340. package/src/console/ui/shell/panels/diff/DiffPanel.tsx +20 -61
  341. package/src/console/ui/shell/panels/flows/ContractEditor.tsx +6 -23
  342. package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +13 -28
  343. package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +9 -29
  344. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +24 -80
  345. package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +20 -58
  346. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +16 -41
  347. package/src/console/ui/shell/panels/runs/RunsPanel.tsx +31 -99
  348. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +38 -101
  349. package/src/console/ui/shell/panels/store/StorePanel.tsx +27 -66
  350. package/src/console/ui/shell/panels/traces/TracesPanel.tsx +28 -89
  351. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +22 -53
  352. package/src/console/ui/shell/setup/Wizard.tsx +3 -5
  353. package/src/console/ui/shell/styles.css +1 -2
  354. package/src/console/ui/signals/SignalsA11yView.tsx +9 -33
  355. package/src/console/ui/signals/a11y.test.tsx +2 -5
  356. package/src/console/ui/signals/confirmation.test.ts +1 -5
  357. package/src/console/ui/signals/confirmation.ts +2 -6
  358. package/src/console/ui/signals/dry-run.ts +1 -3
  359. package/src/console/ui/signals/durable.ts +1 -3
  360. package/src/console/ui/signals/findings.ts +1 -3
  361. package/src/console/ui/signals/group.test.ts +2 -8
  362. package/src/console/ui/signals/group.ts +2 -10
  363. package/src/console/ui/signals/index.ts +1 -4
  364. package/src/console/ui/signals/monitor.ts +2 -7
  365. package/src/console/ui/signals/schema-form.test.ts +3 -14
  366. package/src/console/ui/signals/schema-form.ts +1 -4
  367. package/src/console/ui/signals/search.ts +4 -13
  368. package/src/console/ui/store/StoreA11yView.tsx +3 -9
  369. package/src/console/ui/store/a11y.test.tsx +2 -5
  370. package/src/console/ui/store/confirmation.test.ts +1 -3
  371. package/src/console/ui/store/confirmation.ts +1 -4
  372. package/src/console/ui/store/dry-run.ts +2 -5
  373. package/src/console/ui/store/fixture.ts +1 -2
  374. package/src/console/ui/store/group.test.ts +1 -6
  375. package/src/console/ui/store/group.ts +1 -4
  376. package/src/console/ui/store/index.ts +1 -5
  377. package/src/console/ui/store/search.ts +2 -6
  378. package/src/console/ui/store/will-not-fire.ts +1 -2
  379. package/src/console/ui/traces/TracesA11yView.tsx +10 -29
  380. package/src/console/ui/traces/a11y.test.tsx +1 -2
  381. package/src/console/ui/traces/chain.test.ts +5 -19
  382. package/src/console/ui/traces/chain.ts +1 -3
  383. package/src/console/ui/traces/critical-path.test.ts +1 -3
  384. package/src/console/ui/traces/critical-path.ts +2 -6
  385. package/src/console/ui/traces/filter.test.ts +2 -6
  386. package/src/console/ui/traces/filter.ts +5 -13
  387. package/src/console/ui/traces/fold.test.ts +4 -12
  388. package/src/console/ui/traces/fold.ts +1 -3
  389. package/src/console/ui/traces/mini.ts +1 -3
  390. package/src/console/ui/traces/replay.test.ts +1 -3
  391. package/src/console/ui/traces/replay.ts +1 -3
  392. package/src/console/ui/traces/sampling.ts +1 -4
  393. package/src/console/ui/traces/search.ts +4 -17
  394. package/src/console/ui/traces/tier.ts +1 -3
  395. package/src/console/ui/vault/VaultA11yView.tsx +5 -17
  396. package/src/console/ui/vault/a11y.test.tsx +1 -2
  397. package/src/console/ui/vault/blast-radius.ts +1 -3
  398. package/src/console/ui/vault/confirmation.test.ts +1 -5
  399. package/src/console/ui/vault/confirmation.ts +1 -4
  400. package/src/console/ui/vault/dormant.test.ts +1 -4
  401. package/src/console/ui/vault/export-safe.test.ts +1 -5
  402. package/src/console/ui/vault/export-safe.ts +1 -4
  403. package/src/console/ui/vault/fixture.ts +3 -3
  404. package/src/console/ui/vault/group.ts +1 -2
  405. package/src/console/ui/vault/index.ts +2 -10
  406. package/src/console/ui/vault/search.ts +2 -6
  407. package/src/console/ui/vault/types.ts +1 -1
  408. package/src/console/xss.gate.test.ts +1 -3
  409. package/src/docker/compose.ts +215 -27
  410. package/src/docker/derive.ts +6 -14
  411. package/src/docker/docker.test.ts +118 -15
  412. package/src/docker/dockerfile.integration.test.ts +12 -21
  413. package/src/docker/helpers.ts +5 -4
  414. package/src/docker/index.ts +9 -6
  415. package/src/docker/pin.ts +1 -9
  416. package/src/docker/recipes/index.ts +6 -6
  417. package/src/docker/recipes/mailpit.ts +22 -0
  418. package/src/docker/recipes/openbao.ts +47 -0
  419. package/src/docker/recipes/postgres.ts +17 -6
  420. package/src/docker/recipes/redis.ts +11 -2
  421. package/src/docker/recipes/rustfs.ts +32 -0
  422. package/src/docker/stack-id.test.ts +73 -5
  423. package/src/docker/stack-id.ts +124 -33
  424. package/src/docker/stack.integration.test.ts +6 -21
  425. package/src/docker/stack.ts +36 -4
  426. package/src/docker/types.ts +16 -4
  427. package/src/docs-origin.ts +4 -4
  428. package/src/drivers/ai-anthropic.ts +5 -14
  429. package/src/drivers/ai-mock.ts +2 -5
  430. package/src/drivers/ai-openai-compatible.ts +4 -10
  431. package/src/drivers/ai-providers.test.ts +6 -13
  432. package/src/drivers/bun-native-completeness.test.ts +6 -15
  433. package/src/drivers/channel-console.ts +6 -7
  434. package/src/drivers/channel-fcm.ts +20 -30
  435. package/src/drivers/channel-resend.ts +1 -3
  436. package/src/drivers/channel-smtp.ts +2 -6
  437. package/src/drivers/channel-types.ts +2 -12
  438. package/src/drivers/channel-unifonic.ts +2 -6
  439. package/src/drivers/channel-wa-cloud.ts +2 -6
  440. package/src/drivers/channel-webpush.ts +21 -60
  441. package/src/drivers/conformance.test.ts +2 -10
  442. package/src/drivers/conformance.ts +8 -21
  443. package/src/drivers/drizzle-dialect.test.ts +20 -0
  444. package/src/drivers/drizzle-dialect.ts +37 -0
  445. package/src/drivers/fs.ts +2 -8
  446. package/src/drivers/index.ts +12 -57
  447. package/src/drivers/memory.ts +287 -75
  448. package/src/drivers/pgvector.ts +7 -11
  449. package/src/drivers/postgres.ts +7 -13
  450. package/src/drivers/redis.ts +13 -37
  451. package/src/drivers/s3.ts +15 -18
  452. package/src/drivers/signal-engine.ts +12 -49
  453. package/src/drivers/signal-memory.ts +2 -8
  454. package/src/drivers/signal-nats.ts +4 -9
  455. package/src/drivers/signal-postgres.ts +62 -129
  456. package/src/drivers/signal-redis.ts +7 -21
  457. package/src/drivers/signal-types.ts +1 -4
  458. package/src/drivers/sqlite.ts +3 -12
  459. package/src/drivers/types.ts +4 -23
  460. package/src/drivers/vault-driver-removal.test.ts +55 -0
  461. package/src/drivers/vault-env.ts +2 -6
  462. package/src/drivers/vault-infisical.ts +1 -5
  463. package/src/drivers/vault-managed.ts +1 -5
  464. package/src/drivers/vault-memory.ts +1 -5
  465. package/src/drivers/vault-openbao.test.ts +97 -0
  466. package/src/drivers/vault-openbao.ts +103 -40
  467. package/src/drivers/vault-types.ts +3 -16
  468. package/src/elements/ai/declare.ts +4 -14
  469. package/src/elements/ai/eval.ts +2 -6
  470. package/src/elements/ai/pii.ts +1 -3
  471. package/src/elements/ai/runtime.ts +12 -50
  472. package/src/elements/ai/schema.ts +4 -15
  473. package/src/elements/ai.test.ts +13 -24
  474. package/src/elements/ai.ts +1 -5
  475. package/src/elements/channel/consent.ts +3 -12
  476. package/src/elements/channel/costs.ts +4 -15
  477. package/src/elements/channel/declare.ts +1 -4
  478. package/src/elements/channel/email-auth.ts +1 -4
  479. package/src/elements/channel/locale.ts +2 -10
  480. package/src/elements/channel/mask.ts +1 -3
  481. package/src/elements/channel/mime.ts +2 -12
  482. package/src/elements/channel/outcomes.test.ts +1 -5
  483. package/src/elements/channel/outcomes.ts +5 -15
  484. package/src/elements/channel/receipts.ts +2 -7
  485. package/src/elements/channel/runtime.ts +28 -74
  486. package/src/elements/channel/suppression.ts +8 -14
  487. package/src/elements/channel.test.ts +8 -31
  488. package/src/elements/channel.ts +3 -14
  489. package/src/elements/clock/actions.ts +6 -18
  490. package/src/elements/clock/declare.ts +1 -3
  491. package/src/elements/clock/dst.ts +1 -5
  492. package/src/elements/clock/durable.ts +4 -16
  493. package/src/elements/clock/health.ts +2 -7
  494. package/src/elements/clock/leader.ts +2 -9
  495. package/src/elements/clock/reconcile.ts +4 -15
  496. package/src/elements/clock/runtime.ts +2 -5
  497. package/src/elements/clock/schedule.ts +4 -20
  498. package/src/elements/clock.test.ts +2 -7
  499. package/src/elements/clock.ts +5 -23
  500. package/src/elements/gate/permissions.ts +1 -4
  501. package/src/elements/gate/runtime.ts +7 -28
  502. package/src/elements/gate.ts +1 -5
  503. package/src/elements/index.ts +9 -7
  504. package/src/elements/signal/declare.ts +2 -7
  505. package/src/elements/signal/reconcile.test.ts +1 -4
  506. package/src/elements/signal/runtime.ts +1 -3
  507. package/src/elements/signal.test.ts +3 -15
  508. package/src/elements/signal.ts +2 -8
  509. package/src/elements/store/cache.test.ts +46 -0
  510. package/src/elements/store/cache.ts +3 -11
  511. package/src/elements/store/classify.ts +5 -22
  512. package/src/elements/store/declare.ts +11 -17
  513. package/src/elements/store/domain-ddl.test.ts +86 -0
  514. package/src/elements/store/emit-drizzle.ts +363 -0
  515. package/src/elements/store/files-policy.test.ts +1 -5
  516. package/src/elements/store/files-policy.ts +4 -12
  517. package/src/elements/store/load-plugin-tables.ts +103 -0
  518. package/src/elements/store/missing-relation.test.ts +83 -0
  519. package/src/elements/store/missing-relation.ts +38 -0
  520. package/src/elements/store/replica.ts +1 -4
  521. package/src/elements/store/resource-list-docs.fixture.ts +56 -0
  522. package/src/elements/store/resource-list-docs.test.ts +79 -0
  523. package/src/elements/store/resource.test.ts +253 -0
  524. package/src/elements/store/resource.ts +786 -0
  525. package/src/elements/store/runtime.ts +25 -28
  526. package/src/elements/store/schema-decl.test.ts +227 -0
  527. package/src/elements/store/schema-decl.ts +516 -0
  528. package/src/elements/store/sql-condition.test.ts +132 -0
  529. package/src/elements/store/sql-condition.ts +293 -65
  530. package/src/elements/store/sql-session.test.ts +135 -0
  531. package/src/elements/store/sql-session.ts +241 -84
  532. package/src/elements/store/table.ts +69 -23
  533. package/src/elements/store.test.ts +17 -39
  534. package/src/elements/store.ts +53 -31
  535. package/src/elements/vault/chain.ts +34 -0
  536. package/src/elements/vault/declare.ts +22 -32
  537. package/src/elements/vault/fingerprint.ts +2 -7
  538. package/src/elements/vault/redact.ts +2 -6
  539. package/src/elements/vault/runtime.ts +19 -33
  540. package/src/elements/vault.test.ts +12 -55
  541. package/src/elements/vault.ts +9 -13
  542. package/src/index.ts +5 -2
  543. package/src/kernel/adopt-routes.ts +15 -29
  544. package/src/kernel/app.ts +92 -118
  545. package/src/kernel/auth-resolve.ts +2 -9
  546. package/src/kernel/boot-bind/ai.ts +1 -4
  547. package/src/kernel/boot-bind/channel.test.ts +60 -0
  548. package/src/kernel/boot-bind/channel.ts +61 -5
  549. package/src/kernel/boot-bind/clock.ts +2 -6
  550. package/src/kernel/boot-bind/gate.ts +2 -8
  551. package/src/kernel/boot-bind/runs.ts +1 -3
  552. package/src/kernel/boot-bind/signal.ts +3 -10
  553. package/src/kernel/boot-bind/store.test.ts +27 -18
  554. package/src/kernel/boot-bind/store.ts +93 -52
  555. package/src/kernel/boot-bind/vault.ts +2 -8
  556. package/src/kernel/boot.test.ts +4 -10
  557. package/src/kernel/boot.ts +30 -77
  558. package/src/kernel/budget.test.ts +1 -3
  559. package/src/kernel/capability.ts +3 -10
  560. package/src/kernel/dry-run.test.ts +1 -4
  561. package/src/kernel/dry-run.ts +3 -11
  562. package/src/kernel/edge.test.ts +68 -0
  563. package/src/kernel/effects.test.ts +1 -5
  564. package/src/kernel/effects.ts +4 -11
  565. package/src/kernel/errors.registry.test.ts +2 -8
  566. package/src/kernel/errors.ts +23 -32
  567. package/src/kernel/flow.test.ts +12 -25
  568. package/src/kernel/flow.ts +13 -25
  569. package/src/kernel/fx.test.ts +31 -40
  570. package/src/kernel/fx.ts +135 -90
  571. package/src/kernel/hook-timing.test.ts +3 -6
  572. package/src/kernel/hook-timing.ts +5 -14
  573. package/src/kernel/hooks.test.ts +43 -10
  574. package/src/kernel/hooks.ts +25 -12
  575. package/src/kernel/index.ts +11 -17
  576. package/src/kernel/journal.ts +7 -31
  577. package/src/kernel/on.ts +45 -9
  578. package/src/kernel/pipeline.test.ts +10 -19
  579. package/src/kernel/pipeline.ts +5 -17
  580. package/src/kernel/plug.ts +4 -11
  581. package/src/kernel/plugin/conflicts.test.ts +13 -25
  582. package/src/kernel/plugin/decorate.test.ts +8 -13
  583. package/src/kernel/plugin/scoping.test.ts +4 -11
  584. package/src/kernel/plugin.ts +81 -27
  585. package/src/kernel/registry-isolation.test.ts +74 -0
  586. package/src/kernel/registry.ts +102 -30
  587. package/src/kernel/router.test.ts +15 -13
  588. package/src/kernel/router.ts +9 -29
  589. package/src/kernel/routing-budget.test.ts +1 -3
  590. package/src/kernel/run-telemetry.ts +1 -3
  591. package/src/kernel/triggers.ts +63 -21
  592. package/src/manifest/diff.test.ts +26 -91
  593. package/src/manifest/diff.ts +95 -225
  594. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  595. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  596. package/src/manifest/index.ts +3 -10
  597. package/src/manifest/types.ts +30 -6
  598. package/src/manifest/undeclared.test.ts +4 -12
  599. package/src/manifest/undeclared.ts +1 -2
  600. package/src/manifest/validate.test.ts +2 -6
  601. package/src/manifest/validate.ts +5 -15
  602. package/src/mcp/authorization.ts +8 -27
  603. package/src/mcp/confirmation.ts +3 -10
  604. package/src/mcp/data.ts +2 -6
  605. package/src/mcp/docs-index.ts +4 -7
  606. package/src/mcp/docs-mcp.test.ts +3 -12
  607. package/src/mcp/docs-server.ts +7 -27
  608. package/src/mcp/docs-tools.ts +2 -6
  609. package/src/mcp/index.ts +1 -6
  610. package/src/mcp/injection.gate.test.ts +4 -18
  611. package/src/mcp/mcp.test.ts +18 -49
  612. package/src/mcp/protocol.ts +3 -10
  613. package/src/mcp/server.ts +11 -52
  614. package/src/mcp/session.ts +4 -14
  615. package/src/mcp/tools.ts +14 -50
  616. package/src/plugins/catalogue.test.ts +3 -7
  617. package/src/plugins/catalogue.ts +1 -3
  618. package/src/plugins/compression.test.ts +127 -0
  619. package/src/plugins/compression.ts +94 -0
  620. package/src/plugins/config-source.test.ts +204 -0
  621. package/src/plugins/config-source.ts +209 -0
  622. package/src/plugins/cors.test.ts +138 -0
  623. package/src/plugins/cors.ts +129 -0
  624. package/src/plugins/csrf.test.ts +102 -0
  625. package/src/plugins/csrf.ts +86 -0
  626. package/src/plugins/headers.ts +54 -0
  627. package/src/plugins/index.ts +26 -0
  628. package/src/plugins/ip-allowlist.test.ts +105 -0
  629. package/src/plugins/ip-allowlist.ts +76 -0
  630. package/src/plugins/maintenance-mode.test.ts +91 -0
  631. package/src/plugins/maintenance-mode.ts +85 -0
  632. package/src/plugins/node-import-scan.ts +2 -5
  633. package/src/plugins/security-headers.test.ts +243 -0
  634. package/src/plugins/security-headers.ts +255 -0
  635. package/src/plugins/supply-chain.ts +5 -17
  636. package/src/release/exports.ts +3 -8
  637. package/src/release/measure.exports.test.ts +1 -3
  638. package/src/release/measure.ts +16 -45
  639. package/src/runs/bench.test.ts +1 -3
  640. package/src/runs/collect.ts +8 -25
  641. package/src/runs/drivers/clickhouse.ts +2 -9
  642. package/src/runs/drivers/files.ts +9 -31
  643. package/src/runs/drivers/memory.ts +5 -22
  644. package/src/runs/drivers/postgres.ts +8 -20
  645. package/src/runs/duckdb.ts +2 -8
  646. package/src/runs/index.ts +3 -14
  647. package/src/runs/outlier.ts +3 -10
  648. package/src/runs/parquet.ts +8 -26
  649. package/src/runs/privacy.ts +2 -8
  650. package/src/runs/runs.test.ts +3 -9
  651. package/src/runs/runtime.ts +7 -32
  652. package/src/runs/shred.ts +10 -29
  653. package/src/runs/types.ts +1 -4
  654. package/src/runtime/bun.ts +4 -15
  655. package/src/runtime/cold-start.bench.ts +1 -3
  656. package/src/runtime/dev-request-log.test.ts +1 -4
  657. package/src/runtime/dev-request-log.ts +1 -4
  658. package/src/runtime/index.ts +1 -6
  659. package/src/runtime/primitives.ts +5 -4
  660. package/src/runtime/security.ts +3 -13
  661. package/src/runtime/serve.test.ts +4 -7
  662. package/src/runtime/types.ts +1 -4
  663. package/src/runtime/web-standard.ts +3 -15
  664. package/src/term.test.ts +4 -6
  665. package/src/term.ts +49 -98
  666. package/src/test/create-test-app.test.ts +4 -14
  667. package/src/test/create-test-app.ts +27 -33
  668. package/src/test/provisions.integration.test.ts +3 -11
  669. package/src/upgrade/codemods.test.ts +25 -6
  670. package/src/upgrade/codemods.ts +116 -3
  671. package/src/validation/standard-schema.test.ts +2 -5
  672. package/src/validation/standard-schema.ts +13 -33
  673. package/docs/spec/console.md +0 -739
  674. package/docs/spec/example.md +0 -1196
  675. package/docs/spec/four-applications.md +0 -1195
  676. package/docs/spec/unified-theory.md +0 -489
  677. package/src/cli/doc-drift.test.ts +0 -146
  678. package/src/cli/doc-drift.ts +0 -430
  679. package/src/cli/doctor-diff-examples.ts +0 -92
  680. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +0 -1
  681. package/src/drivers/vault-sops.ts +0 -271
@@ -15,6 +15,7 @@ import type {
15
15
  AiPrompt,
16
16
  Channel,
17
17
  Clock,
18
+ DeclaredColumn,
18
19
  Effects,
19
20
  Flow,
20
21
  Gate,
@@ -66,6 +67,11 @@ interface ProjectScope {
66
67
  bindings: Map<string, InferBinding>;
67
68
  signals: Record<string, Signal>;
68
69
  stores: Record<string, Store>;
70
+ /** Binding or table name → declared columns from `store.schema.table`. */
71
+ schemaTables: Map<
72
+ string,
73
+ { readonly name: string; readonly columns: Record<string, DeclaredColumn> }
74
+ >;
69
75
  clocks: Record<string, Clock>;
70
76
  gates: Record<string, Gate>;
71
77
  /** Local binding name → gate manifest id (policy name or rate expression). */
@@ -82,6 +88,11 @@ interface ProjectScope {
82
88
  flows: Record<string, Flow>;
83
89
  /** Export name → flow id (for agent tools). */
84
90
  flowExports: Map<string, string>;
91
+ /** Local binding name → store.resource declaration (for on(http.resource)). */
92
+ resources: Map<
93
+ string,
94
+ { storeName: string; storeRef: string; unit?: string; breaking?: boolean }
95
+ >;
85
96
  }
86
97
 
87
98
  /**
@@ -89,9 +100,7 @@ interface ProjectScope {
89
100
  *
90
101
  * @param options - Root directory and/or explicit files
91
102
  */
92
- export async function extractManifest(
93
- options: ExtractManifestOptions = {},
94
- ): Promise<Manifest> {
103
+ export async function extractManifest(options: ExtractManifestOptions = {}): Promise<Manifest> {
95
104
  const files = options.files
96
105
  ? [...options.files]
97
106
  : await readSources(options.rootDir ?? ".", options.glob ?? "**/*.{ts,tsx}");
@@ -101,6 +110,7 @@ export async function extractManifest(
101
110
  bindings: new Map(),
102
111
  signals: {},
103
112
  stores: {},
113
+ schemaTables: new Map(),
104
114
  clocks: {},
105
115
  gates: {},
106
116
  gateIds: new Map(),
@@ -111,6 +121,7 @@ export async function extractManifest(
111
121
  drivers: {},
112
122
  flows: {},
113
123
  flowExports: new Map(),
124
+ resources: new Map(),
114
125
  };
115
126
 
116
127
  const parsed = files.map((file) => {
@@ -191,10 +202,7 @@ export async function extractFromSources(
191
202
  return extractManifest({ files, app });
192
203
  }
193
204
 
194
- async function readSources(
195
- rootDir: string,
196
- pattern: string,
197
- ): Promise<SourceFile[]> {
205
+ async function readSources(rootDir: string, pattern: string): Promise<SourceFile[]> {
198
206
  // Bun.Glob (global) — bare `import … from "bun"` is rejected by JSR.
199
207
  const glob = new Bun.Glob(pattern);
200
208
  const files: SourceFile[] = [];
@@ -273,13 +281,304 @@ function finalizeRefs(scope: ProjectScope): void {
273
281
  (id) => scope.flowExports.get(id) ?? scope.bindings.get(id)?.ref ?? id,
274
282
  );
275
283
  }
284
+
285
+ // Merge declared columns into store.tables (handles declare-after-sql order).
286
+ for (const store of Object.values(scope.stores)) {
287
+ if (!store.tables) continue;
288
+ for (const [name, table] of Object.entries(store.tables)) {
289
+ const declared = [...scope.schemaTables.values()].find((t) => t.name === name);
290
+ if (!declared) continue;
291
+ const existing = table.columns ?? {};
292
+ if (Object.keys(existing).length === 0) {
293
+ store.tables[name] = { ...table, columns: declared.columns };
294
+ }
295
+ }
296
+ }
297
+
298
+ // Orphan abstract tables (declared but not wired via store.sql schema):
299
+ // attach under the first sql store, or create a synthetic "app" sql store.
300
+ const attachedNames = new Set<string>();
301
+ for (const store of Object.values(scope.stores)) {
302
+ if (!store.tables) continue;
303
+ for (const name of Object.keys(store.tables)) attachedNames.add(name);
304
+ }
305
+ const orphanByName = new Map<string, { name: string; columns: Record<string, DeclaredColumn> }>();
306
+ for (const t of scope.schemaTables.values()) {
307
+ if (!attachedNames.has(t.name)) orphanByName.set(t.name, t);
308
+ }
309
+ if (orphanByName.size > 0) {
310
+ let target = Object.entries(scope.stores).find(([, s]) => s.facet === "sql")?.[1];
311
+ if (!target) {
312
+ scope.stores.app = scope.stores.app ?? { facet: "sql" };
313
+ target = scope.stores.app!;
314
+ }
315
+ target.tables = target.tables ?? {};
316
+ for (const t of orphanByName.values()) {
317
+ if (target.tables[t.name]) continue;
318
+ target.tables[t.name] = { columns: t.columns };
319
+ }
320
+ }
276
321
  }
277
322
 
278
- function visitDeclarationCall(
279
- call: CallExpression,
280
- program: AstNode,
323
+ function collectSchemaTable(call: CallExpression, program: AstNode, scope: ProjectScope): void {
324
+ const tableName = stringArg(call.arguments[0]);
325
+ if (!tableName) return;
326
+ const colsNode = objectArg(call.arguments[1]);
327
+ const columns = colsNode ? parseDeclaredColumns(colsNode) : {};
328
+ const entry = { name: tableName, columns };
329
+ scope.schemaTables.set(tableName, entry);
330
+ const bindingName = enclosingConstName(call, program);
331
+ if (bindingName) scope.schemaTables.set(bindingName, entry);
332
+ }
333
+
334
+ function attachSchemaOption(
335
+ optsNode: AstNode | undefined,
336
+ store: Store,
281
337
  scope: ProjectScope,
282
338
  ): void {
339
+ const opts = objectArg(optsNode);
340
+ if (!opts) return;
341
+ const schemaNode = objectProp(opts, "schema");
342
+ if (!schemaNode) return;
343
+
344
+ // schema: { notes } or schema: { notes: store.schema.table(...) }
345
+ if (schemaNode.type === "ObjectExpression") {
346
+ store.tables = store.tables ?? {};
347
+ for (const prop of objectProperties(schemaNode)) {
348
+ const key = propKey(prop);
349
+ const value = (prop as AstNode & { value?: AstNode }).value;
350
+ if (!key || !value) continue;
351
+
352
+ if (value.type === "Identifier") {
353
+ const id = (value as Identifier).name;
354
+ const declared = scope.schemaTables.get(id) ?? scope.schemaTables.get(key);
355
+ if (declared) {
356
+ store.tables[declared.name] = { columns: declared.columns };
357
+ } else {
358
+ store.tables[key] = store.tables[key] ?? {};
359
+ }
360
+ continue;
361
+ }
362
+
363
+ if (value.type === "CallExpression") {
364
+ const inline = parseInlineSchemaTable(value as CallExpression);
365
+ if (inline) {
366
+ store.tables[inline.name] = { columns: inline.columns };
367
+ scope.schemaTables.set(inline.name, inline);
368
+ }
369
+ }
370
+ }
371
+ return;
372
+ }
373
+
374
+ // schema: notesModule (identifier — unknown shape; skip columns)
375
+ if (schemaNode.type === "Identifier") {
376
+ // Namespace import — table names unknown at extract time.
377
+ }
378
+ }
379
+
380
+ function parseInlineSchemaTable(
381
+ call: CallExpression,
382
+ ): { name: string; columns: Record<string, DeclaredColumn> } | undefined {
383
+ const callee = call.callee;
384
+ if (callee.type !== "MemberExpression") return undefined;
385
+ const member = callee as AstNode & { object: AstNode; property: AstNode };
386
+ if (identifierName(member.property) !== "table") return undefined;
387
+ if (member.object.type !== "MemberExpression") return undefined;
388
+ const inner = member.object as AstNode & { object: AstNode; property: AstNode };
389
+ if (identifierName(inner.object) !== "store" || identifierName(inner.property) !== "schema") {
390
+ return undefined;
391
+ }
392
+ const tableName = stringArg(call.arguments[0]);
393
+ if (!tableName) return undefined;
394
+ const colsNode = objectArg(call.arguments[1]);
395
+ return {
396
+ name: tableName,
397
+ columns: colsNode ? parseDeclaredColumns(colsNode) : {},
398
+ };
399
+ }
400
+
401
+ function parseDeclaredColumns(obj: AstNode): Record<string, DeclaredColumn> {
402
+ const out: Record<string, DeclaredColumn> = {};
403
+ for (const prop of objectProperties(obj)) {
404
+ const key = propKey(prop);
405
+ const value = (prop as AstNode & { value?: AstNode }).value;
406
+ if (!key || !value) continue;
407
+ const col = parseFieldChain(value, key);
408
+ if (col) out[key] = col;
409
+ }
410
+ return out;
411
+ }
412
+
413
+ /**
414
+ * Walk `field.text().notNull().pii()` (and similar) into a DeclaredColumn.
415
+ *
416
+ * @param node - AST node for the column value
417
+ * @param key - JS key (for default sqlName)
418
+ */
419
+ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined {
420
+ const chain: string[] = [];
421
+ let sqlType: "text" | "integer" | undefined;
422
+ let sqlName: string | undefined;
423
+ let defaultValue: string | number | boolean | null | undefined;
424
+ let hasDefault = false;
425
+ let cur: AstNode | undefined = node;
426
+
427
+ while (cur && cur.type === "CallExpression") {
428
+ const call = cur as CallExpression;
429
+ const callee = call.callee;
430
+ if (callee.type === "MemberExpression") {
431
+ const member = callee as AstNode & { object: AstNode; property: AstNode };
432
+ const method = identifierName(member.property);
433
+ if (method) {
434
+ chain.push(method);
435
+ if (method === "as") {
436
+ sqlName = stringArg(call.arguments[0]) ?? sqlName;
437
+ }
438
+ if (method === "default") {
439
+ const lit = call.arguments[0];
440
+ if (lit && lit.type === "Literal") {
441
+ const v = (lit as Literal).value;
442
+ if (
443
+ v === null ||
444
+ typeof v === "string" ||
445
+ typeof v === "number" ||
446
+ typeof v === "boolean"
447
+ ) {
448
+ defaultValue = v as string | number | boolean | null;
449
+ hasDefault = true;
450
+ }
451
+ }
452
+ }
453
+ }
454
+ cur = member.object;
455
+ continue;
456
+ }
457
+ if (callee.type === "Identifier" && (callee as Identifier).name === "field") {
458
+ // shouldn't happen — field.text() is MemberExpression
459
+ break;
460
+ }
461
+ break;
462
+ }
463
+
464
+ // Remaining should be field.text / field.integer
465
+ if (cur && cur.type === "MemberExpression") {
466
+ const member = cur as AstNode & { object: AstNode; property: AstNode };
467
+ if (identifierName(member.object) === "field") {
468
+ const t = identifierName(member.property);
469
+ if (t === "text" || t === "integer") sqlType = t;
470
+ }
471
+ }
472
+
473
+ // Also: field.text() ends as CallExpression with callee MemberExpression field.text
474
+ if (!sqlType && node.type === "CallExpression") {
475
+ // Re-walk to find field.text() / field.integer() as a call
476
+ let probe: AstNode | undefined = node;
477
+ while (probe && probe.type === "CallExpression") {
478
+ const call = probe as CallExpression;
479
+ const callee = call.callee;
480
+ if (callee.type === "MemberExpression") {
481
+ const member = callee as AstNode & { object: AstNode; property: AstNode };
482
+ if (
483
+ identifierName(member.object) === "field" &&
484
+ (identifierName(member.property) === "text" ||
485
+ identifierName(member.property) === "integer")
486
+ ) {
487
+ sqlType = identifierName(member.property) as "text" | "integer";
488
+ break;
489
+ }
490
+ probe = member.object;
491
+ continue;
492
+ }
493
+ break;
494
+ }
495
+ }
496
+
497
+ if (!sqlType) return undefined;
498
+
499
+ const methods = new Set(chain);
500
+ const col: DeclaredColumn = {
501
+ type: sqlType,
502
+ sqlName: sqlName ?? camelToSnakeKey(key),
503
+ nullable: !(methods.has("notNull") || methods.has("primaryKey")),
504
+ ...(methods.has("primaryKey") ? { primaryKey: true } : {}),
505
+ ...(methods.has("unique") ? { unique: true } : {}),
506
+ ...(hasDefault ? { default: defaultValue ?? null } : {}),
507
+ ...(methods.has("pii") ? { pii: true } : {}),
508
+ ...(methods.has("sensitive") ? { sensitive: true } : {}),
509
+ };
510
+ if (methods.has("retain")) {
511
+ // retain("7y") — find the call in the original chain by re-walk
512
+ let probe: AstNode | undefined = node;
513
+ while (probe && probe.type === "CallExpression") {
514
+ const call = probe as CallExpression;
515
+ const callee = call.callee;
516
+ if (callee.type === "MemberExpression") {
517
+ const member = callee as AstNode & { object: AstNode; property: AstNode };
518
+ if (identifierName(member.property) === "retain") {
519
+ const dur = stringArg(call.arguments[0]);
520
+ if (dur) col.retain = dur;
521
+ break;
522
+ }
523
+ probe = member.object;
524
+ continue;
525
+ }
526
+ break;
527
+ }
528
+ }
529
+ if (methods.has("references")) {
530
+ // .references(() => links.code) — record table.column when statically readable
531
+ let probe: AstNode | undefined = node;
532
+ while (probe && probe.type === "CallExpression") {
533
+ const call = probe as CallExpression;
534
+ const callee = call.callee;
535
+ if (callee.type === "MemberExpression") {
536
+ const member = callee as AstNode & { object: AstNode; property: AstNode };
537
+ if (identifierName(member.property) === "references") {
538
+ const ref = parseReferencesArg(call.arguments[0]);
539
+ if (ref) col.references = ref;
540
+ break;
541
+ }
542
+ probe = member.object;
543
+ continue;
544
+ }
545
+ break;
546
+ }
547
+ }
548
+ return col;
549
+ }
550
+
551
+ /**
552
+ * Parse `.references(() => table.column)` into a Manifest reference.
553
+ *
554
+ * @param arg - First argument to `.references(...)`
555
+ */
556
+ function parseReferencesArg(
557
+ arg: AstNode | undefined,
558
+ ): { table?: string; column?: string } | undefined {
559
+ if (!arg) return undefined;
560
+ // ArrowFunctionExpression: () => links.code
561
+ if (arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression") {
562
+ const fn = arg as AstNode & { body: AstNode };
563
+ const body = fn.body;
564
+ if (body.type === "MemberExpression") {
565
+ const member = body as AstNode & { object: AstNode; property: AstNode };
566
+ const table = identifierName(member.object);
567
+ const column = identifierName(member.property);
568
+ if (table || column) return { ...(table ? { table } : {}), ...(column ? { column } : {}) };
569
+ }
570
+ }
571
+ return {};
572
+ }
573
+
574
+ function camelToSnakeKey(key: string): string {
575
+ return key
576
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
577
+ .replace(/([A-Z])([A-Z][a-z])/g, "$1_$2")
578
+ .toLowerCase();
579
+ }
580
+
581
+ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: ProjectScope): void {
283
582
  const callee = call.callee;
284
583
 
285
584
  // store.sql("name") / store.kv(...) / …
@@ -299,6 +598,9 @@ function visitDeclarationCall(
299
598
  const ref = `${facet}:${storeName}` as const;
300
599
  const bindingName = enclosingConstName(call, program);
301
600
  scope.stores[storeName] = scope.stores[storeName] ?? { facet };
601
+ if (facet === "sql") {
602
+ attachSchemaOption(call.arguments[1], scope.stores[storeName]!, scope);
603
+ }
302
604
  if (bindingName) {
303
605
  scope.bindings.set(bindingName, {
304
606
  kind: "store",
@@ -307,6 +609,35 @@ function visitDeclarationCall(
307
609
  });
308
610
  }
309
611
  }
612
+
613
+ // store.resource(db, table, { … }) — remember for on(http.resource(…))
614
+ if (prop === "resource") {
615
+ const bindingName = enclosingConstName(call, program);
616
+ const dbName = identifierName(call.arguments[0]);
617
+ const storeBinding = dbName ? scope.bindings.get(dbName) : undefined;
618
+ const ref =
619
+ storeBinding?.kind === "store" ? storeBinding.ref : (`sql:${dbName ?? "store"}` as const);
620
+ const storeName = ref.split(":")[1] ?? dbName ?? "store";
621
+ const unit = stringProp(objectArg(call.arguments[2]) ?? ({} as never), "unit");
622
+ const opts = objectArg(call.arguments[2]);
623
+ const breaking = opts ? boolProp(opts, "breaking") === true : false;
624
+ if (bindingName) {
625
+ scope.resources.set(bindingName, {
626
+ storeName,
627
+ storeRef: ref,
628
+ unit,
629
+ ...(breaking ? { breaking: true } : {}),
630
+ });
631
+ }
632
+ }
633
+ }
634
+
635
+ // store.schema.table("notes", { … })
636
+ if (prop === "table" && member.object.type === "MemberExpression") {
637
+ const inner = member.object as AstNode & { object: AstNode; property: AstNode };
638
+ if (identifierName(inner.object) === "store" && identifierName(inner.property) === "schema") {
639
+ collectSchemaTable(call, program, scope);
640
+ }
310
641
  }
311
642
 
312
643
  if (obj === "gate" && prop === "policy") {
@@ -326,8 +657,7 @@ function visitDeclarationCall(
326
657
 
327
658
  if (obj === "gate" && prop === "rate") {
328
659
  const opts = objectArg(call.arguments[0]);
329
- const strategy = (stringProp(opts, "strategy") ??
330
- "sliding-window-counter") as RateStrategy;
660
+ const strategy = (stringProp(opts, "strategy") ?? "sliding-window-counter") as RateStrategy;
331
661
  const max = numberProp(opts, "max");
332
662
  const per = stringProp(opts, "per");
333
663
  const keyBy = stringProp(opts, "keyBy");
@@ -362,9 +692,7 @@ function visitDeclarationCall(
362
692
  const medium = stringProp(opts, "medium");
363
693
  const locales = stringArrayProp(opts, "locales");
364
694
  scope.channels[templateName] = {
365
- ...(medium
366
- ? { medium: medium as Channel["medium"] }
367
- : { medium: "email" }),
695
+ ...(medium ? { medium: medium as Channel["medium"] } : { medium: "email" }),
368
696
  ...(locales ? { locales } : {}),
369
697
  };
370
698
  const bindingName = enclosingConstName(call, program);
@@ -382,12 +710,8 @@ function visitDeclarationCall(
382
710
  const opts = objectArg(call.arguments[1]);
383
711
  if (modelName) {
384
712
  const model: AiModel = {
385
- ...(stringProp(opts, "provider")
386
- ? { provider: stringProp(opts, "provider") }
387
- : {}),
388
- ...(stringProp(opts, "tier")
389
- ? { tier: stringProp(opts, "tier") }
390
- : {}),
713
+ ...(stringProp(opts, "provider") ? { provider: stringProp(opts, "provider") } : {}),
714
+ ...(stringProp(opts, "tier") ? { tier: stringProp(opts, "tier") } : {}),
391
715
  };
392
716
  scope.ai.models = scope.ai.models ?? {};
393
717
  scope.ai.models[modelName] = model;
@@ -463,9 +787,7 @@ function visitDeclarationCall(
463
787
  ...(stringProp(opts, "description")
464
788
  ? { description: stringProp(opts, "description") }
465
789
  : {}),
466
- ...(stringProp(opts, "rotate")
467
- ? { rotate: stringProp(opts, "rotate") }
468
- : {}),
790
+ ...(stringProp(opts, "rotate") ? { rotate: stringProp(opts, "rotate") } : {}),
469
791
  };
470
792
  const bindingName = enclosingConstName(call, program);
471
793
  if (bindingName) {
@@ -512,9 +834,7 @@ function visitDeclarationCall(
512
834
  const opts = objectArg(call.arguments[1]);
513
835
  if (name && opts) {
514
836
  scope.clocks[name] = {
515
- ...(stringProp(opts, "every")
516
- ? { every: stringProp(opts, "every") }
517
- : {}),
837
+ ...(stringProp(opts, "every") ? { every: stringProp(opts, "every") } : {}),
518
838
  ...(stringProp(opts, "cron") ? { cron: stringProp(opts, "cron") } : {}),
519
839
  ...(boolProp(opts, "overridable") !== undefined
520
840
  ? { overridable: boolProp(opts, "overridable") }
@@ -547,9 +867,7 @@ function visitDeclarationCall(
547
867
  },
548
868
  }
549
869
  : {}),
550
- ...(stringProp(opts, "composes")
551
- ? { composes: stringProp(opts, "composes") }
552
- : {}),
870
+ ...(stringProp(opts, "composes") ? { composes: stringProp(opts, "composes") } : {}),
553
871
  ...(flows && flows.length > 0 ? { flows } : {}),
554
872
  };
555
873
  scope.journeys[name] = journey;
@@ -570,9 +888,7 @@ function visitDeclarationCall(
570
888
  ...(stringProp(opts, "description")
571
889
  ? { description: stringProp(opts, "description") }
572
890
  : {}),
573
- ...(stringProp(opts, "rotate")
574
- ? { rotate: stringProp(opts, "rotate") }
575
- : {}),
891
+ ...(stringProp(opts, "rotate") ? { rotate: stringProp(opts, "rotate") } : {}),
576
892
  };
577
893
  const bindingName = enclosingConstName(call, program);
578
894
  if (bindingName) {
@@ -601,20 +917,13 @@ function collectAgent(call: CallExpression, scope: ProjectScope): void {
601
917
  scope.ai.agents[agentName] = agent;
602
918
  }
603
919
 
604
- function collectConfig(
605
- opts: AstNode | undefined,
606
- scope: ProjectScope,
607
- ): void {
920
+ function collectConfig(opts: AstNode | undefined, scope: ProjectScope): void {
608
921
  if (!opts) return;
609
922
 
610
923
  const tenancy = objectProp(opts, "tenancy");
611
924
  if (tenancy) {
612
925
  const isolation = stringProp(tenancy, "isolation");
613
- if (
614
- isolation === "row" ||
615
- isolation === "schema" ||
616
- isolation === "database"
617
- ) {
926
+ if (isolation === "row" || isolation === "schema" || isolation === "database") {
618
927
  scope.tenancy = { isolation };
619
928
  }
620
929
  }
@@ -622,21 +931,15 @@ function collectConfig(
622
931
  const i18n = objectProp(opts, "i18n");
623
932
  if (i18n) {
624
933
  scope.i18n = {
625
- ...(stringArrayProp(i18n, "locales")
626
- ? { locales: stringArrayProp(i18n, "locales") }
627
- : {}),
628
- ...(stringProp(i18n, "default")
629
- ? { default: stringProp(i18n, "default") }
630
- : {}),
934
+ ...(stringArrayProp(i18n, "locales") ? { locales: stringArrayProp(i18n, "locales") } : {}),
935
+ ...(stringProp(i18n, "default") ? { default: stringProp(i18n, "default") } : {}),
631
936
  };
632
937
  const dir = objectProp(i18n, "dir");
633
938
  if (dir && dir.type === "ObjectExpression") {
634
939
  const dirMap: Record<string, "ltr" | "rtl"> = {};
635
940
  for (const prop of objectProperties(dir)) {
636
941
  const key = propKey(prop);
637
- const val = stringArg(
638
- (prop as AstNode & { value?: AstNode }).value,
639
- );
942
+ const val = stringArg((prop as AstNode & { value?: AstNode }).value);
640
943
  if (key && (val === "ltr" || val === "rtl")) dirMap[key] = val;
641
944
  }
642
945
  if (Object.keys(dirMap).length > 0) {
@@ -666,9 +969,7 @@ function collectConfig(
666
969
  if (drivers) {
667
970
  const prodArr = arrayProp(drivers, "prod");
668
971
  if (prodArr && prodArr.length > 0 && prodArr.every((el) => stringArg(el))) {
669
- scope.drivers.prod = prodArr
670
- .map((el) => stringArg(el)!)
671
- .filter((x) => x.length > 0);
972
+ scope.drivers.prod = prodArr.map((el) => stringArg(el)!).filter((x) => x.length > 0);
672
973
  } else {
673
974
  const prod = new Set<string>();
674
975
  collectDriverProtocols(drivers, "prod", prod);
@@ -677,11 +978,7 @@ function collectConfig(
677
978
  }
678
979
  }
679
980
 
680
- function collectDriverProtocols(
681
- node: AstNode,
682
- env: string,
683
- into: Set<string>,
684
- ): void {
981
+ function collectDriverProtocols(node: AstNode, env: string, into: Set<string>): void {
685
982
  if (node.type !== "ObjectExpression") return;
686
983
  for (const prop of objectProperties(node)) {
687
984
  const key = propKey(prop);
@@ -704,10 +1001,7 @@ function collectDriverProtocols(
704
1001
  // role: { dev, test, prod } or nested store: { sql: { prod } }
705
1002
  const envVal = objectProp(value, env);
706
1003
  if (envVal) {
707
- if (
708
- envVal.type === "Literal" &&
709
- typeof (envVal as Literal).value === "string"
710
- ) {
1004
+ if (envVal.type === "Literal" && typeof (envVal as Literal).value === "string") {
711
1005
  into.add((envVal as Literal).value as string);
712
1006
  } else if (envVal.type === "ObjectExpression") {
713
1007
  const driver = stringProp(envVal, "driver");
@@ -720,11 +1014,7 @@ function collectDriverProtocols(
720
1014
  }
721
1015
  }
722
1016
 
723
- function collectFlows(
724
- file: SourceFile,
725
- program: AstNode,
726
- scope: ProjectScope,
727
- ): void {
1017
+ function collectFlows(file: SourceFile, program: AstNode, scope: ProjectScope): void {
728
1018
  walk(program, (node) => {
729
1019
  if (node.type !== "CallExpression") return;
730
1020
  const call = node as CallExpression;
@@ -733,7 +1023,14 @@ function collectFlows(
733
1023
  if (callee === "on") {
734
1024
  const triggerNode = call.arguments[0];
735
1025
  const flowNode = call.arguments[1];
736
- if (!triggerNode || !flowNode) return;
1026
+ if (!triggerNode) return;
1027
+
1028
+ // on(http.resource(path, bag)) — expand the mount into five bindings.
1029
+ if (!flowNode) {
1030
+ registerResourceMount(call, triggerNode, file, program, scope);
1031
+ return;
1032
+ }
1033
+
737
1034
  const flowCall = unwrapFlowCall(flowNode);
738
1035
  if (!flowCall) return;
739
1036
  const exportName = enclosingConstName(call, program);
@@ -780,7 +1077,10 @@ function isOnFlowArgument(flowCall: CallExpression, program: AstNode): boolean {
780
1077
  }
781
1078
 
782
1079
  function unwrapFlowCall(node: AstNode): CallExpression | undefined {
783
- if (node.type === "CallExpression" && identifierName((node as CallExpression).callee) === "flow") {
1080
+ if (
1081
+ node.type === "CallExpression" &&
1082
+ identifierName((node as CallExpression).callee) === "flow"
1083
+ ) {
784
1084
  return node as CallExpression;
785
1085
  }
786
1086
  return undefined;
@@ -845,15 +1145,11 @@ function registerFlow(args: {
845
1145
  }
846
1146
  }
847
1147
 
848
- const trigger = args.triggerNode
849
- ? parseTrigger(args.triggerNode, args.scope)
850
- : undefined;
1148
+ const trigger = args.triggerNode ? parseTrigger(args.triggerNode, args.scope) : undefined;
851
1149
 
852
1150
  const gates = trigger?.gates;
853
1151
  const liveFromTrigger = trigger?.live;
854
- const manifestTrigger = trigger
855
- ? stripTriggerExtras(trigger.trigger)
856
- : undefined;
1152
+ const manifestTrigger = trigger ? stripTriggerExtras(trigger.trigger) : undefined;
857
1153
 
858
1154
  const flow: Flow = {};
859
1155
 
@@ -877,15 +1173,13 @@ function registerFlow(args: {
877
1173
 
878
1174
  if (boolProp(opts, "durable")) flow.durable = true;
879
1175
  if (boolProp(opts, "live") || liveFromTrigger) flow.live = true;
1176
+ if (boolProp(opts, "breaking")) flow.breaking = true;
880
1177
 
881
1178
  const slo = parseSlo(objectProp(opts, "slo"));
882
1179
  if (slo) flow.slo = slo;
883
1180
 
884
1181
  const stepsFromOpts = stringArrayProp(opts, "steps");
885
- const steps =
886
- stepsFromOpts && stepsFromOpts.length > 0
887
- ? stepsFromOpts
888
- : inferred.steps;
1182
+ const steps = stepsFromOpts && stepsFromOpts.length > 0 ? stepsFromOpts : inferred.steps;
889
1183
  if (steps.length > 0) flow.steps = steps;
890
1184
 
891
1185
  if (boolProp(opts, "nondeterministic") || inferred.nondeterministic) {
@@ -898,17 +1192,14 @@ function registerFlow(args: {
898
1192
  ...(numberProp(cost, "estimatePerCall") !== undefined
899
1193
  ? { estimatePerCall: numberProp(cost, "estimatePerCall") }
900
1194
  : {}),
901
- ...(numberProp(cost, "budget") !== undefined
902
- ? { budget: numberProp(cost, "budget") }
903
- : {}),
1195
+ ...(numberProp(cost, "budget") !== undefined ? { budget: numberProp(cost, "budget") } : {}),
904
1196
  };
905
1197
  } else if (inferred.nondeterministic) {
906
1198
  // Derive budget from the first ask prompt when present.
907
1199
  const ask = effects?.asks?.[0];
908
1200
  if (ask) {
909
1201
  const promptName = ask.split("@")[0]!;
910
- const promptBudget = args.scope.ai.prompts?.[promptName]?.budget
911
- ?.maxCostPerCall;
1202
+ const promptBudget = args.scope.ai.prompts?.[promptName]?.budget?.maxCostPerCall;
912
1203
  if (promptBudget !== undefined) {
913
1204
  flow.cost = {
914
1205
  estimatePerCall: Number((promptBudget * 0.55).toFixed(3)),
@@ -935,8 +1226,7 @@ function registerFlow(args: {
935
1226
  if (inferred.cacheIneligible) {
936
1227
  flow.cache = false;
937
1228
  } else {
938
- const cache = (opts &&
939
- objectProperties(opts).find((p) => propKey(p) === "cache")) as
1229
+ const cache = (opts && objectProperties(opts).find((p) => propKey(p) === "cache")) as
940
1230
  | AstNode
941
1231
  | undefined;
942
1232
  if (cache) {
@@ -958,6 +1248,93 @@ function registerFlow(args: {
958
1248
  args.scope.flows[name] = flow;
959
1249
  }
960
1250
 
1251
+ /**
1252
+ * Expand `on(http.resource(path, bag))` into the five CRUD flows.
1253
+ *
1254
+ * The flows are synthesized at runtime by `store.resource(…)`; statically we
1255
+ * register list/create/get/update/remove with their HTTP triggers and the
1256
+ * resource's store effects so the Manifest stays complete.
1257
+ *
1258
+ * @param onCall - The outer `on(…)` call
1259
+ * @param triggerNode - `http.resource(…)` expression
1260
+ * @param file - Source file
1261
+ * @param program - Program root (for export-name lookup)
1262
+ * @param scope - Project scope
1263
+ */
1264
+ function registerResourceMount(
1265
+ onCall: CallExpression,
1266
+ triggerNode: AstNode,
1267
+ file: SourceFile,
1268
+ program: AstNode,
1269
+ scope: ProjectScope,
1270
+ ): void {
1271
+ if (triggerNode.type !== "CallExpression") return;
1272
+ const httpCall = triggerNode as CallExpression;
1273
+ const callee = httpCall.callee;
1274
+ if (callee.type !== "MemberExpression") return;
1275
+ const member = callee as AstNode & { object: AstNode; property: AstNode };
1276
+ if (identifierName(member.object) !== "http") return;
1277
+ if (identifierName(member.property) !== "resource") return;
1278
+
1279
+ const path = stringArg(httpCall.arguments[0]);
1280
+ if (!path) return;
1281
+
1282
+ // bag: `notesR.all()` → resolve `notesR` to its store.resource declaration.
1283
+ const bagNode = httpCall.arguments[1];
1284
+ let baseName: string | undefined;
1285
+ if (bagNode?.type === "CallExpression") {
1286
+ const bagCallee = (bagNode as CallExpression).callee;
1287
+ if (bagCallee.type === "MemberExpression") {
1288
+ baseName = identifierName((bagCallee as AstNode & { object: AstNode }).object);
1289
+ }
1290
+ } else if (bagNode?.type === "Identifier") {
1291
+ baseName = identifierName(bagNode);
1292
+ }
1293
+ const resource = baseName ? scope.resources.get(baseName) : undefined;
1294
+
1295
+ const unit = resource?.unit;
1296
+ const storeRef = resource?.storeRef as Effects["reads"] extends readonly (infer R)[] | undefined
1297
+ ? R
1298
+ : never;
1299
+ const effects: Effects | undefined = storeRef
1300
+ ? { reads: [storeRef], writes: [storeRef] }
1301
+ : undefined;
1302
+ const idPath = `${path}/:id`;
1303
+ const line = lineAt(file.source, onCall.start ?? 0);
1304
+
1305
+ const verbs = [
1306
+ { op: "list", method: "GET", p: path, eff: storeRef ? { reads: [storeRef] } : undefined },
1307
+ { op: "create", method: "POST", p: path, eff: storeRef ? { writes: [storeRef] } : undefined },
1308
+ { op: "get", method: "GET", p: idPath, eff: storeRef ? { reads: [storeRef] } : undefined },
1309
+ { op: "update", method: "PATCH", p: idPath, eff: effects },
1310
+ {
1311
+ op: "remove",
1312
+ method: "DELETE",
1313
+ p: idPath,
1314
+ eff: storeRef ? { writes: [storeRef] } : undefined,
1315
+ },
1316
+ ] as const;
1317
+
1318
+ for (const v of verbs) {
1319
+ // Short op names match `export const list = mounted.list` so Manifest Diff
1320
+ // keeps flow identity across a handwritten → store.resource migration.
1321
+ const name = v.op;
1322
+ const flow: Flow = {
1323
+ trigger: { http: { method: v.method as never, path: v.p } },
1324
+ ...(v.eff ? { effects: v.eff as Effects } : {}),
1325
+ ...(resource?.breaking ? { breaking: true } : {}),
1326
+ source: `${file.path}:${line}`,
1327
+ };
1328
+ scope.flows[name] = flow;
1329
+ scope.bindings.set(name, { kind: "flow", ref: name });
1330
+ }
1331
+
1332
+ const exportName = enclosingConstName(onCall, program);
1333
+ if (exportName) {
1334
+ scope.bindings.set(exportName, { kind: "flow", ref: unit ?? baseName ?? exportName });
1335
+ }
1336
+ }
1337
+
961
1338
  interface ParsedTrigger {
962
1339
  trigger: Trigger;
963
1340
  gates?: string[];
@@ -1002,10 +1379,7 @@ function parseTrigger(node: AstNode, scope: ProjectScope): ParsedTrigger | undef
1002
1379
  return undefined;
1003
1380
  }
1004
1381
 
1005
- function parseHttpTrigger(
1006
- call: CallExpression,
1007
- scope: ProjectScope,
1008
- ): ParsedTrigger | undefined {
1382
+ function parseHttpTrigger(call: CallExpression, scope: ProjectScope): ParsedTrigger | undefined {
1009
1383
  // Walk the chain: http.METHOD(path).gate(...).live()
1010
1384
  let current: AstNode = call;
1011
1385
  let method: string | undefined;
@@ -1059,15 +1433,7 @@ function parseHttpTrigger(
1059
1433
 
1060
1434
  if (!method || !path) return undefined;
1061
1435
 
1062
- const httpMethods = [
1063
- "GET",
1064
- "POST",
1065
- "PUT",
1066
- "PATCH",
1067
- "DELETE",
1068
- "HEAD",
1069
- "OPTIONS",
1070
- ] as const;
1436
+ const httpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] as const;
1071
1437
  if (!(httpMethods as readonly string[]).includes(method)) return undefined;
1072
1438
 
1073
1439
  return {
@@ -1082,9 +1448,7 @@ function parseHttpTrigger(
1082
1448
  };
1083
1449
  }
1084
1450
 
1085
- function parseCdcTrigger(
1086
- call: CallExpression,
1087
- ): Trigger["cdc"] | undefined {
1451
+ function parseCdcTrigger(call: CallExpression): Trigger["cdc"] | undefined {
1088
1452
  // *.changed("col") or *.changed()
1089
1453
  const callee = call.callee;
1090
1454
  if (callee.type !== "MemberExpression") return undefined;
@@ -1098,25 +1462,17 @@ function parseCdcTrigger(
1098
1462
  const inner = obj as CallExpression;
1099
1463
  const innerCallee = inner.callee;
1100
1464
  if (identifierName(innerCallee) === "table") {
1101
- const tableName =
1102
- stringArg(inner.arguments[0]) ?? identifierName(inner.arguments[0]);
1465
+ const tableName = stringArg(inner.arguments[0]) ?? identifierName(inner.arguments[0]);
1103
1466
  if (!tableName) return undefined;
1104
- return column
1105
- ? { table: tableName, column }
1106
- : { table: tableName };
1467
+ return column ? { table: tableName, column } : { table: tableName };
1107
1468
  }
1108
1469
  if (
1109
1470
  innerCallee.type === "MemberExpression" &&
1110
- identifierName(
1111
- (innerCallee as AstNode & { property: AstNode }).property,
1112
- ) === "table"
1471
+ identifierName((innerCallee as AstNode & { property: AstNode }).property) === "table"
1113
1472
  ) {
1114
- const tableName =
1115
- stringArg(inner.arguments[0]) ?? identifierName(inner.arguments[0]);
1473
+ const tableName = stringArg(inner.arguments[0]) ?? identifierName(inner.arguments[0]);
1116
1474
  if (!tableName) return undefined;
1117
- return column
1118
- ? { table: tableName, column }
1119
- : { table: tableName };
1475
+ return column ? { table: tableName, column } : { table: tableName };
1120
1476
  }
1121
1477
  }
1122
1478
  return undefined;
@@ -1146,9 +1502,7 @@ function parseEffectsObject(node: AstNode | undefined): Effects | undefined {
1146
1502
  return effects;
1147
1503
  }
1148
1504
 
1149
- function parseErrors(
1150
- node: AstNode | undefined,
1151
- ): Flow["errors"] | undefined {
1505
+ function parseErrors(node: AstNode | undefined): Flow["errors"] | undefined {
1152
1506
  if (!node) return undefined;
1153
1507
  if (node.type === "ObjectExpression") {
1154
1508
  const names: string[] = [];
@@ -1187,10 +1541,7 @@ function parseSlo(node: AstNode | undefined): Slo | undefined {
1187
1541
  };
1188
1542
  }
1189
1543
 
1190
- function schemaProp(
1191
- obj: AstNode | undefined,
1192
- key: string,
1193
- ): string | undefined {
1544
+ function schemaProp(obj: AstNode | undefined, key: string): string | undefined {
1194
1545
  const node = objectProp(obj, key);
1195
1546
  if (!node) return undefined;
1196
1547
  // Identifier / member schemas → opaque placeholder matching the spec excerpt.
@@ -1205,10 +1556,7 @@ function schemaProp(
1205
1556
 
1206
1557
  // ── AST helpers ────────────────────────────────────────────────────────────
1207
1558
 
1208
- function enclosingConstName(
1209
- call: CallExpression,
1210
- program: AstNode,
1211
- ): string | undefined {
1559
+ function enclosingConstName(call: CallExpression, program: AstNode): string | undefined {
1212
1560
  const targetStart = call.start;
1213
1561
  if (targetStart === undefined) return undefined;
1214
1562
  let found: string | undefined;
@@ -1239,10 +1587,7 @@ function objectArg(node: AstNode | undefined): AstNode | undefined {
1239
1587
  return undefined;
1240
1588
  }
1241
1589
 
1242
- function objectProp(
1243
- obj: AstNode | undefined,
1244
- key: string,
1245
- ): AstNode | undefined {
1590
+ function objectProp(obj: AstNode | undefined, key: string): AstNode | undefined {
1246
1591
  if (!obj || obj.type !== "ObjectExpression") return undefined;
1247
1592
  for (const prop of objectProperties(obj)) {
1248
1593
  if (propKey(prop) === key) {
@@ -1253,9 +1598,9 @@ function objectProp(
1253
1598
  }
1254
1599
 
1255
1600
  function objectProperties(obj: AstNode): AstNode[] {
1256
- return (
1257
- (obj as AstNode & { properties?: AstNode[] }).properties ?? []
1258
- ).filter((p) => p.type === "Property" || p.type === "ObjectProperty");
1601
+ return ((obj as AstNode & { properties?: AstNode[] }).properties ?? []).filter(
1602
+ (p) => p.type === "Property" || p.type === "ObjectProperty",
1603
+ );
1259
1604
  }
1260
1605
 
1261
1606
  function propKey(prop: AstNode): string | undefined {
@@ -1268,50 +1613,33 @@ function propKey(prop: AstNode): string | undefined {
1268
1613
  return undefined;
1269
1614
  }
1270
1615
 
1271
- function stringProp(
1272
- obj: AstNode | undefined,
1273
- key: string,
1274
- ): string | undefined {
1616
+ function stringProp(obj: AstNode | undefined, key: string): string | undefined {
1275
1617
  return stringArg(objectProp(obj, key));
1276
1618
  }
1277
1619
 
1278
- function numberProp(
1279
- obj: AstNode | undefined,
1280
- key: string,
1281
- ): number | undefined {
1620
+ function numberProp(obj: AstNode | undefined, key: string): number | undefined {
1282
1621
  const node = objectProp(obj, key);
1283
1622
  if (!node || node.type !== "Literal") return undefined;
1284
1623
  const v = (node as Literal).value;
1285
1624
  return typeof v === "number" ? v : undefined;
1286
1625
  }
1287
1626
 
1288
- function boolProp(
1289
- obj: AstNode | undefined,
1290
- key: string,
1291
- ): boolean | undefined {
1627
+ function boolProp(obj: AstNode | undefined, key: string): boolean | undefined {
1292
1628
  const node = objectProp(obj, key);
1293
1629
  if (!node || node.type !== "Literal") return undefined;
1294
1630
  const v = (node as Literal).value;
1295
1631
  return typeof v === "boolean" ? v : undefined;
1296
1632
  }
1297
1633
 
1298
- function stringArrayProp(
1299
- obj: AstNode | undefined,
1300
- key: string,
1301
- ): string[] | undefined {
1634
+ function stringArrayProp(obj: AstNode | undefined, key: string): string[] | undefined {
1302
1635
  const node = objectProp(obj, key);
1303
1636
  if (!node || node.type !== "ArrayExpression") return undefined;
1304
1637
  const els = (node as AstNode & { elements?: AstNode[] }).elements ?? [];
1305
- const out = els
1306
- .map((el) => stringArg(el))
1307
- .filter((x): x is string => typeof x === "string");
1638
+ const out = els.map((el) => stringArg(el)).filter((x): x is string => typeof x === "string");
1308
1639
  return out;
1309
1640
  }
1310
1641
 
1311
- function arrayProp(
1312
- obj: AstNode | undefined,
1313
- key: string,
1314
- ): AstNode[] | undefined {
1642
+ function arrayProp(obj: AstNode | undefined, key: string): AstNode[] | undefined {
1315
1643
  const node = objectProp(obj, key);
1316
1644
  if (!node || node.type !== "ArrayExpression") return undefined;
1317
1645
  return ((node as AstNode & { elements?: AstNode[] }).elements ?? []).filter(