@voltro/cli 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/CHANGELOG.md +1968 -0
  2. package/bin/nodeEnvironment.d.mts +30 -0
  3. package/bin/nodeEnvironment.mjs +158 -0
  4. package/bin/voltro.mjs +69 -5
  5. package/dist/addCommand-BNeoeSxe.js +124 -0
  6. package/dist/addCommand-aXSQveak.js +2 -0
  7. package/dist/agentsMd-BTchIZku.js +2 -0
  8. package/dist/agentsMd-mhQMF1bx.js +254 -0
  9. package/dist/apiBuild-B8aoJvuw.js +2 -0
  10. package/dist/{apiBuild-h9VHtnlw.js → apiBuild-DYD_ONLD.js} +46 -46
  11. package/dist/{appGraph-gQ_6GkQQ.js → appGraph-KGDPTuTy.js} +1 -1
  12. package/dist/appGraph-zuMGKVYX.js +2 -0
  13. package/dist/appPort-B_HpJ_ck.js +48 -0
  14. package/dist/baselineCommand-C2ClWZN3.js +2 -0
  15. package/dist/baselineCommand-DIttzO8A.js +227 -0
  16. package/dist/bin.js +71 -28
  17. package/dist/build-CD8K4XOr.js +711 -0
  18. package/dist/cacheCommand-DA4OH9xt.js +42 -0
  19. package/dist/capabilitiesCommand-nq_pz5xd.js +123 -0
  20. package/dist/checkCommand-Ct9xkTrS.js +232 -0
  21. package/dist/checkCommand-DKpDLlqu.js +2 -0
  22. package/dist/{cliArgs-qdZSElM3.js → cliArgs-D4p8n7EE.js} +12 -1
  23. package/dist/cliError-BmdYnghb.js +10 -0
  24. package/dist/cliOutput-D1tSBoRM.js +15 -0
  25. package/dist/{cliRuntime-Oh517vCV.js → cliRuntime-Dh7UDinH.js} +20 -20
  26. package/dist/cloudClient-DWL-Hw_T.js +67 -0
  27. package/dist/cloudCmd-Cvv5HGaZ.js +364 -0
  28. package/dist/clusterCmd-D5wsCmA_.js +54 -0
  29. package/dist/codegen-CYM3Zqrf.js +605 -0
  30. package/dist/codegen-ChBi_hVa.js +2 -0
  31. package/dist/codegenCommand-C4YoQIc2.js +30 -0
  32. package/dist/codemodRunner-BnFq3Fgu.js +5384 -0
  33. package/dist/commandRunner-BLAEFLjp.js +47 -0
  34. package/dist/commands-BE8E7zF3.js +816 -0
  35. package/dist/connectionConfig-UFlIEiys.js +66 -0
  36. package/dist/dashboardCommand-D7SgZGaN.js +25 -0
  37. package/dist/dataCommand-BhYwDgg-.js +537 -0
  38. package/dist/dataProfile-dW-PsfLB.js +15 -0
  39. package/dist/dbCommand-DS4b97Is.js +2 -0
  40. package/dist/{dbCommand-DTLKAfbA.js → dbCommand-O8HA63s2.js} +552 -402
  41. package/dist/{dev-C_P8FLSx.js → dev-C7sFZq3m.js} +3670 -3169
  42. package/dist/dev-D2BikO7a.js +3 -0
  43. package/dist/devActivity-Dx_3nnGv.js +100 -0
  44. package/dist/devActivity.js +1 -1
  45. package/dist/dialectDriver-CgXnDfec.js +39 -0
  46. package/dist/discover-C9XKJDco.js +25 -0
  47. package/dist/doctorCommand-CM4Ch9C7.js +2 -0
  48. package/dist/{checkCommand-xGhRFFg2.js → doctorCommand-DnimF5IM.js} +523 -1278
  49. package/dist/dormancyCommand-QewYug_s.js +69 -0
  50. package/dist/e2eCmd-BRabZww-.js +147 -0
  51. package/dist/embeddingsCommand-BfiLS_QI.js +73 -0
  52. package/dist/envCommand-CCGPRQY1.js +60 -0
  53. package/dist/evalCommand-6RUfPen4.js +118 -0
  54. package/dist/evolveCommand-CHsLCtDf.js +281 -0
  55. package/dist/fileTaxonomy-CJfgOllU.js +457 -0
  56. package/dist/frameworkTableAssembly-YyVe32Cb.js +2 -0
  57. package/dist/frameworkTableAssembly-oMPBKqlE.js +511 -0
  58. package/dist/generateCommand-DbgcUpGw.js +147 -0
  59. package/dist/index.d.ts +45 -0
  60. package/dist/index.js +4 -3
  61. package/dist/infoCommand-DwOgK1t6.js +60 -0
  62. package/dist/{inspect-BUUjt773.js → inspect-CjTYzAs_.js} +113 -41
  63. package/dist/inspect-P4pxoMaV.js +2 -0
  64. package/dist/inspectCmd-EHFZ9yYu.js +224 -0
  65. package/dist/inspectFetch-EMuhTG_9.js +151 -0
  66. package/dist/inspectMetrics-CfdKLh6t.js +72 -0
  67. package/dist/loadEnv-D9nEOClM.js +44 -0
  68. package/dist/logFileSink-C_D2wRN1.js +105 -0
  69. package/dist/logsCmd-D36xK7Zu.js +260 -0
  70. package/dist/manifestBuild-COkJoyAr.js +2 -0
  71. package/dist/{manifestBuild-BLrVuSlM.js → manifestBuild-hpPLaGxV.js} +1 -1
  72. package/dist/metaCommands-7MJfZ5cf.js +196 -0
  73. package/dist/migrate-BV7I-ZHZ.js +83 -0
  74. package/dist/mssqlClusterPatch-_4cE_nun.js +44 -0
  75. package/dist/newCommand-COWOJ1_E.js +156 -0
  76. package/dist/nodeEnvironment-cGFAj1J8.js +28 -0
  77. package/dist/packageCommand-Cug_3Ogl.js +271 -0
  78. package/dist/pageConvention-cEiRxdab.js +5 -0
  79. package/dist/privacyCommand-C-Df56U_.js +146 -0
  80. package/dist/probeCommand-CZfaaUOZ.js +122 -0
  81. package/dist/projectScaffold-DmzEKHib.js +2 -0
  82. package/dist/projectScaffold-LMMtaavR.js +814 -0
  83. package/dist/renderModeScan-D7J1B7Kw.js +105 -0
  84. package/dist/renderProfile-1OWWAAtx.js +81 -0
  85. package/dist/runtimeRegistry-DMeKfTHP.js +81 -0
  86. package/dist/runtimeTrace-CH3eUiMw.js +91 -0
  87. package/dist/scheduleCmd-DQRu6BZC.js +149 -0
  88. package/dist/scheduleManifestCmd-D2x0CTTY.js +249 -0
  89. package/dist/schemaIr-UJybUUZW.js +103 -0
  90. package/dist/{sdkgen-C81QIkiL.js → sdkgen-BLkvGRfX.js} +111 -209
  91. package/dist/seedRunner-ZmLSqNe2.js +333 -0
  92. package/dist/serveCommand-CbDHU6l-.js +2 -0
  93. package/dist/serveCommand-iwlUBNS1.js +1766 -0
  94. package/dist/serveEntry.js +5 -5
  95. package/dist/serverlessCommand-CfJZy6dS.js +482 -0
  96. package/dist/start-BgN62boB.js +3 -0
  97. package/dist/start-T4VesWiM.js +1087 -0
  98. package/dist/startEntry.js +2 -2
  99. package/dist/staticCommand-Dr2M6tpU.js +304 -0
  100. package/dist/storageCommand-Co6NfLqN.js +42 -0
  101. package/dist/templates-De8IR5-c.js +102 -0
  102. package/dist/test-CI6iDsYc.js +115 -0
  103. package/dist/tracesCmd-DStmCJPi.js +232 -0
  104. package/dist/tsconfigPaths-BWXBWgcl.js +107 -0
  105. package/dist/tsxLoader-EuXmSJ1K.js +51 -0
  106. package/dist/typecheckCommand-BlsWiCNq.js +61 -0
  107. package/dist/updateCommand-BlMZhWgO.js +2 -0
  108. package/dist/updateCommand-x0pI_x-B.js +585 -0
  109. package/dist/webDev-BcykISYQ2.js +2 -0
  110. package/dist/{inspectMetrics-1xzTKAFx.js → webDev-Dybxew86.js} +988 -1560
  111. package/dist/webhookDiscovery-CrGAfhIG.js +2 -0
  112. package/dist/webhookDiscovery-D7VaeMlz.js +51 -0
  113. package/dist/webhooksCommand-DlAgS2Iw.js +267 -0
  114. package/dist/workflowsCmd-BGF-mRZ5.js +608 -0
  115. package/package.json +209 -17
  116. package/templates/AGENTS.core.md +58 -3
  117. package/templates/AGENTS.md +64 -7
  118. package/templates/agent-docs/_index.md +6 -4
  119. package/templates/agent-docs/_manifest.json +23 -6
  120. package/templates/agent-docs/ai.md +370 -0
  121. package/templates/agent-docs/authentication.md +313 -31
  122. package/templates/agent-docs/caching.md +6 -0
  123. package/templates/agent-docs/cli.md +853 -50
  124. package/templates/agent-docs/data.md +608 -12
  125. package/templates/agent-docs/database/migrations.md +223 -25
  126. package/templates/agent-docs/database/misc.md +156 -40
  127. package/templates/agent-docs/database/querying.md +19 -1
  128. package/templates/agent-docs/database/scaling.md +60 -0
  129. package/templates/agent-docs/database/schema.md +1 -0
  130. package/templates/agent-docs/database/seedsdialects.md +208 -19
  131. package/templates/agent-docs/database/transactions.md +68 -0
  132. package/templates/agent-docs/deployment.md +284 -25
  133. package/templates/agent-docs/introduction.md +88 -17
  134. package/templates/agent-docs/local-first-mobile.md +79 -4
  135. package/templates/agent-docs/multi-tenancy.md +188 -42
  136. package/templates/agent-docs/observability.md +58 -3
  137. package/templates/agent-docs/plugins/ai-flows.md +247 -2
  138. package/templates/agent-docs/plugins/analytics-postgres.md +1 -1
  139. package/templates/agent-docs/plugins/audit.md +37 -1
  140. package/templates/agent-docs/plugins/auth-social.md +143 -0
  141. package/templates/agent-docs/plugins/auth-workos.md +4 -2
  142. package/templates/agent-docs/plugins/auth.md +131 -6
  143. package/templates/agent-docs/plugins/billing.md +132 -15
  144. package/templates/agent-docs/plugins/cdc-out.md +46 -7
  145. package/templates/agent-docs/plugins/clickhouse.md +32 -1
  146. package/templates/agent-docs/plugins/duckdb.md +1 -1
  147. package/templates/agent-docs/plugins/flags.md +132 -0
  148. package/templates/agent-docs/plugins/governance.md +105 -7
  149. package/templates/agent-docs/plugins/multitenancy.md +9 -4
  150. package/templates/agent-docs/plugins/presence.md +13 -2
  151. package/templates/agent-docs/plugins/ratelimit.md +9 -0
  152. package/templates/agent-docs/plugins/search.md +162 -8
  153. package/templates/agent-docs/plugins/sso-saml.md +47 -8
  154. package/templates/agent-docs/plugins/storage.md +11 -0
  155. package/templates/agent-docs/plugins/webhooks.md +105 -0
  156. package/templates/agent-docs/plugins.md +152 -18
  157. package/templates/agent-docs/reference.md +60 -3
  158. package/templates/agent-docs/releases.md +1117 -0
  159. package/templates/agent-docs/routing.md +43 -25
  160. package/templates/agent-docs/scheduling.md +27 -0
  161. package/templates/agent-docs/schema-driven-ui.md +92 -12
  162. package/templates/agent-docs/security.md +426 -0
  163. package/templates/agent-docs/templates/apibackends.md +87 -18
  164. package/templates/agent-docs/templates/appshells.md +32 -14
  165. package/templates/agent-docs/templates/overview.md +13 -8
  166. package/templates/agent-docs/testing.md +211 -14
  167. package/templates/agent-docs/whats-new.md +98 -136
  168. package/templates/agent-docs/workflows.md +231 -14
  169. package/templates/apps/api-ai/actions/summarize.action.ts +11 -0
  170. package/templates/apps/api-ai/package.json +8 -7
  171. package/templates/apps/api-auth/actions/me.action.ts +13 -0
  172. package/templates/apps/api-auth/package.json +9 -8
  173. package/templates/apps/api-backend/mutations/notes.create.mutation.ts +9 -0
  174. package/templates/apps/api-backend/package.json +12 -8
  175. package/templates/apps/api-backend/queries/notes.query.ts +30 -8
  176. package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +14 -0
  177. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +8 -0
  178. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +10 -0
  179. package/templates/apps/api-backend-deactivation/package.json +8 -7
  180. package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +17 -0
  181. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +9 -0
  182. package/templates/apps/api-backend-mail/package.json +9 -8
  183. package/templates/apps/api-backend-mail/queries/notes.query.ts +30 -8
  184. package/templates/apps/api-backend-mariadb/.env.example +14 -0
  185. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +9 -0
  186. package/templates/apps/api-backend-mariadb/package.json +10 -9
  187. package/templates/apps/api-backend-mariadb/queries/notes.query.ts +30 -8
  188. package/templates/apps/api-backend-sqlite/.env.example +14 -0
  189. package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.ts +9 -0
  190. package/templates/apps/api-backend-sqlite/package.json +9 -8
  191. package/templates/apps/api-backend-sqlite/queries/notes.query.ts +30 -8
  192. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +13 -0
  193. package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +12 -0
  194. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +9 -0
  195. package/templates/apps/api-backend-storage/package.json +9 -8
  196. package/templates/apps/api-backend-storage/queries/notes.query.ts +30 -8
  197. package/templates/apps/api-cms/actions/content.get.action.ts +7 -0
  198. package/templates/apps/api-cms/actions/content.types.action.ts +6 -0
  199. package/templates/apps/api-cms/actions/me.action.ts +13 -0
  200. package/templates/apps/api-cms/app.config.ts +19 -0
  201. package/templates/apps/api-cms/authz.ts +63 -0
  202. package/templates/apps/api-cms/mutations/content.publish.mutation.ts +10 -0
  203. package/templates/apps/api-cms/mutations/content.saveDraft.mutation.ts +10 -0
  204. package/templates/apps/api-cms/mutations/content.unpublish.mutation.ts +5 -0
  205. package/templates/apps/api-cms/package.json +11 -10
  206. package/templates/apps/api-cms/queries/content.list.query.ts +23 -7
  207. package/templates/apps/api-cms/tests/accessDecisions.test.ts +121 -0
  208. package/templates/apps/api-cms/tests/content.descriptors.test.ts +8 -4
  209. package/templates/apps/api-collab/mutations/documents.create.mutation.ts +8 -0
  210. package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +13 -0
  211. package/templates/apps/api-collab/package.json +9 -8
  212. package/templates/apps/api-collab/queries/documents.query.ts +19 -7
  213. package/templates/apps/api-data-advanced/package.json +9 -8
  214. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +12 -0
  215. package/templates/apps/api-data-advanced/queries/books.search.query.ts +9 -0
  216. package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +17 -0
  217. package/templates/apps/api-durable/mutations/orders.place.mutation.ts +9 -0
  218. package/templates/apps/api-durable/package.json +9 -8
  219. package/templates/apps/api-feature-flags/actions/notes.export.action.ts +16 -0
  220. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +12 -0
  221. package/templates/apps/api-feature-flags/package.json +10 -9
  222. package/templates/apps/api-governance/README.md +30 -8
  223. package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -1
  224. package/templates/apps/api-governance/actions/profiles.get.action.ts +29 -4
  225. package/templates/apps/api-governance/database/schema.ts +16 -4
  226. package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +12 -0
  227. package/templates/apps/api-governance/package.json +9 -8
  228. package/templates/apps/api-kv/actions/sync.pull.action.ts +15 -0
  229. package/templates/apps/api-kv/actions/sync.reset.action.ts +13 -0
  230. package/templates/apps/api-kv/actions/sync.status.action.ts +7 -0
  231. package/templates/apps/api-kv/package.json +9 -8
  232. package/templates/apps/api-kv/queries/events.list.query.ts +19 -8
  233. package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +11 -0
  234. package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +13 -0
  235. package/templates/apps/api-moderation/package.json +9 -8
  236. package/templates/apps/api-observability/mutations/notes.create.mutation.ts +8 -0
  237. package/templates/apps/api-observability/package.json +9 -8
  238. package/templates/apps/api-observability/queries/notes.list.query.ts +13 -0
  239. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +14 -0
  240. package/templates/apps/api-ratelimit/package.json +9 -8
  241. package/templates/apps/api-rbac/package.json +9 -8
  242. package/templates/apps/api-rest/package.json +8 -7
  243. package/templates/apps/api-saas/mutations/projects.create.mutation.ts +13 -0
  244. package/templates/apps/api-saas/package.json +12 -11
  245. package/templates/apps/api-saas/queries/projects.list.query.ts +11 -0
  246. package/templates/apps/api-saas-starter/actions/me.action.ts +13 -0
  247. package/templates/apps/api-saas-starter/app.config.ts +19 -0
  248. package/templates/apps/api-saas-starter/authz.ts +75 -0
  249. package/templates/apps/api-saas-starter/mutations/invites.create.mutation.ts +10 -0
  250. package/templates/apps/api-saas-starter/mutations/projects.create.mutation.ts +9 -0
  251. package/templates/apps/api-saas-starter/package.json +15 -11
  252. package/templates/apps/api-saas-starter/queries/invites.list.query.ts +19 -8
  253. package/templates/apps/api-saas-starter/queries/projects.list.query.ts +18 -7
  254. package/templates/apps/api-saas-starter/tests/accessDecisions.test.ts +135 -0
  255. package/templates/apps/api-search/mutations/articles.create.mutation.ts +14 -0
  256. package/templates/apps/api-search/package.json +9 -8
  257. package/templates/apps/api-search/queries/articles.list.query.ts +21 -8
  258. package/templates/apps/api-status/README.md +10 -3
  259. package/templates/apps/api-status/app.config.ts +8 -3
  260. package/templates/apps/api-status/authz.ts +5 -3
  261. package/templates/apps/api-status/package.json +9 -8
  262. package/templates/apps/api-status/queries/components.list.query.ts +14 -6
  263. package/templates/apps/api-status/queries/incidents.live.query.ts +23 -10
  264. package/templates/apps/api-status/queries/updates.list.query.ts +16 -9
  265. package/templates/apps/api-status/tests/status.test.ts +9 -1
  266. package/templates/apps/api-versioning/actions/documents.asOf.action.ts +12 -0
  267. package/templates/apps/api-versioning/actions/documents.history.action.ts +13 -0
  268. package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +9 -0
  269. package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +12 -0
  270. package/templates/apps/api-versioning/package.json +9 -8
  271. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +16 -0
  272. package/templates/apps/api-webhooks/package.json +10 -9
  273. package/templates/apps/api-webhooks/queries/orders.list.query.ts +10 -0
  274. package/templates/apps/changelog/package.json +8 -6
  275. package/templates/apps/edge-functions/package.json +2 -2
  276. package/templates/apps/frontend-admin/package.json +10 -8
  277. package/templates/apps/frontend-admin/src/lib/admin.ts +20 -9
  278. package/templates/apps/frontend-admin/src/locales/de.ts +11 -1
  279. package/templates/apps/frontend-admin/src/locales/en.ts +13 -1
  280. package/templates/apps/frontend-admin/src/pages/admin/[entity]/page.tsx +65 -22
  281. package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +130 -23
  282. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +3 -2
  283. package/templates/apps/frontend-admin/src/pages/admin/page.test.tsx +19 -2
  284. package/templates/apps/frontend-admin/src/pages/admin/page.tsx +9 -4
  285. package/templates/apps/frontend-app/app.config.ts +4 -3
  286. package/templates/apps/frontend-app/package.json +11 -8
  287. package/templates/apps/frontend-app/src/lib/api.ts +25 -0
  288. package/templates/apps/frontend-app/src/pages/page.test.tsx +130 -82
  289. package/templates/apps/frontend-app/src/pages/page.tsx +14 -18
  290. package/templates/apps/frontend-auth/package.json +10 -8
  291. package/templates/apps/frontend-blank/package.json +9 -7
  292. package/templates/apps/frontend-cms/package.json +11 -9
  293. package/templates/apps/frontend-collab/package.json +12 -9
  294. package/templates/apps/frontend-collab/src/pages/page.test.tsx +122 -78
  295. package/templates/apps/frontend-contact/package.json +9 -7
  296. package/templates/apps/frontend-dashboard/package.json +9 -7
  297. package/templates/apps/frontend-docs/package.json +9 -7
  298. package/templates/apps/frontend-i18n/package.json +8 -6
  299. package/templates/apps/frontend-landing/package.json +9 -7
  300. package/templates/apps/frontend-portal/package.json +10 -8
  301. package/templates/apps/frontend-saas/app.config.ts +10 -6
  302. package/templates/apps/frontend-saas/package.json +10 -8
  303. package/templates/apps/frontend-saas/src/lib/api.ts +27 -32
  304. package/templates/apps/frontend-saas/src/pages/dashboard/billing/page.tsx +7 -8
  305. package/templates/apps/frontend-saas/src/pages/dashboard/page.test.tsx +27 -3
  306. package/templates/apps/frontend-saas/src/pages/dashboard/page.tsx +4 -4
  307. package/templates/apps/frontend-saas/src/pages/dashboard/team/page.tsx +3 -4
  308. package/templates/apps/frontend-spa/package.json +9 -7
  309. package/templates/apps/frontend-ssr/package.json +9 -7
  310. package/templates/apps/frontend-ssr-api/package.json +10 -8
  311. package/templates/apps/frontend-static-blog/package.json +8 -6
  312. package/templates/apps/frontend-status/package.json +10 -8
  313. package/templates/apps/mobile-app/README.md +1 -0
  314. package/templates/apps/mobile-app/package.json +4 -2
  315. package/templates/apps/mobile-app/src/app/index.tsx +22 -12
  316. package/templates/apps/mobile-app/src/app/orders/[id].tsx +1 -1
  317. package/templates/apps/mobile-app/src/lib/api.ts +34 -0
  318. package/templates/apps/mobile-app/voltro.mobile.ts +4 -2
  319. package/templates/baselines/bare/.env.example +14 -0
  320. package/templates/baselines/bare/baseline.json +4 -4
  321. package/templates/baselines/compose/.env.example +14 -0
  322. package/templates/baselines/compose/README.md +1 -1
  323. package/templates/baselines/compose/baseline.json +5 -5
  324. package/templates/baselines/compose-mariadb/.env.example +14 -0
  325. package/templates/baselines/compose-mariadb/README.md +1 -1
  326. package/templates/baselines/compose-mariadb/baseline.json +5 -5
  327. package/templates/baselines/helm/.env.example +14 -0
  328. package/templates/baselines/helm/baseline.json +4 -4
  329. package/dist/apiBuild-C-x9YacA.js +0 -2
  330. package/dist/appGraph-CvQCte0z.js +0 -2
  331. package/dist/checkCommand-DRovTKza.js +0 -2
  332. package/dist/commands-CJfepbm4.js +0 -11541
  333. package/dist/dbCommand-b1gum4td.js +0 -2
  334. package/dist/dev-iiMtlkfs.js +0 -3
  335. package/dist/devActivity-BhIu6ncs.js +0 -159
  336. package/dist/frameworkTableAssembly-BwIrO5nv.js +0 -638
  337. package/dist/frameworkTableAssembly-D-EebUQX.js +0 -2
  338. package/dist/inspect-mmBuRXmy.js +0 -2
  339. package/dist/manifestBuild-Dj8Jjoto.js +0 -2
  340. package/dist/seedRunner-Bqxgp7HZ.js +0 -230
  341. package/dist/serveCommand-DdaM4Hup.js +0 -1608
  342. package/dist/start-C0koT0UO.js +0 -1084
  343. /package/templates/apps/api-ai/actions/{summarize.action.server.tsx → summarize.action.server.ts} +0 -0
