@voltro/cli 0.50.1 → 0.52.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 (170) hide show
  1. package/CHANGELOG.md +231 -0
  2. package/dist/{agentsMd-D6yD7IQv.js → agentsMd-0l980yhL.js} +4 -1
  3. package/dist/agentsMd-SDDSkyl4.js +2 -0
  4. package/dist/apiBuild-BYBpL7Pz.js +2 -0
  5. package/dist/{apiBuild-D3s7lx57.js → apiBuild-CSFI8QGq.js} +3 -3
  6. package/dist/appGraph-CFmFKHb2.js +2 -0
  7. package/dist/appGraph-yRuKjOba.js +111 -0
  8. package/dist/bin.js +1 -1
  9. package/dist/build-CPgcMQug.js +793 -0
  10. package/dist/checkCommand-2SbqzukH.js +2 -0
  11. package/dist/{checkCommand-DsKIjL6k.js → checkCommand-COmqc2cB.js} +107 -60
  12. package/dist/{cloudCmd-F4YJeqM3.js → cloudCmd-QUXh-b5w.js} +1 -1
  13. package/dist/codegen-CctkDO-1.js +2 -0
  14. package/dist/{codegen-CrMXs4hb.js → codegen-VF479Cnb.js} +1 -1
  15. package/dist/{codegenCommand-C0S27nbB.js → codegenCommand-DCdG2JN-.js} +12 -12
  16. package/dist/{codemodRunner-DwY8Rjbl.js → codemodRunner-r7J9lIa7.js} +771 -636
  17. package/dist/{commands-Brp4DI8u.js → commands-Cc_nV8WI.js} +37 -36
  18. package/dist/{dashboardCommand-bbLfc9FW.js → dashboardCommand-C-gKvwqh.js} +3 -3
  19. package/dist/{dataCommand-k9mXTXin.js → dataCommand-BgpBHnlB.js} +299 -253
  20. package/dist/dbCommand-DNb6yeOG.js +2 -0
  21. package/dist/{dbCommand-Bf7Cy0mn.js → dbCommand-sHedr-NJ.js} +3 -3
  22. package/dist/dev--A3nsxA3.js +3 -0
  23. package/dist/{dev-C0cbqysu.js → dev-CRHoCEiy.js} +2258 -2219
  24. package/dist/doctorCommand-CqoWA2p5.js +2 -0
  25. package/dist/{doctorCommand-DyViaaCP.js → doctorCommand-DtfJ3FA6.js} +314 -234
  26. package/dist/{dormancyCommand-DMZPA_HW.js → dormancyCommand-Drn7o0No.js} +1 -1
  27. package/dist/{embeddingsCommand-CMxRPkg6.js → embeddingsCommand-Z-jO1fWN.js} +1 -1
  28. package/dist/{envCommand-CDLr-yD4.js → envCommand-D4gCrrTZ.js} +15 -15
  29. package/dist/{evolveCommand-Ct4NesL3.js → evolveCommand-CMROeKeA.js} +2 -2
  30. package/dist/fileConventions-DOqD3lPS.js +34 -0
  31. package/dist/{fileTaxonomy-CJfgOllU.js → fileTaxonomy-DvDUV9wq.js} +1 -1
  32. package/dist/frameworkTableAssembly-C6ETawPR.js +2 -0
  33. package/dist/{frameworkTableAssembly-BwJVEKLr.js → frameworkTableAssembly-w-XnLa3q.js} +1 -1
  34. package/dist/{generateCommand-BDdOxhnv.js → generateCommand-DTNPGjxo.js} +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/{infoCommand-orsKJKUy.js → infoCommand-DXM868o_.js} +1 -1
  37. package/dist/inspectMetrics-CGF94puw.js +143 -0
  38. package/dist/{metaCommands-CfRLra0s.js → metaCommands-C6RFmF1r.js} +2 -2
  39. package/dist/{migrate-D3MK9BpK.js → migrate-D0F-eTlK.js} +2 -2
  40. package/dist/{pageConvention-cEiRxdab.js → pageConvention-CzUiSbtU.js} +1 -1
  41. package/dist/{privacyCommand-XejDMvmu.js → privacyCommand-DGdopOI6.js} +1 -1
  42. package/dist/{projectScaffold-mIX_DpSe.js → projectScaffold-B4dmTlwT.js} +1 -1
  43. package/dist/{projectScaffold-BIl97_E6.js → projectScaffold-EzlErR4E.js} +1 -1
  44. package/dist/{renderModeScan-D7J1B7Kw.js → renderModeScan-CcH2X1_D.js} +25 -10
  45. package/dist/{renderProfile-1OWWAAtx.js → renderProfile-Ck32Fzxr.js} +2 -2
  46. package/dist/{runtimeTrace-Bo8jWsNe.js → runtimeTrace-BPQyCmC5.js} +1 -1
  47. package/dist/{sdkgen-OZhiT2vz.js → sdkgen-Se88ifTd.js} +1 -1
  48. package/dist/serveCommand-DLc-BznW.js +2 -0
  49. package/dist/{serveCommand-YbD5VgfV.js → serveCommand-DkP3OT0W.js} +885 -868
  50. package/dist/serveEntry.js +1 -1
  51. package/dist/start-DfL3fOiN.js +3 -0
  52. package/dist/start-jw89Xbqy.js +1339 -0
  53. package/dist/startEntry.js +1 -1
  54. package/dist/{staticCommand-Dr2M6tpU.js → staticCommand-BwNEDlSU.js} +1 -1
  55. package/dist/{test-rXFq4S76.js → test-f3amja6a.js} +1 -1
  56. package/dist/updateCommand-5gFVfK5q.js +2 -0
  57. package/dist/{updateCommand-BgH6vTtq.js → updateCommand-BMk2e4ky.js} +1 -1
  58. package/dist/{webDev-BbdkzMbd.js → webDev-BgWL9gKV.js} +1233 -853
  59. package/dist/webDev-CZbTsDcH.js +2 -0
  60. package/dist/{webhooksCommand-DjE6u0r4.js → webhooksCommand-CoIO3jbj.js} +1 -1
  61. package/package.json +48 -17
  62. package/templates/AGENTS.core.md +26 -5
  63. package/templates/AGENTS.md +30 -9
  64. package/templates/agent-docs/_index.md +4 -4
  65. package/templates/agent-docs/_manifest.json +11 -11
  66. package/templates/agent-docs/cli.md +125 -14
  67. package/templates/agent-docs/data.md +210 -7
  68. package/templates/agent-docs/database/schema.md +1 -1
  69. package/templates/agent-docs/database/seedsdialects.md +1 -1
  70. package/templates/agent-docs/deployment.md +22 -3
  71. package/templates/agent-docs/internationalization.md +131 -13
  72. package/templates/agent-docs/introduction.md +46 -0
  73. package/templates/agent-docs/local-first-mobile.md +34 -7
  74. package/templates/agent-docs/plugins/ai-flows.md +1 -1
  75. package/templates/agent-docs/plugins/audit.md +5 -5
  76. package/templates/agent-docs/plugins/cdc-out.md +2 -2
  77. package/templates/agent-docs/plugins/{versioning.md → row-history.md} +22 -22
  78. package/templates/agent-docs/plugins/storage.md +2 -2
  79. package/templates/agent-docs/plugins.md +29 -7
  80. package/templates/agent-docs/reference.md +39 -2
  81. package/templates/agent-docs/routing.md +341 -47
  82. package/templates/agent-docs/schema-driven-ui.md +78 -2
  83. package/templates/agent-docs/security.md +125 -8
  84. package/templates/agent-docs/templates/apibackends.md +14 -14
  85. package/templates/agent-docs/templates/overview.md +1 -1
  86. package/templates/agent-docs/whats-new.md +100 -29
  87. package/templates/apps/api-ai/package.json +6 -7
  88. package/templates/apps/api-ai/tests/summarize.test.ts +1 -1
  89. package/templates/apps/api-auth/package.json +8 -8
  90. package/templates/apps/api-backend/package.json +7 -7
  91. package/templates/apps/api-backend-deactivation/package.json +7 -7
  92. package/templates/apps/api-backend-mail/package.json +8 -8
  93. package/templates/apps/api-backend-mariadb/package.json +9 -9
  94. package/templates/apps/api-backend-sqlite/package.json +8 -8
  95. package/templates/apps/api-backend-storage/package.json +8 -8
  96. package/templates/apps/api-cms/package.json +9 -10
  97. package/templates/apps/api-collab/package.json +8 -8
  98. package/templates/apps/api-data-advanced/package.json +8 -8
  99. package/templates/apps/api-durable/package.json +8 -8
  100. package/templates/apps/api-feature-flags/package.json +9 -9
  101. package/templates/apps/api-governance/package.json +8 -8
  102. package/templates/apps/api-kv/package.json +8 -8
  103. package/templates/apps/api-moderation/package.json +8 -8
  104. package/templates/apps/api-observability/package.json +8 -8
  105. package/templates/apps/api-ratelimit/package.json +8 -8
  106. package/templates/apps/api-rbac/package.json +8 -8
  107. package/templates/apps/api-rest/package.json +7 -7
  108. package/templates/apps/{api-versioning → api-row-history}/README.md +3 -3
  109. package/templates/apps/{api-versioning → api-row-history}/actions/documents.asOf.action.server.ts +1 -1
  110. package/templates/apps/{api-versioning → api-row-history}/actions/documents.history.action.server.ts +1 -1
  111. package/templates/apps/{api-versioning → api-row-history}/app.config.ts +3 -3
  112. package/templates/apps/{api-versioning → api-row-history}/database/schema.ts +1 -1
  113. package/templates/apps/{api-versioning → api-row-history}/mutations/documents.create.mutation.server.ts +1 -1
  114. package/templates/apps/{api-versioning → api-row-history}/mutations/documents.create.mutation.ts +1 -1
  115. package/templates/apps/{api-versioning → api-row-history}/mutations/documents.update.mutation.ts +1 -1
  116. package/templates/apps/{api-versioning → api-row-history}/package.json +8 -8
  117. package/templates/apps/api-row-history/template.json +6 -0
  118. package/templates/apps/{api-versioning → api-row-history}/tests/documents.create.test.ts +1 -1
  119. package/templates/apps/api-saas/app.config.ts +1 -0
  120. package/templates/apps/api-saas/package.json +10 -11
  121. package/templates/apps/api-saas-starter/package.json +10 -10
  122. package/templates/apps/api-search/package.json +8 -8
  123. package/templates/apps/api-status/package.json +8 -8
  124. package/templates/apps/api-webhooks/package.json +9 -9
  125. package/templates/apps/changelog/package.json +6 -6
  126. package/templates/apps/edge-functions/package.json +2 -2
  127. package/templates/apps/frontend-admin/package.json +8 -8
  128. package/templates/apps/frontend-app/package.json +9 -9
  129. package/templates/apps/frontend-auth/package.json +8 -8
  130. package/templates/apps/frontend-blank/package.json +7 -7
  131. package/templates/apps/frontend-cms/package.json +9 -9
  132. package/templates/apps/frontend-collab/package.json +10 -10
  133. package/templates/apps/frontend-collab/src/pages/page.test.tsx +10 -9
  134. package/templates/apps/frontend-collab/src/pages/page.tsx +42 -65
  135. package/templates/apps/frontend-contact/package.json +7 -7
  136. package/templates/apps/frontend-dashboard/package.json +7 -7
  137. package/templates/apps/frontend-docs/package.json +6 -7
  138. package/templates/apps/frontend-i18n/package.json +6 -6
  139. package/templates/apps/frontend-landing/package.json +6 -7
  140. package/templates/apps/frontend-portal/package.json +8 -8
  141. package/templates/apps/frontend-saas/package.json +8 -8
  142. package/templates/apps/frontend-spa/package.json +7 -7
  143. package/templates/apps/frontend-ssr/package.json +7 -7
  144. package/templates/apps/frontend-ssr-api/package.json +8 -8
  145. package/templates/apps/frontend-static-blog/package.json +6 -6
  146. package/templates/apps/frontend-status/package.json +8 -8
  147. package/templates/apps/mobile-app/package.json +4 -4
  148. package/dist/agentsMd-Bu_XQgVf.js +0 -2
  149. package/dist/apiBuild-BZ-plaQN.js +0 -2
  150. package/dist/appGraph-CM9wvUmD.js +0 -2
  151. package/dist/appGraph-Cv2W_wO5.js +0 -111
  152. package/dist/build-CFmqF39c.js +0 -721
  153. package/dist/checkCommand-DomvYDi1.js +0 -2
  154. package/dist/codegen-DiMn2KkZ.js +0 -2
  155. package/dist/dbCommand-Bnl7MSGC.js +0 -2
  156. package/dist/dev-Dz9GqA6P.js +0 -3
  157. package/dist/doctorCommand-BlciWUJb.js +0 -2
  158. package/dist/fileConventions-Cof68_BL.js +0 -33
  159. package/dist/frameworkTableAssembly-CVDB2hCq.js +0 -2
  160. package/dist/inspectMetrics-CfdKLh6t.js +0 -72
  161. package/dist/serveCommand-Bbgg7Paq.js +0 -2
  162. package/dist/start-7b_bL8Uc.js +0 -3
  163. package/dist/start-B1nH9ahf.js +0 -1150
  164. package/dist/updateCommand-BweY17z-.js +0 -2
  165. package/dist/webDev-BcRFTsMA.js +0 -2
  166. package/templates/apps/api-versioning/template.json +0 -6
  167. /package/templates/apps/{api-versioning → api-row-history}/actions/documents.asOf.action.ts +0 -0
  168. /package/templates/apps/{api-versioning → api-row-history}/actions/documents.history.action.ts +0 -0
  169. /package/templates/apps/{api-versioning → api-row-history}/mutations/documents.update.mutation.server.ts +0 -0
  170. /package/templates/apps/{api-versioning → api-row-history}/tsconfig.json +0 -0
