@voltro/cli 0.31.0 → 0.33.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 (81) hide show
  1. package/CHANGELOG.md +368 -0
  2. package/THIRD-PARTY-NOTICES.md +1 -29
  3. package/dist/apiBuild-C-x9YacA.js +2 -0
  4. package/dist/{apiBuild-CLPt-pX6.js → apiBuild-h9VHtnlw.js} +2 -2
  5. package/dist/bin.js +3 -3
  6. package/dist/checkCommand-DRovTKza.js +2 -0
  7. package/dist/{checkCommand-D7r6_pk7.js → checkCommand-xGhRFFg2.js} +330 -290
  8. package/dist/{commands-CNVVv03h.js → commands-CJfepbm4.js} +139 -13
  9. package/dist/{dbCommand-BH8GAYuf.js → dbCommand-DTLKAfbA.js} +431 -263
  10. package/dist/dbCommand-b1gum4td.js +2 -0
  11. package/dist/{dev-DdQEn2Ia.js → dev-C_P8FLSx.js} +2141 -2081
  12. package/dist/{dev-DOZVMVfi.js → dev-iiMtlkfs.js} +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/{inspectMetrics-BeEAnjqw.js → inspectMetrics-1xzTKAFx.js} +750 -685
  15. package/dist/{sdkgen-v9WV8zRi.js → sdkgen-C81QIkiL.js} +1 -1
  16. package/dist/serveCommand-DdaM4Hup.js +1608 -0
  17. package/dist/serveEntry.js +2 -2
  18. package/dist/{start-CI2QdKy9.js → start-C0koT0UO.js} +1 -1
  19. package/dist/startEntry.js +2 -2
  20. package/package.json +431 -18
  21. package/templates/AGENTS.md +1 -1
  22. package/templates/agent-docs/_index.md +1 -1
  23. package/templates/agent-docs/database/misc.md +37 -0
  24. package/templates/agent-docs/database/schema.md +4 -2
  25. package/templates/agent-docs/deployment.md +87 -1
  26. package/templates/agent-docs/plugins/audit.md +24 -0
  27. package/templates/agent-docs/plugins.md +50 -0
  28. package/templates/agent-docs/scheduling.md +4 -1
  29. package/templates/agent-docs/security.md +31 -1
  30. package/templates/agent-docs/whats-new.md +149 -59
  31. package/templates/apps/api-ai/package.json +7 -7
  32. package/templates/apps/api-auth/package.json +8 -8
  33. package/templates/apps/api-backend/package.json +7 -7
  34. package/templates/apps/api-backend-deactivation/package.json +7 -7
  35. package/templates/apps/api-backend-mail/package.json +8 -8
  36. package/templates/apps/api-backend-mariadb/package.json +9 -9
  37. package/templates/apps/api-backend-sqlite/package.json +8 -8
  38. package/templates/apps/api-backend-storage/package.json +8 -8
  39. package/templates/apps/api-cms/package.json +10 -10
  40. package/templates/apps/api-collab/package.json +8 -8
  41. package/templates/apps/api-data-advanced/package.json +8 -8
  42. package/templates/apps/api-durable/package.json +8 -8
  43. package/templates/apps/api-feature-flags/package.json +9 -9
  44. package/templates/apps/api-governance/package.json +8 -8
  45. package/templates/apps/api-kv/package.json +8 -8
  46. package/templates/apps/api-moderation/package.json +8 -8
  47. package/templates/apps/api-observability/package.json +8 -8
  48. package/templates/apps/api-ratelimit/package.json +8 -8
  49. package/templates/apps/api-rbac/package.json +8 -8
  50. package/templates/apps/api-rest/package.json +7 -7
  51. package/templates/apps/api-saas/package.json +11 -11
  52. package/templates/apps/api-saas-starter/package.json +10 -10
  53. package/templates/apps/api-search/package.json +8 -8
  54. package/templates/apps/api-status/package.json +8 -8
  55. package/templates/apps/api-versioning/package.json +8 -8
  56. package/templates/apps/api-webhooks/package.json +9 -9
  57. package/templates/apps/changelog/package.json +6 -6
  58. package/templates/apps/edge-functions/package.json +2 -2
  59. package/templates/apps/frontend-admin/package.json +8 -8
  60. package/templates/apps/frontend-app/package.json +8 -8
  61. package/templates/apps/frontend-auth/package.json +8 -8
  62. package/templates/apps/frontend-blank/package.json +7 -7
  63. package/templates/apps/frontend-cms/package.json +9 -9
  64. package/templates/apps/frontend-collab/package.json +9 -9
  65. package/templates/apps/frontend-contact/package.json +7 -7
  66. package/templates/apps/frontend-dashboard/package.json +7 -7
  67. package/templates/apps/frontend-docs/package.json +7 -7
  68. package/templates/apps/frontend-i18n/package.json +6 -6
  69. package/templates/apps/frontend-landing/package.json +7 -7
  70. package/templates/apps/frontend-portal/package.json +8 -8
  71. package/templates/apps/frontend-saas/package.json +8 -8
  72. package/templates/apps/frontend-spa/package.json +7 -7
  73. package/templates/apps/frontend-ssr/package.json +7 -7
  74. package/templates/apps/frontend-ssr-api/package.json +8 -8
  75. package/templates/apps/frontend-static-blog/package.json +6 -6
  76. package/templates/apps/frontend-status/package.json +8 -8
  77. package/templates/apps/mobile-app/package.json +2 -2
  78. package/dist/apiBuild-BOqL1dlg.js +0 -2
  79. package/dist/checkCommand-ClRvH_SN.js +0 -2
  80. package/dist/dbCommand-BwQ_Jl98.js +0 -2
  81. package/dist/serveCommand-KsKXXWn4.js +0 -1578
