@voltro/cli 0.48.0 → 0.50.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 (101) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/dist/apiBuild-BWo8KI6n.js +2 -0
  3. package/dist/{apiBuild-Dz8TJSxp.js → apiBuild-CFYXo06U.js} +1 -1
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-DLme2ppa.js → build-CnSH-zru.js} +6 -6
  6. package/dist/{checkCommand-Dals7g-I.js → checkCommand-Do3SQbLx.js} +3 -3
  7. package/dist/{checkCommand-BUBqPBlI.js → checkCommand-OL41BMIb.js} +1 -1
  8. package/dist/{codegenCommand-L6vacVRW.js → codegenCommand-BJzaRz6q.js} +39 -47
  9. package/dist/{codemodRunner-DSZjgyCL.js → codemodRunner-De3k0pRd.js} +859 -629
  10. package/dist/{commands-CAxFLqG4.js → commands-By23Ljny.js} +68 -60
  11. package/dist/{dashboardCommand-CJC8Kg-s.js → dashboardCommand-DN1rAABE.js} +1 -1
  12. package/dist/dataCommand-B93TUBIx.js +1306 -0
  13. package/dist/dataProfile-Du3ztYxv.js +51 -0
  14. package/dist/{dbCommand-ZGNItjoJ.js → dbCommand-Bf7Cy0mn.js} +2 -2
  15. package/dist/dbCommand-Bnl7MSGC.js +2 -0
  16. package/dist/{dev-lvnVq5RD.js → dev-1yu42ubu.js} +2 -2
  17. package/dist/{dev-cGGsLNn6.js → dev-BWwE0QjK.js} +1 -1
  18. package/dist/doctorCommand-BmovAwb7.js +2 -0
  19. package/dist/{doctorCommand-BK0qu7eD.js → doctorCommand-y-EbP8st.js} +12 -12
  20. package/dist/{dormancyCommand-CyIk-zq4.js → dormancyCommand-Dc7KRNCG.js} +1 -1
  21. package/dist/{embeddingsCommand-D7TLklps.js → embeddingsCommand-kPs132bB.js} +1 -1
  22. package/dist/{envCommand-BHxKkGXq.js → envCommand-E12zSt51.js} +1 -1
  23. package/dist/{evolveCommand-CQh6dw21.js → evolveCommand-B3nagPVc.js} +2 -2
  24. package/dist/{frameworkTableAssembly-BNod_DKN.js → frameworkTableAssembly-BwJVEKLr.js} +3 -3
  25. package/dist/frameworkTableAssembly-CVDB2hCq.js +2 -0
  26. package/dist/index.js +1 -1
  27. package/dist/{infoCommand-CuNl9cbh.js → infoCommand-Cq0Yz98u.js} +1 -1
  28. package/dist/{interruptedReplace-C1ZI84xi.js → interruptedReplace-C3O3M1MM.js} +2 -1
  29. package/dist/interruptedReplace-CvmiAM9K.js +2 -0
  30. package/dist/{migrate-CL5Ed01M.js → migrate-D3MK9BpK.js} +2 -2
  31. package/dist/{runtimeTrace-C0SlIoQY.js → runtimeTrace-BpLUcrfy.js} +1 -1
  32. package/dist/{sdkgen-BXm7zx7F.js → sdkgen-dl4-WKF6.js} +1 -1
  33. package/dist/serveCommand-8CTzQCY0.js +2362 -0
  34. package/dist/serveCommand-C_WI4TTG.js +2 -0
  35. package/dist/serveEntry.js +5 -5
  36. package/dist/{subcommandNames-CKG5Dz3a.js → subcommandNames-DpYs3DXr.js} +12 -2
  37. package/dist/updateCommand-D91-TZ7E.js +2 -0
  38. package/dist/{updateCommand-BlyXavoG.js → updateCommand-i8JbrLAA.js} +2 -1
  39. package/dist/{webhooksCommand-QlAk_tED.js → webhooksCommand-B0bdyuQ4.js} +1 -1
  40. package/package.json +47 -17
  41. package/templates/AGENTS.md +1 -1
  42. package/templates/agent-docs/_index.md +1 -1
  43. package/templates/agent-docs/cli.md +124 -16
  44. package/templates/agent-docs/whats-new.md +30 -52
  45. package/templates/apps/api-ai/package.json +7 -7
  46. package/templates/apps/api-auth/package.json +8 -8
  47. package/templates/apps/api-backend/package.json +7 -7
  48. package/templates/apps/api-backend-deactivation/package.json +7 -7
  49. package/templates/apps/api-backend-mail/package.json +8 -8
  50. package/templates/apps/api-backend-mariadb/package.json +9 -9
  51. package/templates/apps/api-backend-sqlite/package.json +8 -8
  52. package/templates/apps/api-backend-storage/package.json +8 -8
  53. package/templates/apps/api-cms/package.json +10 -10
  54. package/templates/apps/api-collab/package.json +8 -8
  55. package/templates/apps/api-data-advanced/package.json +8 -8
  56. package/templates/apps/api-durable/package.json +8 -8
  57. package/templates/apps/api-feature-flags/package.json +9 -9
  58. package/templates/apps/api-governance/package.json +8 -8
  59. package/templates/apps/api-kv/package.json +8 -8
  60. package/templates/apps/api-moderation/package.json +8 -8
  61. package/templates/apps/api-observability/package.json +8 -8
  62. package/templates/apps/api-ratelimit/package.json +8 -8
  63. package/templates/apps/api-rbac/package.json +8 -8
  64. package/templates/apps/api-rest/package.json +7 -7
  65. package/templates/apps/api-saas/package.json +11 -11
  66. package/templates/apps/api-saas-starter/package.json +10 -10
  67. package/templates/apps/api-search/package.json +8 -8
  68. package/templates/apps/api-status/package.json +8 -8
  69. package/templates/apps/api-versioning/package.json +8 -8
  70. package/templates/apps/api-webhooks/package.json +9 -9
  71. package/templates/apps/changelog/package.json +6 -6
  72. package/templates/apps/edge-functions/package.json +2 -2
  73. package/templates/apps/frontend-admin/package.json +8 -8
  74. package/templates/apps/frontend-app/package.json +9 -9
  75. package/templates/apps/frontend-auth/package.json +8 -8
  76. package/templates/apps/frontend-blank/package.json +7 -7
  77. package/templates/apps/frontend-cms/package.json +9 -9
  78. package/templates/apps/frontend-collab/package.json +10 -10
  79. package/templates/apps/frontend-contact/package.json +7 -7
  80. package/templates/apps/frontend-dashboard/package.json +7 -7
  81. package/templates/apps/frontend-docs/package.json +7 -7
  82. package/templates/apps/frontend-i18n/package.json +6 -6
  83. package/templates/apps/frontend-landing/package.json +7 -7
  84. package/templates/apps/frontend-portal/package.json +8 -8
  85. package/templates/apps/frontend-saas/package.json +8 -8
  86. package/templates/apps/frontend-spa/package.json +7 -7
  87. package/templates/apps/frontend-ssr/package.json +7 -7
  88. package/templates/apps/frontend-ssr-api/package.json +8 -8
  89. package/templates/apps/frontend-static-blog/package.json +6 -6
  90. package/templates/apps/frontend-status/package.json +8 -8
  91. package/templates/apps/mobile-app/package.json +4 -4
  92. package/dist/apiBuild-DOnvi2zm.js +0 -2
  93. package/dist/dataCommand-hIaq2iKY.js +0 -1052
  94. package/dist/dataProfile-Cm0YVKSy.js +0 -18
  95. package/dist/dbCommand-D1Q33ktl.js +0 -2
  96. package/dist/doctorCommand-CWgUPye-.js +0 -2
  97. package/dist/frameworkTableAssembly-Rft-DPUg.js +0 -2
  98. package/dist/interruptedReplace-DNJ7MKph.js +0 -2
  99. package/dist/serveCommand-CV3YO9L3.js +0 -2035
  100. package/dist/serveCommand-ZP_Cb-yz.js +0 -2
  101. package/dist/updateCommand-CT5AvVg7.js +0 -2