@@ -0,0 +1,2 @@
1
+ import { b as e, v as t } from "./webDev-BgWL9gKV.js";
2
+ export { t as tryRunWebServe, e as walkPagesTree };
@@ -222,7 +222,7 @@ createVerifier({ secret: [process.env.WEBHOOK_SECRET, process.env.WEBHOOK_SECRET
222
222
  ...t === void 0 ? {} : { payload: t }
223
223
  };
224
224
  }, S = u({ scope: "voltro:webhooks" }), C = ["--out", "--name"], w = async (e) => {
225
- let { walk: t, loadDiscovered: n } = await import("./dev-Dz9GqA6P.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev--A3nsxA3.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
226
226
  return r((await n(await t(e))).events.map((e) => ({
227
227
  file: e.file,
228
228
  descriptor: e.descriptor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.50.1",
3
+ "version": "0.52.0",
4
4
  "description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -700,6 +700,36 @@
700
700
  "title": "A COMPUTED `source:` can keep its typed names — do not widen the whole registry",
701
701
  "kind": "manual"
702
702
  },
703
+ {
704
+ "version": "0.51.0",
705
+ "id": "0.51.0/02_restore-refuses-without-marker",
706
+ "title": "`voltro data restore` refuses when it cannot write the in-progress marker",
707
+ "kind": "manual"
708
+ },
709
+ {
710
+ "version": "0.51.0",
711
+ "id": "0.51.0/03_restore-assets-returns-counts",
712
+ "title": "`restoreAssetsFromCas` returns `{ count, objects }`, not a number",
713
+ "kind": "manual"
714
+ },
715
+ {
716
+ "version": "0.51.0",
717
+ "id": "0.51.0/04_i18n-timezone-prop",
718
+ "title": "`<I18nProvider>`: `timeZone` is its own prop, not an `intlConfig` member",
719
+ "kind": "transform"
720
+ },
721
+ {
722
+ "version": "0.52.0",
723
+ "id": "0.52.0/01_plugin-versioning-to-row-history",
724
+ "title": "@voltro/plugin-versioning → @voltro/plugin-row-history (the name now says what it does)",
725
+ "kind": "transform"
726
+ },
727
+ {
728
+ "version": "0.52.0",
729
+ "id": "0.52.0/02_http-interceptor-fail-closed",
730
+ "title": "A throwing `onHttpRequest` interceptor now refuses the request (500) instead of being ignored",
731
+ "kind": "manual"
732
+ },
703
733
  {
704
734
  "version": "0.6.0",
705
735
  "id": "0.6.0/01_no-dev-session-secret",
@@ -775,22 +805,23 @@
775
805
  "@effect/platform-node": "^0.108.0",
776
806
  "@effect/sql": "^0.52.0",
777
807
  "@effect/workflow": "^0.19.0",
778
- "@voltro/ai": "0.50.1",
779
- "@voltro/cache": "0.50.1",
780
- "@voltro/data-transfer": "0.50.1",
781
- "@voltro/database": "0.50.1",
782
- "@voltro/env": "0.50.1",
783
- "@voltro/kv": "0.50.1",
784
- "@voltro/logger": "0.50.1",
785
- "@voltro/plugin-auth": "0.50.1",
786
- "@voltro/plugin-broadcast": "0.50.1",
787
- "@voltro/plugin-mail": "0.50.1",
788
- "@voltro/plugin-storage": "0.50.1",
789
- "@voltro/plugin-webhooks": "0.50.1",
790
- "@voltro/protocol": "0.50.1",
791
- "@voltro/runtime": "0.50.1",
792
- "@voltro/serverless": "0.50.1",
793
- "@voltro/workflow": "0.50.1",
808
+ "@voltro/ai": "0.52.0",
809
+ "@voltro/cache": "0.52.0",
810
+ "@voltro/client": "0.52.0",
811
+ "@voltro/data-transfer": "0.52.0",
812
+ "@voltro/database": "0.52.0",
813
+ "@voltro/env": "0.52.0",
814
+ "@voltro/kv": "0.52.0",
815
+ "@voltro/logger": "0.52.0",
816
+ "@voltro/plugin-auth": "0.52.0",
817
+ "@voltro/plugin-broadcast": "0.52.0",
818
+ "@voltro/plugin-mail": "0.52.0",
819
+ "@voltro/plugin-storage": "0.52.0",
820
+ "@voltro/plugin-webhooks": "0.52.0",
821
+ "@voltro/protocol": "0.52.0",
822
+ "@voltro/runtime": "0.52.0",
823
+ "@voltro/serverless": "0.52.0",
824
+ "@voltro/workflow": "0.52.0",
794
825
  "chokidar": "^5.0.0",
795
826
  "ioredis": "^5.11.1",
796
827
  "tinyglobby": "^0.2.17",
@@ -404,11 +404,21 @@ Do NOT write `x.page.tsx`, `index.page.tsx` or `[id].page.tsx` — those were th
404
404
  **Every suffix above is a CONTRACT, not a label.** Something else depends on each
405
405
  promise, and `voltro doctor` enforces all of them:
406
406
 
407
- - `*.component.ui.tsx` may read (`useT`, `useCan`, `usePermissions`) but must
408
- never import a write hook (`useMutation`, `useAction`, `useUpload`, …). That is
409
- what lets a caller render it ten thousand times in a list, reuse it across
410
- features, and prerender it without reading its source. Lift the write into the
411
- owning `*.component.tsx` and pass a handler down.
407
+ - `*.component.ui.tsx` may read (`useT`, `useTFn`, `useCan`, `usePermissions`,
408
+ the `@voltro/i18n` formatters) but must never import a write hook
409
+ (`useMutation`, `useAction`, `useUpload`, …). That is what lets a caller render
410
+ it ten thousand times in a list, reuse it across features, and prerender it
411
+ without reading its source. Lift the write into the owning `*.component.tsx`
412
+ and pass a handler down.
413
+
414
+ **Reading is a recommendation, not a grudging allowance — call the hook, do
415
+ not take a prop.** Threading `t` / `formatDate` / `useCan` through props is
416
+ prop-drilling: it grows the prop surface with every formatter, has to be
417
+ restated in every intermediate signature and every test, and does NOT make the
418
+ component more reusable — it makes every call site worse. The one argument for
419
+ the prop, "it renders without a provider", is already paid for the moment any
420
+ test mounts one. Pick one way per component head: a `t` prop beside a
421
+ formatter hook is the worst of both.
412
422
  - `*.internal.ts` is the promise that refactoring inside that directory breaks
413
423
  nobody. An import from another subtree revokes it.
414
424
  - `*.types.ts` having no runtime export is what makes importing it free and makes
@@ -686,6 +696,17 @@ primitive → just save; the supervised dev loop respawns and regenerates.
686
696
  - **Don't roll your own session token / ICU/i18n / job runner** — use
687
697
  `signSession`/`verifySession`, `@voltro/i18n`, `@effect/workflow`.
688
698
  - **Don't add `bullmq`/`temporal`/`react-query`/SWR** — see Mental model.
699
+ - **Don't build a GraphQL layer over the stores.** It would bypass the
700
+ reactivity path (source-based invalidation, per-delivery guards) — a second,
701
+ dead read path. External consumers → REST + OpenAPI (`defineRestRoute` +
702
+ `@voltro/plugin-openapi`); internal clients → RPC + live subscriptions.
703
+ - **Don't write `'use server'` / `'use client'` — there is no RSC.** Islands +
704
+ loaders + streaming SSR are the model; those directives mark a boundary this
705
+ framework does not have.
706
+ - **Don't split the app into microservices with service-to-service RPC.** One
707
+ monolith process, scaled by running more replicas (`@effect/cluster` shards
708
+ durable workflows across instances), is the architecture. An external system
709
+ boundary is REST + OpenAPI; a reliable outbound effect is `ctx.outbox`.
689
710
 
690
711
  ## Plugins
691
712
 
@@ -404,11 +404,21 @@ Do NOT write `x.page.tsx`, `index.page.tsx` or `[id].page.tsx` — those were th
404
404
  **Every suffix above is a CONTRACT, not a label.** Something else depends on each
405
405
  promise, and `voltro doctor` enforces all of them:
406
406
 
407
- - `*.component.ui.tsx` may read (`useT`, `useCan`, `usePermissions`) but must
408
- never import a write hook (`useMutation`, `useAction`, `useUpload`, …). That is
409
- what lets a caller render it ten thousand times in a list, reuse it across
410
- features, and prerender it without reading its source. Lift the write into the
411
- owning `*.component.tsx` and pass a handler down.
407
+ - `*.component.ui.tsx` may read (`useT`, `useTFn`, `useCan`, `usePermissions`,
408
+ the `@voltro/i18n` formatters) but must never import a write hook
409
+ (`useMutation`, `useAction`, `useUpload`, …). That is what lets a caller render
410
+ it ten thousand times in a list, reuse it across features, and prerender it
411
+ without reading its source. Lift the write into the owning `*.component.tsx`
412
+ and pass a handler down.
413
+
414
+ **Reading is a recommendation, not a grudging allowance — call the hook, do
415
+ not take a prop.** Threading `t` / `formatDate` / `useCan` through props is
416
+ prop-drilling: it grows the prop surface with every formatter, has to be
417
+ restated in every intermediate signature and every test, and does NOT make the
418
+ component more reusable — it makes every call site worse. The one argument for
419
+ the prop, "it renders without a provider", is already paid for the moment any
420
+ test mounts one. Pick one way per component head: a `t` prop beside a
421
+ formatter hook is the worst of both.
412
422
  - `*.internal.ts` is the promise that refactoring inside that directory breaks
413
423
  nobody. An import from another subtree revokes it.
414
424
  - `*.types.ts` having no runtime export is what makes importing it free and makes
@@ -686,6 +696,17 @@ primitive → just save; the supervised dev loop respawns and regenerates.
686
696
  - **Don't roll your own session token / ICU/i18n / job runner** — use
687
697
  `signSession`/`verifySession`, `@voltro/i18n`, `@effect/workflow`.
688
698
  - **Don't add `bullmq`/`temporal`/`react-query`/SWR** — see Mental model.
699
+ - **Don't build a GraphQL layer over the stores.** It would bypass the
700
+ reactivity path (source-based invalidation, per-delivery guards) — a second,
701
+ dead read path. External consumers → REST + OpenAPI (`defineRestRoute` +
702
+ `@voltro/plugin-openapi`); internal clients → RPC + live subscriptions.
703
+ - **Don't write `'use server'` / `'use client'` — there is no RSC.** Islands +
704
+ loaders + streaming SSR are the model; those directives mark a boundary this
705
+ framework does not have.
706
+ - **Don't split the app into microservices with service-to-service RPC.** One
707
+ monolith process, scaled by running more replicas (`@effect/cluster` shards
708
+ durable workflows across instances), is the architecture. An external system
709
+ boundary is REST + OpenAPI; a reliable outbound effect is `ctx.outbox`.
689
710
 
690
711
  ## Plugins
691
712
 
@@ -707,7 +728,7 @@ each plugin's own README.
707
728
 
708
729
  | Topic | Open | Summary |
709
730
  |---|---|---|
710
- | **What's new in 0.50.1** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
731
+ | **What's new in 0.52.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
711
732
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
712
733
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
713
734
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -765,7 +786,7 @@ each plugin's own README.
765
786
  | auth-workos | `node_modules/@voltro/cli/templates/agent-docs/plugins/auth-workos.md` (or `node_modules/@voltro/plugin-auth-workos/README.md`) | WorkOS AuthStrategy — verifies WorkOS AuthKit / SSO JWTs via JWKS (no API key) and maps org_id → tenantId. |
766
787
  | billing | `node_modules/@voltro/cli/templates/agent-docs/plugins/billing.md` (or `node_modules/@voltro/plugin-billing/README.md`) | Subscriptions, plans, entitlements, and usage metering over a pluggable provider (Stripe + mock). Money is integer minor units. |
767
788
  | broadcast | `node_modules/@voltro/cli/templates/agent-docs/plugins/broadcast.md` (or `node_modules/@voltro/plugin-broadcast/README.md`) | Cross-replica reactivity over a pub/sub bus (Redis / NATS) for non-postgres dialects — closes the single-instance gap so a write on one pod surfaces on another. |
768
- | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook / Kafka / Snowflake / BigQuery) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
789
+ | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
769
790
  | clickhouse | `node_modules/@voltro/cli/templates/agent-docs/plugins/clickhouse.md` (or `node_modules/@voltro/plugin-clickhouse/README.md`) | Production-grade OLAP AnalyticsSink over ClickHouse — self-hosted or ClickHouse Cloud — for billions of events with millisecond aggregates. |
770
791
  | datadog | `node_modules/@voltro/cli/templates/agent-docs/plugins/datadog.md` (or `node_modules/@voltro/plugin-datadog/README.md`) | Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake. |
771
792
  | deactivation | `node_modules/@voltro/cli/templates/agent-docs/plugins/deactivation.md` (or `node_modules/@voltro/plugin-deactivation/README.md`) | A schema mixin that marks a subject as deactivated (can't log in) while keeping its data fully visible — the deliberate opposite of soft-delete. |
@@ -785,14 +806,14 @@ each plugin's own README.
785
806
  | prometheus | `node_modules/@voltro/cli/templates/agent-docs/plugins/prometheus.md` (or `node_modules/@voltro/plugin-prometheus/README.md`) | Prometheus exporter — scrapes the unified Metrics-API at GET /metrics in text exposition format. |
786
807
  | ratelimit | `node_modules/@voltro/cli/templates/agent-docs/plugins/ratelimit.md` (or `node_modules/@voltro/plugin-ratelimit/README.md`) | Per-endpoint, per-subject and per-tenant request limits via the rpc interceptors. Sliding-window / fixed-window / token-bucket, memory / postgres / redis stores. |
787
808
  | rbac | `node_modules/@voltro/cli/templates/agent-docs/plugins/rbac.md` (or `node_modules/@voltro/plugin-rbac/README.md`) | Roles + permissions + the permission() handler guard. Roles compile to scopes. |
809
+ | row-history | `node_modules/@voltro/cli/templates/agent-docs/plugins/row-history.md` (or `node_modules/@voltro/plugin-row-history/README.md`) | Full row history + time-travel. audit() records who/when; row-history records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
788
810
  | scim | `node_modules/@voltro/cli/templates/agent-docs/plugins/scim.md` (or `node_modules/@voltro/plugin-scim/README.md`) | SCIM 2.0 provisioning — Users + Groups REST endpoints so an enterprise IdP (Okta, Entra, OneLogin) can create / update / deactivate users in your app. |
789
811
  | search | `node_modules/@voltro/cli/templates/agent-docs/plugins/search.md` (or `node_modules/@voltro/plugin-search/README.md`) | Keep an external search index (Typesense / Meilisearch / Algolia) in sync with your tables via the ChangeEvent tap, query it tenant-scoped through a typed action + hook. |
790
812
  | sentry | `node_modules/@voltro/cli/templates/agent-docs/plugins/sentry.md` (or `node_modules/@voltro/plugin-sentry/README.md`) | Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and opt-in performance traces. |
791
813
  | soft-delete | `node_modules/@voltro/cli/templates/agent-docs/plugins/soft-delete.md` (or `node_modules/@voltro/plugin-soft-delete/README.md`) | The softDelete() schema mixin — deletedAt / deletedBy columns, delete() redirected to an UPDATE, default reads filtered, hardDelete() bypass. |
792
814
  | sso-saml | `node_modules/@voltro/cli/templates/agent-docs/plugins/sso-saml.md` (or `node_modules/@voltro/plugin-sso-saml/README.md`) | Enterprise SAML 2.0 SSO — SP-initiated login, ACS assertion consumer, SP metadata. Signature verification via @node-saml/node-saml; framework session minting built in. |
793
- | storage | `node_modules/@voltro/cli/templates/agent-docs/plugins/storage.md` (or `node_modules/@voltro/plugin-storage/README.md`) | File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / R2 / GCS / MinIO / filesystem / memory providers, presigned URLs, a dashboard browser. |
815
+ | storage | `node_modules/@voltro/cli/templates/agent-docs/plugins/storage.md` (or `node_modules/@voltro/plugin-storage/README.md`) | File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / MinIO (R2 and GCS via their S3 interop) / Azure / database / filesystem / memory providers, presigned URLs, a dashboard browser. |
794
816
  | tinybird | `node_modules/@voltro/cli/templates/agent-docs/plugins/tinybird.md` (or `node_modules/@voltro/plugin-tinybird/README.md`) | Hosted-ClickHouse AnalyticsSink over Tinybird's Events API + Pipes — pay-as-you-go OLAP without operating a cluster. |
795
- | versioning | `node_modules/@voltro/cli/templates/agent-docs/plugins/versioning.md` (or `node_modules/@voltro/plugin-versioning/README.md`) | Full row history + time-travel. audit() records who/when; versioning records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
796
817
  | webhooks | `node_modules/@voltro/cli/templates/agent-docs/plugins/webhooks.md` (or `node_modules/@voltro/plugin-webhooks/README.md`) | First-class outgoing + incoming webhooks — declared events with a webhook: block, defineIncomingWebhook in *.webhook.tsx files, runtime subscriptions, durable delivery via @effect/workflow, HMAC signing, provider presets, and idempotency. |
797
818
 
798
819
 
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.50.1** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
12
+ | **What's new in 0.52.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
13
13
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
14
14
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
15
15
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -67,7 +67,7 @@ each plugin's own README.
67
67
  | auth-workos | `node_modules/@voltro/cli/templates/agent-docs/plugins/auth-workos.md` (or `node_modules/@voltro/plugin-auth-workos/README.md`) | WorkOS AuthStrategy — verifies WorkOS AuthKit / SSO JWTs via JWKS (no API key) and maps org_id → tenantId. |
68
68
  | billing | `node_modules/@voltro/cli/templates/agent-docs/plugins/billing.md` (or `node_modules/@voltro/plugin-billing/README.md`) | Subscriptions, plans, entitlements, and usage metering over a pluggable provider (Stripe + mock). Money is integer minor units. |
69
69
  | broadcast | `node_modules/@voltro/cli/templates/agent-docs/plugins/broadcast.md` (or `node_modules/@voltro/plugin-broadcast/README.md`) | Cross-replica reactivity over a pub/sub bus (Redis / NATS) for non-postgres dialects — closes the single-instance gap so a write on one pod surfaces on another. |
70
- | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook / Kafka / Snowflake / BigQuery) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
70
+ | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
71
71
  | clickhouse | `node_modules/@voltro/cli/templates/agent-docs/plugins/clickhouse.md` (or `node_modules/@voltro/plugin-clickhouse/README.md`) | Production-grade OLAP AnalyticsSink over ClickHouse — self-hosted or ClickHouse Cloud — for billions of events with millisecond aggregates. |
72
72
  | datadog | `node_modules/@voltro/cli/templates/agent-docs/plugins/datadog.md` (or `node_modules/@voltro/plugin-datadog/README.md`) | Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake. |
73
73
  | deactivation | `node_modules/@voltro/cli/templates/agent-docs/plugins/deactivation.md` (or `node_modules/@voltro/plugin-deactivation/README.md`) | A schema mixin that marks a subject as deactivated (can't log in) while keeping its data fully visible — the deliberate opposite of soft-delete. |
@@ -87,12 +87,12 @@ each plugin's own README.
87
87
  | prometheus | `node_modules/@voltro/cli/templates/agent-docs/plugins/prometheus.md` (or `node_modules/@voltro/plugin-prometheus/README.md`) | Prometheus exporter — scrapes the unified Metrics-API at GET /metrics in text exposition format. |
88
88
  | ratelimit | `node_modules/@voltro/cli/templates/agent-docs/plugins/ratelimit.md` (or `node_modules/@voltro/plugin-ratelimit/README.md`) | Per-endpoint, per-subject and per-tenant request limits via the rpc interceptors. Sliding-window / fixed-window / token-bucket, memory / postgres / redis stores. |
89
89
  | rbac | `node_modules/@voltro/cli/templates/agent-docs/plugins/rbac.md` (or `node_modules/@voltro/plugin-rbac/README.md`) | Roles + permissions + the permission() handler guard. Roles compile to scopes. |
90
+ | row-history | `node_modules/@voltro/cli/templates/agent-docs/plugins/row-history.md` (or `node_modules/@voltro/plugin-row-history/README.md`) | Full row history + time-travel. audit() records who/when; row-history records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
90
91
  | scim | `node_modules/@voltro/cli/templates/agent-docs/plugins/scim.md` (or `node_modules/@voltro/plugin-scim/README.md`) | SCIM 2.0 provisioning — Users + Groups REST endpoints so an enterprise IdP (Okta, Entra, OneLogin) can create / update / deactivate users in your app. |
91
92
  | search | `node_modules/@voltro/cli/templates/agent-docs/plugins/search.md` (or `node_modules/@voltro/plugin-search/README.md`) | Keep an external search index (Typesense / Meilisearch / Algolia) in sync with your tables via the ChangeEvent tap, query it tenant-scoped through a typed action + hook. |
92
93
  | sentry | `node_modules/@voltro/cli/templates/agent-docs/plugins/sentry.md` (or `node_modules/@voltro/plugin-sentry/README.md`) | Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and opt-in performance traces. |
93
94
  | soft-delete | `node_modules/@voltro/cli/templates/agent-docs/plugins/soft-delete.md` (or `node_modules/@voltro/plugin-soft-delete/README.md`) | The softDelete() schema mixin — deletedAt / deletedBy columns, delete() redirected to an UPDATE, default reads filtered, hardDelete() bypass. |
94
95
  | sso-saml | `node_modules/@voltro/cli/templates/agent-docs/plugins/sso-saml.md` (or `node_modules/@voltro/plugin-sso-saml/README.md`) | Enterprise SAML 2.0 SSO — SP-initiated login, ACS assertion consumer, SP metadata. Signature verification via @node-saml/node-saml; framework session minting built in. |
95
- | storage | `node_modules/@voltro/cli/templates/agent-docs/plugins/storage.md` (or `node_modules/@voltro/plugin-storage/README.md`) | File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / R2 / GCS / MinIO / filesystem / memory providers, presigned URLs, a dashboard browser. |
96
+ | storage | `node_modules/@voltro/cli/templates/agent-docs/plugins/storage.md` (or `node_modules/@voltro/plugin-storage/README.md`) | File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / MinIO (R2 and GCS via their S3 interop) / Azure / database / filesystem / memory providers, presigned URLs, a dashboard browser. |
96
97
  | tinybird | `node_modules/@voltro/cli/templates/agent-docs/plugins/tinybird.md` (or `node_modules/@voltro/plugin-tinybird/README.md`) | Hosted-ClickHouse AnalyticsSink over Tinybird's Events API + Pipes — pay-as-you-go OLAP without operating a cluster. |
97
- | versioning | `node_modules/@voltro/cli/templates/agent-docs/plugins/versioning.md` (or `node_modules/@voltro/plugin-versioning/README.md`) | Full row history + time-travel. audit() records who/when; versioning records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
98
98
  | webhooks | `node_modules/@voltro/cli/templates/agent-docs/plugins/webhooks.md` (or `node_modules/@voltro/plugin-webhooks/README.md`) | First-class outgoing + incoming webhooks — declared events with a webhook: block, defineIncomingWebhook in *.webhook.tsx files, runtime subscriptions, durable delivery via @effect/workflow, HMAC signing, provider presets, and idempotency. |
@@ -460,7 +460,7 @@
460
460
  {
461
461
  "slug": "cdc-out",
462
462
  "title": "CDC-out (reverse-ETL)",
463
- "description": "Declaratively mirror table changes outward to external sinks (webhook / Kafka / Snowflake / BigQuery) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered.",
463
+ "description": "Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered.",
464
464
  "pkg": "@voltro/plugin-cdc-out",
465
465
  "doc": "plugins/cdc-out.md",
466
466
  "module": "agent-docs/plugins/cdc-out.md"
@@ -617,6 +617,14 @@
617
617
  "doc": "plugins/rbac.md",
618
618
  "module": "agent-docs/plugins/rbac.md"
619
619
  },
620
+ {
621
+ "slug": "row-history",
622
+ "title": "Row history",
623
+ "description": "Full row history + time-travel. audit() records who/when; row-history records what-changed-to-what — a value snapshot of every row on every write, with as-of queries.",
624
+ "pkg": "@voltro/plugin-row-history",
625
+ "doc": "plugins/row-history.md",
626
+ "module": "agent-docs/plugins/row-history.md"
627
+ },
620
628
  {
621
629
  "slug": "scim",
622
630
  "title": "SCIM",
@@ -660,7 +668,7 @@
660
668
  {
661
669
  "slug": "storage",
662
670
  "title": "Storage",
663
- "description": "File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / R2 / GCS / MinIO / filesystem / memory providers, presigned URLs, a dashboard browser.",
671
+ "description": "File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / MinIO (R2 and GCS via their S3 interop) / Azure / database / filesystem / memory providers, presigned URLs, a dashboard browser.",
664
672
  "pkg": "@voltro/plugin-storage",
665
673
  "doc": "plugins/storage.md",
666
674
  "module": "agent-docs/plugins/storage.md"
@@ -673,14 +681,6 @@
673
681
  "doc": "plugins/tinybird.md",
674
682
  "module": "agent-docs/plugins/tinybird.md"
675
683
  },
676
- {
677
- "slug": "versioning",
678
- "title": "Row versioning",
679
- "description": "Full row history + time-travel. audit() records who/when; versioning records what-changed-to-what — a value snapshot of every row on every write, with as-of queries.",
680
- "pkg": "@voltro/plugin-versioning",
681
- "doc": "plugins/versioning.md",
682
- "module": "agent-docs/plugins/versioning.md"
683
- },
684
684
  {
685
685
  "slug": "webhooks",
686
686
  "title": "Webhooks",
@@ -710,9 +710,9 @@
710
710
  "api-ratelimit": "agent-docs/templates/apibackends.md",
711
711
  "api-rbac": "agent-docs/templates/apibackends.md",
712
712
  "api-rest": "agent-docs/templates/apibackends.md",
713
+ "api-row-history": "agent-docs/templates/apibackends.md",
713
714
  "api-saas": "agent-docs/templates/apibackends.md",
714
715
  "api-search": "agent-docs/templates/apibackends.md",
715
- "api-versioning": "agent-docs/templates/apibackends.md",
716
716
  "api-webhooks": "agent-docs/templates/apibackends.md",
717
717
  "app": "agent-docs/templates/appshells.md",
718
718
  "blank": "agent-docs/templates/appshells.md",
@@ -142,6 +142,12 @@ config value always winning:
142
142
  | `VOLTRO_CSP_HTML` | CSP for HTML responses (under `strict` it defaults to the same policy as `VOLTRO_CSP`). `off` drops just this one. |
143
143
  | `VOLTRO_HSTS` | `Strict-Transport-Security` value. `off` drops just this one. |
144
144
  | `VOLTRO_MAX_RPC_BODY_BYTES` | Cap on the buffered `POST /rpc` JSON body (default 8 MiB) — an oversized body is refused `413` and never buffered past the cap. File uploads ride plugin routes with their own limits. |
145
+ | `VOLTRO_MAX_BODY_BYTES` | Cap on every OTHER body read — plugin HTTP routes, REST routes, incoming webhooks (default 8 MiB, matching the rpc cap). The config-file spelling is `http.maxBodyBytes` in `app.config.ts`; per-route overrides (`defineRestRoute({ maxBodyBytes })`, a webhook handler's `maxBodyBytes`) win over both. Oversize is `413` for `Content-Length` and chunked alike. |
146
+
147
+ Response compression for the buffered non-rpc surfaces (and `voltro start`'s
148
+ HTML) is configured in the same `http:` block — `http.compression.{enabled,minBytes}`
149
+ (default on, 1 KiB threshold; `POST /rpc` is never compressed). Details + the
150
+ BREACH reasoning: [Security → compression](/docs/security/overview#response-compression--and-where-breach-sits).
145
151
 
146
152
  Generate any of the secret vars above with `voltro secret generate <purpose>` (see [`secret`](#command-quick-reference)) — it picks the right length and format. A lower environment's secrets must always differ from production's.
147
153
 
@@ -317,36 +323,48 @@ fails to update.
317
323
 
318
324
  ```
319
325
  observed: 12/34 procedures exercised (35%)
326
+ 1 declared/observed mismatch among the 12 that ran
320
327
  mutation(orders.place) (api/orders/place.mutation.ts)
321
328
  writes 'inventory' (update) but declares no target for it
322
329
  subscriptions on that table are not invalidated by this mutation
323
330
  fix: add { table: 'inventory', op: 'update' } to this procedure's targets
324
- 22 unexercised — no recording, so nothing is claimed about them
331
+ 20 never ran — no observation exists, so nothing is claimed about them
332
+ 2 ran with no table access recorded — indistinguishable from touching nothing, so nothing is claimed about them either
325
333
  ```
326
334
 
327
- Three things about that output are deliberate:
335
+ Four things about that output are deliberate:
328
336
 
329
337
  - **Coverage comes first.** Three findings at 8% coverage and three at 95% are
330
338
  different claims. Hiding the denominator is how a check starts overstating
331
339
  what it knows.
332
- - **`unexercised` is a third state, not a finding.** A procedure no test and no
333
- dev session ever ran has *no observation* which is not the same as "touches
334
- nothing". It is counted and never reported as a problem.
340
+ - **Every result line carries the count it is a result about.** `1 mismatch
341
+ among the 12 that ran` never a bare verdict. A sentence that still reads as
342
+ a conclusion once it is cut out of this block will eventually be cut out of
343
+ it, and quoted as a clean bill of health for a surface nobody measured.
344
+ - **The counts partition the declared set — they add up to the total, always.**
345
+ There are two ways to say nothing about a procedure, and both get a line:
346
+ it never ran, or it ran and no table access was recorded for it. A procedure
347
+ that appeared in no line at all would be indistinguishable from a defect in
348
+ `check` itself, so the two kinds of blindness are named separately and never
349
+ folded into the findings.
335
350
  - **Observed diagnostics never fail the build.** An observation is evidence
336
351
  about the runs that happened, not a proof about the ones that didn't, and
337
352
  `check`'s exit code gates CI. They are always warnings.
338
353
 
339
- A fourth thing is not visible in that output and matters more than any of the
340
- three: **the recorder only knows what ran.** A boot is not a run. An idle dev
354
+ A fifth thing is not visible in that output and matters more than any of the
355
+ four: **the recorder only knows what ran.** A boot is not a run. An idle dev
341
356
  instance that started, served nothing and stopped produces
342
357
 
343
358
  ```
344
- observed: 0/2755 procedures exercised (0%)
345
- 2753 unexercisedno recording, so nothing is claimed about them
359
+ observed: 0/34 procedures exercised (0%)
360
+ nothing was compared a declaration is only checked against a procedure that RAN
361
+ 34 never ran — no observation exists, so nothing is claimed about them
346
362
  ```
347
363
 
348
- which is the honest answer and also a useless one the section says nothing
349
- about 99.9% of the surface. If you want this as a CI gate, the recording pass has
364
+ Note what that is NOT: it is not "no mismatches found". At zero coverage there
365
+ is no result to report, so the section reports the absence of the comparison
366
+ instead. The honest answer is also a useless one — it says nothing about any of
367
+ the surface. If you want this as a CI gate, the recording pass has
350
368
  to be a run that actually *calls* the procedures. `voltro e2e` is one: it spawns
351
369
  `voltro dev` for the api, which turns recording on, and then drives the specs. A
352
370
  harness of your own that boots the app in its own process needs
@@ -365,6 +383,29 @@ handlers: a static pass over code that reaches the store through shared helpers,
365
383
  behind conditionals, has a long tail of both false positives and false negatives
366
384
  — and a check that is *sometimes* wrong is one people stop reading.
367
385
 
386
+ #### Declared vs LIVE — against a running server
387
+
388
+ When `check` runs against a live api (a local `voltro dev`, or a deployed one
389
+ via `--url`), it also diffs the tags your source declares — plugin routes
390
+ included — against the server's actual rpc registry:
391
+
392
+ ```
393
+ declared vs live: 2 of 214 source-declared tags are NOT registered on the server
394
+ ⚠ presence.heartbeat — a generated client calls this and gets "Unknown request tag"
395
+ ⚠ presence.list — a generated client calls this and gets "Unknown request tag"
396
+ fix: if the server runs older code, redeploy; if it is current, plugin route registration was dropped — check the boot line "plugin routes registered"
397
+ ```
398
+
399
+ Only the source→live direction is a finding — a tag the server carries that your
400
+ source does not declare is normal runtime synthesis (agents, undo, approvals).
401
+ This exists because exactly that gap has shipped once: every
402
+ plugin-contributed procedure dead under `voltro serve`, with nothing anywhere
403
+ saying so — the only evidence was a `Defect` frame in the browser console of
404
+ whoever happened to look. (That registration defect is fixed; the diff is the
405
+ runtime backstop for the next one, e.g. a stale deploy.) The server now also
406
+ logs every `Defect` frame it sends (`rpc defect sent to client`), so an unknown
407
+ tag is an operator-visible event rather than a client-only one.
408
+
368
409
  The HTTP surface is reachable directly too — e.g. `curl -s localhost:4000/_voltro/inspect/rpc | jq` (there is no `/_voltro/inspect/queries` endpoint; it's `rpc` for procedures, `routes` for the web page tree, `subscriptions` for active subscribers).
369
410
 
370
411
  ## Where to read next
@@ -1364,7 +1405,7 @@ Point the Kubernetes liveness probe at `/internal/liveness` and the readiness pr
1364
1405
  For WebSocket connections to land on the same backend (required for in-process subscription state):
1365
1406
 
1366
1407
  - Reverse proxy: `lb_policy ip_hash` (Caddy) / `ip_hash` (nginx).
1367
- - Or use `@voltro/plugin-cluster` to share subscription state across instances any-load-balancer-works.
1408
+ - Cross-instance subscription invalidation is built in on Postgres (LISTEN/NOTIFY) and MySQL/MariaDB (binlog CDC); on any other dialect add [`@voltro/plugin-broadcast`](/docs/plugins/broadcast) (Redis/NATS). With that in place a reconnect may land on any replica and still sees every change — sticky sessions then only keep one live connection pinned, they are not a correctness requirement.
1368
1409
 
1369
1410
  ## `voltro build api --target <swift|kotlin>` — native SDK generation
1370
1411
 
@@ -1796,6 +1837,47 @@ matching rule lives inside the CLI, so you cannot re-derive the list with your
1796
1837
  own grep. `--json` prints the complete scan, nothing elided, with no preflight
1797
1838
  output mixed in:
1798
1839
 
1840
+ ### Unimported `@voltro/*` dependencies
1841
+
1842
+ A declared framework dependency nobody imports still gets installed, walked on
1843
+ every `voltro update`, and read as evidence the package is in use — its
1844
+ breaking-change notes included. The usual origin is a migration: the app moves
1845
+ off a framework package to a third-party one, and the `package.json` entry
1846
+ stays. `voltro doctor` checks every `@voltro/*` in `dependencies` and
1847
+ `devDependencies` for an import site:
1848
+
1849
+ ```
1850
+ unimported deps · 8 @voltro/* package(s) declared, 214 file(s) scanned
1851
+ ⚠ @voltro/i18n — declared in dependencies, imported nowhere
1852
+ a dependency nobody imports still gets installed, updated, and read as
1853
+ evidence the package is in use — its breaking-change notes included.
1854
+ Remove it, or if it IS imported through an assembled specifier the scan
1855
+ cannot see, keep it and ignore this line — the rule is advisory.
1856
+ · (2 loaded by the framework itself: @voltro/cli, @voltro/sql-postgres)
1857
+ ```
1858
+
1859
+ Scoped to `@voltro/*` deliberately: for third-party packages the same question
1860
+ has a long tail of legitimate no-import shapes, and a rule that is sometimes
1861
+ wrong is one people stop reading. Three states are distinguished, and each is
1862
+ printed:
1863
+
1864
+ - **Exempt, by name** — packages the framework loads on your declaration
1865
+ (`@voltro/cli` is the binary; `@voltro/devtools` is mounted by `voltro dev`;
1866
+ the `@voltro/sql-*` dialect drivers are loaded from your config). An
1867
+ exemption you cannot see is a finding you cannot question.
1868
+ - **Not measurable yet** — `@voltro/client` / `@voltro/web` are normally
1869
+ imported by *generated* code. On a tree where codegen has never run, their
1870
+ absence is a missing measurement, not a dead dependency; the section says so
1871
+ and tells you to run `voltro dev` once.
1872
+ - **Unimported** — advisory, never fatal. A mention in a comment or an error
1873
+ string does not count as an import (a commented-out import is exactly the
1874
+ residue this looks for), and an import assembled at runtime from string
1875
+ pieces is invisible to the scan — the finding text says both.
1876
+
1877
+ The full report is in `voltro doctor --json` under `unimportedDeps`
1878
+ (`null` when there is no `package.json` to read — "could not check" and
1879
+ "checked, clean" never print the same).
1880
+
1799
1881
  ### Duplicate package instances
1800
1882
 
1801
1883
  `voltro doctor` also reports any identity-sensitive package resolved at more than
@@ -2850,7 +2932,7 @@ Not every run can take it, and a run that cannot **says why** rather than quietl
2850
2932
  taking the slower path:
2851
2933
 
2852
2934
  - a store the framework cannot send DDL to (the in-memory store).
2853
- - a **write recorder** on any table in the set — `versioningPlugin({ timing:
2935
+ - a **write recorder** on any table in the set — `rowHistoryPlugin({ timing:
2854
2936
  'in-transaction' })` and friends. A recorder is keyed by table name, so a
2855
2937
  staged write would find none and the recorder would silently not run. Its
2856
2938
  promise is "if the change committed, the entry is there", so the run keeps the
@@ -2906,7 +2988,7 @@ ITSELF is not a cycle: one statement carries the whole table.
2906
2988
 
2907
2989
  ### A `replace` does not write per-row history
2908
2990
 
2909
- Write recorders — `versioningPlugin({ timing: 'in-transaction' })` and anything
2991
+ Write recorders — `rowHistoryPlugin({ timing: 'in-transaction' })` and anything
2910
2992
  else registered through the same seam — are **suspended for a `replace`**. A
2911
2993
  replace sets a state; it does not change rows, so a per-row history entry would
2912
2994
  describe something that did not happen. On a large bundle that is not a detail:
@@ -3412,8 +3494,22 @@ Aborting on the first one made `--assets` unusable for exactly the deployment th
3412
3494
 
3413
3495
  Only a genuine *not found* is treated this way. A 403 from a rotated credential or a 5xx from a backend outage still fails the capture — calling those "the object is gone" would turn a recoverable outage into a backup that quietly contains nothing.
3414
3496
 
3497
+ **Three numbers, because they answer three questions.** `_voltro_storage_refs` holds one row per *reference*, several of which legitimately name one *key*, and the content-addressed store keeps one body per distinct *object*:
3498
+
3499
+ ```
3500
+ 57 reference(s) → 16 key(s) → 16 object(s), 65476 byte(s) under assets/
3501
+ ```
3502
+
3503
+ The stamp carries all three (`references`, `count`, `objects`, with `totalBytes` and `objectBytes` beside them). It used to carry only the reference count under the name `count`, so a stamp read `57` over a directory holding 16 files — and anyone answering *"are all the blobs there?"* after a restore compared the two and found a 3.5× gap that was not one. A key named by several references is also fetched once now, rather than downloaded and hashed once per row.
3504
+
3415
3505
  Resume is per blob key, so re-running a `--assets` capture that was interrupted transfers only what is missing. The **dump itself has no resume** — a vendor artifact is one opaque file with no offset to restart from. If you need a resumable, chunkable, observable transfer, that is the logical path (`export` / `import`), and it is why the logical path exists.
3416
3506
 
3507
+ ### The stamp's skew warning compares the backup against the TARGET
3508
+
3509
+ `restore` reads `voltro-backup-stamp.json` before touching anything and warns when the backup's schema fingerprint differs from the target's. That warning used to say the difference was against "what this code declares", and it was not — the value it compares against is the target database's *live* schema, read by introspection at restore time. Bringing a target to the backup's shape makes the warning disappear while the declared fingerprint is a third value entirely, which is how the mislabel was caught. The comparison was always the useful one; only the sentence was wrong, and it sent readers looking for a code change where a database differed.
3510
+
3511
+ The same distinction shows up in `voltro db plan`, which prints `live … · declared …` rather than `from → to` for the same reason: **a hash of a live database never equals the hash of the declaration it came from.** Introspection cannot recover everything a declaration carries — generated expressions, `maxLength`, sensitivity markers — so the two are not comparable and are not meant to match. The plan's operation list is what says whether they agree; `0 operations` under two different fingerprints means they do.
3512
+
3417
3513
  ### A restore that is interrupted refuses the next boot
3418
3514
 
3419
3515
  `restore` writes one row into `_voltro_replace_in_progress` **before** the first destructive statement and removes it **after** the last write — the blobs included. Its presence at boot is a refusal naming the artifact that was going in.
@@ -3427,6 +3523,17 @@ Two changes, covering different dumps:
3427
3523
  - **A backup taken by `voltro data backup` excludes the marker table** (`--exclude-table` / `--ignore-table`). It can no longer carry the thing that erases the guard on the way back in.
3428
3524
  - **`restore` writes the marker back after the tool exits**, on the failing path as well as the succeeding one. That covers dumps taken before this version and dumps made by hand. If it was removed and rewritten you get a warning saying so; if it could not be rewritten you get an error, because the guard is then off for that run and nothing will stop the next boot.
3429
3525
 
3526
+ **A restore that cannot write the marker at all is refused.** Two things can prevent it — the bookkeeping store will not open (wrong credentials, an unreachable database, a missing env var, no `app.config.ts` from here), or the table is not there yet — and both mean the same thing to you: this restore would run with no guard. The refusal names which one it was:
3527
+
3528
+ ```
3529
+ ✗ refusing to restore: the in-progress marker cannot be written.
3530
+ reason: bookkeeping is unavailable: connect ECONNREFUSED 127.0.0.1:5432
3531
+ ```
3532
+
3533
+ This used to be a silent hole rather than a refusal, and worse than silent. The failure to open the store was caught and discarded, and the discarded value guarded *every* branch below it — including the refusal that would have reported the guard missing. So a restore ran on and, over a database with zero marker rows, printed *"the next boot will REFUSE, by design"*. The next boot did not refuse, and `voltro data clear-replace-marker` had nothing to clear. A restore is the operation you run against a target that is already unwell, so the precaution was falling away exactly when it was needed.
3534
+
3535
+ **`--no-marker`** restores without the guard, deliberately. It warns every time and names the reason the marker was unavailable. It exists because the accidental way did: if going unguarded is ever right, it should be something you typed.
3536
+
3430
3537
  | dialect | shape | effect |
3431
3538
  |---|---|---|
3432
3539
  | postgres, mysql, mariadb | the dump names the whole database, so the restore drops the marker table too | our backups exclude it; for any other dump the marker is written back after the tool exits |
@@ -3435,6 +3542,10 @@ Two changes, covering different dumps:
3435
3542
 
3436
3543
  Clear a marker deliberately with `voltro data clear-replace-marker --yes` once you have decided the current state is correct.
3437
3544
 
3545
+ **`_voltro_data_transfers` is excluded for the same reason, one table over.** The restore opens its own run row there *before* the tool starts; a dump that carried the table dropped it mid-flight, and the update recording the outcome then wrote into a table that no longer held the row. The visible result was that a failed native restore did not appear in `voltro data transfers` at all — only the `backup` row the dump had brought over from the *source* database. The command that answers "did the restore finish" could not see the run asking.
3546
+
3547
+ Exactly those two tables are excluded, and the line is deliberate: a native restore into the same deployment *should* bring the migration ledger, the stored plans, the CDC offsets and the schedule claims — they describe the data being restored. These two describe the *restore*, and a record of an operation must not be overwritten by the operation it records.
3548
+
3438
3549
  ### Both directions are in the history
3439
3550
 
3440
3551
  `backup` and `restore` write a row to the same `_voltro_data_transfers` record `import` and `export` use, so `voltro data transfers` answers "did last night's backup finish" from the instance that ran it: