@voltro/cli 0.45.0 → 0.46.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 (112) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/dist/{apiBuild-CDQBNRDx.js → apiBuild-BKdd0I1Z.js} +2 -2
  3. package/dist/apiBuild-P2gewrfQ.js +2 -0
  4. package/dist/bin.d.ts +8 -0
  5. package/dist/bin.js +1 -1
  6. package/dist/{build-yvsLG77U.js → build-co6yadYz.js} +7 -7
  7. package/dist/{checkCommand-Boxov8lr.js → checkCommand-B-eiW-G_.js} +3 -3
  8. package/dist/{checkCommand-lrzwOzV1.js → checkCommand-CXAXTjZL.js} +1 -1
  9. package/dist/{codegenCommand-CQOLytL-.js → codegenCommand-CB6_3sX0.js} +15 -15
  10. package/dist/{codemodRunner-5ZhDH3rB.js → codemodRunner-DSZjgyCL.js} +8 -0
  11. package/dist/{commands-CEkVoFM5.js → commands-C-hxgAGr.js} +23 -23
  12. package/dist/{dashboardCommand-0ahyp-zA.js → dashboardCommand-CEVO0GWF.js} +3 -3
  13. package/dist/{dataCommand-D4IlZcih.js → dataCommand-k0ezbQWa.js} +271 -230
  14. package/dist/{dbCommand-B237qI-j.js → dbCommand-BwkyNxn-.js} +110 -110
  15. package/dist/dbCommand-C2XK63Dd.js +2 -0
  16. package/dist/{dev-DAM5CnAa.js → dev-8hTEnN6s.js} +2080 -2057
  17. package/dist/{dev-P4HyeY9W.js → dev-DyoYst4j.js} +1 -1
  18. package/dist/devActivity.d.ts +8 -0
  19. package/dist/{doctorCommand-DW2cwAvC.js → doctorCommand-B6D7HH1-.js} +14 -14
  20. package/dist/doctorCommand-Btl2Jz1a.js +2 -0
  21. package/dist/{dormancyCommand-BqQz7lbM.js → dormancyCommand-CrPAwOgo.js} +1 -1
  22. package/dist/{embeddingsCommand-DJyArpU4.js → embeddingsCommand-DsSxsn_b.js} +1 -1
  23. package/dist/{envCommand-CviRZx1L.js → envCommand-B_NPVGUf.js} +2 -2
  24. package/dist/{evalCommand-6RUfPen4.js → evalCommand-Db01Kbng.js} +24 -24
  25. package/dist/{evolveCommand-qdlP-R9G.js → evolveCommand-DUuaCIyn.js} +2 -2
  26. package/dist/{frameworkTableAssembly-oMPBKqlE.js → frameworkTableAssembly-BPlPfU07.js} +82 -78
  27. package/dist/frameworkTableAssembly-Bob3vj9R.js +2 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.js +1 -1
  30. package/dist/{infoCommand-CNWPSUcW.js → infoCommand-CuNl9cbh.js} +1 -1
  31. package/dist/inspectState.d.ts +8 -0
  32. package/dist/interruptedReplace-C1ZI84xi.js +27 -0
  33. package/dist/interruptedReplace-DNJ7MKph.js +2 -0
  34. package/dist/{migrate-DN47p2PG.js → migrate-CM1qwalu.js} +27 -27
  35. package/dist/{runtimeTrace-BDZSt9W_.js → runtimeTrace-BRNM06vK.js} +1 -1
  36. package/dist/{sdkgen-sNaD3oZB.js → sdkgen-CRCw_XcR.js} +1 -1
  37. package/dist/{seedRunner-IdHEprqf.js → seedRunner-Dgsiwk_e.js} +59 -59
  38. package/dist/serveCommand-BNHMVXyp.js +2 -0
  39. package/dist/serveCommand-CHOr74WM.js +1928 -0
  40. package/dist/serveEntry.d.ts +8 -0
  41. package/dist/serveEntry.js +1 -1
  42. package/dist/{start-Ds3xyn8R.js → start-7HcSIKEp.js} +1 -1
  43. package/dist/{start-BAlGcjF5.js → start-DvlYDyAr.js} +1 -1
  44. package/dist/startEntry.d.ts +8 -0
  45. package/dist/startEntry.js +1 -1
  46. package/dist/startup.d.ts +8 -0
  47. package/dist/{updateCommand-rCnhaj84.js → updateCommand-BlyXavoG.js} +1 -1
  48. package/dist/updateCommand-CT5AvVg7.js +2 -0
  49. package/dist/{webDev-101csg6Q.js → webDev-B4IEMB0n.js} +6 -6
  50. package/dist/webDev-B79ji8M5.js +2 -0
  51. package/dist/{webhooksCommand-C0iZZfaI.js → webhooksCommand-BRrG_sGf.js} +1 -1
  52. package/package.json +23 -17
  53. package/templates/AGENTS.md +1 -1
  54. package/templates/agent-docs/_index.md +1 -1
  55. package/templates/agent-docs/cli.md +138 -2
  56. package/templates/agent-docs/data.md +70 -4
  57. package/templates/agent-docs/whats-new.md +127 -54
  58. package/templates/apps/api-ai/package.json +7 -7
  59. package/templates/apps/api-auth/package.json +8 -8
  60. package/templates/apps/api-backend/package.json +7 -7
  61. package/templates/apps/api-backend-deactivation/package.json +7 -7
  62. package/templates/apps/api-backend-mail/package.json +8 -8
  63. package/templates/apps/api-backend-mariadb/package.json +9 -9
  64. package/templates/apps/api-backend-sqlite/package.json +8 -8
  65. package/templates/apps/api-backend-storage/package.json +8 -8
  66. package/templates/apps/api-cms/package.json +10 -10
  67. package/templates/apps/api-collab/package.json +8 -8
  68. package/templates/apps/api-data-advanced/package.json +8 -8
  69. package/templates/apps/api-durable/package.json +8 -8
  70. package/templates/apps/api-feature-flags/package.json +9 -9
  71. package/templates/apps/api-governance/package.json +8 -8
  72. package/templates/apps/api-kv/package.json +8 -8
  73. package/templates/apps/api-moderation/package.json +8 -8
  74. package/templates/apps/api-observability/package.json +8 -8
  75. package/templates/apps/api-ratelimit/package.json +8 -8
  76. package/templates/apps/api-rbac/package.json +8 -8
  77. package/templates/apps/api-rest/package.json +7 -7
  78. package/templates/apps/api-saas/package.json +11 -11
  79. package/templates/apps/api-saas-starter/package.json +10 -10
  80. package/templates/apps/api-search/package.json +8 -8
  81. package/templates/apps/api-status/package.json +8 -8
  82. package/templates/apps/api-versioning/package.json +8 -8
  83. package/templates/apps/api-webhooks/package.json +9 -9
  84. package/templates/apps/changelog/package.json +6 -6
  85. package/templates/apps/edge-functions/package.json +2 -2
  86. package/templates/apps/frontend-admin/package.json +8 -8
  87. package/templates/apps/frontend-app/package.json +9 -9
  88. package/templates/apps/frontend-auth/package.json +8 -8
  89. package/templates/apps/frontend-blank/package.json +7 -7
  90. package/templates/apps/frontend-cms/package.json +9 -9
  91. package/templates/apps/frontend-collab/package.json +10 -10
  92. package/templates/apps/frontend-contact/package.json +7 -7
  93. package/templates/apps/frontend-dashboard/package.json +7 -7
  94. package/templates/apps/frontend-docs/package.json +7 -7
  95. package/templates/apps/frontend-i18n/package.json +6 -6
  96. package/templates/apps/frontend-landing/package.json +7 -7
  97. package/templates/apps/frontend-portal/package.json +8 -8
  98. package/templates/apps/frontend-saas/package.json +8 -8
  99. package/templates/apps/frontend-spa/package.json +7 -7
  100. package/templates/apps/frontend-ssr/package.json +7 -7
  101. package/templates/apps/frontend-ssr-api/package.json +8 -8
  102. package/templates/apps/frontend-static-blog/package.json +6 -6
  103. package/templates/apps/frontend-status/package.json +8 -8
  104. package/templates/apps/mobile-app/package.json +4 -4
  105. package/dist/apiBuild-yJCqJYQ8.js +0 -2
  106. package/dist/dbCommand-BvQeKQOb.js +0 -2
  107. package/dist/doctorCommand-2K1dIzYk.js +0 -2
  108. package/dist/frameworkTableAssembly-DgylYh9_.js +0 -2
  109. package/dist/serveCommand-BBhT2U94.js +0 -1885
  110. package/dist/serveCommand-DY2jLy0o.js +0 -2
  111. package/dist/updateCommand-BH6EGuCc.js +0 -2
  112. package/dist/webDev-BLKBO34K.js +0 -2