@@ -0,0 +1,2 @@
1
+ import { n as e } from "./serveCommand-8CTzQCY0.js";
2
+ export { e as runServeDispatch };
@@ -1,5 +1,5 @@
1
- import { t as e } from "./loadEnv-D9nEOClM.js";
2
- import { r as t } from "./appModuleLoader-C9r9mxZt.js";
3
- import { i as n } from "./dialectDriver-czCHYpeH.js";
4
- import { t as r } from "./serveCommand-CV3YO9L3.js";
5
- export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
1
+ import { t as e } from "./serveCommand-8CTzQCY0.js";
2
+ import { t } from "./loadEnv-D9nEOClM.js";
3
+ import { r as n } from "./appModuleLoader-C9r9mxZt.js";
4
+ import { i as r } from "./dialectDriver-czCHYpeH.js";
5
+ export { t as loadDotEnv, n as registerAppModules, r as registerDriver, e as runServe };
@@ -19,9 +19,19 @@ var e = [
19
19
  "scan-credentials",
20
20
  "encrypt-column"
21
21
  ], t = [
22
+ "export",
23
+ "import",
24
+ "transfers",
25
+ "inspect",
26
+ "unpack",
27
+ "backup",
28
+ "restore",
29
+ "clear-replace-marker",
30
+ "clear-staging"
31
+ ], n = [
22
32
  "scope",
23
33
  "export",
24
34
  "erase"
25
- ], n = (e) => `<${e.join("|")}>`;
35
+ ], r = (e) => `<${e.join("|")}>`;
26
36
  //#endregion
27
- export { t as n, n as r, e as t };
37
+ export { r as i, e as n, n as r, t };
@@ -0,0 +1,2 @@
1
+ import { f as e, p as t } from "./updateCommand-i8JbrLAA.js";
2
+ export { e as runApplyCodemods, t as runUpdateCommand };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-DSZjgyCL.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-De3k0pRd.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";
@@ -579,6 +579,7 @@ with \`--only\` / \`--root\`.`;
579
579
  for (let e of r.manual) {
580
580
  console.log(`\n▸ ${e.id} — ${e.title}`);
581
581
  for (let t of e.note.split("\n")) console.log(` ${t}`);
582
+ e.certain || (console.log(""), console.log(" ── nothing in this repository matched. That is NOT a clean bill of health:"), console.log(" `voltro update` reads the files git tracks here, and a CLI call also lives"), console.log(" in places it cannot reach — an inline script in a CI runner, a CronJob"), console.log(" spec in another repo, a runbook, a deploy job. Search those by hand."));
582
583
  }
583
584
  }
584
585
  return n.dryRun && console.log("\n(dry-run — no files written)"), 0;
@@ -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-cGGsLNn6.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-BWwE0QjK.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.48.0",
3
+ "version": "0.50.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",
@@ -652,6 +652,18 @@
652
652
  "title": "an aggregate’s `incremental.source` is typechecked against the app’s tables",
653
653
  "kind": "manual"
654
654
  },
655
+ {
656
+ "version": "0.49.0",
657
+ "id": "0.49.0/01_data-imports-to-transfers",
658
+ "title": "`voltro data imports` → `voltro data transfers`",
659
+ "kind": "manual"
660
+ },
661
+ {
662
+ "version": "0.49.0",
663
+ "id": "0.49.0/02_import-run-symbols",
664
+ "title": "`ImportRun` / `describeImportRun` / `IMPORT_RUNS_TABLE` → their `…TransferRun` names",
665
+ "kind": "transform"
666
+ },
655
667
  {
656
668
  "version": "0.5.0",
657
669
  "id": "0.5.0/01_one-terminal-exactly-one",
@@ -664,6 +676,24 @@
664
676
  "title": "store.query() now returns the row type — casts on row fields may stop compiling",
665
677
  "kind": "manual"
666
678
  },