@@ -1,4 +1,4 @@
1
- # What's new in 0.31.0
1
+ # What's new in 0.33.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,113 +7,203 @@ 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
+ ### ⚠ BREAKING
11
+
12
+ - **@voltro/protocol, @voltro/runtime, @voltro/voltro** — `CoordinatedScheduleHandle` gained `wake()`, `currentIntervalMs()` and `isArmed()`.
13
+
14
+ The type change that carries the poller work in this release (see *A coordinated tick is a FLOOR*). Two of the three shapes it touches are NOT breaking and are listed here so the classification is checkable rather than asserted:
15
+
16
+ - the effect parameter was **widened** — it may now return a tick outcome, and an existing `() => Promise<void>` still satisfies it; - `Coordinator.tryClaim` gained an **optional** third parameter (the caller's bucket width), so an existing implementation still conforms.
17
+
18
+ (The plugin-facing `scheduleCoordinated` also gained an OPTIONAL fourth argument, `{ disarmWhenIdle }` — additive, and how a plugin opts its own task out of polling entirely.)
19
+
20
+ What breaks is code that **constructs** a handle rather than receiving one: a hand-written test double of `PluginBindContext`, which is the ordinary way to unit-test a plugin's `bindDataStore`. Four of the framework's own suites carried one, and three of those compiled only because the stub was cast — which is also why the two new members must be REQUIRED rather than optional. An optional `wake()` would let a caller subscribe a change channel to a handle that silently has none, and a poller that never wakes is the failure this release exists to remove, arriving quietly.
21
+
22
+ The codemod is `manual`: the object literal needing the two fields carries no importable symbol and usually sits behind an `as never`, so no transform can tell it apart from an unrelated literal in the same test file. It is gated on the app mentioning `scheduleCoordinated` at all.
23
+ - **@voltro/runtime** — `@effect/opentelemetry` is now an **optional peer** of `@voltro/runtime` instead of a dependency. **If you export traces or metrics, install it:**
24
+
25
+ ```sh
26
+ pnpm add @effect/opentelemetry
27
+ ```
28
+
29
+ If you do not (no `FRAMEWORK_TRACING`, no `FRAMEWORK_METRICS`, no `OTEL_EXPORTER_OTLP_*`), nothing changes and your install gets 24 lines quieter.
30
+
31
+ It is reached from one dynamic `import()`, only when tracing is on, and it declares seven non-optional OpenTelemetry peers of which we supply five. So every `pnpm install` of every consumer ended with an unmet-peer block describing a condition that broke nothing. Declaring the two missing peers as real dependencies was the wrong direction — one of them is `@opentelemetry/sdk-trace-web`, the BROWSER tracer — and 0.64.0 is the current stable, so there is no upstream release marking them optional to wait for.
32
+
33
+ The reporting consumer's argument is what decided it: *"a check that is loud on every upgrade teaches people to skip the output, and the next warning in that block is the one that matters. We read past this one for four releases."*
34
+
35
+ A boot with tracing enabled and the package absent fails with a message naming this install line — a startup failure, not a silent loss of telemetry.
36
+
37
+ **`voltro update` carries you across this** — codemod `0.33.0/01_opentelemetry-optional-peer`.
38
+
10
39
  ### Added
11
40
 
12
- - **@voltro/i18n, @voltro/cli** — **`LOCALE_COOKIE` and `THEME_COOKIE` are exported from `@voltro/i18n`.**
41
+ - **@voltro/cli** — `voltro agents-md` now reports which `@voltro/cli` it seeded from, and warns when that is not the one the project installs.
42
+
43
+ ```
44
+ seeded from @voltro/cli 0.31.0 (project has 0.32.0; whats-new describes 0.31.0;
45
+ modules COPIED into ./agent-docs)
46
+ WARN the `voltro` binary that ran is 0.31.0, but this project installs 0.32.0 —
47
+ everything just written describes the OLDER version.
48
+ ```
13
49
 
14
- `voltro doctor` flags a hand-written `'voltro:locale'` and tells you to "import the constant from `@voltro/ui-shadcn`" which was the only package exporting one. An app on the framework's i18n and not on the shadcn kit could not follow that advice without adopting a UI kit for two strings. Reported by a consumer, who added that the rule "does not fire for us, and we think that is correct-by-accident".
50
+ A consumer reported a freshly-seeded `agent-docs/whats-new.md` one release behind their installed version, twice. The published packages are correct (verified with `npm pack`), so the content came from a different `@voltro/cli` than the one they installed the command reads its templates relative to the RUNNING binary, and a globally-installed `voltro`, a stale `dist`, or a parent workspace's copy all produce exactly that, with output that looked identical either way.
15
51
 
16
- The names live in `@voltro/i18n` now (the package that resolves the locale from a request already hardcoded the literal), doctor's remedy names it, and `cookieNameParity.test.ts` asserts every declaration in the repo agrees AND that the remedy names a package which actually exports what it names.
52
+ It does not refuse and does not pick a cli for you: running the workspace binary against a checkout is legitimate and common.
53
+ - **@voltro/cli** — `voltro db encrypt-column <table>.<column>` — the data migration `.encrypted()` always needed.
17
54
 
18
- That second assertion is one `scripts/check-message-apis.mjs` structurally cannot make: it verifies a member EXISTS in the published surface, and `LOCALE_COOKIE` always did just not anywhere the reader could import it from. A reachability claim needs its own check.
19
- - **@voltro/cli** — **`voltro doctor` reports two things that were decidable and unreported: dead endpoints and never-fired events.**
55
+ `.encrypted()` encrypts on WRITE, so adding it to a populated column converts nothing that is already there, and there was no supported way to convert it. A consumer carried three plaintext credential columns for months with no next step: *"`.encrypted()` braucht einen Cipher UND eine Datenmigration der bestehenden Zeilen; gemeldet, nicht behoben."*
20
56
 
21
- **A procedure requiring a scope no declared role grants.** A consumer shipped `webhooks.test` guarded by `webhooks:test` while their roles granted `webhooks:read` and `webhooks:write` and nothing else — uncallable by every user in every team, with nothing failing at boot to say so — and asked us to build the rule. The rule already existed: `rbac/unknown-scope`, in `voltro check`. It was in the wrong PLACE for them. `check` is the CI gate; `doctor` is what someone runs when something feels wrong, and a rule that only fires where you already suspect the problem fires for the people who did not need it. Doctor now surfaces the same finding from the same function — reused, not re-derived, because two answers to "which scope is ungranted" would diverge on the day it mattered.
57
+ ```sh
58
+ voltro db encrypt-column integrations.webhookSecret --dry-run
59
+ voltro db encrypt-column integrations.webhookSecret employees.meilisearchKey --yes
60
+ ```
22
61
 
23
- **A declared event with no emit call site.** Asked for as a dashboard column, with the number that motivated it: seven of eleven advertised events had no emit anywhere, a week of work to remove. From a partner's side that is the expensive failure — they write a subscriber, test it, see nothing, and cannot tell "not implemented" from "my endpoint is broken". It is source-decidable, so it lands before deploy for every app rather than for whoever opens a panel afterwards. ADVISORY, and it says so: an emit through a variable reads as missing here.
62
+ Five guards, each for a way a naive version succeeds and destroys data:
24
63
 
25
- Both reach `--json` and the human report. `serverOnly` and `authz` were each in one and absent from the other, twice, and "absent" read as "nothing to report".
26
- - **@voltro/cli** — **`voltro dev` and `voltro doctor` report `@voltro/*` packages that are not on the same release.**
64
+ - **Idempotent** an already-ciphertext value is skipped, so an interrupted run is resumed by running it again. Double encryption is unrecoverable without the key history. - **Round-trip verified before the write** — every value is decrypted back in-process first, so a broken cipher fails with nothing written. - **Key checked against what the column already holds** — a *different* key round-trips fine, so the check above cannot see it. Resuming with the wrong key would leave a column readable with neither key alone. - **Width pre-flight** — ciphertext is `49 + 4×ceil(bytes/3)` characters, so a 64-char key needs 137 and a `varchar(100)` fails partway. Refuses with both numbers and the `.maxLength()` to set. Measured in BYTES: `'ä'.repeat(10)` is 10 characters and 20 bytes. - **`--yes` required**, `--dry-run` shows the counts, and no value plaintext or ciphertext — is ever printed.
27
65
 
28
- A consumer bumped 34 packages to 0.30.2 and `@voltro/i18n` stayed on 0.30.0 their bump script's pattern was `@voltro/[a-z-]+`, and `i18n` contains digits. Their point was not the typo:
66
+ Verified against a real postgres: the conversion, the re-run no-op, both refusals writing nothing, and a decrypt back to the original including multi-byte content.
29
67
 
30
- > *"Der erwähnenswerte Teil ist, dass eine Versionsschiefe innerhalb einer > Release-Familie nirgends auffällt: tsc, Tests, doctor und drei Builds waren > mit dem 0.30.0-Paket im Baum alle grün. Wenn ihr eine Stelle habt, an der das > billig zu prüfen wäre, wäre eine Warnung dort mehr wert als eine perfekte > Fehlermeldung anderswo."*
68
+ ### Changed
69
+
70
+ - **@voltro/runtime** — `_voltro_schedule_claims` swaps its `(scheduleName, bucket)` index for `(scheduleName, claimedAt)`.
71
+
72
+ A consumer read `pg_stat_user_indexes` on their live table and measured, over its whole lifetime:
73
+
74
+ ```
75
+ _voltro_schedule_claims_pkey 348 978 scans
76
+ _voltro_schedule_claims_claimedAt_idx 3 949
77
+ _voltro_schedule_claims_scheduleName_bucket_idx 4
78
+ ```
31
79
 
32
- `tsc` in four apps, 19 317 tests, `voltro doctor` exit 0, three production buildsall green with a package two releases behind. None of those checks is ABOUT version agreement, so none of them could have caught it.
80
+ Four. It was declared "for the case where you would rather ask by field", and nothing ever asks by field every read of this table goes through the primary key, which *is* `<scheduleName>@<bucket>`. An index nothing uses is not free: it is written on every INSERT, into a table written once per tick per schedule.
33
81
 
34
- The framework ships in LOCKSTEP (`prepare-publish.mjs` stamps one version across every package), which is what makes this exact rather than a compatibility guess: two versions in one tree is a state the release process cannot produce. Reported at dev boot where they asked for it, because that is where it is cheap — and in doctor's human report and `--json`. WARN, never fatal: we have no evidence a skewed tree cannot work, only that the combination was never released as a set.
82
+ `(scheduleName, claimedAt)` is the shape of a query that now exists the per-schedule prune a winning claim runs (`WHERE scheduleName = ? AND claimedAt < ?`). The `claimedAt` index stays: the retention sweep's cutoff spans every schedule and needs it leading, which the composite cannot provide.
35
83
 
36
- Distinct from the duplicate-instance check, which asks a different question ("is one package resolved at two versions?") and has a different fix.
84
+ No codemod: a `_voltro_*` change rides the declarative differ on `voltro db apply` and on a `voltro dev` boot, on every dialect.
85
+
86
+ The same measurement corrected something the reporter had said in an earlier round and we had repeated back to them — that both indexes went unused. The primary key is used constantly. That makes the finding sharper rather than weaker: the ability to answer this question in one lookup is not merely available, it is demonstrably in use on the same table, and the one read path that needed it was the one not taking it.
37
87
 
38
88
  ### Fixed
39
89
 
40
- - **@voltro/cli** — **`voltro db scan-credentials` on 0.30.0–0.30.2 did not look at the column credentials are in, and exited 0.**
90
+ - **@voltro/database** — A column ADDED with a `reference()` now gets its foreign key in the same plan.
91
+
92
+ `ADD COLUMN` emits no `REFERENCES` clause on any dialect, and the planner's FK branch lived only in the path for a column present on both sides — so adding a `reference()` column to an existing table planned an `add-column` and nothing else. The constraint appeared on the SECOND `voltro db apply`, when the column was live and the diff finally saw a live column with no FK.
93
+
94
+ Two applies converged, so the state was reachable, which is why this survived as a low-priority note for a long time. It is worse under `voltro dev`: the boot diff refuses to record a fingerprint while the re-plan is non-empty, so an app whose only pending change was such a column re-planned on every boot and never converged.
95
+
96
+ Both callers share one `addForeignKeyOps` builder now, and the existing dependency tiering already orders `add-column` before `add-foreign-key`.
97
+ - **@voltro/runtime, @voltro/protocol, @voltro/workflow, @voltro/cli** — A coordinated tick is a FLOOR now, and a claim no longer outlives its bucket.
98
+
99
+ A consumer's `_voltro_schedule_claims` reached **86 214 rows / 33 MB** on two days of uptime and took their deployment down: ten of a fifteen-slot pooler pinned on the claim read, an SSR render measured at **300 490 ms** behind them, every page in three frontends unusable, and a `rollout restart` that could not complete because the surge pod could not get a connection. Two hours of their own measurement produced the diagnosis, and both halves of it were right.
100
+
101
+ **Where the rows came from.** They declare one workflow, have never started it, use no flow control and no offloaded inference. Over one hour, with two replicas:
102
+
103
+ ```
104
+ voltro.ai.inference 1 259 rows/h (250 ms ticks) framework
105
+ voltro.workflow.admission 1 247 rows/h (1 s ticks) framework
106
+ their own eight schedules 18 rows/h
107
+ ```
108
+
109
+ 99.3 % of the ledger was the framework polling two structurally empty queues. A fixed interval has no way to learn that, so:
110
+
111
+ - **`scheduleCoordinated`'s effect may now REPORT its tick.** Return `{ idle: true }` and the runner backs off toward a ceiling; return `{ idle: true, nextDueInMs }` and it arms for that instant instead — which is what keeps a `debounce` window from being slept through. Returning nothing keeps the fixed interval, so every existing plugin task ticks exactly as before. - **Where an arrival is guaranteed to wake it, an idle task STOPS ENTIRELY** (`{ disarmWhenIdle: true }`). Both framework tasks do, on any deployment where a peer replica's write is visible locally — Postgres LISTEN/NOTIFY, or a broadcast broker. Measured against a real Postgres on a deployment that uses neither queue: **2 claim rows in five minutes**, one per task, both at boot. Where that guarantee does not hold, the ceiling (`VOLTRO_POLL_CEILING_MS`, default 30 s) is the correct behaviour and is what they get. - **`handle.wake()` runs a tick now.** Both framework queues are tables with the framework's own CDC triggers on them, so an enqueue already produces a change event on every replica; both dispatchers subscribe to it. The idle case gets ~120× cheaper and the busy case gets FASTER — work starts on the INSERT rather than up to a tick later. - The claim bucket stays floored by the BASE interval. Replicas do not share a backoff state, and two replicas computing different keys for one moment would both win.
41
112
 
42
- 0.30.0 fixed a crash the scan asked every table for a column called `subject`, `_voltro_row_history` has none, and the command died on postgres with `code=42703`. The fix replaced `subject` with `subjectId` on BOTH tables. That is right for one table and wrong for the other: `_voltro_audit_log.subject` is a `json()` blob and is the exact column the 0.28.0 upgrade note named the one a reporting team found 117 rows of `jiraToken` in. `subjectId` is a flat opaque id that cannot hold a credential.
113
+ **The cadence is declarable.** `scheduling: { admissionDrainMs, inferenceTickMs, cancelSweepMs, pollCeilingMs }` in `app.config.ts`, each with a matching `VOLTRO_*` env var that overrides it the same ordering as `VOLTRO_TENANT_ISOLATION` over `tenancy.isolation`. They were internal constants, and a number the framework picks on a user's behalf belongs somewhere they can read it without reading our source. One resolver, called by both boot paths, so there is no second default to drift.
43
114
 
44
- So for three releases a security command ran cleanly, printed a scanned count beside a hit count, and had never looked where credentials are. **A clean answer from a scan that searched the wrong place is worse than the crash it replaced.**
115
+ **Why the rows never left.** A claim answers one question about one bucket and was already answered the moment the bucket passed. A winning claim now deletes that schedule's own predecessors, so the table's size is a small multiple of the number of schedules rather than a function of uptime. How far back it prunes scales with the caller's bucket width — a cron keeps ~68 minutes of them (its firings carry their own instant, so a stalled one can re-present an old bucket), a 250 ms task ~1 minute (it recomputes its bucket at tick time, so an old one is unreachable). Deleting too early is a double fire; that grace is the whole safety argument. The 24-hour retention sweep stays as the backstop for a schedule that was renamed or deleted, which the per-schedule prune can never revisit.
45
116
 
46
- The default targets are derived from where a credential can physically land now: every `json()` column on `_voltro_audit_log` (`subject`, `actor`, `scope`, `metadata`, plus `input` / `outcome` — a procedure's arguments and result, the likeliest accidental home for a token), and `data` on `_voltro_row_history` the full-row snapshot, which carries a credential column from ANY user table and outlives deleting the source row. The test that signed off on the narrowing is derived from the table declarations now, so it fails in both directions: a target naming a column that does not exist, and a column that exists and goes unscanned.
117
+ Where reactivity is absent a non-Postgres dialect with no broadcast broker — a remote replica's enqueue produces no local event and the ceiling is the whole latency budget. `VOLTRO_POLL_CEILING_MS` is there for that case and documented as such.
118
+ - **@voltro/logger** — The pretty log format now prints a nested `Error`'s `message`. It did not, and the JSON format did.
47
119
 
48
- A manual codemod under 0.31.0 tells anyone who ran the command on 0.30.x to run it again a codemod note is delivered once at a version boundary and cannot be revised, so a correction has to be re-issued under a version nobody has reached.
49
- - **@voltro/cli** — **`voltro dev` announced `ready` while a different process served its port.**
120
+ `Error.prototype.message` is non-enumerable, so `JSON.stringify(err)` emits the metadata and drops the message. `expandCauseForJson` has existed for a long time to solve exactly that and it was wired into `jsonFormat` only. The section heading above it said "(JSON path)", which was literally accurate.
50
121
 
51
- Reported as an aside — "it fails with `Port 5190 is already in use` and the OLD process keeps answering" — and measured to be worse than that. With a squatter on IPv4 `127.0.0.1:5299`, vite's `host: true` bound IPv6 `*:5299` SUCCESSFULLY and the boot printed `listening on http://localhost:5299` and `ready in 56 ms`. Two listeners, one port, different stacks; every request went to the old process, with the old module graph. Not a failed boot — a boot that reports ready while your edits do nothing.
122
+ `voltro dev` prints the pretty format. What a consumer saw when their boot died on a saturated pooler:
52
123
 
53
- `strictPort: true` was set and working: it asks "can I bind?", and the failure is "is somebody already serving this?", which on a dual-stack host is a different question with a different answer. Both dev boot paths (api and web) now CONNECT to `127.0.0.1` and `::1` before creating a server and refuse with a `bootRefusal` naming the port, the stack, and `lsof -nP -iTCP:<port> -sTCP:LISTEN`. Exit 1, no `ready` line. Deliberately dev-only: in production the ambiguous cases are real (a sidecar, a health proxy, a rolling restart sharing a namespace) and refusing a legitimate boot is the worse failure — `serve` keeps failing on the bind alone.
54
- - **@voltro/cli****`voltro doctor`'s duplicate-version check could not see the tree the consumer who asked for it actually had.**
124
+ ```
125
+ auto-migrate failedaborting boot
126
+ err={"failure":{"cause":{"length":117,…,"code":"XX000"},"message":"PgClient: Failed to connect"}}
127
+ ```
128
+
129
+ `length: 117` is the length of a message that is not there. Recovered by hand, it was `(EMAXCONNSESSION) max clients reached in session mode - max clients are limited to pool_size: 15` — the whole diagnosis in one sentence, naming the fix.
55
130
 
56
- They shipped the app on `@voltro/i18n@0.30.1` while `packages/ui-admin` and `ui-admin-shared` which depend on it themselves — were left on 0.30.0. Two physical copies of a package carrying a React context; nothing failed at boot, no test caught it, and the framework's own dev-SSR diagnosis names that condition as a cause. They asked us to build the check. **It existed, and it was blind to their case.**
131
+ Both the field tail and the plain-object cause branch expand now. The fix is in the formatter, not at the reporting call site: every `log.error('…', { err })` anywhere had the same hole.
132
+ - **@voltro/cli** — Every `@voltro/*` package now exports its own `package.json`, so `require('@voltro/cli/package.json').version` works.
57
133
 
58
- It walked the app's `node_modules` plus every ancestor's, on the reasoning that the pnpm virtual store is reached through the symlinks those contain. True for one copy: an ancestor's `node_modules/@voltro/i18n` is ONE symlink to ONE version. A second version pulled in by a SIBLING workspace package is linked only from that sibling's own `node_modules`, which is neither the app root nor an ancestor of it — so it was structurally invisible from the directory doctor is run in.
134
+ It threw. Node has enforced this since 12: a package with an `exports` field exposes only what that field lists, and none of the 77 packages listed `"./package.json"`.
59
135
 
60
- The pnpm store is read directly now, from its directory NAMES (`@voltro+i18n@0.30.0`), which is one `readdir` rather than a descent into thousands of inner `node_modules`. The peer-hash suffix pnpm appends is not read as a version one package linked twice for two peer sets is normal, and reporting it would train people to ignore the rule.
61
- - **@voltro/database, @voltro/sql-mysql** — **`json().default([])` emitted MySQL-only DDL and died mid-plan on MariaDB.**
136
+ Reported by a consumer for whom it was the instruction WE gave for settling whether a security command had been running on stale code so the verification step for a security question could not run at all. Both the workspace `exports` and the shipped `publishConfig.exports` are fixed, and a guard sweeps every package so a new one cannot ship without it.
137
+ - **@voltro/cli, @voltro/sql-postgres** — The `db pool:` boot line now counts the connections this process holds OUTSIDE the pool, and names them.
62
138
 
63
- The emitter had one branch for both servers, under a comment asserting MariaDB 10.2.7+ accepts the MySQL form. It does not. Measured against the real servers:
139
+ It reported `max × replicas` and called that the connection count. A consumer sizing a per-pod budget against a pooler measured the gap:
64
140
 
65
- | | statement | result | |---|---|---| | MariaDB 11.8.8 | `SET DEFAULT (CAST('[]' AS JSON))` | `ERROR 1064 … near 'JSON))'` | | MariaDB 11.8.8 | `SET DEFAULT ('[]')` | OK | | MySQL 8.4.10 | `SET DEFAULT ('[]')` | `ERROR 1101 … can't have a default value` | | MySQL 8.4.10 | `SET DEFAULT (CAST('[]' AS JSON))` | OK | | BOTH | `SET DEFAULT (CONVERT('[]' USING utf8mb4))` | OK |
141
+ > `LISTEN` läuft außerhalb von `dbMaxConnections` (eine pro Pod, gemessen sogar > 3). Der echte Bedarf ist `dbMaxConnections + 1`.
66
142
 
67
- MariaDB has no JSON *type* the column is `LONGTEXT` with `CHECK (json_valid(...))` — so `AS JSON` is not a cast target it has; MySQL treats a parenthesised literal as a literal and demands a real expression. The failure was not graceful: the plan died mid-apply, leaving a boot migration part-applied.
143
+ Their measurement was right and their conclusion was one short. The framework opens a standalone connection in three places, and a full deployment holds all three:
68
144
 
69
- `CONVERT(… USING utf8mb4)` is emitted for both, chosen over splitting the branch because the declarative applier cannot tell the two servers apart `sql.onDialectOrElse` collapses the family to one token — so a per-server answer would have meant plumbing the real dialect through three call sites. Both forms introspect back to something `normalizeDefault` unwraps, so the next plan is EMPTY instead of re-emitting the same op forever.
145
+ | Process | Connection | When | |---|---|---| | api `voltro serve` | CDC `LISTEN` consumer | `changeStrategy: 'cdc'` | | web `voltro start` | ISR invalidator `LISTEN` | a page declares `cacheInvalidatesOn` | | web `voltro start` | postgres ISR cache client | `SSR_CACHE=postgres` |
70
146
 
71
- `sql-mysql/src/jsonColumnDefault.integration.test.ts` boots BOTH servers, applies the schema and asserts the inserted row carries the default verified red against the old emitter (`ER_PARSE_ERROR 1064`) before it was made green.
72
- - **@voltro/cli** — **`locales:` declared the app's languages in `voltro dev` and demanded catalogs in `voltro build` — so an app on its own i18n stack got the right language in development and `lang="en"` in production.**
147
+ The third is not a `LISTEN`, which is why counting `LISTEN` rows in `pg_stat_activity` undercounts, and why `+1` could not have been documented as a constant: the count is per PROCESS and only the process knows what it armed.
73
148
 
74
- `voltro dev` already separates the two: the catalogs decide the CATALOG wiring, `locales:` decides the language facts (`<html lang>`, the resolved locale, `meta.locale`). The build did not, which is this repo's dev/build drift in its worse direction the environment nobody watches, on the day of the first deploy.
149
+ The line says `No connections outside the pool in this process` when there are none silence about it is what made "counted, zero" indistinguishable from "not counted". The `maxConnections` docstring, which promised `+1` as if it were the deployment's number, is corrected. Production-hardening docs (both languages) gain the table plus the `maxSurge` arithmetic a rolling update needs.
150
+ - **@voltro/cli** — The retention sweep is registered on every dialect — it was postgres-only, and silently.
75
151
 
76
- Reported by a consumer on react-i18next, who left `locales:` out because it demanded `src/locales/<lang>.ts` they do not have, and got `<html lang="en">` plus `locale: 'en'` handed to every page's `meta` in a German app:
152
+ `wireRetentionSweep` opened with `if (dialect !== 'postgres') return`, so on mariadb, mysql, mssql and sqlite **none of its seven policies was registered, nothing was ever deleted, and the boot printed no armed-policies line** so there was nothing to notice either. A consumer on MariaDB 11.8.8 measured it by reading the published bundle rather than their logs:
153
+
154
+ ```
155
+ _voltro_schedule_claims 109 520 rows 32.8 MB over 20 days
156
+ _voltro_schedule_runs 17 507 rows 7.4 MB
157
+ ```
77
158
 
78
- > *"Der Parameter `locale` ist damit für uns nicht nur nutzlos, sondern > gefährlich: hätten wir ihm vertraut, wäre nach der Hydration jeder deutsche > Tab englisch geworden."*
159
+ Their seven `VOLTRO_*_TTL_HOURS` variables were inert read only inside the branch that never ran and two of them were already set in their Helm chart.
79
160
 
80
- A value the framework hands you that is confidently wrong is worse than one it withholds.
161
+ **The gate was aimed at the right thing and applied to the wrong scope.** What is postgres-specific is the fast DELETE (`"camelCase"` quoting, `DELETE … RETURNING`), which is one branch of one function that has always had a portable fallback beside it. Gating the REGISTRATION on it turned a performance choice into a feature that does not exist. The dialect check now sits on the branch it describes.
81
162
 
82
- The build now emits a resolver-only i18n runtime when `locales:` is declared with no catalogs: `resolveLocale` (cookie > `Accept-Language` > default, negotiated against the declared set) with an identity `outerWrap`. No provider, no React, no catalog imports — and `voltro start` sets `<html lang>` and `meta.locale` from the real negotiation instead of falling back to `'en'`.
83
- - **@voltro/cli** — **A plugin's depth was addressed as a 1 KB README that says "read the website" — a dead end for an agent working inside a repo.**
163
+ Two things came out with it:
84
164
 
85
- We spent a round correcting a documented recommendation (`resolveSubjectId`) that the one team who had lived it had retracted. That consumer went looking for the correction in the channel our own `AGENTS.md` advertises as authoritative:
165
+ - **The fallback deleted row by row.** Acceptable while the path was unreachable; against the reporter's backlog it is 20 000 round trips per sweep pass. It reads a bounded batch of ids and issues ONE set-based delete for them — still bounded, so the DELETE never grows to lock the whole backlog. - **Two tests asserted the defect as intended behaviour**, with reasoning that was internally consistent and rested on the premise that was itself the bug (*"the sweep is postgres-only, and announcing a delete that will not happen is the mirror image of the defect"*). Both are inverted now and run across all five dialects.
166
+
167
+ This is the third turn of the same screw, and the reporter's framing is the one to keep: we fixed *a standing delete that never introduces itself*, then shipped *one that introduces itself and does not run* — and beside both of those sat one that silently did not exist.
168
+ - **@voltro/cli** — `voltro db scan-credentials` no longer reports the framework's own redaction markers as credentials, and no longer claims a match was a *key*.
169
+
170
+ A consumer with correctly-redacting plugins got:
86
171
 
87
172
  ```
88
- resolveSubjectId → 0 Treffer
89
- | notifications | node_modules/@voltro/plugin-notifications/README.md |
173
+ ✗ _voltro_row_history.data 69 of 149 row(s) match a credential-shaped key
174
+ matched (rows per needle, may overlap): token (69)
90
175
  ```
91
176
 
92
- > *"Diese Datei ist 1 039 Byte Für einen Menschen ist das ein Klick. Für einen > Agenten, der im Repo arbeitet, ist es eine Sackgasse."*
177
+ All 69 rows were `"_omitted": ["token"]` `@voltro/plugin-versioning`'s record that a `.serverOnly().sensitive('secret')` column was deliberately left OUT of the snapshot. The scan matched the proof that nothing is stored there, called it a credential, and printed *purge them AND rotate the credentials* underneath.
178
+
179
+ Two changes. The headline says what the predicate does — it is a substring match over the whole serialized column, so it finds a credential-shaped **name** anywhere in the value, which it always did. And each hit is now EXPLAINED: a bounded second pass (500 matched rows per target) reads them back in-process and separates a JSON **key** from a **redaction marker** (`_omitted`, `__redacted`). Values are never printed and never logged.
180
+
181
+ A target whose every matched row is a marker reports as explained and exits `0`. The bar is deliberately high — every matched row examined, every one a marker and nothing else. A capped read-back, one real key, or one row that will not parse as JSON keeps the target a finding and still exits `1`.
182
+
183
+ The shape mattered more than the one key name: the more columns an app classifies correctly, the more markers it writes, and the redder the scan turned.
184
+ - **@voltro/runtime, @voltro/cli** — `advisoryLock` scheduling no longer reads the whole `_voltro_schedule_claims` table to answer whether one claim row exists, and that table is now swept on the scale it fills.
93
185
 
94
- Their measurement was slightly off the correction WAS in `agent-docs/`, in `whats-new.md`and the truth is worse than their reading. `whats-new.md` is regenerated from the LATEST release section only, so the lesson would have vanished at 0.31.0 regardless. Same structure as a codemod note: delivered once, at a boundary, unrevisable. **A durable lesson needs a durable address.**
186
+ The existence check ran `SELECT "id" FROM "_voltro_schedule_claims"` with no `WHERE` and no `LIMIT`, then filtered in JavaScript twice per claim attempt (the fast path, and the re-read that separates "lost the race" from "the claims table is broken"), on every replica, for every schedule firing. A consumer measured ten concurrent copies of that scan holding every connection of a 15-slot pooler, with an SSR render behind them at **300 490 ms**. It is a primary-key lookup bounded to one row now (`id` *is* the claim key).
95
187
 
96
- Each plugin's docs page is now compiled into `agent-docs/plugins/<slug>.md` and the index points there first, README second in the generated template AND in the per-project file the seeder writes, which is the one users actually read. The per-plugin modules stay out of the TOPIC table on purpose: 42 rows all titled "Plugins" would be a worse index than the dead end it replaces.
97
- - **@voltro/database, @voltro/plugin-webhooks, @voltro/runtime** — **A `QueryDescriptor` without `order` took the SELECT compiler down, and the framework reported it as a database failure.**
188
+ The pool-acquire bound added in 0.32.0 turns that from a hang into an error; it does not stop the scan from filling the pool. Both are needed.
98
189
 
99
- {"msg":"mutation.webhooks.test failed: e.order is not iterable", "dbCauseChain":"TypeError: e.order is not iterable"}
190
+ `VOLTRO_SCHEDULE_CLAIMS_TTL_HOURS` also defaults to **24 hours** instead of 30 days. The 30-day default was copied from the framework's history tables (`_voltro_schedule_runs` and friends), and a claim row is a lock ledger — it answers a question about one firing instant and nothing reads yesterday's. At the 1 557 rows/hour that consumer measured, a 30-day window reaches ~1.1 million rows before the first one ages out. Raise it deliberately if you need to; the number to reason about is the longest a replica may be paused and still be trusted not to re-fire a bucket it already lost.
100
191
 
101
- `dbCauseChain` sent the reporter to the connection, the driver and the dialect. The database was never involved the SELECT was never built. Nothing they could write in app code reached it either: the descriptor is constructed inside `@voltro/plugin-webhooks`, so `ctx.webhooks.testTarget(...)` was unusable and the outgoing-webhook path could not be exercised end to end.
192
+ The boot announcement can now express an age under a day (`older than 1h`); it previously rounded every TTL to whole days, so an operator setting one hour read their own policy back as `older than 0d`.
193
+ - **@voltro/runtime** — A coordinated periodic task armed below one second now runs at the interval it was given.
102
194
 
103
- The field is DECLARED required, which is why this looked impossible. A descriptor is hand-built at ~130 sites and nearly all of them cast (`as never`) past the generic row type and a cast switches the required-field check off for the whole literal. Four sites shipped `order: undefined`.
195
+ `scheduleCoordinated` floors the wall clock to its `intervalMs` and races on that instant; the claim key truncated it to second precision. A task at 250 ms therefore produced four bucket instants per second that collapsed to one key — the first tick won and the other three were dropped as "lost the claim". Measured: 1 of 4.
104
196
 
105
- Fixed as ONE read path (`orderClausesOf`) rather than a guard at the reported line: `order` was dereferenced bare in TWELVE places across two compilers (three in `sqlCompiler.ts`, nine in `jsonEagerCompiler.ts`), so patching the crash site would have moved it rather than removed it. The four lying call sites are corrected, and a source scan fails on a fifth.
106
- - **@voltro/cli, @voltro/i18n** — **Dev SSR could build an `<I18nProvider>` the app's own `useT()` could not see, and the diagnosis we shipped pointed at a check that cannot observe the cause.**
197
+ `voltro.ai.inference` is armed at 250 ms and was dispatching once per second, on every multi-replica deployment, with nothing above `warn` to say so.
107
198
 
108
- `ssr.noExternal: ['react-intl']` without `@voltro/i18n` is itself an instance splitter: Vite externalizes a node_modules dependency in SSR, so Node — not Vite — resolves everything IT imports. An externalized `@voltro/i18n` therefore got Node's react-intl while every Vite-transformed module got the bundled one. Two instances, two contexts, and a provider invisible to a `useT()` with an error byte-identical to having no provider at all. Both are now bundled and deduped together in `voltro dev`, `voltro build` and `voltro start`, pinned by `i18nSingleInstance.test.ts` across all three configs.
199
+ This is the defect the coordinator's own comment describes at minute precision (6-field crons firing once a minute), one decimal place down; that comment was written before `scheduleCoordinated` existed, and `scheduleCoordinated` is the caller that goes below a second.
109
200
 
110
- The framework's diagnosis told readers to check with `pnpm ls -r --depth 10 @voltro/i18n react-intl`, and to report a framework bug if that showed one version of each. A consumer did exactly that — one version, one directory in the store, four fresh boots — and was still at a 500 on every SSR page. **The criterion was the defect: `pnpm ls` enumerates versions ON DISK and the failure is module INSTANCES in a process.** One file reached down two paths is two instances, and no package manager can see it. `@voltro/i18n` now registers each of its evaluations with the identity of the react-intl it is bound to, and the diagnosis reports the counts and the paths from the process where the render failed. When it counts one of each, it says the framework is at fault instead of making that conditional on a check that could not come back false.
201
+ Milliseconds join the claim key only when non-zero, so every cron key is byte-identical to before load-bearing during a rolling deploy, where old and new replicas computing different keys for one firing would both win and double-fire.
111
202
 
112
- The transferable half: an instruction to VERIFY has to be able to return false for the cause you are diagnosing. A check that cannot turns "I don't know" into "the framework is at fault", and the reader stops looking.
113
- - **@voltro/plugin-webhooks** — **A webhook routing filter's operators were ANDed in the documentation and first-match-wins in the matcher, so the documented range OVER-matched.**
203
+ Note the consequence for table size: a sub-second task now writes claim rows at its true rate. Bounded by `VOLTRO_SCHEDULE_CLAIMS_TTL_HOURS` (24 h), and `ai.tickIntervalMs` raises the interval if you want fewer.
114
204
 
115
- `{ gte: 100, lt: 1000 }` — the second example on the plugin's docs page — evaluated `gte` and returned, ignoring `lt`. A `total` of 5000 satisfied a filter declared as 100–1000. That is the worse direction of wrong: an under-matching filter delivers nothing and gets noticed, an over-matching one posts a partner data their own filter says they must not receive, and nothing anywhere reports it. Every present operator must now hold, and an operator object with no recognised key (`{ gtE: 5 }`, `{}`) matches nothing rather than everything.
205
+ ### Internal (no consumer-facing effect)
116
206
 
117
- `gt` / `gte` / `lt` / `lte` have always been TYPED `number | string` and compared only when both sides were numbers — so a string bound type-checked, subscribed, stored and matched nothing, which reads exactly like an event that never fired. Strings now compare lexicographically, which is what makes `{ 'payload.at': { lt: '2026-01-01' } }` work on an ISO timestamp. Mixed types still match nothing, deliberately: `'10' < 9` depends on which side JavaScript converts.
207
+ - **@voltro/cli** No separate consumer-facing note on purpose: this refines the per-needle breakdown described in the UNRELEASED 0.32.0 section, and that section which is what a reader will actually see carries the correction. Documenting it twice would describe one change as two.
118
208
 
119
- And the doc comment above the matcher called the equality form "v1" with operators as a future possibility, while `compareValue` implemented all six twenty lines below it. A consumer read that, refused a `resourceIds` filter for the whole life of the feature, and shipped a typed `ValidationError` telling their own users it was impossible. A comment describing an intention rather than the code under it is not a smaller doc — it is a wrong one, and more expensive than none.
209
+ The refinement: the per-needle counts OVERLAP and do not sum to the hit count (a row holding both a token and a secret is counted by both). The output line says so now, because two numbers printed under a total invite being added up, and a reader who adds them and gets more than the total loses confidence in the whole report.
@@ -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.31.0",
16
- "@voltro/cli": "0.31.0",
17
- "@voltro/database": "0.31.0",
18
- "@voltro/env": "0.31.0",
19
- "@voltro/protocol": "0.31.0",
20
- "@voltro/runtime": "0.31.0",
15
+ "@voltro/ai": "0.33.0",
16
+ "@voltro/cli": "0.33.0",
17
+ "@voltro/database": "0.33.0",
18
+ "@voltro/env": "0.33.0",
19
+ "@voltro/protocol": "0.33.0",
20
+ "@voltro/runtime": "0.33.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.31.0",
24
+ "@voltro/testing": "0.33.0",
25
25
  "typescript": "^6.0.3",
26
26
  "vitest": "^4.1.10"
27
27
  }
@@ -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.31.0",
17
- "@voltro/database": "0.31.0",
18
- "@voltro/env": "0.31.0",
19
- "@voltro/plugin-auth": "0.31.0",
20
- "@voltro/protocol": "0.31.0",
21
- "@voltro/runtime": "0.31.0",
22
- "@voltro/sql-postgres": "0.31.0",
16
+ "@voltro/cli": "0.33.0",
17
+ "@voltro/database": "0.33.0",
18
+ "@voltro/env": "0.33.0",
19
+ "@voltro/plugin-auth": "0.33.0",
20
+ "@voltro/protocol": "0.33.0",
21
+ "@voltro/runtime": "0.33.0",
22
+ "@voltro/sql-postgres": "0.33.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.31.0",
26
+ "@voltro/testing": "0.33.0",
27
27
  "typescript": "^6.0.3",
28
28
  "vitest": "^4.1.10"
29
29
  }
@@ -13,16 +13,16 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.31.0",
17
- "@voltro/database": "0.31.0",
18
- "@voltro/env": "0.31.0",
19
- "@voltro/plugin-multitenancy": "0.31.0",
20
- "@voltro/protocol": "0.31.0",
21
- "@voltro/runtime": "0.31.0",
16
+ "@voltro/cli": "0.33.0",
17
+ "@voltro/database": "0.33.0",
18
+ "@voltro/env": "0.33.0",
19
+ "@voltro/plugin-multitenancy": "0.33.0",
20
+ "@voltro/protocol": "0.33.0",
21
+ "@voltro/runtime": "0.33.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.31.0",
25
+ "@voltro/testing": "0.33.0",
26
26
  "typescript": "^6.0.3",
27
27
  "vitest": "^4.1.10"
28
28
  }
@@ -13,16 +13,16 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.31.0",
17
- "@voltro/database": "0.31.0",
18
- "@voltro/env": "0.31.0",
19
- "@voltro/plugin-deactivation": "0.31.0",
20
- "@voltro/protocol": "0.31.0",
21
- "@voltro/runtime": "0.31.0",
16
+ "@voltro/cli": "0.33.0",
17
+ "@voltro/database": "0.33.0",
18
+ "@voltro/env": "0.33.0",
19
+ "@voltro/plugin-deactivation": "0.33.0",
20
+ "@voltro/protocol": "0.33.0",
21
+ "@voltro/runtime": "0.33.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.31.0",
25
+ "@voltro/testing": "0.33.0",
26
26
  "typescript": "^6.0.3",
27
27
  "vitest": "^4.1.10"
28
28
  }
@@ -13,18 +13,18 @@
13
13
  "dependencies": {
14
14
  "@react-email/components": "^1.0.12",
15
15
  "@react-email/render": "^1.4.0",
16
- "@voltro/cli": "0.31.0",
17
- "@voltro/database": "0.31.0",
18
- "@voltro/env": "0.31.0",
19
- "@voltro/plugin-mail": "0.31.0",
20
- "@voltro/plugin-multitenancy": "0.31.0",
21
- "@voltro/protocol": "0.31.0",
22
- "@voltro/runtime": "0.31.0",
16
+ "@voltro/cli": "0.33.0",
17
+ "@voltro/database": "0.33.0",
18
+ "@voltro/env": "0.33.0",
19
+ "@voltro/plugin-mail": "0.33.0",
20
+ "@voltro/plugin-multitenancy": "0.33.0",
21
+ "@voltro/protocol": "0.33.0",
22
+ "@voltro/runtime": "0.33.0",
23
23
  "effect": "^3.22.0",
24
24
  "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.31.0",
27
+ "@voltro/testing": "0.33.0",
28
28
  "typescript": "^6.0.3",
29
29
  "vitest": "^4.1.10"
30
30
  }
@@ -13,18 +13,18 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.31.0",
17
- "@voltro/database": "0.31.0",
18
- "@voltro/env": "0.31.0",
19
- "@voltro/plugin-multitenancy": "0.31.0",
20
- "@voltro/plugin-storage": "0.31.0",
21
- "@voltro/protocol": "0.31.0",
22
- "@voltro/runtime": "0.31.0",
23
- "@voltro/sql-mysql": "0.31.0",
16
+ "@voltro/cli": "0.33.0",
17
+ "@voltro/database": "0.33.0",
18
+ "@voltro/env": "0.33.0",
19
+ "@voltro/plugin-multitenancy": "0.33.0",
20
+ "@voltro/plugin-storage": "0.33.0",
21
+ "@voltro/protocol": "0.33.0",
22
+ "@voltro/runtime": "0.33.0",
23
+ "@voltro/sql-mysql": "0.33.0",
24
24
  "effect": "^3.22.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.31.0",