@@ -36,3 +36,11 @@ declare module '@voltro/protocol' {
36
36
  '_voltro_undo_log': true;
37
37
  }
38
38
  }
39
+
40
+
41
+ declare module '@voltro/protocol' {
42
+ interface VoltroTableNames {
43
+ 'invoices': true;
44
+ 'invoice_lines': true;
45
+ }
46
+ }
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
2
  import { r as t } from "./appModuleLoader-C9r9mxZt.js";
3
3
  import { i as n } from "./dialectDriver-czCHYpeH.js";
4
- import { t as r } from "./serveCommand-BBhT2U94.js";
4
+ import { t as r } from "./serveCommand-CHOr74WM.js";
5
5
  export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
@@ -1,6 +1,6 @@
1
1
  import { i as e, t } from "./nodeEnvironment-cGFAj1J8.js";
2
2
  import { r as n } from "./appPort-B_HpJ_ck.js";
3
- import { A as r, C as i, D as a, E as o, F as s, H as c, I as l, J as ee, L as u, M as d, N as f, O as te, P as p, R as m, S as h, T as ne, V as g, W as re, X as _, Y as v, Z as y, a as b, b as x, d as S, f as C, ht as w, i as ie, j as T, k as E, n as D, nt as O, q as k, s as ae, tt as A, vt as j, x as oe, y as M, z as se } from "./webDev-101csg6Q.js";
3
+ import { A as r, C as i, D as a, E as o, F as s, H as c, I as l, J as ee, L as u, M as d, N as f, O as te, P as p, R as m, S as h, T as ne, V as g, W as re, X as _, Y as v, Z as y, a as b, b as x, d as S, f as C, ht as w, i as ie, j as T, k as E, n as D, nt as O, q as k, s as ae, tt as A, vt as j, x as oe, y as M, z as se } from "./webDev-B4IEMB0n.js";
4
4
  import { o as N, t as P } from "./connectionConfig-Bk9IC7D0.js";