679
+ {
680
+ "version": "0.50.0",
681
+ "id": "0.50.0/01_transfer-runs-endpoint",
682
+ "title": "`GET /_voltro/admin/imports` → `GET /_voltro/admin/transfers`",
683
+ "kind": "manual"
684
+ },
685
+ {
686
+ "version": "0.50.0",
687
+ "id": "0.50.0/02_reissue-data-imports-rename",
688
+ "title": "RE-CHECK: `voltro data imports` → `voltro data transfers` (0.49.0's note could not see your scripts)",
689
+ "kind": "manual"
690
+ },
691
+ {
692
+ "version": "0.50.0",
693
+ "id": "0.50.0/03_interrupted-replace-tables-nullable",
694
+ "title": "`InterruptedReplace.tables` is `number | null`",
695
+ "kind": "manual"
696
+ },
667
697
  {
668
698
  "version": "0.6.0",
669
699
  "id": "0.6.0/01_no-dev-session-secret",
@@ -739,22 +769,22 @@
739
769
  "@effect/platform-node": "^0.108.0",
740
770
  "@effect/sql": "^0.52.0",
741
771
  "@effect/workflow": "^0.19.0",
742
- "@voltro/ai": "0.48.0",
743
- "@voltro/cache": "0.48.0",
744
- "@voltro/data-transfer": "0.48.0",
745
- "@voltro/database": "0.48.0",
746
- "@voltro/env": "0.48.0",
747
- "@voltro/kv": "0.48.0",
748
- "@voltro/logger": "0.48.0",
749
- "@voltro/plugin-auth": "0.48.0",
750
- "@voltro/plugin-broadcast": "0.48.0",
751
- "@voltro/plugin-mail": "0.48.0",
752
- "@voltro/plugin-storage": "0.48.0",
753
- "@voltro/plugin-webhooks": "0.48.0",
754
- "@voltro/protocol": "0.48.0",
755
- "@voltro/runtime": "0.48.0",
756
- "@voltro/serverless": "0.48.0",
757
- "@voltro/workflow": "0.48.0",
772
+ "@voltro/ai": "0.50.0",
773
+ "@voltro/cache": "0.50.0",
774
+ "@voltro/data-transfer": "0.50.0",
775
+ "@voltro/database": "0.50.0",
776
+ "@voltro/env": "0.50.0",
777
+ "@voltro/kv": "0.50.0",
778
+ "@voltro/logger": "0.50.0",
779
+ "@voltro/plugin-auth": "0.50.0",
780
+ "@voltro/plugin-broadcast": "0.50.0",
781
+ "@voltro/plugin-mail": "0.50.0",
782
+ "@voltro/plugin-storage": "0.50.0",
783
+ "@voltro/plugin-webhooks": "0.50.0",
784
+ "@voltro/protocol": "0.50.0",
785
+ "@voltro/runtime": "0.50.0",
786
+ "@voltro/serverless": "0.50.0",
787
+ "@voltro/workflow": "0.50.0",
758
788
  "chokidar": "^5.0.0",
759
789
  "ioredis": "^5.11.1",
760
790
  "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.48.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.50.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.48.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.50.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. |
@@ -2885,7 +2885,7 @@ recorder is for. Every suspended run says so, and the suspension is scoped to th
2885
2885
  run rather than the process, so requests served alongside it keep recording.
2886
2886
 
2887
2887
  Instead of per-row history, the operation records **itself**. One row in
2888
- `_voltro_data_imports` per run: the mode, the transport, the bundle, the source
2888
+ `_voltro_data_transfers` per run — in either direction: the mode, the transport, the bundle, the source
2889
2889
  deployment's schema fingerprint, the counts — and the failure, for the run you
2890
2890
  are usually looking for. A trail that only records successes goes quiet exactly
2891
2891
  when it is needed.
@@ -2895,9 +2895,57 @@ interlock and a run which cannot write it must not proceed, while this is
2895
2895
  history. A target whose schema is not migrated yet still imports, and says the
2896
2896
  trace could not be written.
2897
2897
 
2898
- ### Watching an import you cannot see
2898
+ ### Every request fits a budget
2899
2899
 
2900
- Over `--target api` the import runs INSIDE the instance. Everything it decides
2900
+ Some environments cap a single request: a job runner that kills a client after
2901
+ ten minutes, an ingress with a 30-second ceiling, a CI step with a deadline. A
2902
+ whole transfer may still take an hour — it just has to do so as a series of short
2903
+ requests, each individually abandonable and individually retryable.
2904
+
2905
+ The rule the endpoints follow:
2906
+
2907
+ > **A request that carries bytes never runs a transfer. A request that starts a
2908
+ > transfer never carries bytes.**
2909
+
2910
+ ```bash
2911
+ # Nothing here may take longer than 30 seconds per request.
2912
+ voltro data import ./bundle --target api --api-url https://app.example \
2913
+ --mode replace --max-request-seconds 30
2914
+
2915
+ # Or: start it and walk away. The outcome is NOT known when this returns.
2916
+ voltro data import ./bundle --target api --api-url https://app.example --detach
2917
+ ```
2918
+
2919
+ Both staging areas — the uploaded bundle on its way in, the produced one on its
2920
+ way out — sit in the system temp directory by default and move with
2921
+ `VOLTRO_IMPORT_UPLOAD_DIR` / `VOLTRO_EXPORT_ARTIFACT_DIR`. Worth setting on a
2922
+ container: a bundle is the size your database is, and a default `/tmp` is
2923
+ frequently a small tmpfs.
2924
+
2925
+ `--max-request-seconds` is DECLARED rather than probed. The thing that kills a
2926
+ request is a policy on your side, and only you know it — in the one environment
2927
+ this was measured against, the ingress would happily hold a connection for an
2928
+ hour and the caller's own toolchain killed the client at ten minutes.
2929
+
2930
+ **An import is three steps.** The bundle goes up (in 16 MiB chunks when it is
2931
+ big, resumable, one plain request when it is small); `import/start` begins the
2932
+ run and answers as soon as the run is recorded; then the client watches the
2933
+ record. `start` is idempotent — retrying it under a budget returns the run that
2934
+ is already going rather than beginning a second destructive one.
2935
+
2936
+ **An export is three steps too.** Ask, wait for the record, then fetch the bytes
2937
+ in ranges from `GET /_voltro/admin/export/download`. The archive used to arrive
2938
+ in the response body, which made the request last as long as reading your whole
2939
+ database.
2940
+
2941
+ **`--detach` returns once the run has started** and says so plainly: exit `0`
2942
+ there means "it started", not "it worked". Attached, the exit code comes from the
2943
+ run's record — `0` finished, `1` failed, `2` still going when you stopped
2944
+ watching. Ctrl-C loses the watching and never the run.
2945
+
2946
+ ### Watching a transfer you cannot see
2947
+
2948
+ Over `--target api` the run happens INSIDE the instance. Everything it decides
2901
2949
  — whether it stages, whether recorders are suspended, how far it has got — is
2902
2950
  printed in the pod's log, and someone reaching for `--target api` is by
2903
2951
  construction someone who cannot reach the database directly and usually cannot
@@ -2906,9 +2954,12 @@ killing the client does not stop it, which is what keeps a dead client from
2906
2954
  leaving a half-emptied target. Both properties are right, and together they used
2907
2955
  to mean an operator could neither see the run nor learn how it ended.
2908
2956
 
2909
- Two things close that, and neither is a streamed response a chunked body has
2910
- to survive every proxy in between, and a buffering reverse proxy turns a
2911
- progress feed into exactly the silence it was meant to replace.
2957
+ Two things close that, and neither is a streamed response on the upload
2958
+ connection. That connection is the thing an operator is most likely to lose: a
2959
+ client killed by a job timeout while the import runs on inside the instance is
2960
+ the ordinary case, and a stream would go quiet at exactly the moment somebody
2961
+ needs to know what happened. A poll can be run from a different machine than
2962
+ the one that started the import.
2912
2963
 
2913
2964
  **Ask before you upload.** A `replace` over the api asks the instance what it is
2914
2965
  going to do, before a byte of the bundle goes up. The instance answers from the
@@ -2918,7 +2969,8 @@ same function the run itself calls, so the answer cannot drift from the run:
2918
2969
  $ voltro data import ./bundle --target api --api-url https://app.example --mode replace
2919
2970
  api replace: the instance WILL stage — 104 table(s) load into copies and the
2920
2971
  target keeps its rows until one short swap at the end. Interrupting the load leaves the target intact.
2921
- api import: uploading the bundle this request stays open until the instance has imported it
2972
+ importing 41200 row(s) across 18 table(s) (staged; the target still holds its own rows)
2973
+ done — 228866 row(s) across 104 table(s)
2922
2974
  ```
2923
2975
 
2924
2976
  With `--bundle-key` the client never holds the bundle, so it sends the key and
@@ -2932,22 +2984,28 @@ That is the difference between "interrupting this is safe" and "interrupting
2932
2984
  this empties the target", which is exactly the decision an operator is making
2933
2985
  while they watch.
2934
2986
 
2935
- **Read the run from anywhere.** `_voltro_data_imports` is opened before the
2936
- first table, advanced every couple of seconds as tables land, and closed with the
2937
- outcome — so polling it IS the progress feed:
2987
+ **Read the run from anywhere.** `_voltro_data_transfers` carries one row per run
2988
+ in EITHER direction: opened before the first table, advanced every couple of
2989
+ seconds as tables land, closed with the outcome — so polling it IS the progress
2990
+ feed:
2938
2991
 
2939
2992
  ```
2940
- $ voltro data imports --target api --api-url https://app.example
2993
+ $ voltro data transfers --target api --api-url https://app.example
2941
2994
  1 import(s) IN FLIGHT — re-run this to watch the counters move
2942
- 2026-08-22T09:04:01.000Z · replace — started and never reported finishing · via api · from /tmp/b
2943
- 2026-08-20T10:00:00.000Z · replace — 228866 row(s) across 104 table(s) · via api · from /tmp/a
2995
+ 2026-08-23T09:04:01.000Z · replace — started and never reported finishing · via api · staged (target untouched until the swap) · from /tmp/b
2996
+ 2026-08-23T08:00:00.000Z · export all — 228866 row(s) across 104 table(s) · via api · from backups/nightly.vbundle
2944
2997
  ```
2945
2998
 
2946
- Same data over `GET /_voltro/admin/imports?limit=20`, behind the same
2999
+ Same data over `GET /_voltro/admin/transfers?limit=20`, behind the same
2947
3000
  data-transfer secret (the row names bundles and schema fingerprints). It works
2948
3001
  from a different machine than the one that started the import, and through
2949
3002
  anything that forwards a GET.
2950
3003
 
3004
+ The `staged` part is a separate claim from the preflight's, and both are needed.
3005
+ The preflight says what the instance WILL do; this says what it did. Without it
3006
+ the two could only be closed by reading the pod's log, which is the one place a
3007
+ `--target api` caller cannot reach.
3008
+
2951
3009
  ### An interrupted `replace` cannot be silent
2952
3010
 
2953
3011
  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.
@@ -3272,12 +3330,62 @@ yield* runImport({ store: target, bundleDir: './backup', targetSnapshot }).pipe(
3272
3330
  ## Native backup / restore
3273
3331
 
3274
3332
  ```bash
3275
- voltro data backup ./backups/2026-07-01 # pg_dump --format=custom / mysqldump --single-transaction / …
3276
- voltro data restore ./backups/2026-07-01 # pg_restore / mysql /
3333
+ voltro data backup ./backups/2026-07-01 # pg_dump --format=custom / mariadb-dump --single-transaction / …
3334
+ voltro data backup ./backups/2026-07-01 --assets # rows AND the stored blobs
3335
+ voltro data restore ./backups/2026-07-01 --assets # pg_restore / mariadb / … + the blobs
3277
3336
  ```
3278
3337
 
3279
3338
  These shell out to the vendor tools resolved from your `DB_DIALECT` + connection env. They produce a dialect-native artifact (`db.dump`, `db.sql`, `db.sqlite`, `db.bacpac`) that is lossless and point-in-time consistent for **same-dialect** restore — the right tool for disaster recovery. Secrets are passed via the tools' environment variables (`PGPASSWORD`, `MYSQL_PWD`), never on the command line, where the tool supports it. The named tool must be installed and on `PATH`.
3280
3339
 
3340
+ ### `--assets` — the blobs are not in the dump
3341
+
3342
+ A vendor dump contains rows. Your blobs are in object storage, and no `pg_dump` has ever seen them. So a rows-only backup restores a database whose rows reference objects that are not there — and the reference and the object are checked at different times, which is why that state is discovered by a user, months later, rather than by the restore.
3343
+
3344
+ `--assets` captures them alongside the dump, through the same content-addressed pipeline `voltro data export --assets` uses: each blob is streamed (never buffered whole), stored under `assets/<sha256>` so identical content is stored once, and listed in `assets/index.ndjson`. `restore --assets` streams them back and **re-hashes on the way**, so a corrupted artifact can never silently overwrite good bytes.
3345
+
3346
+ ```
3347
+ backups/2026-07-01/
3348
+ ├─ db.dump # the vendor artifact (rows)
3349
+ ├─ voltro-backup-stamp.json # provenance, incl. what --assets captured
3350
+ ├─ assets/index.ndjson # key → sha256 → size → contentType
3351
+ └─ assets/<sha256> # the blob bodies, deduped by content
3352
+ ```
3353
+
3354
+ Three refusals, each for a belief that is otherwise acted on silently:
3355
+
3356
+ - **`backup --assets` with no storage provider configured → refused.** There is nothing to capture, and a flag that is accepted and ignored lets you build a rollback story on an artifact that does not contain what you asked for.
3357
+ - **`restore --assets` on a rows-only backup → refused.** You believe the blobs are in there. Restoring the rows anyway produces exactly the dangling state this exists to prevent.
3358
+ - **restore *without* `--assets` on a backup that HAS them → warned, not refused.** Restoring rows without blobs is legitimate (a schema drill, a lower environment), and refusing it would push people at `--force`.
3359
+
3360
+ 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.
3361
+
3362
+ ### A restore that is interrupted refuses the next boot
3363
+
3364
+ `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.
3365
+
3366
+ This is the counterpart to `--allow-live`, and it guards from the other side: `--allow-live` asks you not to restore over a running instance, and this says *this database is mid-restore, do not serve it*. A half-restored database looks exactly like a normal one from the inside — every query answers, nothing errors.
3367
+
3368
+ Whether the marker survives the restore depends on the dialect's restore shape, and that is stated rather than implied:
3369
+
3370
+ | dialect | shape | effect |
3371
+ |---|---|---|
3372
+ | postgres, mysql, mariadb | drops only the objects the dump names | the marker survives, so a killed restore is caught at the next boot |
3373
+ | sqlite, turso | whole-file replacement — made **atomic** (temp file + rename) | there is no half-restored state to catch; a killed restore leaves the live file untouched |
3374
+ | mssql | `sqlpackage /Action:Import` replaces the database | a *failed* import is the one case not covered here — verify with `--drill` |
3375
+
3376
+ Clear a marker deliberately with `voltro data clear-replace-marker --yes` once you have decided the current state is correct.
3377
+
3378
+ ### Both directions are in the history
3379
+
3380
+ `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:
3381
+
3382
+ ```
3383
+ 2026-08-23T02:00:00.000Z · backup native postgres + assets — finished (412 blob(s)) · via cli · from ./backups/2026-08-23/db.dump
3384
+ 2026-08-22T09:14:02.000Z · FAILED restore native mariadb — mariadb-dump: exited with code 2 · via cli · from ./backups/2026-08-21/db.sql
3385
+ ```
3386
+
3387
+ A native run reports **blobs**, not rows: the vendor tool reports no row count we can trust, and printing `0 row(s)` over a `pg_dump` that worked would be a measurement, and a wrong one. A target with no `_voltro_data_transfers` table still gets its backup — the closing line says it was not recorded, rather than implying it was.
3388
+
3281
3389
  ### The provenance stamp — a restore that refuses the wrong DB
3282
3390
 
3283
3391
  A native dump is opaque: it doesn't say which dialect made it, which schema shape it carries, or when. `backup` writes a sidecar `voltro-backup-stamp.json` next to the artifact recording exactly that — `dialect`, the live schema `fingerprint`, the `@voltro/cli` version, and the timestamp.
@@ -1,4 +1,4 @@
1
- # What's new in 0.48.0
1
+ # What's new in 0.50.0
2
2
 
3
3
  Read this FIRST when a task touches an area you have not worked in recently.
4
4
  It is the cheapest way to notice that the framework grew the thing you were
@@ -7,78 +7,56 @@ workaround for something that shipped two versions ago.
7
7
 
8
8
  BREAKING entries name a codemod; run `voltro update` to apply it.
9
9
 
10
- ### Added
11
-
12
- - **@voltro/cli, @voltro/data-transfer** — An import over `--target api` can be asked what it will do, and watched while it does it.
13
-
14
- The transport's two correct properties combined into one blind spot: the run happens INSIDE the instance, so every line it produces goes to a pod log; and it is deliberately decoupled from the caller, so killing the client does not stop it (which is what keeps a dead client from leaving a half-emptied target). Someone reaching for `--target api` cannot reach the database directly and usually cannot read that log either, so "watch for the staging line" was advice they could not follow.
15
-
16
- **A preflight, before a byte is uploaded.** A `replace` over the api now asks the instance whether it will stage, and prints the answer — including the reason when it will not. It answers from `decideStaging`, the same function the run itself calls, so the two cannot drift; a second copy of that decision would answer confidently and diverge on the next change. With `--bundle-key`, where the client never holds the bundle, it sends the key and the instance reads the table list out of the archive's own manifest — the caller about to have an instance empty its own database is the last one who should be told to check a log they cannot read.
17
-
18
- **`voltro data imports`** (and `GET /_voltro/admin/imports`, behind the same data-transfer secret) reads the history and the run in flight. `_voltro_data_imports` was write-only; it is now opened before the first table, **advanced every couple of seconds as tables land**, and closed with the outcome — so polling it is the progress feed. Not a streamed response on the upload connection: a chunked body has to survive every proxy in between, and a buffering reverse proxy turns a progress feed into exactly the silence it was meant to replace.
19
-
20
- **And a `replace` that does not stage now says why.** The staging set was an early `return []` three conditions deep, and the empty array met a `length > 0` further down and read as "do not stage". A bundle table the target does not have — the ordinary case for a development source against a production target — turned the non-destructive path off in silence. Every reason routes through one message now; the cycle case additionally used to be gated on `atomic`, so the mode where an interrupted run leaves the worst outcome was also the one that said the least.
21
-
22
- ### Changed
23
-
24
- - **@voltro/cli** — The declared framework schema no longer depends on `NODE_ENV`.
10
+ ### ⚠ BREAKING
25
11
 
26
- `_voltro_traces` and `_voltro_undo_log` defaulted to on outside production. That was allowed with an explicit justification one decider makes every command in a deployment agree — and the justification was about the schema FINGERPRINT, which only ever compares processes inside ONE deployment.
12
+ - **@voltro/cli**The read-only run-history endpoint moved:
27
13
 
28
- The declared set has a second reader that spans two, and it was never considered: `voltro data`. A bundle exported from a development database carries the tables that database has, and a staged (non-destructive) `replace` needs every bundle table to exist in the target. So one source tree produced a bundle a production target could not stage, and the run fell back to truncating it — with nothing red anywhere, on the one path where that difference is the entire point.
14
+ GET /_voltro/admin/imports GET /_voltro/admin/transfers
29
15
 
30
- Both tables are declared in **every environment** now. The trade is the one `_voltro_cdc_offsets` already makes: an unused declared table costs one empty table and buys agreement. `VOLTRO_UNDO` / `VOLTRO_TRACING_PERSIST` still decide what a process WRITES; they never decided the schema and still do not. Set `schema: { traces: false, undo: false }` in `app.config.ts` to keep one out — in every environment, or the divergence is back by hand.
16
+ Same secret, same query parameters, same response shape. Only the path changed.
31
17
 
32
- **Upgrade:** a production app that never declared them gains two empty tables. `voltro db apply` (or a `voltro dev` boot) plans and applies them like any other framework table, on every dialect. Run it before the pods roll, as with any schema change a migrate job and a fleet that disagree is exactly what this removes.
18
+ It answers for four directions now import, export, native backup, native restore and it named one of them. The subcommand and the table were renamed for exactly that reason in 0.49.0 and this path was left behind, which is worse than renaming none of them: a reader who follows a rename tries the matching path, gets a 404, and concludes the instance is too old.
33
19
 
34
- `voltro doctor` now prints the three decided tables and what decided each, on a HEALTHY run. That text existed and was reachable only from the `prod-mismatch` refusal a message that appears exclusively after a fleet is down is an explanation, not a warning.
20
+ `voltro data transfers --target api` uses the new path for you. What needs a hand is anything calling it directly — a monitoring check, an uptime probe, a curl in a runbook, a dashboard datasource. The codemod is `manual` and declares `reach: 'beyond-source'`, so it prints whether or not the path is found in your repository.
21
+ - **@voltro/database, @voltro/voltro** — `InterruptedReplace.tables` is `number | null`, and the interface gains `kind?: 'replace' | 'restore'`.
35
22
 
36
- ### Fixed
37
-
38
- - **@voltro/cli, @voltro/data-transfer** — `voltro data export --exclude a,b` made the bundle BIGGER, and made it unusable for a `replace`.
39
-
40
- It expanded into `{ kind: 'tables', tables: <everything else> }`, on the reasoning that a manifest should record what was exported rather than claim "everything". Right goal, wrong mechanism, and it cost two things at once. `all` is the only scope that filters out the tables which describe a deployment, so excluding two names silently added nine others back — the migration ledger among them, whose foreign row takes an environment down at the next boot. And `replace` refuses a named scope, so the honest way to leave a table out was also the way to make the bundle unusable for the mode it was being prepared for.
41
-
42
- The exclusion is a FIELD of the `all` scope now: the filter still runs, the manifest still says "everything except these" (a different and truer claim than "these"), and `replace` accepts it while naming the tables it will therefore not touch. `--exclude` also works over `--target api` now — it no longer expands against a table list only the instance has, so the instance resolves it where that list already is.
23
+ `_voltro_replace_in_progress` records two destructive operations now — an import's `--mode replace` and a native `voltro data restore`. A restore replaces the whole database from an artefact, so "how many tables was this going to empty and refill" has no answer: not known up front, not meaningful after.
43
24
 
44
- Every framework table is classified as portable or environment-local, with the reason, and a new one fails the build until somebody decides. That guard existed and did not help: it was satisfied by a second, hand-kept list inside its own test, and the two disagreed about `_voltro_traces` and `_voltro_undo_log`nothing was unclassified, something was classified twice, once wrongly. There is one list now, read by both guards. Traces, undo, the outbox and its attempts, idempotency keys, storage grants, spend and usage accounting, delivery attempts and schedule-firing history joined the environment-local side: a row from elsewhere would make the target act, or claim history it did not live.
45
- - **@voltro/sql-turso** — Opening a second pooled turso connection could fail instantly with `database is locked` — from inside the constructor, before a single query ran.
25
+ Writing `0` would have preserved the type and been worse. That number is rendered into the boot refusal, which is read under pressure, and it would have said `began emptying 0 table(s)` — a measurement that was never taken. The framework's own text says "this database" instead; `Number(null)` is `0`, so coercing it reintroduces exactly the sentence being avoided.
46
26
 
47
- `makeConnection` set its pragmas in the order `journal_mode` `foreign_keys` `busy_timeout`. The engine defaults `busy_timeout` to **0**, so a statement that meets a held lock fails on the spot instead of waiting and `journal_mode= experimental_mvcc` needs the file exclusively. Since `makeConnection` runs once per POOLED connection (default 4), opening connection two while connection one held the file hit that exclusive pragma with no lock-wait configured yet:
27
+ `DataTransferRun.direction` also widens to name `'backup'` and `'restore'` explicitly. That one is documentary — the union already ended in `| string`, so no assignability changes.
48
28
 
49
- SqlError: Failed to enable Turso MVCC (journal_mode=experimental_mvcc): database is locked
29
+ **`voltro update` carries you across this** codemod `0.50.0/03_interrupted-replace-tables-nullable`.
50
30
 
51
- `busy_timeout` is set FIRST now. Nothing else changed — same value, same pragmas, same connection.
52
-
53
- **Why it hid for so long.** The file already carried a long, correct note about `busy_timeout` being mandatory with a pool, and a separate fix had closed the DDL half (`retryFilter` + bounded retries in `applySchema`). Both are about the same lock class, so the constructor read as covered — but a setting cannot protect the two pragmas that run before it.
54
-
55
- It surfaces as an unrelated flaky test, because the failure lands wherever the second connection happens to be opened: a `CREATE TABLE` in one run, an MVCC pragma in the next. It cost three release gates — twice locally, once on a CI runner — and was twice diagnosed as machine contention and closed. It is contention-DEPENDENT, which is not the same as being the machine's fault.
31
+ ### Added
56
32
 
57
- Verified: 8 serial runs and 6 concurrent suites at load 12 0 failures, 0 occurrences of the message. The failure was intermittent before, so this is evidence rather than proof; the mechanism, however, is not in doubt.
58
- - **@voltro/runtime, @voltro/data-transfer, @voltro/cli** — The `source:` recorder broke every WRITE on the in-memory store, and actions are recorded now.
33
+ - **@voltro/cli, @voltro/data-transfer, @voltro/database** — `voltro data backup --assets` and `voltro data restore --assets` now move the stored blobs alongside the vendor dump, through the same content-addressed phase the logical `export` / `import` path uses streamed, deduped by sha256, verified on the way back, resumable per key.
59
34
 
60
- The recording wrapper is a `Proxy`, and it handed methods back unbound so `this` was the PROXY, and a class with `#private` fields answers that with `TypeError: Receiver must be an instance of class InMemoryDataStore`. Under `voltro dev` on the memory store, where the recorder is installed by default, that is every write in the app.
35
+ `--assets` was accepted by `backup` and silently ignored, with the only signal a field in the closing JSON reading `NOT included`; `restore` did not accept it at all. A rows-only backup restores a database whose rows reference objects nothing puts back, and the reference and the object are checked at different times, so that state is discovered by a user rather than by the restore.
61
36
 
62
- Every test passed throughout, and the reason is worth more than the fix: `query` is the one method the wrapper invokes with an explicit receiver, so everything that only READ through it worked. The wrapper's whole purpose is reading, so nothing in its own suite ever wrote. It was found by a test about something else entirely asking what `crud.create` reads which needed a write to answer.
37
+ Three refusals, each for a belief that is otherwise acted on silently: `backup --assets` with no storage provider is refused rather than swallowed; `restore --assets` on a rows-only artefact is refused; a restore WITHOUT `--assets` over an artefact that has them warns and proceeds, because restoring rows without blobs is legitimate and refusing it would push people at `--force`.
63
38
 
64
- **Actions are recorded too now**, and an action's `source:` means something different from a query's. A query's is a reactive trigger set; an action's declares what it TOUCHES, and the field's own documentation records what an undeclared read costs: `voltro check` reported a table five action paths read and wrote as an orphan, and advised removing it. That is not a quiet subscription, it is advice to delete a live table.
39
+ The dump itself still has no resume a vendor artefact is one opaque file with no offset to restart from, and the logical path is what exists for that.
40
+ - **@voltro/cli, @voltro/database, @voltro/data-transfer** — A native `backup` / `restore` now writes 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. A native run reports blobs rather than rows — a vendor tool reports no row count we can trust, and printing `0 row(s)` over a dump that worked would be a wrong measurement.
65
41
 
66
- **Measured rather than reasoned about:** `crud.create`, `crud.update` and `crud.remove` issue NO read at all, so a read recorder has nothing to say about them but `crud.getById` does read, and with `include:` it eager-loads, so it is covered like `crud.list`.
42
+ `restore` also writes the `_voltro_replace_in_progress` marker before its first destructive statement and clears it after the last write, blobs included, so a killed restore refuses the next boot instead of serving a half-loaded database. `--allow-live` guards from the wrong side — it asks you not to — and this guards from the right one. The marker carries a `kind` so the refusal can say something different for a `replace` (re-import the capture) and a `restore` (finish the restore), and `tables` is nullable because a native restore has no table count to claim.
67
43
 
68
- **And a kill test for the one moment nobody had reproduced.** The existing test kills mid-LOAD, which staging turned into the harmless part; the destructive second went untested precisely because it became short. The new case kills as the swap begins and asserts the property rather than the race: the target is one state or the other, never a mix, and never empty. Two defects in the harness came out of writing it — a worker that died SILENTLY (its failure now goes into the marker the parent already reads, instead of looking like a slow start), and an `exit` listener attached only after the SIGKILL, which hung to the full timeout whenever the child finished first.
44
+ The sqlite / turso restore is atomic now (temp file + rename): its marker lives in the very file being replaced, so a plain in-place copy would leave a truncated database with nothing left to catch it.
69
45
 
70
- **And the import trace was never written on the path most likely to be used.** A deployment ran a successful `voltro data import` against a database that HAD the table, and got no row and no message at all. The write was gated on this PROCESS's table registry, and `voltro data`'s own boot builds a store and introspects the live schema it never registers the framework set, so the gate was `undefined` exactly there. The authority for "does the target have this table" is the target's SCHEMA, which that boot already introspects; the table is also registered before the write, because a CLI run has not done it and the write needs the column metadata.
46
+ A target with no `_voltro_data_transfers` table still gets its backup; the closing line says it was not recorded rather than implying it was.
47
+ - **@voltro/cli** — A re-issued migration note for the `voltro data imports` → `voltro data transfers` rename that shipped in 0.49.0.
71
48
 
72
- The sharper half is the silence, and it was self-inflicted: the skip was written three lines under a comment about how an absent table cannot be detected by the write failing. The message now lives in `voltro data import`, which is the layer that INTROSPECTED a first attempt put it in the importer, where a `targetSnapshot` may legitimately be narrow rather than complete, so it fired at callers whose snapshot simply did not mention a framework table.
49
+ Codemods are selected by `from < version <= to`, so the 0.49.0 note fires once, on the jump that crosses 0.49.0, and cannot be corrected for anyone already past it. Its gate searched `.ts` / `.tsx` while the command it is about lives in shell scripts, CI job definitions and runbooks so a project whose only occurrence sat in `.gitlab-ci.yml` crossed 0.49.0 and was told there was nothing to apply.
73
50
 
74
- ### Internal (no consumer-facing effect)
51
+ The re-issue is filed under 0.50.0, prints unconditionally, and says what to grep for. Redundant for anyone already fixed; the alternative is firing for nobody.
75
52
 
76
- - **@voltro/data-transfer** — `SAVEPOINT_BATCH_SIZE` carries its documentation again.
53
+ ### Fixed
77
54
 
78
- A new `TRACE_ADVANCE_MS` was declared BETWEEN the constant's doc block and the constant, so TypeScript attached the block to whatever now followed it and the exported symbol was left bare — the api golden recorded it as `// @public (undocumented)` and the published report shipped it that way.
55
+ - **@voltro/cli** — A `manual` codemod's `appliesTo` can now search every text file the project owns `.sh`, `.yml`, `.json`, `.md`, `Makefile`, the `.js` scripts through a new `ctx.text` on the predicate context, and a codemod may declare `reach: 'beyond-source'` to print its note even when nothing matched.
79
56
 
80
- Third time this exact shape has appeared (`sourceKeys`, `recordsTable`, now this one), always the same mechanism: an insertion above a documented declaration silently re-homes the comment. Nothing warns, because both the code and the doc block are individually valid only the golden's `(undocumented)` marker notices, and it reads as noise unless someone diffs it against the last TAG.
57
+ Both halves close the same gap, and it was in the codemod most in need of a gate. A manual codemod exists BECAUSE its subject could not be transformed, which usually means it is not source at all — and `appliesTo` was reading the ts-morph project, which holds `.ts` / `.tsx` and nothing else. Measured with one identical CLI invocation in four files: the `.ts` one printed the note, the `.sh`, `.yml` and `.md` ones printed nothing, and the run reported `codemods: nothing to apply for this jump`. That is an acquittal from a check that never looked at the file, and an acquittal gets acted on.
81
58
 
82
- Documentation only; no behaviour, no signature change.
59
+ `reach: 'beyond-source'` covers what no scan of one repository can reach — an inline script in a CI runner's own UI, a CronJob spec in another repo, a wiki runbook. The note prints either way; only its framing changes, and an uncertain one says plainly that we could not look there.
60
+ - **@voltro/data-transfer** — When `mariadb-dump` is absent and the mysql-family fallback runs Oracle's `mysqldump` against a MariaDB server, the failure now names the way out.
83
61
 
84
- **`apiSurface: compatible`, and the reason is the whole point of the change.** The golden line that moved is `// @public (undocumented)` `// @public`: an api-extractor MARKER describing whether a doc comment is present. No type, no signature, no name. `SAVEPOINT_BATCH_SIZE` is still `= 200`, still exported, still the same literal type nothing that compiled can stop compiling.
62
+ The stderr it prints is the child's own words and is the right first thing to show and on its own it is a dead end: `Unknown table 'COLUMN_STATISTICS' in information_schema (1109)` names a table nobody asked for, in a schema nobody wrote, about a feature nobody enabled. Everything needed to act on it was known where the fallback was DECIDED: which binary we wanted, which one we took, and why the difference matters. That travels with the step now and prints under the stderr, including the counter-move a reader reaches for on their own (`--column-statistics=0` does not exist on `mariadb-dump`, so it fixes the wrong client and breaks the right one).
@@ -12,16 +12,16 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.97.0",
14
14
  "@effect/rpc": "^0.76.0",
15
- "@voltro/ai": "0.48.0",
16
- "@voltro/cli": "0.48.0",
17
- "@voltro/database": "0.48.0",
18
- "@voltro/env": "0.48.0",
19
- "@voltro/protocol": "0.48.0",
20
- "@voltro/runtime": "0.48.0",
15
+ "@voltro/ai": "0.50.0",
16
+ "@voltro/cli": "0.50.0",
17
+ "@voltro/database": "0.50.0",
18
+ "@voltro/env": "0.50.0",
19
+ "@voltro/protocol": "0.50.0",
20
+ "@voltro/runtime": "0.50.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.48.0",
24
+ "@voltro/testing": "0.50.0",
25
25
  "typescript": "^6.0.3",
26
26
  "@vitest/coverage-v8": "^4.1.10",
27
27
  "vitest": "^4.1.10"
@@ -13,17 +13,17 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.48.0",
17
- "@voltro/database": "0.48.0",
18
- "@voltro/env": "0.48.0",
19
- "@voltro/plugin-auth": "0.48.0",
20
- "@voltro/protocol": "0.48.0",
21
- "@voltro/runtime": "0.48.0",
22
- "@voltro/sql-postgres": "0.48.0",
16
+ "@voltro/cli": "0.50.0",
17
+ "@voltro/database": "0.50.0",
18
+ "@voltro/env": "0.50.0",
19
+ "@voltro/plugin-auth": "0.50.0",
20
+ "@voltro/protocol": "0.50.0",
21
+ "@voltro/runtime": "0.50.0",
22
+ "@voltro/sql-postgres": "0.50.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.48.0",
26
+ "@voltro/testing": "0.50.0",
27
27
  "typescript": "^6.0.3",
28
28
  "@vitest/coverage-v8": "^4.1.10",
29
29
  "vitest": "^4.1.10"