@@ -1,7 +1,22 @@
1
+ // Executor for `profiles.get`.
2
+ //
3
+ // `p['ssn']` is PLAINTEXT here — the store middleware decrypted the
4
+ // `.encrypted()` column on the way out, and the handler never sees the
5
+ // `enc:v1:…` ciphertext. That is the round-trip this template exists to show,
6
+ // and the last-four projection below is what proves it happened.
7
+ //
8
+ // The projection is not decoration: the column is `.serverOnly()`, so the full
9
+ // value must not reach a client. Deriving the suffix HERE — rather than sending
10
+ // the whole string and trimming it in the browser — is the difference between a
11
+ // value that never left the server and one that did.
1
12
  import type { AppContext } from '@voltro/runtime'
2
13
  import { eq } from '@voltro/database'
3
14
  import { database } from '../database/schema'
4
15
 
16
+ /** Last four characters, or '' when there is nothing to show. Never throws on a
17
+ * short or empty value — a masked field is not a place to fail a read. */
18
+ const last4 = (value: string): string => (value.length >= 4 ? value.slice(-4) : '')
19
+
5
20
  const execute = async (input: { id: string }, ctx: AppContext) => {
6
21
  const rows = await ctx.store.query(database.profiles.where(eq('id', input.id)).descriptor)
7
22
  const p = rows[0]
@@ -10,7 +25,8 @@ const execute = async (input: { id: string }, ctx: AppContext) => {
10
25
  id: p['id'] as string,
11
26
  name: p['name'] as string,
12
27
  email: p['email'] as string,
13
- ssn: p['ssn'] as string, // decrypted by the store middleware
28
+ // Decrypted by the store middleware, then narrowed to what may leave.
29
+ ssnLast4: last4(p['ssn'] as string),
14
30
  }
15
31
  }
16
32
 
@@ -1,17 +1,42 @@
1
- // Reads a profile back `ssn` comes out as PLAINTEXT. The store middleware
2
- // decrypts the `.encrypted()` column on read; the handler never touches the
3
- // `enc:v1:…` ciphertext. (In a real app you'd gate this read behind auth.)
1
+ // Reads a profile back. The store middleware DECRYPTS the `.encrypted()` column
2
+ // on read, so the handler holds `ssn` as plaintext and never touches the
3
+ // `enc:v1:…` ciphertext that is the round-trip this template exists to show.
4
+ //
5
+ // What crosses the WIRE is a SEPARATE decision, and the schema makes it: `ssn`
6
+ // is also `.serverOnly()`, so the full value may not leave the server. The
7
+ // executor proves the decryption worked by returning the last four digits it
8
+ // derived from the plaintext — which is what a support UI would actually
9
+ // render. Encryption at rest is not an exposure marker; `.serverOnly()` is.
4
10
 
5
11
  import { defineAction } from '@voltro/protocol'
6
12
  import { Schema } from 'effect'
7
13
 
8
14
  export const getProfile = defineAction({
9
15
  name: 'profiles.get',
16
+ // Open, and the reason is a claim you can check against the two files beside
17
+ // this one: the full `ssn` is `.serverOnly()` in `database/schema.ts` and is
18
+ // not named in the output below, so the sensitive value cannot cross the wire
19
+ // at all. What is left — name, email, four digits — is the row this demo's
20
+ // own `profiles.create` put there; the template ships no seed and no other
21
+ // writer.
22
+ //
23
+ // A `guards: [{ scope: 'profiles:read' }]` is what a real deployment wants
24
+ // here, and it is unsatisfiable in THIS app: no auth strategy and no rbac are
25
+ // configured, so every caller resolves to an anonymous Subject holding no
26
+ // scopes and the guard would deny 100% of traffic — an outage, not security.
27
+ // Add an identity (see `api-auth` / `api-rbac`), then the guard.
28
+ openAccess:
29
+ 'returns name, email and the last four digits derived server-side from the decrypted '
30
+ + '`ssn`; the full value is `.serverOnly()` and is absent from this output, so it cannot '
31
+ + 'cross the wire. Rows come only from this demo\'s own `profiles.create` — no seed, no '
32
+ + 'other writer.',
10
33
  input: Schema.Struct({ id: Schema.String }),
11
34
  output: Schema.NullOr(Schema.Struct({
12
35
  id: Schema.String,
13
36
  name: Schema.String,
14
37
  email: Schema.String,
15
- ssn: Schema.String,
38
+ // Proof the cipher decrypted the column, without publishing it. Empty when
39
+ // the stored value is shorter than four characters.
40
+ ssnLast4: Schema.String,
16
41
  })),
17
42
  })
@@ -28,10 +28,22 @@ export const profiles = table('profiles', {
28
28
  id: id({ prefix: 'prof' }),
29
29
  name: text(),
30
30
  email: text(),
31
- // .encrypted() AES-256-GCM at rest. The handler always sees plaintext; the
32
- // stored value is an opaque `enc:v1:…` string. Encrypt only what you read
33
- // back WHOLE (you can't filter/sort on it in SQL — it's ciphertext on disk).
34
- ssn: text().encrypted(),
31
+ // TWO markers, because they answer two different questions say both when
32
+ // you mean both:
33
+ //
34
+ // .encrypted() → AES-256-GCM AT REST. The handler always sees plaintext;
35
+ // the stored value is an opaque `enc:v1:…` string. Encrypt
36
+ // only what you read back WHOLE (you can't filter/sort on
37
+ // it in SQL — it's ciphertext on disk).
38
+ // .serverOnly() → may this value leave the server AT ALL. It may not.
39
+ //
40
+ // Reading `.encrypted()` as "safe to expose" is a category error and a
41
+ // tempting one: the runtime DECRYPTS for the handler, so an encrypted column
42
+ // flows to a client exactly like any other unless it is also `.serverOnly()`.
43
+ // `crud.*` strips serverOnly columns for you; a hand-written procedure must
44
+ // simply not name it in its output — see `actions/profiles.get.action.ts`,
45
+ // which returns the last four digits it derived server-side instead.
46
+ ssn: text().encrypted().serverOnly(),
35
47
  })