27
+ "@voltro/testing": "0.33.0",
28
28
  "typescript": "^6.0.3",
29
29
  "vitest": "^4.1.10"
30
30
  }
@@ -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.31.0",
17
- "@voltro/database": "0.31.0",
18
- "@voltro/env": "0.31.0",
19
- "@voltro/plugin-multitenancy": "0.31.0",
20
- "@voltro/protocol": "0.31.0",
21
- "@voltro/runtime": "0.31.0",
22
- "@voltro/sql-sqlite": "0.31.0",
16
+ "@voltro/cli": "0.33.0",
17
+ "@voltro/database": "0.33.0",
18
+ "@voltro/env": "0.33.0",
19
+ "@voltro/plugin-multitenancy": "0.33.0",
20
+ "@voltro/protocol": "0.33.0",
21
+ "@voltro/runtime": "0.33.0",
22
+ "@voltro/sql-sqlite": "0.33.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.31.0",
26
+ "@voltro/testing": "0.33.0",
27
27
  "typescript": "^6.0.3",
28
28
  "vitest": "^4.1.10"
29
29
  }
@@ -11,17 +11,17 @@
11
11
  "test": "voltro test"
12
12
  },
13
13
  "dependencies": {
14
- "@voltro/cli": "0.31.0",
15
- "@voltro/database": "0.31.0",
16
- "@voltro/env": "0.31.0",
17
- "@voltro/plugin-multitenancy": "0.31.0",
18
- "@voltro/plugin-storage": "0.31.0",
19
- "@voltro/protocol": "0.31.0",
20
- "@voltro/runtime": "0.31.0",
14
+ "@voltro/cli": "0.33.0",
15
+ "@voltro/database": "0.33.0",
16
+ "@voltro/env": "0.33.0",
17
+ "@voltro/plugin-multitenancy": "0.33.0",
18
+ "@voltro/plugin-storage": "0.33.0",
19
+ "@voltro/protocol": "0.33.0",
20
+ "@voltro/runtime": "0.33.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.31.0",
24
+ "@voltro/testing": "0.33.0",
25
25
  "typescript": "^6.0.3",
26
26
  "vitest": "^4.1.10"
27
27
  }