5
5
  import { A as F, D as I, a as ce, h as le, j as ue, k as de } from "./inspect-B7U7Cl_Z.js";
6
6
  import { a as fe, c as L, d as pe, f as R, l as me, p as he, u as ge } from "./renderModeScan-D7J1B7Kw.js";
@@ -1,3 +1,3 @@
1
1
  import "./renderModeScan-D7J1B7Kw.js";
2
- import { t as e } from "./start-Ds3xyn8R.js";
2
+ import { t as e } from "./start-7HcSIKEp.js";
3
3
  export { e as runStartCommand };
@@ -19,3 +19,11 @@ declare module '@voltro/protocol' {
19
19
  '_voltro_undo_log': true;
20
20
  }
21
21
  }
22
+
23
+
24
+ declare module '@voltro/protocol' {
25
+ interface VoltroTableNames {
26
+ 'invoices': true;
27
+ 'invoice_lines': true;
28
+ }
29
+ }
@@ -1,3 +1,3 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
- import { t } from "./start-Ds3xyn8R.js";
2
+ import { t } from "./start-7HcSIKEp.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
package/dist/startup.d.ts CHANGED
@@ -79,3 +79,11 @@ declare module '@voltro/protocol' {
79
79
  '_voltro_undo_log': true;
80
80
  }
81
81
  }
82
+
83
+
84
+ declare module '@voltro/protocol' {
85
+ interface VoltroTableNames {
86
+ 'invoices': true;
87
+ 'invoice_lines': true;
88
+ }
89
+ }
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-5ZhDH3rB.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-DSZjgyCL.js";
2
2
  import { basename as r, dirname as i, join as a, relative as o, resolve as s } from "node:path";
3
3
  import { existsSync as c, readFileSync as l, readdirSync as u, statSync as d, writeFileSync as f } from "node:fs";
4
4
  import { totalmem as p } from "node:os";
@@ -0,0 +1,2 @@
1
+ import { f as e, p as t } from "./updateCommand-BlyXavoG.js";
2
+ export { e as runApplyCodemods, t as runUpdateCommand };
@@ -14,8 +14,8 @@ import { existsSync as j, promises as M, readFileSync as N, readdirSync as P, st
14
14
  import { fileURLToPath as te, pathToFileURL as L } from "node:url";
15
15
  import { addSink as R, createLogger as z, printReadyBlock as ne } from "@voltro/logger";
16
16
  import { createRequire as B } from "node:module";
17
- import { homedir as V } from "node:os";
18
- import { currentTraceContext as re, resolveSecret as H } from "@voltro/runtime";
17
+ import { currentTraceContext as V, resolveSecret as re } from "@voltro/runtime";
18
+ import { homedir as H } from "node:os";
19
19
  import { FRAMEWORK_ENV_CATALOG as U, appEnvEntries as ie, checkEnvNaming as W, formatEnvErrors as ae, installEnvSnapshot as G, isEnvContract as oe, pluginEnvEntries as se, renderDotEnvExample as ce, resolveEnv as le } from "@voltro/env";
20
20
  import { __installPublicEnv as ue } from "@voltro/env/public";
21
21
  import { mkdir as de, writeFile as fe } from "node:fs/promises";
@@ -68,7 +68,7 @@ __installPublicEnv(publicEnvValues)
68
68
  }, Ce = async (e) => {
69
69
  let t = oe(e.rawEnv) ? e.rawEnv : void 0, n = await le({
70
70
  contract: t,
71
- resolveSecret: H
71
+ resolveSecret: re
72
72
  }), r = [...n.errors, ...W(t, e.appType)];
73
73
  if (r.length > 0) throw Error(ae(r));
74
74
  G(n.values), e.appType === "web" && ue(n.publicValues);
@@ -780,7 +780,7 @@ __installPublicEnv(publicEnvValues)
780
780
  if (zt(t[i], a), !r) {
781
781
  r = !0;
782
782
  try {
783
- let t = a.length > 0 ? It(a[0]) : "", r = t.length > 500 ? t.slice(0, 500) + "…" : t, o = re();
783
+ let t = a.length > 0 ? It(a[0]) : "", r = t.length > 500 ? t.slice(0, 500) + "…" : t, o = V();
784
784
  e.emit("log", {
785
785
  level: i,
786
786
  message: r,
@@ -818,7 +818,7 @@ __installPublicEnv(publicEnvValues)
818
818
  argsCount: 1,
819
819
  ...t.scope === void 0 ? {} : { scope: t.scope }
820
820
  });
821
- let a = re(), o = a !== void 0 && t.fields.traceId === void 0 ? {
821
+ let a = V(), o = a !== void 0 && t.fields.traceId === void 0 ? {
822
822
  ...t.fields,
823
823
  traceId: a.traceId
824
824
  } : t.fields;
@@ -1190,7 +1190,7 @@ import {
1190
1190
  a || (a = !0, i.destroy(), r(e));
1191
1191
  };
1192
1192
  i.once("connect", () => o(!0)), i.once("error", () => o(!1)), setTimeout(() => o(!1), n);
1193
- }), Dn = () => !!(process.env.VOLTRO_DASHBOARD === "off" || process.argv.includes("--disable-dashboard")), On = () => process.env[Tn] === "1", kn = () => process.stdout.isTTY === !0, An = O(V(), ".voltro", "dashboard-launch.lock"), jn = 1e4, Mn = async () => {
1193
+ }), Dn = () => !!(process.env.VOLTRO_DASHBOARD === "off" || process.argv.includes("--disable-dashboard")), On = () => process.env[Tn] === "1", kn = () => process.stdout.isTTY === !0, An = O(H(), ".voltro", "dashboard-launch.lock"), jn = 1e4, Mn = async () => {
1194
1194
  try {
1195
1195
  await M.mkdir(D(An), { recursive: !0 });
1196
1196
  let e = await M.open(An, "wx");
@@ -0,0 +1,2 @@
1
+ import { _ as e, y as t } from "./webDev-B4IEMB0n.js";
2
+ export { e as tryRunWebServe, t 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-P4HyeY9W.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-DyoYst4j.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.45.0",
3
+ "version": "0.46.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",
@@ -646,6 +646,12 @@
646
646
  "title": "`source:` is typechecked against the app’s tables, from the next `voltro dev`",
647
647
  "kind": "manual"
648
648
  },
649
+ {
650
+ "version": "0.46.0",
651
+ "id": "0.46.0/01_typed-aggregate-source",
652
+ "title": "an aggregate’s `incremental.source` is typechecked against the app’s tables",
653
+ "kind": "manual"
654
+ },
649
655
  {
650
656
  "version": "0.5.0",
651
657
  "id": "0.5.0/01_one-terminal-exactly-one",
@@ -733,22 +739,22 @@
733
739
  "@effect/platform-node": "^0.108.0",
734
740
  "@effect/sql": "^0.52.0",
735
741
  "@effect/workflow": "^0.19.0",
736
- "@voltro/ai": "0.45.0",
737
- "@voltro/cache": "0.45.0",
738
- "@voltro/data-transfer": "0.45.0",
739
- "@voltro/database": "0.45.0",
740
- "@voltro/env": "0.45.0",
741
- "@voltro/kv": "0.45.0",
742
- "@voltro/logger": "0.45.0",
743
- "@voltro/plugin-auth": "0.45.0",
744
- "@voltro/plugin-broadcast": "0.45.0",
745
- "@voltro/plugin-mail": "0.45.0",
746
- "@voltro/plugin-storage": "0.45.0",
747
- "@voltro/plugin-webhooks": "0.45.0",
748
- "@voltro/protocol": "0.45.0",
749
- "@voltro/runtime": "0.45.0",
750
- "@voltro/serverless": "0.45.0",
751
- "@voltro/workflow": "0.45.0",
742
+ "@voltro/ai": "0.46.0",
743
+ "@voltro/cache": "0.46.0",
744
+ "@voltro/data-transfer": "0.46.0",
745
+ "@voltro/database": "0.46.0",
746
+ "@voltro/env": "0.46.0",
747
+ "@voltro/kv": "0.46.0",
748
+ "@voltro/logger": "0.46.0",
749
+ "@voltro/plugin-auth": "0.46.0",
750
+ "@voltro/plugin-broadcast": "0.46.0",
751
+ "@voltro/plugin-mail": "0.46.0",
752
+ "@voltro/plugin-storage": "0.46.0",
753
+ "@voltro/plugin-webhooks": "0.46.0",
754
+ "@voltro/protocol": "0.46.0",
755
+ "@voltro/runtime": "0.46.0",
756
+ "@voltro/serverless": "0.46.0",
757
+ "@voltro/workflow": "0.46.0",
752
758
  "chokidar": "^5.0.0",
753
759
  "ioredis": "^5.11.1",
754
760
  "tinyglobby": "^0.2.17",
@@ -707,7 +707,7 @@ each plugin's own README.
707
707
 
708
708
  | Topic | Open | Summary |
709
709
  |---|---|---|
710
- | **What's new in 0.45.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. |
710
+ | **What's new in 0.46.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
711
  | 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
712
  | 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
713
  | 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. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.45.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. |
12
+ | **What's new in 0.46.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. |
@@ -2610,11 +2610,25 @@ Rows are written as newline-delimited JSON — streamable, resumable by line, an
2610
2610
 
2611
2611
  | Flag | Selects |
2612
2612
  |---|---|
2613
- | *(none)* | Every table (the default). |
2613
+ | *(none)* | Every table the app has DATA in (the default) — see the note below on the handful that describe a deployment rather than filling it. |
2614
2614
  | `--tenant <id>` | Every `tenant()`-scoped table filtered to that tenant, **plus** the FK closure in BOTH directions: (1) the transitive **FK-parent** closure of those rows — closure-pulled shared tables (a global `users` / reference table) are **row-subset to the ids the tenant's rows actually reference**, never exported in full; and (2) the **child** closure — rows that *reference* the tenant's rows (the comments on the tenant's projects) come along too, each scoped to the ids that actually point into the tenant set. The child walk is anchored on the `tenant()` tables, so a row that references **only a shared parent** (a global `users` another tenant also references) is **not** pulled — that would be a cross-tenant leak. A `--tenant` bundle therefore carries the tenant's parents AND children and **no other tenant's rows** — that's what makes it safe as a GDPR / per-tenant takeout. |
2615
2615
  | `--tables a,b` | An explicit set (you own referential integrity; the importer's deferred-FK resolution covers load-order dangles, see below). |
2616
2616
  | `--exclude a,b` | Everything EXCEPT these, expanded against the live table list. Direct target only. Cannot be combined with `--tables` or `--tenant`; a name that does not exist is refused, because an exclusion that excludes nothing leaves the run looking like it worked. |
2617
2617
 
2618
+ #### What `all` deliberately leaves out
2619
+
2620
+ A few framework tables hold rows **about a deployment** rather than an app's data — its migration ledger, its file-migration and seed records, its CDC offsets, its schedule claims and wakeups, its workflow watermarks / pending starts / admissions / pauses. They are dropped from `all`, skipped on import, and never emptied by a `replace`, and the run says which ones and why.
2621
+
2622
+ The reason is worth one paragraph, because it cost a real environment ninety minutes. A `scope: all` bundle carried `_voltro_migration_plans`, `replace` wrote it, and the target's next boot refused:
2623
+
2624
+ ```
2625
+ auto-migrate: SCHEMA FINGERPRINT MISMATCH — declared=6e2c61081a9ed80c live=28af9a54414f22f1
2626
+ ```
2627
+
2628
+ The refusal was right. That fingerprint is computed over the **declared table set**, which legitimately differs per environment — `NODE_ENV=production` declares `_voltro_traces` and `_voltro_undo_log`, a `development` run does not. So the imported row was not out of date, it was **foreign**: it stated a schema decision made somewhere else. Two of the others would have made the target *act* — a pending workflow start runs a workflow somebody queued elsewhere, a pause silently stops one here.
2629
+
2630
+ `all` is the only scope filtered. **Name one of these in `--tables` and you get it** — an explicit name is an expectation, and this command refuses to drop those silently.
2631
+
2618
2632
  **Every table needs a single-column primary key.** The export is keyset-paginated, so it orders by one column and resumes from the last value on the next page. That column comes from the real primary key — a declared `id()` where there is one, otherwise the table's actual PK, whatever it is called.
2619
2633
 
2620
2634
  A table with a **composite** primary key, or none at all, is **refused by name** rather than exported. Ordering by one column of a composite key splits equal values across page boundaries, which drops or duplicates rows into a bundle that reports success — and a short backup is discovered at the restore. Leave such a table out with `--exclude`.
@@ -2753,6 +2767,128 @@ returns **`409` schema drift** with the fingerprints + diff; `--force` sends
2753
2767
 
2754
2768
  The pre-flight only runs when the importer has a target schema (the CLI introspects it; the API endpoint uses the instance's declared schema). Importing into a fresh/empty database with no comparable schema simply skips the check.
2755
2769
 
2770
+ ### `replace` writes down what it is about to destroy
2771
+
2772
+ Before the first delete, a `replace` exports the target's **current** rows for exactly the tables it is going to empty, as an ordinary bundle beside yours:
2773
+
2774
+ ```
2775
+ rollback capture: 240172 row(s) across 75 table(s) → ./out.rollback-2026-08-21T09-10-11-000Z
2776
+ If this run does not finish, restore with: voltro data import ./out.rollback-… --mode replace
2777
+ ```
2778
+
2779
+ It is on disk **before** anything is destroyed, so it does not depend on a transaction, or on the process being alive to roll one back. That distinction is the whole reason it exists: a deployment lost 240 172 rows to a `replace` whose api pod disappeared nine minutes in, and recovered from an export they had taken twenty minutes earlier out of habit. This is that habit, made into the tool's behaviour.
2780
+
2781
+ **It is fail-closed.** A capture that cannot be taken stops the import before it starts, and the target is untouched. A safety net you believe in and do not have is worse than none — the belief is what stops you taking your own export.
2782
+
2783
+ `--no-rollback` turns it off, and `--rollback-dir <path>` puts it somewhere else. It is only taken for `replace`: `upsert` and `append` do not destroy, so there is no moment where the old state has silently become unreachable.
2784
+
2785
+ **Over `--target api` it goes to the instance's object storage**, because there the process that would roll a transaction back IS the instance — a capture in the pod's filesystem would go away with the failure it exists for. Name a key:
2786
+
2787
+ ```bash
2788
+ voltro data import ./out --target api --api-url <url> --token $SECRET --mode replace --rollback-key backups/before.vbundle
2789
+ ```
2790
+
2791
+ The instance writes the capture there **before the first delete**, and refuses the run (409) if it has no storage configured — asking for a capture and being served without one is the answer that removes your own precaution while looking like agreement. A `replace` that names no key still runs, and says what it did not keep.
2792
+
2793
+ ### `replace` loads somewhere else first
2794
+
2795
+ The target keeps its rows until the load stands. `replace` creates a staging
2796
+ table per table, loads the bundle into those, and then swaps the CONTENT across
2797
+ in one short transaction of server-side SQL. It says so when it does:
2798
+
2799
+ ```
2800
+ staging 114 table(s) before the swap — the target keeps its rows until the load stands.
2801
+ The destructive step is one server-side transaction at the end, not the whole load.
2802
+ ```
2803
+
2804
+ The difference is what a dead process costs. Loading straight into the target
2805
+ holds the destructive transaction open for the whole load — minutes, for a large
2806
+ bundle — and the target only survives because the database rolls that
2807
+ transaction back. With staging, a process that dies during the load leaves the
2808
+ target exactly as it was, because nothing has been deleted yet.
2809
+
2810
+ Not every run can take it, and a run that cannot **says why** rather than quietly
2811
+ taking the slower path:
2812
+
2813
+ - a store the framework cannot send DDL to (the in-memory store).
2814
+ - a **write recorder** on any table in the set — `versioningPlugin({ timing:
2815
+ 'in-transaction' })` and friends. A recorder is keyed by table name, so a
2816
+ staged write would find none and the recorder would silently not run. Its
2817
+ promise is "if the change committed, the entry is there", so the run keeps the
2818
+ path that can keep it.
2819
+
2820
+ **`--no-atomic` stages too, and that is where it changes the most.** The flag
2821
+ exists for resumability on a large bundle, and it used to be the mode with the
2822
+ worst failure: the target emptied and partially refilled, in neither state.
2823
+ Staged, the ledger keeps its exact meaning — a recorded table is one fully
2824
+ loaded, it just lands in staging — while the target stays untouched until the
2825
+ swap. Resumable and all-or-nothing at once, which the two could not be before.
2826
+
2827
+ A resumed run continues from what it already staged rather than reloading it,
2828
+ and a run whose swap has not happened keeps its staged rows and says so:
2829
+
2830
+ ```
2831
+ the staged rows are KEPT so a re-run can continue from them rather than reload.
2832
+ If you are not going to re-run this bundle, drop them: voltro data clear-staging --yes
2833
+ ```
2834
+
2835
+ **A failed swap names every offending row, not the first.** Staging carries no
2836
+ foreign keys — a staged row whose parent has not been staged yet must not be
2837
+ refused — so a dangling reference surfaces at the swap, where the database
2838
+ reports one constraint. The importer then asks staging the same question and
2839
+ lists every row that fails it:
2840
+
2841
+ ```
2842
+ the swap could not run: 2 row(s) in the bundle reference a row the bundle does not carry.
2843
+ tasks.t_41: ownerId = "u_9" — no such row in users
2844
+ tasks.t_88: ownerId = "u_12" — no such row in users
2845
+ The target is UNCHANGED — the swap runs in one transaction and none of it committed.
2846
+ ```
2847
+
2848
+ Staging tables from a run that died mid-load are collected by the next
2849
+ `replace` over the same tables. One over a DIFFERENT set leaves them, and
2850
+ nothing else removes them:
2851
+
2852
+ ```bash
2853
+ voltro data clear-staging --yes
2854
+ ```
2855
+
2856
+ Deliberately a command and not a boot sweep: a booting process cannot tell a
2857
+ leftover from a staging table another replica is loading into right now, and
2858
+ deleting the second would destroy an import in flight.
2859
+
2860
+ A **cycle** in the bundle's foreign keys is detected before the load, not after
2861
+ it. The swap inserts parents first, so two tables referencing each other cannot
2862
+ both be satisfied by a bulk copy on postgres, sqlite or SQL Server — `SET
2863
+ CONSTRAINTS ALL DEFERRED` does not help, because postgres only defers a
2864
+ constraint declared `DEFERRABLE`. Such a run says so and takes the row-by-row
2865
+ path, whose deferred-FK pass exists for exactly that shape. A table referencing
2866
+ ITSELF is not a cycle: one statement carries the whole table.
2867
+
2868
+ ### An interrupted `replace` cannot be silent
2869
+
2870
+ The capture only helps if somebody knows to reach for it. A half-replaced database is indistinguishable from an empty one **from the inside** — every table exists, every constraint holds, every query returns nothing without erroring — so a run that emptied a target and disappeared can be served over for hours before anyone asks the right question.
2871
+
2872
+ So a `replace` writes one row before the first delete and removes it after the last insert. Finding it at boot is a **refusal**, not a warning:
2873
+
2874
+ ```
2875
+ refusing to start: a destructive import did not finish.
2876
+ - a `replace` over api began emptying 114 table(s) 12 minute(s) ago and never reported finishing.
2877
+ The target's previous rows were captured first:
2878
+ ./out.rollback-2026-08-21T09-10-11-000Z
2879
+ Restore them with: voltro data import ./out.rollback-… --mode replace
2880
+ ```
2881
+
2882
+ The row lives in the same transaction as the emptying, so it is present **exactly when the emptying is**: a run that rolls back cleanly takes the marker with it, and a boot over a database nothing happened to is not refused. A completed `replace` clears its own marker and any older one — so the recovery import both restores the data and silences the alarm, in one command.
2883
+
2884
+ Nothing expires. A half-replaced database does not become whole with time, so clearing it is a decision:
2885
+
2886
+ ```bash
2887
+ voltro data clear-replace-marker --yes
2888
+ ```
2889
+
2890
+ The devtools import panel takes the same two precautions as the command line — a capture before the first delete, and the marker — because a button is easier to press than a command is to type.
2891
+
2756
2892
  ### Deferred-FK resolution
2757
2893
 
2758
2894
  A row whose write fails on a foreign-key constraint — a forward reference from a `live`-consistency export, a genuine FK **cycle** between tables (which the exporter orders by breaking the closing edge), or an intra-table self-reference to a later row — does **not** fail the import. It is held and resolved after every table has streamed:
@@ -2803,7 +2939,7 @@ Unlike the rpc wire, this string DOES include the driver's own words. The audien
2803
2939
  |---|---|---|---|
2804
2940
  | `upsert` (default) | INSERT-or-UPDATE by primary key | sync / idempotent re-import | overwrites per row |
2805
2941
  | `append` | INSERT only | additive data (event log, new seed) | `--on-conflict skip` (default) or `fail` |
2806
- | `replace` | empty the target tables, then INSERT | full refresh — target ends up **exactly** the bundle | — |
2942
+ | `replace` | capture the target, empty it, then INSERT | full refresh — target ends up **exactly** the bundle | — |
2807
2943
 
2808
2944
  `replace` **refuses a partial bundle** (a subset / tenant / table scope): emptying would delete rows the bundle never carried. Re-export with full scope, or use `upsert`. (`append --on-conflict fail` throws on a duplicate primary key only where the store enforces the constraint — every SQL dialect does; the in-memory dev store overwrites.)
2809
2945
 
@@ -397,10 +397,59 @@ changes. **That is an authorization staleness, not a cosmetic one:** revoke
397
397
  someone's membership and their open subscription keeps serving rows they may no
398
398
  longer see, until something else happens to invalidate it.
399
399
 
400
- Nothing warns about this at runtime a query that silently stops reacting looks
401
- exactly like one with nothing to report. Reported by a team whose own invariant
402
- caught it after five computed queries under-declared their `source`; the fix was
403
- array sources.
400
+ Reported by a team whose own invariant caught it after five computed queries
401
+ under-declared their `source`; the fix was array sources.
402
+
403
+ ### `voltro dev` now says when a query reads a table it did not declare
404
+
405
+ That paragraph used to end "nothing warns about this at runtime". It does now.
406
+
407
+ While `voltro dev` is running, every read a query makes is attributed to it and
408
+ compared against its own `source:`. Read a table you did not declare and the
409
+ terminal says so, once:
410
+
411
+ ```
412
+ source: tasks.list: read `task_sub_tasks` without declaring it in `source:`.
413
+ A write to that table will not re-run this query, so an open view keeps
414
+ showing what it showed before. The write itself is fine, which is why nothing
415
+ else reports this.
416
+ ```
417
+
418
+ It is deliberately narrow, and knowing where its edges are is the difference
419
+ between acting on it and learning to skim it:
420
+
421
+ - **It reports what it SAW.** A branch that did not run contributes nothing, so
422
+ it never claims your `source:` is otherwise complete — only that a table it
423
+ watched you read is missing from it.
424
+ - **Once per query per boot.** A per-request warning on a hot list would be its
425
+ own outage.
426
+ - **A query with no `source:` at all is left alone.** It has made no claim; the
427
+ finding is about an incomplete list, not a missing one.
428
+ - **An eager-loaded relation COUNTS**, and it is the case worth knowing about.
429
+ `.with({ subTasks: true })` issues no second read — the whole spec folds into
430
+ one round trip — so the loaded table never appears as a read of its own. The
431
+ recorder resolves it through the relation registry instead, target and (for a
432
+ many-to-many) junction alike. A write to the junction changes membership,
433
+ which is exactly the change a user makes.
434
+ - **A table read only to NARROW a result is not counted** — a parent reached
435
+ through `inSubquery(...)`, or a read the framework made to resolve your row
436
+ filter. Those decide which rows come back rather than contributing rows, and
437
+ putting every one of them in `source:` would re-run every list on every
438
+ membership write.
439
+
440
+ That last rule is a judgement the recorder makes for the common case and
441
+ deliberately does not make for yours. The section above is the case where you
442
+ want a restricting read in `source:` anyway — an authorization read whose
443
+ staleness you care about. The recorder will not nag you into it and will not
444
+ argue when you add it.
445
+
446
+ **Dev only.** `voltro serve` installs none of it — it costs a wrapper per read,
447
+ and a production log is not where this gets read. `VOLTRO_SOURCE_RECORDER=off`
448
+ turns it off in dev.
449
+
450
+ If your own helper resolves access somewhere the framework does not call it, wrap
451
+ it in `restrictingReads` (from `@voltro/runtime`) and its reads stop counting —
452
+ inside or outside a recording session, so it is safe to leave in place.
404
453
 
405
454
  ## `output` is the serializer — `timestampMs`
406
455
 
@@ -3168,6 +3217,23 @@ When a shape can't be incremental, leave `incremental` off and use a refresh
3168
3217
  policy — the engine recomputes. Incremental is an optimisation for hot, bounded
3169
3218
  aggregates, not a different result.
3170
3219
 
3220
+ ### `source` is a table name, and it is checked
3221
+
3222
+ `incremental.source` is typed against your app's own tables — the same
3223
+ `TableName` a query's `source:` uses — so a misspelled or renamed-away name is a
3224
+ compile error from your next `voltro dev`.
3225
+
3226
+ That check exists because the failure has no other symptom. A source matching no
3227
+ table does not error: the runner subscribes to something nothing writes, no delta
3228
+ ever arrives, and the aggregate quietly stops tracking its input while every read
3229
+ of it still succeeds and still returns a number.
3230
+
3231
+ The boot says it too. An aggregate whose CDC source resolves to no live table is
3232
+ named in the same stale-`source:` warning queries and streams appear in, on both
3233
+ boot paths — a name can be correct at the type level and still be a table this
3234
+ deployment does not have. An aggregate with no `incremental` block declares no
3235
+ source and is not audited.
3236
+
3171
3237
  ## Boot strategy
3172
3238
 
3173
3239
  Each aggregate declares `bootRefresh` (default `'persistent'`). All three modes are wired in the aggregate runner (`attachAggregates`):