36
48
  .with(audit()) // createdAt drives the retention sweep
37
49
 
@@ -8,6 +8,18 @@ import { Schema } from 'effect'
8
8
  export const createProfile = defineMutation({
9
9
  name: 'profiles.create',
10
10
  target: { table: 'profiles', op: 'insert' },
11
+ // Open: writes exactly the three fields the caller passed and echoes back
12
+ // only `{ id, name, email }` — `ssn` is `.serverOnly()` and is deliberately
13
+ // absent from the output, so what the caller sent in never comes back out.
14
+ // It reads nothing and cannot reach an existing row.
15
+ //
16
+ // No auth strategy and no rbac ship in this template, so every caller is an
17
+ // anonymous Subject holding no scopes; a `guards: [{ scope }]` would deny all
18
+ // of them rather than the wrong ones. Add an identity, then the guard.
19
+ openAccess:
20
+ 'inserts only the fields the caller supplied and echoes back `{ id, name, email }` — the '
21
+ + '`ssn` it takes is `.serverOnly()` and never appears in a response. Reads nothing, '
22
+ + 'modifies no existing row.',
11
23
  input: Schema.Struct({
12
24
  name: Schema.NonEmptyString,
13
25
  email: Schema.String,
@@ -13,18 +13,19 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/database": "0.33.0",
18
- "@voltro/env": "0.33.0",
19
- "@voltro/plugin-audit": "0.33.0",
20
- "@voltro/plugin-governance": "0.33.0",
21
- "@voltro/protocol": "0.33.0",
22
- "@voltro/runtime": "0.33.0",
16
+ "@voltro/cli": "0.35.0",
17
+ "@voltro/database": "0.35.0",
18
+ "@voltro/env": "0.35.0",
19
+ "@voltro/plugin-audit": "0.35.0",
20
+ "@voltro/plugin-governance": "0.35.0",
21
+ "@voltro/protocol": "0.35.0",
22
+ "@voltro/runtime": "0.35.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.33.0",
26
+ "@voltro/testing": "0.35.0",
27
27
  "typescript": "^6.0.3",
28
+ "@vitest/coverage-v8": "^4.1.10",
28
29
  "vitest": "^4.1.10"
29
30
  }
30
31
  }
@@ -13,6 +13,21 @@ import { Schema } from 'effect'
13
13
 
14
14
  export const syncPull = defineAction({
15
15
  name: 'sync.pull',
16
+ // Open: every KV key this touches is namespaced with the request's tenant
17
+ // (`sync:<tenantId>:cursor` / `:seen:*` in the executor) and the rows it
18
+ // writes go into a `tenant()` table, so one caller's sync state is not
19
+ // reachable from another's. The "upstream" is a pure local generator — there
20
+ // is no network call and no credential.
21
+ //
22
+ // No auth strategy and no rbac ship in this template, so a
23
+ // `guards: [{ scope: 'sync:run' }]` would be unsatisfiable: every caller is
24
+ // an anonymous Subject holding no scopes, and the guard would deny all of
25
+ // them. Growing a role map here purely to look guarded would teach a pattern
26
+ // this template is not about.
27
+ openAccess:
28
+ 'advances the request tenant\'s own sync watermark; every KV key is namespaced by that '
29
+ + 'tenant and the rows land in a `tenant()` table. The upstream is a local pure generator '
30
+ + '— no network call, no credential.',
16
31
  input: Schema.Struct({
17
32
  // How many upstream events to pull this call (default 5, capped in the executor).
18
33
  limit: Schema.optional(Schema.Int.pipe(Schema.greaterThan(0))),
@@ -11,6 +11,19 @@ import { Schema } from 'effect'
11
11
 
12
12
  export const syncReset = defineAction({
13
13
  name: 'sync.reset',
14
+ // Open — but note what bounds the blast radius, because it is a deliberate
15
+ // choice in the executor and not a property of "reset": it deletes only keys
16
+ // under `sync:<request tenantId>:`, never `ctx.kv.clear()`, which would drop
17
+ // the app's ENTIRE KV namespace across every tenant in one call. It destroys
18
+ // no rows; a following `sync.pull` rebuilds the watermark.
19
+ //
20
+ // No auth strategy and no rbac ship here, so a scope guard would deny every
21
+ // caller rather than the wrong ones. An operational reset is a good candidate
22
+ // for a `guards: [{ scope: 'sync:admin' }]` the moment you have an identity.
23
+ openAccess:
24
+ 'deletes only the request tenant\'s own `sync:<tenant>:` keys — never `ctx.kv.clear()`, '
25
+ + 'which would wipe every tenant\'s namespace — and destroys no rows; the next `sync.pull` '
26
+ + 'rebuilds the watermark. An operational reset: guard it once an identity exists.',
14
27
  input: Schema.Struct({
15
28
  // Also delete the idempotency markers (default: keep them).
16
29
  markers: Schema.optional(Schema.Boolean),
@@ -9,6 +9,13 @@ import { Schema } from 'effect'
9
9
 
10
10
  export const syncStatus = defineAction({
11
11
  name: 'sync.status',
12
+ // Open: returns two integers about the request tenant's OWN sync state — a
13
+ // watermark and a marker count read from `sync:<tenantId>:` keys. No row
14
+ // contents, no other tenant's keys, no writes. No auth strategy or rbac ships
15
+ // in this template, so a scope guard would deny every caller.
16
+ openAccess:
17
+ 'returns two integers describing the request tenant\'s own sync state (watermark + live '
18
+ + 'marker count) from its own KV namespace; no row contents, no writes.',
12
19
  input: Schema.Struct({}),
13
20
  output: Schema.Struct({
14
21
  cursor: Schema.Int, // the durable watermark
@@ -12,18 +12,19 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.97.0",
14
14
  "@effect/rpc": "^0.76.0",
15
- "@voltro/cli": "0.33.0",
16
- "@voltro/database": "0.33.0",
17
- "@voltro/env": "0.33.0",
18
- "@voltro/kv": "0.33.0",
19
- "@voltro/plugin-multitenancy": "0.33.0",
20
- "@voltro/protocol": "0.33.0",
21
- "@voltro/runtime": "0.33.0",
15
+ "@voltro/cli": "0.35.0",
16
+ "@voltro/database": "0.35.0",
17
+ "@voltro/env": "0.35.0",
18
+ "@voltro/kv": "0.35.0",
19
+ "@voltro/plugin-multitenancy": "0.35.0",
20
+ "@voltro/protocol": "0.35.0",
21
+ "@voltro/runtime": "0.35.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.33.0",
25
+ "@voltro/testing": "0.35.0",
26
26
  "typescript": "^6.0.3",
27
+ "@vitest/coverage-v8": "^4.1.10",
27
28
  "vitest": "^4.1.10"
28
29
  }
29
30
  }
@@ -11,13 +11,24 @@ import { Schema } from 'effect'
11
11
 
12
12
  export const listEvents = defineQuery({
13
13
  name: 'events.list',
14
+ // Open: `tenant()` confines every delivery to the tenantId on the request, so
15
+ // one caller's synced rows never reach another's subscription. With no auth
16
+ // strategy configured that tenantId is the caller's own `x-tenant` header —
17
+ // it shapes the feed, it does not authorize the caller — and a
18
+ // `guards: [{ scope }]` would be unsatisfiable, denying 100% of traffic.
19
+ openAccess:
20
+ 'streams the synced event rows of the request\'s tenant (`tenant()` scopes every '
21
+ + 'delivery); the rows are this demo\'s own generated events. No auth strategy ships here, '
22
+ + 'so the tenant comes from the caller\'s `x-tenant` header — add a strategy, then a `guards:`.',
14
23
  input: Schema.Struct({}),
15
- output: Schema.Struct({
16
- id: Schema.String,
17
- externalId: Schema.String,
18
- kind: Schema.String,
19
- sequence: Schema.Number,
20
- tenantId: Schema.String,
21
- createdAt: Schema.Date,
22
- }),
24
+ output: Schema.Array(
25
+ Schema.Struct({
26
+ id: Schema.String,
27
+ externalId: Schema.String,
28
+ kind: Schema.String,
29
+ sequence: Schema.Number,
30
+ tenantId: Schema.String,
31
+ createdAt: Schema.Date,
32
+ }),
33
+ ),
23
34
  })
@@ -9,6 +9,17 @@ import { Schema } from 'effect'
9
9
  export const createComment = defineMutation({
10
10
  name: 'comments.create',
11
11
  target: { table: 'comments', op: 'insert' },
12
+ // Open, same reasoning as `posts.create` — and here the `flag` action makes
13
+ // the distinction sharper still: it does not reject anything, it writes the
14
+ // row and queues it for review. A rule that never refuses is plainly not an
15
+ // access control, so the access decision has to be its own line.
16
+ //
17
+ // No auth strategy and no rbac ship here, so a `guards: [{ scope }]` would
18
+ // deny every caller instead of the wrong ones.
19
+ openAccess:
20
+ 'inserts caller-supplied text into the request\'s tenant; the `flag` moderation rule '
21
+ + 'queues it for review and rejects nothing, so it is a review workflow rather than an '
22
+ + 'access control.',
12
23
  input: Schema.Struct({
13
24
  body: Schema.String,
14
25
  }),
@@ -11,6 +11,19 @@ import { Schema } from 'effect'
11
11
  export const createPost = defineMutation({
12
12
  name: 'posts.create',
13
13
  target: { table: 'posts', op: 'insert' },
14
+ // Content moderation is not access control, and this template is the place
15
+ // to be exact about that: the `block` rule inspects WHAT is being written and
16
+ // can reject it; `guards:` / `openAccess:` decide WHO may write at all. The
17
+ // moderation interceptor lets an authorized troll through and stops an
18
+ // unauthorized poet — neither substitutes for the other.
19
+ //
20
+ // Open because no auth strategy and no rbac ship here: every caller is an
21
+ // anonymous Subject with no scopes, so a scope guard would deny all of them.
22
+ // The write carries only caller-supplied text into the request's tenant.
23
+ openAccess:
24
+ 'inserts caller-supplied text into the request\'s tenant, after the `block` moderation '
25
+ + 'rule has vetted it. Moderation judges the CONTENT, not the caller — it is not an '
26
+ + 'access decision, which is why this line exists beside it.',
14
27
  input: Schema.Struct({
15
28
  title: Schema.NonEmptyString,
16
29
  body: Schema.String,
@@ -13,18 +13,19 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/database": "0.33.0",
18
- "@voltro/env": "0.33.0",
19
- "@voltro/plugin-moderation": "0.33.0",
20
- "@voltro/plugin-multitenancy": "0.33.0",
21
- "@voltro/protocol": "0.33.0",
22
- "@voltro/runtime": "0.33.0",
16
+ "@voltro/cli": "0.35.0",
17
+ "@voltro/database": "0.35.0",
18
+ "@voltro/env": "0.35.0",
19
+ "@voltro/plugin-moderation": "0.35.0",
20
+ "@voltro/plugin-multitenancy": "0.35.0",
21
+ "@voltro/protocol": "0.35.0",
22
+ "@voltro/runtime": "0.35.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.33.0",
26
+ "@voltro/testing": "0.35.0",
27
27
  "typescript": "^6.0.3",
28
+ "@vitest/coverage-v8": "^4.1.10",
28
29
  "vitest": "^4.1.10"
29
30
  }
30
31
  }
@@ -4,6 +4,14 @@ import { Schema } from 'effect'
4
4
  export const createNote = defineMutation({
5
5
  name: 'notes.create',
6
6
  target: { table: 'notes', op: 'insert' },
7
+ // Open: writes only the title + body the caller sent, into the request's
8
+ // tenant (`tenant()` auto-stamps `tenantId`), and touches no existing row.
9
+ // No auth strategy and no rbac ship here, so every caller is an anonymous
10
+ // Subject holding no scopes and a scope guard would deny all of them.
11
+ openAccess:
12
+ 'inserts a note built only from caller-supplied fields into the request\'s tenant; reads '
13
+ + 'nothing and modifies no existing row. No auth strategy ships here, so there is no '
14
+ + 'identity a scope guard could name.',
7
15
  input: Schema.Struct({ title: Schema.NonEmptyString, body: Schema.String }),
8
16
  output: Schema.Struct({
9
17
  id: Schema.String,
@@ -13,18 +13,19 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/database": "0.33.0",
18
- "@voltro/plugin-multitenancy": "0.33.0",
19
- "@voltro/plugin-prometheus": "0.33.0",
20
- "@voltro/plugin-sentry": "0.33.0",
21
- "@voltro/protocol": "0.33.0",
22
- "@voltro/runtime": "0.33.0",
16
+ "@voltro/cli": "0.35.0",
17
+ "@voltro/database": "0.35.0",
18
+ "@voltro/plugin-multitenancy": "0.35.0",
19
+ "@voltro/plugin-prometheus": "0.35.0",
20
+ "@voltro/plugin-sentry": "0.35.0",
21
+ "@voltro/protocol": "0.35.0",
22
+ "@voltro/runtime": "0.35.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.33.0",
26
+ "@voltro/testing": "0.35.0",
27
27
  "typescript": "^6.0.3",
28
+ "@vitest/coverage-v8": "^4.1.10",
28
29
  "vitest": "^4.1.10"
29
30
  }
30
31
  }
@@ -4,6 +4,19 @@ import { Schema } from 'effect'
4
4
  export const listNotes = defineQuery({
5
5
  name: 'notes.list',
6
6
  source: 'notes',
7
+ // Open: `tenant()` confines every delivery to the tenantId on the request, so
8
+ // one tenant's notes never reach another's subscription. With no auth
9
+ // strategy configured that tenantId comes from the caller's own `x-tenant`
10
+ // header — result shaping, not access control — and a `guards: [{ scope }]`
11
+ // would be unsatisfiable, denying 100% of traffic rather than some of it.
12
+ //
13
+ // Worth stating in the OBSERVABILITY template specifically: a denied call is
14
+ // still a span and still a metric. Traces show you what happened, not who was
15
+ // allowed to make it happen.
16
+ openAccess:
17
+ 'lists notes for the request\'s tenant only (`tenant()` scopes every delivery). No auth '
18
+ + 'strategy ships here, so the tenant comes from the caller\'s `x-tenant` header — add a '
19
+ + 'strategy, then a `guards:`. Tracing records calls; it does not authorize them.',
7
20
  input: Schema.Struct({}),
8
21
  output: Schema.Array(Schema.Struct({
9
22
  id: Schema.String,
@@ -10,6 +10,20 @@ import { Schema } from 'effect'
10
10
 
11
11
  export const createNote = defineMutation({
12
12
  name: 'notes.create',
13
+ // A rate limit is not an access decision, and this template is where that is
14
+ // easiest to get wrong. The interceptor answers HOW OFTEN a caller may do
15
+ // this; `guards:` / `openAccess:` answer WHETHER THEY MAY AT ALL. A limit
16
+ // slows an attacker down and lets them through 99 times an hour, so it can
17
+ // never stand in for the second answer — the two live side by side.
18
+ //
19
+ // Open here because no auth strategy and no rbac ship in this template: every
20
+ // caller resolves to an anonymous Subject holding no scopes, so a
21
+ // `guards: [{ scope }]` would deny all of them. `assertOwnTenant` still
22
+ // rejects a `tenantId` that does not match the resolved subject.
23
+ openAccess:
24
+ 'inserts a note carrying only what the caller sent, into the caller\'s own tenant '
25
+ + '(`assertOwnTenant` rejects a mismatch). The rate limit bounds HOW OFTEN, never WHETHER '
26
+ + '— it is not an access decision, which is why this line exists beside it.',
13
27
  target: {
14
28
  table: 'notes',
15
29
  op: 'insert',
@@ -13,18 +13,19 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/database": "0.33.0",
18
- "@voltro/env": "0.33.0",
19
- "@voltro/plugin-multitenancy": "0.33.0",
20
- "@voltro/plugin-ratelimit": "0.33.0",
21
- "@voltro/protocol": "0.33.0",
22
- "@voltro/runtime": "0.33.0",
16
+ "@voltro/cli": "0.35.0",
17
+ "@voltro/database": "0.35.0",
18
+ "@voltro/env": "0.35.0",
19
+ "@voltro/plugin-multitenancy": "0.35.0",
20
+ "@voltro/plugin-ratelimit": "0.35.0",
21
+ "@voltro/protocol": "0.35.0",
22
+ "@voltro/runtime": "0.35.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.33.0",
26
+ "@voltro/testing": "0.35.0",
27
27
  "typescript": "^6.0.3",
28
+ "@vitest/coverage-v8": "^4.1.10",
28
29
  "vitest": "^4.1.10"
29
30
  }
30
31
  }
@@ -13,18 +13,19 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/database": "0.33.0",
18
- "@voltro/env": "0.33.0",
19
- "@voltro/plugin-multitenancy": "0.33.0",
20
- "@voltro/plugin-rbac": "0.33.0",
21
- "@voltro/protocol": "0.33.0",
22
- "@voltro/runtime": "0.33.0",
16
+ "@voltro/cli": "0.35.0",
17
+ "@voltro/database": "0.35.0",
18
+ "@voltro/env": "0.35.0",
19
+ "@voltro/plugin-multitenancy": "0.35.0",
20
+ "@voltro/plugin-rbac": "0.35.0",
21
+ "@voltro/protocol": "0.35.0",
22
+ "@voltro/runtime": "0.35.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.33.0",
26
+ "@voltro/testing": "0.35.0",
27
27
  "typescript": "^6.0.3",
28
+ "@vitest/coverage-v8": "^4.1.10",
28
29
  "vitest": "^4.1.10"
29
30
  }
30
31
  }
@@ -13,17 +13,18 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/database": "0.33.0",
18
- "@voltro/plugin-openapi": "0.33.0",
19
- "@voltro/protocol": "0.33.0",
20
- "@voltro/runtime": "0.33.0",
21
- "@voltro/sql-postgres": "0.33.0",
16
+ "@voltro/cli": "0.35.0",
17
+ "@voltro/database": "0.35.0",
18
+ "@voltro/plugin-openapi": "0.35.0",
19
+ "@voltro/protocol": "0.35.0",
20
+ "@voltro/runtime": "0.35.0",
21
+ "@voltro/sql-postgres": "0.35.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.33.0",
25
+ "@voltro/testing": "0.35.0",
26
26
  "typescript": "^6.0.3",
27
+ "@vitest/coverage-v8": "^4.1.10",
27
28
  "vitest": "^4.1.10"
28
29
  }
29
30
  }
@@ -9,6 +9,19 @@ import { Schema } from 'effect'
9
9
  export const createProject = defineMutation({
10
10
  name: 'projects.create',
11
11
  target: { table: 'projects', op: 'insert' },
12
+ // An entitlement check is not an access decision. `requireEntitlement`
13
+ // answers HOW MANY the tenant has paid for; `guards:` / `openAccess:` answer
14
+ // WHO MAY CALL IT. A quota gate refuses the fourth project and cheerfully
15
+ // allows the first three — from anyone.
16
+ //
17
+ // Open because this template ships no auth strategy and no rbac (it is the
18
+ // billing bundle; `api-saas-starter` is the same domain WITH auth, and it
19
+ // guards these procedures). With no identity, every caller is an anonymous
20
+ // Subject holding no scopes and a `guards: [{ scope }]` would deny all of them.
21
+ openAccess:
22
+ 'inserts a project named by the caller into the request\'s tenant, after the billing '
23
+ + 'entitlement gate. A quota bounds HOW MANY, never WHO — see `api-saas-starter` for the '
24
+ + 'same procedure with an auth strategy and a `projects:write` guard.',
12
25
  input: Schema.Struct({ name: Schema.NonEmptyString }),
13
26
  output: Schema.Struct({
14
27
  id: Schema.String,
@@ -13,21 +13,22 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/database": "0.33.0",
18
- "@voltro/plugin-analytics-postgres": "0.33.0",
19
- "@voltro/plugin-billing": "0.33.0",
20
- "@voltro/plugin-multitenancy": "0.33.0",
21
- "@voltro/plugin-notifications": "0.33.0",
22
- "@voltro/plugin-presence": "0.33.0",
23
- "@voltro/protocol": "0.33.0",
24
- "@voltro/runtime": "0.33.0",
25
- "@voltro/sql-postgres": "0.33.0",
16
+ "@voltro/cli": "0.35.0",
17
+ "@voltro/database": "0.35.0",
18
+ "@voltro/plugin-analytics-postgres": "0.35.0",
19
+ "@voltro/plugin-billing": "0.35.0",
20
+ "@voltro/plugin-multitenancy": "0.35.0",
21
+ "@voltro/plugin-notifications": "0.35.0",
22
+ "@voltro/plugin-presence": "0.35.0",
23
+ "@voltro/protocol": "0.35.0",
24
+ "@voltro/runtime": "0.35.0",
25
+ "@voltro/sql-postgres": "0.35.0",
26
26
  "effect": "^3.22.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@voltro/testing": "0.33.0",
29
+ "@voltro/testing": "0.35.0",
30
30
  "typescript": "^6.0.3",
31
+ "@vitest/coverage-v8": "^4.1.10",
31
32
  "vitest": "^4.1.10"
32
33
  }
33
34
  }
@@ -8,6 +8,17 @@ import { Schema } from 'effect'
8
8
  export const listProjects = defineQuery({
9
9
  name: 'projects.list',
10
10
  source: 'projects',
11
+ // Open: `tenant()` confines every delivery to the tenantId on the request, so
12
+ // one tenant's projects never reach another's subscription. This template
13
+ // bundles billing + notifications + presence and deliberately ships NO auth
14
+ // strategy — pair it with `api-auth` / `api-saas-starter` for the identity
15
+ // half. Until then the tenantId is the caller's own `x-tenant` header, and a
16
+ // `guards: [{ scope }]` would be unsatisfiable: an anonymous Subject holds no
17
+ // scopes, so the guard would deny 100% of traffic.
18
+ openAccess:
19
+ 'lists the request tenant\'s own projects (`tenant()` scopes every delivery). This '
20
+ + 'template ships no auth strategy — see `api-saas-starter`, which does and guards these '
21
+ + 'same procedures — so the tenant comes from the caller\'s `x-tenant` header.',
11
22
  input: Schema.Struct({}),
12
23
  output: Schema.Array(Schema.Struct({
13
24
  id: Schema.String,
@@ -9,6 +9,19 @@ import { Schema } from 'effect'
9
9
 
10
10
  export const me = defineAction({
11
11
  name: 'session.me',
12
+ // The ONE open procedure in this app, and it has to be. The four domain
13
+ // procedures beside it carry guards an anonymous caller cannot satisfy —
14
+ // which is correct — but the dashboard's SSR gate has to be able to ask
15
+ // "who am I" while it is still nobody, or it has nothing to branch on and the
16
+ // redirect to /login never happens.
17
+ //
18
+ // Safe because the answer is a projection of the caller's own request: the
19
+ // Subject `voltroPasswordStrategy` resolved from the cookie they sent. It
20
+ // reads no table and can say nothing about anybody else.
21
+ openAccess:
22
+ 'echoes the caller\'s own resolved Subject (type/id/tenantId) — reads no table and reveals '
23
+ + 'nothing the caller did not present. Anonymous callers must reach it, or the SSR gate '
24
+ + 'cannot redirect them to sign in.',
12
25
  input: Schema.Struct({}),
13
26
  output: Schema.Struct({
14
27
  // 'anonymous' | 'user' | 'apiKey' | 'serviceAccount'