@voltro/cli 0.41.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/apiBuild-B7NgSMhX.js +2 -0
  3. package/dist/{apiBuild-B_iH24qf.js → apiBuild-DF0f69Gf.js} +2 -2
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-DUdi6F_Q.js → build-LC_IJaHL.js} +7 -7
  6. package/dist/{checkCommand-B2RWrpWi.js → checkCommand-DBpTSK8d.js} +1 -1
  7. package/dist/{checkCommand-DWL2_PPR.js → checkCommand-DDxwJCja.js} +2 -2
  8. package/dist/{codegenCommand-DkjoJ30Q.js → codegenCommand-LjxfK4Am.js} +2 -2
  9. package/dist/{codemodRunner-sFuWDETH.js → codemodRunner-Cnnz4-I7.js} +19 -4
  10. package/dist/{commands-D0VR1yBB.js → commands-CbQcuWsE.js} +22 -22
  11. package/dist/{dashboardCommand-Bro7Jwts.js → dashboardCommand-CAHjCe_9.js} +3 -3
  12. package/dist/{dataCommand-OKC5FscP.js → dataCommand-CrqvbVOv.js} +298 -170
  13. package/dist/dataProfile-Cm0YVKSy.js +18 -0
  14. package/dist/{dbCommand-BHRamDsy.js → dbCommand-B237qI-j.js} +361 -361
  15. package/dist/dbCommand-BvQeKQOb.js +2 -0
  16. package/dist/{dev-D4l_YOzI.js → dev-B2rZfqxw.js} +2 -2
  17. package/dist/{dev-DuqBJC7g.js → dev-D0E5x2Ue.js} +1 -1
  18. package/dist/doctorCommand-CdsctSwg.js +2 -0
  19. package/dist/{doctorCommand-TmZTAepc.js → doctorCommand-Ct_3BHgd.js} +226 -216
  20. package/dist/{dormancyCommand-CUKhRmGG.js → dormancyCommand-D1DKnYce.js} +1 -1
  21. package/dist/{embeddingsCommand-Db-iQM3l.js → embeddingsCommand-1sZRBKlM.js} +1 -1
  22. package/dist/{envCommand-DQ_URZq9.js → envCommand-7qjlQgLD.js} +2 -2
  23. package/dist/{evolveCommand-CghRPrGO.js → evolveCommand-D5opB48I.js} +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/{infoCommand-BCs_ZFnl.js → infoCommand-Bs_8RU22.js} +1 -1
  26. package/dist/{migrate-DLtPv2_D.js → migrate-DN47p2PG.js} +1 -1
  27. package/dist/{runtimeTrace-jFKvOPBz.js → runtimeTrace-Wmi7V-wN.js} +1 -1
  28. package/dist/{sdkgen-COQqHB9V.js → sdkgen-DbTFGCjg.js} +1 -1
  29. package/dist/{serveCommand-Cba_M2Su.js → serveCommand-C8xzYCwa.js} +417 -411
  30. package/dist/serveCommand-CGNTXafv.js +2 -0
  31. package/dist/serveEntry.js +1 -1
  32. package/dist/{start-fifc0xpW.js → start-BAlGcjF5.js} +1 -1
  33. package/dist/{start-BjcuFME_.js → start-Ds3xyn8R.js} +1 -1
  34. package/dist/startEntry.js +1 -1
  35. package/dist/startup.d.ts +1 -1
  36. package/dist/updateCommand-BvkKlV7D.js +2 -0
  37. package/dist/{updateCommand-HZQVBU3T.js → updateCommand-CrI9B4d7.js} +1 -1
  38. package/dist/{webDev-Dk8lFUzf.js → webDev-101csg6Q.js} +2 -2
  39. package/dist/webDev-BLKBO34K.js +2 -0
  40. package/dist/{webhooksCommand-D7ZpH7w0.js → webhooksCommand-BXUP5YjW.js} +1 -1
  41. package/package.json +23 -17
  42. package/templates/AGENTS.md +1 -1
  43. package/templates/agent-docs/_index.md +1 -1
  44. package/templates/agent-docs/authentication.md +1 -1
  45. package/templates/agent-docs/cli.md +75 -8
  46. package/templates/agent-docs/data.md +46 -11
  47. package/templates/agent-docs/database/migrations.md +1 -1
  48. package/templates/agent-docs/internationalization.md +2 -2
  49. package/templates/agent-docs/observability.md +1 -1
  50. package/templates/agent-docs/plugins/openapi.md +1 -1
  51. package/templates/agent-docs/plugins/versioning.md +1 -1
  52. package/templates/agent-docs/plugins/webhooks.md +1 -1
  53. package/templates/agent-docs/plugins.md +1 -1
  54. package/templates/agent-docs/routing.md +1 -1
  55. package/templates/agent-docs/scheduling.md +1 -1
  56. package/templates/agent-docs/security.md +2 -2
  57. package/templates/agent-docs/templates/apibackends.md +1 -1
  58. package/templates/agent-docs/whats-new.md +54 -37
  59. package/templates/apps/api-ai/package.json +7 -7
  60. package/templates/apps/api-auth/package.json +8 -8
  61. package/templates/apps/api-backend/package.json +7 -7
  62. package/templates/apps/api-backend-deactivation/package.json +7 -7
  63. package/templates/apps/api-backend-mail/package.json +8 -8
  64. package/templates/apps/api-backend-mariadb/package.json +9 -9
  65. package/templates/apps/api-backend-sqlite/package.json +8 -8
  66. package/templates/apps/api-backend-storage/package.json +8 -8
  67. package/templates/apps/api-cms/package.json +10 -10
  68. package/templates/apps/api-collab/package.json +8 -8
  69. package/templates/apps/api-data-advanced/package.json +8 -8
  70. package/templates/apps/api-durable/package.json +8 -8
  71. package/templates/apps/api-feature-flags/package.json +9 -9
  72. package/templates/apps/api-governance/package.json +8 -8
  73. package/templates/apps/api-kv/package.json +8 -8
  74. package/templates/apps/api-moderation/package.json +8 -8
  75. package/templates/apps/api-observability/package.json +8 -8
  76. package/templates/apps/api-ratelimit/package.json +8 -8
  77. package/templates/apps/api-rbac/package.json +8 -8
  78. package/templates/apps/api-rest/package.json +7 -7
  79. package/templates/apps/api-saas/package.json +11 -11
  80. package/templates/apps/api-saas-starter/package.json +10 -10
  81. package/templates/apps/api-search/package.json +8 -8
  82. package/templates/apps/api-status/package.json +8 -8
  83. package/templates/apps/api-versioning/package.json +8 -8
  84. package/templates/apps/api-webhooks/package.json +9 -9
  85. package/templates/apps/changelog/package.json +6 -6
  86. package/templates/apps/edge-functions/package.json +2 -2
  87. package/templates/apps/frontend-admin/package.json +8 -8
  88. package/templates/apps/frontend-app/package.json +9 -9
  89. package/templates/apps/frontend-auth/package.json +8 -8
  90. package/templates/apps/frontend-blank/package.json +7 -7
  91. package/templates/apps/frontend-cms/package.json +9 -9
  92. package/templates/apps/frontend-collab/package.json +10 -10
  93. package/templates/apps/frontend-contact/package.json +7 -7
  94. package/templates/apps/frontend-dashboard/package.json +7 -7
  95. package/templates/apps/frontend-docs/package.json +7 -7
  96. package/templates/apps/frontend-i18n/package.json +6 -6
  97. package/templates/apps/frontend-landing/package.json +7 -7
  98. package/templates/apps/frontend-portal/package.json +8 -8
  99. package/templates/apps/frontend-saas/package.json +8 -8
  100. package/templates/apps/frontend-spa/package.json +7 -7
  101. package/templates/apps/frontend-ssr/package.json +7 -7
  102. package/templates/apps/frontend-ssr-api/package.json +8 -8
  103. package/templates/apps/frontend-static-blog/package.json +6 -6
  104. package/templates/apps/frontend-status/package.json +8 -8
  105. package/templates/apps/mobile-app/package.json +4 -4
  106. package/dist/apiBuild-RM5W5YXk.js +0 -2
  107. package/dist/dataProfile-dW-PsfLB.js +0 -15
  108. package/dist/dbCommand-C9iE1pd9.js +0 -2
  109. package/dist/doctorCommand-Bw3V4L1j.js +0 -2
  110. package/dist/serveCommand-DRbYK646.js +0 -2
  111. package/dist/updateCommand-B2alql9k.js +0 -2
  112. package/dist/webDev-D6QeXw3P.js +0 -2
@@ -1,4 +1,4 @@
1
- # What's new in 0.41.0
1
+ # What's new in 0.43.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
@@ -9,70 +9,87 @@ BREAKING entries name a codemod; run `voltro update` to apply it.
9
9
 
10
10
  ### ⚠ BREAKING
11
11
 
12
- - **@voltro/web, @voltro/cli** — `middleware.ts` exports `defineMiddleware(...)` (from `@voltro/web/middleware`) instead of a bare function, and each export carries its own `match`. Several middlewares per file are allowed; **at most one may match a given route**.
12
+ - **@voltro/data-transfer, @voltro/cli** — `DanglingReferenceError` is now `RowsRefusedError`, and it separates the rows that failed from the rows that failed because those rows did.
13
13
 
14
- Migration: the codemod wraps the existing default export. That is behaviour-preservingno `match` means every server-rendered route, which is what an unwrapped middleware did and its note explains how to replace a hand-written path gate with a `match`.
14
+ **The name.** The import raised it for EVERY row still refused after deferred-FK resolution, whatever the reason a NOT NULL violation, a duplicate key, a value the database computes for itself. The tag named ONE possible cause and put it where a reader looks first, so any other refusal arrived mislabelled. The new name states the outcome; each row's `reason` states the cause, which is where a cause can honestly be claimed.
15
15
 
16
- **Why it was worth a break.** A hand-written `if (!req.pathname.startsWith('/app')) return` is invisible: nothing can tell you a middleware runs nowhere, or that two of them claim one route. `match` puts it where both the boot and `voltro doctor` can read it.
16
+ **The split.** A row is `derived` when one of its reference columns holds the primary key of another row that also failed in this run: it could not have landed whatever it contained, so its reason describes the parent's problem. The relation is transitive, decided from the DATA (the failed ids against the reference-typed values), so no schema knowledge is needed. `primaryCount` is the number an operator acts on, `rows` lists primary failures FIRST so the cap never spends its budget on consequences, and the CLI leads with both numbers:
17
17
 
18
- **The matcher speaks ROUTES, not URL patterns** — `under` / `routes` / `except`, validated against the app's own route patterns. A path matching no route refuses the boot instead of silently never firing. This is the deliberate difference from the `'/((?!api|_next/static|…).*)'` shape: our hook runs after route matching, so an app has never needed to know its own asset layout, and non-page requests are reachable only by asking (`assets: true`) — where, note, there is no render, so only `setCookies` takes effect.
18
+ ```
19
+ import refused 300 row(s), of which 1 are the actual failures — the rest could
20
+ not land because a row they reference did not.
21
+ teams t1 foreign key teams_ibfk_1: the referenced row does not exist [1452]
22
+ … and 299 row(s) behind them. Fix the 1 above and re-run; they resolve with
23
+ their parents.
24
+ ```
19
25
 
20
- An overlap refuses the boot and names both middlewares plus the route. Declaration order is not a semantic, "most specific wins" silently drops the broader hook, and merging needs a per-field rule nobody remembers so two hooks writing one `authorization` header is a refusal, not a resolution.
26
+ In an FK-dense bundle one refused parent takes its whole subtree with it, so the length of a flat list says how connected the data is, not how many problems there are and the single row that explains all of them sits somewhere in the middle of it.
21
27
 
22
- **The web bundle budget moved UP, and the split is worth stating** because only one half is a cost the framework imposes:
28
+ The codemod rewrites the import and every use of the symbol. It does NOT rewrite a tag STRING (`Effect.catchTag('DanglingReferenceError', …)`, `err._tag === '…'`) — change those to `'RowsRefusedError'` — and the payload gained `primaryCount` plus a `derived` flag per row.
23
29
 
24
- | measured | before | after | | --- | --- | --- | | first load | 184 955 B | 185 309 B (**+354**) | | lazy route chunks | 3 502 B | 4 415 B (+913) |
30
+ ### Fixed
25
31
 
26
- The **+354 B of first load is the real price** — one `serverContext` chunk, 0.2 KB gz, which every app now carries whether or not it declares a middleware. That is the number to argue with, and it leaves 6.7 KB of headroom under the ceiling.
32
+ - **@voltro/cli** — `voltro doctor`'s `subject-write-no-guard` rule reads the DESCRIPTOR before it reports. It looked only at the executor, and the access decision is not declared there.
27
33
 
28
- The +913 B is NOT a per-route regression: the fixture gained four routes (`exact`, `exact/[id]`, `mw`, `mw/skip`) to exercise the feature end to end, at 0.1–0.2 KB gz each, which accounts for the growth without remainder. Re-pinned with `--update` rather than by hand, so the `slackFloor` keeps ratcheting a ceiling nobody lowers again silently permits re-inflating to the old number.
34
+ Every form of access decision the framework has `internal: true`, `guards: [...]`, `openAccess:` is declared on the DESCRIPTOR. So an app that declares them properly got a finding for each one, and on a codebase whose procedures are mostly `internal: true` the rule fires on essentially all of them and is wrong essentially every time.
29
35
 
30
- ### Fixed
36
+ That is worse than a rule that finds nothing: it is the longest line in the report and it reads like a security finding, so it teaches the reader to skim the place a real finding would have appeared.
37
+
38
+ The premise was structurally impossible for most of them, and the framework says so itself: `security.defaultDeny` refuses at boot any wire-exposed procedure declaring neither `guards:` nor `openAccess:`. So "an anonymous caller reaches the write" can only be true of an `openAccess` procedure. That is what the rule looks for now — plus the handler check it always honoured, plus an ownership comparison against `subject.id`, which is the refusal the rule says is missing. Where no descriptor can be read, it says nothing: the claim is about a declaration, and a finding whose evidence was never opened is the failure mode this fixes.
39
+
40
+ The `use:` line changed with it. It recommended `.guard(requireScope('…'))`; the shipped guide teaches declarative `guards:` and calls a hand-written per-executor scope check the thing `guards:` exists to delete. A rule may not recommend the shape the guide argues against.
41
+ - **@voltro/database, @voltro/data-transfer** — A `voltro data` transfer no longer carries GENERATED column values, and no longer loses every row that has one.
42
+
43
+ The export wrote the computed values into the bundle and the import sent them back in the `INSERT` column list. MariaDB refuses that for any value except NULL (`1906: The value specified for generated column 'x' in table 't' has been ignored`); postgres refuses a non-DEFAULT value outright. So the transfer failed per ROW, not per table, and only for the rows whose generated value was non-NULL.
44
+
45
+ That selectivity is the dangerous part. `.uniqueActive()` lowers to exactly such a column on mysql/mariadb — a STORED generated column holding the key while the row is live and NULL once it is soft-deleted — so the refused rows are the LIVE ones and the accepted ones are the tombstones. A table can come out looking like "a few rows failed" or empty, depending only on how many of its rows are deleted, and any table with a foreign key into it fails behind it.
31
46
 
32
- - **@voltro/data-transfer, @voltro/cli** `voltro data export` could not export a table whose primary key is not named `id`, and one of its two failure modes reported success.
47
+ Both ends are fixed from one source of truth: **introspection now reports generated columns on every dialect** (`generatedAs`, from `information_schema.generation_expression` on mysql/mariadb, `is_generated` on postgres, `PRAGMA table_xinfo`'s hidden flag on sqlite, `sys.computed_columns` on mssql). It was declared-side only before invisible to the planner, which does not compare it, and load-bearing for anything that writes rows back.
33
48
 
34
- The keyset column was `columns.find(c => c.type === 'id')?.name ?? 'id'`, and `type: 'id'` is tagged only on a column that is BOTH the single-column primary key AND literally named `id` identically in all four dialect introspectors. So any introspected table with another PK name was ordered by a column that does not exist. It now comes from the real primary key (the synthesised `<table>_pkey` index), with the declared `id()` column still winning where there is one.
49
+ The exporter omits those columns, values and all. The importer strips them from every incoming row using the TARGET's snapshot, because a bundle already written still carries them and a file on disk is data, read where it is.
35
50
 
36
- **A composite or absent primary key is now REFUSED**, not silently ordered by the first column: keyset pagination on a non-unique order splits equal values across page boundaries, so rows are dropped or duplicated into a bundle that reports success. Bounded exports are recoverable; a quietly short backup is discovered at the restore.
51
+ **`voltro serve`'s admin export/import needed a second fix, and without it this one reached only the direct transport.** The running instance hands those handlers `declaredSnapshot(tables)` — no dialect — and `.uniqueActive()` lowers to a generated column only when the snapshot knows the engine. So over `--target api` the snapshot described a schema with no generated columns while the database had several, and the export wrote their values back into the bundle exactly as before. It passes the dialect now (the variable was already on the next line).
37
52
 
38
- **A requested table missing from the schema is refused too.** `scope: { kind: 'tables' }` used to drop unknown names, so a run that explicitly named a table wrote `"tables": []` and printed `export complete` with exit 0. `kind: 'all'` over an empty database is still a legal empty export the asymmetry is deliberate: a named table is an expectation.
53
+ `generatedAs` is also kept OUT of the schema fingerprint. Introspection can read the fact back but not a comparable value the engine returns its own normalisation of the expression, never the declared spelling so hashing it would make declared and live disagree permanently: a schema nobody touched reporting a changed declaration on every boot, and a transfer target that matches its bundle exactly refused as drifted.
39
54
 
40
- **Failure reasons survive.** `String(e?.message ?? e)` produced `"write table failed: "` with nothing after the colon — `??` falls back on null/undefined, and an Effect `TaggedError` carries an empty-string `message`. Every catch site in the exporter now reports tag, message or cause.
55
+ Covered by a live mariadb→mariadb and mysql→mysql round trip over a `.uniqueActive()` table with live and soft-deleted rows, asserting the target recomputed the value rather than that the row merely arrived.
56
+ - **@voltro/sql-mysql** — On mysql/mariadb, a write REJECTED by the database through `insertIgnore` was reported as a conflict when the caller was not inside a transaction — and never reached the caller as a typed `ConstraintViolation` at all.
41
57
 
42
- **New: `voltro data export --exclude a,b`** everything except these, resolved against the live table list. It is the escape hatch the refusals above require; without it a single unkeyable table would block a whole-database export. An unknown name is refused for the same reason. Direct target only (the expansion needs the live table list), and it expands to an explicit `tables` scope, so the manifest records what was actually exported.
58
+ Two causes, both measured against live MySQL 8.4 and MariaDB 11.
43
59
 
44
- Reported with a reduced repro, a four-way variation over PK TYPES that ruled type out, and two disproved hypotheses. The affected tables include `@effect/cluster`'s own (`cluster_locks`, `cluster_migrations`), so no app running workflows could take a whole-database export.
45
- - **@voltro/database, @voltro/runtime, @voltro/cli** — `.encrypted()` had three writers and two encodings. The store wrote `encrypt(JSON.stringify(v))`; `encryptField` — the documented raw-SQL escape hatch — and `voltro db encrypt-column` wrote `encrypt(v)`. All three produce the same `enc:v1:` envelope and nothing distinguished them, so a value written by one and read by another either threw with the wrong diagnosis or came back subtly wrong (`decryptField` handed back the JSON encoding verbatim, quotes and all, raising nothing).
60
+ **The connection.** `INSERT IGNORE` demotes every error to a warning, so the store reads `SHOW WARNINGS` to tell a rejection from a conflict. That describes the last statement on a CONNECTION, and outside a transaction every statement acquires its own from the pool — so the read was unattributable and came back empty. The rejected write then surfaced as "the insert was skipped as a conflict, but no existing row matches conflictColumns […] the constraint that fired is unknown", whose enumeration lists only conflict causes. A rejection described as a conflict is the exact sentence this path was fixed once already for producing; it survived on the path that had no transaction to read on. The store now pins one connection for the whole decision — the same pinning `insertRecoverAutoId` does for `LAST_INSERT_ID()`.
46
61
 
47
- There is one encoding for every WRITE now, and every READ resolves BOTH forms so **no data has to be rewritten and nothing is blocked**. That second half is the point: the old form is already on staging and production disks, and a fix that needs the rows rewritten before the app works is an outage with a migration attached.
62
+ **The classification.** The store swallowed the driver's error and raised a prose one of its own, so `classifyConstraintViolation` had nothing to read: this was the one write path where the typed error could not fire, while every other one produced it. The warning IS the driver's payload — `INSERT IGNORE` only changed how it was delivered — so it is handed on in the shape the driver would have thrown. A rejection now arrives as the same `ConstraintViolation { kind: 'foreignKey', }` a plain `insert` produces. Nothing new crosses the wire: the classifier extracts the constraint NAME as a delimited group, never the sentence.
48
63
 
49
- Reading two forms is deterministic, not a heuristic. After decrypting, a parse failure is the raw form; a parse to a STRING is the JSON form; a parse to a non-string depends on the column's declared type (a text column cannot hold a number, so `12345` is a raw string that parsed by accident). The one case nothing can separate — a raw secret whose literal text is `"abc"`, quotes included is stated in the code rather than hidden.
64
+ This is the default `voltro data import` path (`--mode append`, `--on-conflict skip`, without `--atomic`), so a row rejected by a foreign key was reported to the operator as a conflict with an unknown cause.
50
65
 
51
- **`voltro db encrypt-column` verified itself against the wrong decoder.** It wrote the raw form and checked it with `cipher.decrypt` a decoder nothing reads these columns with — so it reported success over columns the app could not read. It round-trips through `decodeFieldValue` now, the same function the store calls. A self-check against a decoder the runtime does not use is not a weaker check; it is a second opinion from the same mistake.
66
+ `constraintViolation.integration.test.ts` now asserts the `insertIgnore` seam per dialect against live postgres 17, MySQL 8.4, MariaDB 11 and SQL Server 2022. It was the missing assertion behind a claim derived from the wiring the classification does sit on all ten write paths, which is not the same as a classifiable error arriving on all ten.
67
+ - **@voltro/protocol, @voltro/runtime** — The framework's store errors — `ConstraintViolation`, `TenantScopeViolation`, `TenantRowNotFound`, `ServerOnlyColumnWrite`, `TableValidationFailed`, `StoreOperationFailed` — are exported from `@voltro/protocol` and can therefore be declared in a descriptor's `error:` union. They could not be.
52
68
 
53
- The command also NORMALISES rows in the old encoding as it goes (reported separately from the ones it encrypts), so an operator does not write a script per column. It skips anything ambiguous and anything it cannot decrypt.
69
+ They lived in `@voltro/runtime`, which reaches `node:child_process`, `node:http` and `node:crypto`. A descriptor is loaded VALUE-LEVEL by the web client (the `RpcClient` needs every procedure's Schema), so a descriptor importing from there is refused at boot by the browser-safety guard — correctly. The typed half of these errors was therefore unreachable: the docs told you to declare them, and the boot said no.
54
70
 
55
- **The width pre-flight measured the wrong thing after the encoding changed.** It sized the ciphertext from the PLAINTEXT's byte length while the cipher is handed the JSON encoding two characters more at minimum, and more for every escape. Measured on a real MariaDB: a 63-byte value in a `varchar(135)` passed the check and the UPDATE answered `ER_DATA_TOO_LONG`, which is the failure that check exists to prevent, mid-column with the rest already converted. It measures the encoded length now, and the refusal says "encodes to" rather than "is" so an operator measuring their own column finds the number it names.
71
+ What that leaves is the untyped half only. The error still arrives as an `InternalError` carrying a readable sentence, so telling `foreignKey` ("the row you picked is gone") from `foreignKeyInUse` ("this row is still referenced") two different messages for the user means parsing that sentence. Over a set of generated delete mutations that is a string comparison per procedure, which is the thing the typed error exists to delete.
56
72
 
57
- **Two dialect defects, both found by running the command against real servers.** SQL Server reports `-1` for `NVARCHAR(MAX)` — its spelling of unbounded — and the pre-flight read it as a one-character column, so it refused the widest column the dialect has and printed `declared as -1` at the operator. And SQLITE has no `information_schema` at all: the shared catalog query died there with `Failed to prepare statement` and no statement attached, on a dialect the command claims to support. It uses `pragma_table_info` now, reporting no length because sqlite enforces none.
73
+ `@voltro/runtime` re-exports all six, so server code is unchanged. The classes have no server dependency of any kind the file imports `Schema` from `effect` and nothing else, and `browserSafetyGuard.test.ts` now pins a descriptor that declares one, so moving them back reads as a boot failure rather than as a passing rename.
74
+ - **@voltro/database, @voltro/sql-mysql** — A blocked `alter-column-type` told every dialect to write a postgres cast.
58
75
 
59
- Measured end to end on postgres, mysql, mariadb, mssql and sqlite: a table holding plaintext, the old encoding and the current encoding side by side converts, every row decodes back to its original value, a re-run writes nothing, and a wrong key refuses with exit 1.
76
+ The refusal is correct a bare type change may not be value-preserving, so it is refused until acknowledged. Its `fix:` line was not:
60
77
 
61
- **Backups and restores were never affected and now say so.** `voltro data export` reads through the raw dialect store, so ciphertext travels verbatim in either encoding — pinned by a test, because a future change that wrapped that store would put plaintext credentials in a bundle.
62
- - **@voltro/protocol, @voltro/cli** Three findings from one consumer round, all of the same shape: something the framework knows and does not say.
78
+ ```
79
+ acknowledge it on the column: `.narrowedFrom('json', { using: 'meta::text' })`
80
+ ```
63
81
 
64
- **A decode failure on a GUARDED procedure now says the guard did not run.** The payload decodes before the handler, so a guard on a procedure with a malformed payload never gets the chance to refuse. A consumer auditing a guard called one with an incomplete payload, got a decode error instead of a `ScopeError`, and concluded the guard was not applied the wrong conclusion in the dangerous direction. The title now carries `(guarded the guard did NOT run: the payload failed to decode first, so this says nothing about access)`. It discloses nothing new: that a procedure is guarded is already visible to anyone who sends a VALID payload. An `openAccess:` declaration is not an enforced guard and gets no such sentence — `hasEnforcedGuard` is the one predicate, read by both the label and the wire error union, because two copies of that rule would disagree invisibly.
82
+ `USING <expr>` is postgres syntax and a postgres capability. The mysql arm of the applier emits `MODIFY COLUMN`, the mssql arm `ALTER COLUMN`, and sqlite rebuilds the table none of them can carry a cast expression and none of them reads `using`. So an operator on any other engine was handed a line to paste into their schema containing syntax their database has never seen, inside an argument that is discarded. A refusal is read as an instruction, and the more carefully it is read the more thoroughly a wrong one is followed.
65
83
 
66
- **`middleware.ts`'s `httpOnly` default is documented at the field, and warned about.** It defaults to `HttpOnly`, which is wrong for a session cookie a browser SDK reads back: Supabase's `createBrowserClient` reads `document.cookie`, so a forgotten `httpOnly: false` gives the browser a session it cannot see the SSR render is perfect and the user is signed out at the first client-side call. The consumer only avoided shipping it because their probes already set the flag. `voltro dev` warns once per cookie when a session-shaped name is written with no `httpOnly` decision; an explicit decision either way silences it, because warning on a decision is how a diagnostic becomes noise.
84
+ The fix line is dialect-aware now: postgres keeps the `using` half, everything else gets `.narrowedFrom('<type>')` plus the fact that the engine converts in place — because a refusal that offers no expressible fix reads as "the framework cannot do this at all".
67
85
 
68
- **`voltro dev` restarts when `middleware.ts` changes.** It is loaded once per boot, that is documented, and a consumer read it and still lost an afternoon: they sabotaged the middleware, saw no change, and concluded it was not wired in an environment where everything else hot-reloads. It now restarts through the same respawn a hard-restart field in `app.config.ts` uses, extracted so there is one copy of the `execArgv` inheritance and the signal forwarding.
69
- - **@voltro/cli** — `middleware.ts` now produces ONE view of the request that every downstream reader takes. Previously only `buildLoaderQuery` saw the hook's result, while the loader context (`ctx.headers`), the SSR request snapshot (`useServerRequest()`) and the locale resolver kept reading the raw request — four readers, two answers, within eighty lines of one function.
86
+ Covered on live MySQL and MariaDB by both halves at once: what the refusal SAYS, and that following it applies AND converges with the row intact. A message test alone would keep passing over a broken apply; a convergence test alone is what let the wrong message survive this long.
70
87
 
71
- The consequence was worse than an inconsistency: a hook that renews purely via `setCookies` no `headers` at all, which is the normal shape for a cookie-session IdP and the reason the response half exists moved nothing for the render that ran it. The rpc call still sent the old `Cookie` header, because a renewed cookie only reached the browser.
88
+ Also on the same path: `upsert` with a PARTIAL row (one omitting a NOT NULL column that has no default) failed on MariaDB and succeeded on MySQL. The native `INSERT ON DUPLICATE KEY UPDATE` validates its insert half even when only the update half runs, so the row was rejected although the target existed and only needed patching. A partial row takes the lookup path on both engines now; the single-statement form still covers the complete-row case, which is what a data transfer and every generated CRUD write send.
89
+ - **@voltro/sql-mysql** — On MariaDB, `upsert` could write a DIFFERENT row than the one it was given and report success.
72
90
 
73
- `setCookies` is applied to the cookie jar before the render, the `Cookie` header is rebuilt from that jar (an explicit `cookie` in the hook's own `headers` still wins), and `maxAge <= 0` deletes, so a hook that signs someone out renders them signed out. Both SSR boot paths shadow the raw headers out of scope after the hook runs, so a new reader added below is correct without knowing any of this.
74
- - **@voltro/cli** — `voltro start` dropped `middleware.ts`'s `Set-Cookie` on **streamed** responses — which is the arm a plain `renderMode: 'ssr'` page takes, so it was the common case. The hook renewed the session server-side, the render used the fresh value, and the browser kept the consumed one. Against an IdP that rotates refresh tokens and detects reuse, that is worse than not renewing at all.
91
+ `INSERT ON DUPLICATE KEY UPDATE` fires on ANY unique key, not on the one named in `conflictColumns`. So an incoming row whose (say) `email` already belonged to a different primary key updated THAT row instead — and since `id` is excluded from the SET list, the row the caller handed over was never written. Measured on a live server: the call returned a row, the target kept the old id, the new row was absent, and the existing row had silently taken the incoming values. A bulk transfer on top of that prints `import complete` over missing data, which is the worst failure shape available: there is nothing to investigate.
75
92
 
76
- The cause is worth stating because it read as handled: a streamed response hands the socket to `stream(res)` and the caller never looks at the returned `headers`, so the `withCookies(...)` wrapper on that arm was dead code — sitting under a comment promising the cookies were written on every arm. The cookies now travel with the headers `streamSsrResponse` itself writes, and the dead wrapper is gone.
93
+ The two engines of the family disagreed here, which is part of why it survived. The non-RETURNING path (MySQL) looks the row up by `conflictColumns` first, does not find it, and lets the INSERT fail with a duplicate-key error. Loud was always right.
77
94
 
78
- Found by booting real `voltro dev` and `voltro start` servers against a fixture and reading the response. Every unit test was green throughout, and the render's own HTML was correctonly the wire was wrong.
95
+ Both refuse now, with a message naming both ids and the fact that the collision was on a constraint other than the one named. The check runs inside a transaction a short one of the store's own when the caller is not already in one so the wrong row is rolled back rather than reported after the fact: detecting this afterwards still leaves someone else's row overwritten.
@@ -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.41.0",
16
- "@voltro/cli": "0.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/protocol": "0.41.0",
20
- "@voltro/runtime": "0.41.0",
15
+ "@voltro/ai": "0.43.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/protocol": "0.43.0",
20
+ "@voltro/runtime": "0.43.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.41.0",
24
+ "@voltro/testing": "0.43.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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/plugin-auth": "0.41.0",
20
- "@voltro/protocol": "0.41.0",
21
- "@voltro/runtime": "0.41.0",
22
- "@voltro/sql-postgres": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/plugin-auth": "0.43.0",
20
+ "@voltro/protocol": "0.43.0",
21
+ "@voltro/runtime": "0.43.0",
22
+ "@voltro/sql-postgres": "0.43.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.41.0",
26
+ "@voltro/testing": "0.43.0",
27
27
  "typescript": "^6.0.3",
28
28
  "@vitest/coverage-v8": "^4.1.10",
29
29
  "vitest": "^4.1.10"
@@ -16,16 +16,16 @@
16
16
  "dependencies": {
17
17
  "@effect/platform": "^0.97.0",
18
18
  "@effect/rpc": "^0.76.0",
19
- "@voltro/cli": "0.41.0",
20
- "@voltro/database": "0.41.0",
21
- "@voltro/env": "0.41.0",
22
- "@voltro/plugin-multitenancy": "0.41.0",
23
- "@voltro/protocol": "0.41.0",
24
- "@voltro/runtime": "0.41.0",
19
+ "@voltro/cli": "0.43.0",
20
+ "@voltro/database": "0.43.0",
21
+ "@voltro/env": "0.43.0",
22
+ "@voltro/plugin-multitenancy": "0.43.0",
23
+ "@voltro/protocol": "0.43.0",
24
+ "@voltro/runtime": "0.43.0",
25
25
  "effect": "^3.22.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@voltro/testing": "0.41.0",
28
+ "@voltro/testing": "0.43.0",
29
29
  "typescript": "^6.0.3",
30
30
  "@vitest/coverage-v8": "^4.1.10",
31
31
  "vitest": "^4.1.10"
@@ -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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/plugin-deactivation": "0.41.0",
20
- "@voltro/protocol": "0.41.0",
21
- "@voltro/runtime": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/plugin-deactivation": "0.43.0",
20
+ "@voltro/protocol": "0.43.0",
21
+ "@voltro/runtime": "0.43.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.41.0",
25
+ "@voltro/testing": "0.43.0",
26
26
  "typescript": "^6.0.3",
27
27
  "@vitest/coverage-v8": "^4.1.10",
28
28
  "vitest": "^4.1.10"
@@ -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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/plugin-mail": "0.41.0",
20
- "@voltro/plugin-multitenancy": "0.41.0",
21
- "@voltro/protocol": "0.41.0",
22
- "@voltro/runtime": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/plugin-mail": "0.43.0",
20
+ "@voltro/plugin-multitenancy": "0.43.0",
21
+ "@voltro/protocol": "0.43.0",
22
+ "@voltro/runtime": "0.43.0",
23
23
  "effect": "^3.22.0",
24
24
  "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.41.0",
27
+ "@voltro/testing": "0.43.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "vitest": "^4.1.10"
@@ -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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/plugin-multitenancy": "0.41.0",
20
- "@voltro/plugin-storage": "0.41.0",
21
- "@voltro/protocol": "0.41.0",
22
- "@voltro/runtime": "0.41.0",
23
- "@voltro/sql-mysql": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/plugin-multitenancy": "0.43.0",
20
+ "@voltro/plugin-storage": "0.43.0",
21
+ "@voltro/protocol": "0.43.0",
22
+ "@voltro/runtime": "0.43.0",
23
+ "@voltro/sql-mysql": "0.43.0",
24
24
  "effect": "^3.22.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.41.0",
27
+ "@voltro/testing": "0.43.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/plugin-multitenancy": "0.41.0",
20
- "@voltro/protocol": "0.41.0",
21
- "@voltro/runtime": "0.41.0",
22
- "@voltro/sql-sqlite": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/plugin-multitenancy": "0.43.0",
20
+ "@voltro/protocol": "0.43.0",
21
+ "@voltro/runtime": "0.43.0",
22
+ "@voltro/sql-sqlite": "0.43.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.41.0",
26
+ "@voltro/testing": "0.43.0",
27
27
  "typescript": "^6.0.3",
28
28
  "@vitest/coverage-v8": "^4.1.10",
29
29
  "vitest": "^4.1.10"
@@ -11,17 +11,17 @@
11
11
  "test": "voltro test"
12
12
  },
13
13
  "dependencies": {
14
- "@voltro/cli": "0.41.0",
15
- "@voltro/database": "0.41.0",
16
- "@voltro/env": "0.41.0",
17
- "@voltro/plugin-multitenancy": "0.41.0",
18
- "@voltro/plugin-storage": "0.41.0",
19
- "@voltro/protocol": "0.41.0",
20
- "@voltro/runtime": "0.41.0",
14
+ "@voltro/cli": "0.43.0",
15
+ "@voltro/database": "0.43.0",
16
+ "@voltro/env": "0.43.0",
17
+ "@voltro/plugin-multitenancy": "0.43.0",
18
+ "@voltro/plugin-storage": "0.43.0",
19
+ "@voltro/protocol": "0.43.0",
20
+ "@voltro/runtime": "0.43.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.41.0",
24
+ "@voltro/testing": "0.43.0",
25
25
  "typescript": "^6.0.3",
26
26
  "@vitest/coverage-v8": "^4.1.10",
27
27
  "vitest": "^4.1.10"
@@ -13,19 +13,19 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.41.0",
17
- "@voltro/cms": "0.41.0",
18
- "@voltro/database": "0.41.0",
19
- "@voltro/env": "0.41.0",
20
- "@voltro/plugin-auth": "0.41.0",
21
- "@voltro/plugin-multitenancy": "0.41.0",
22
- "@voltro/protocol": "0.41.0",
23
- "@voltro/runtime": "0.41.0",
24
- "@voltro/sql-postgres": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/cms": "0.43.0",
18
+ "@voltro/database": "0.43.0",
19
+ "@voltro/env": "0.43.0",
20
+ "@voltro/plugin-auth": "0.43.0",
21
+ "@voltro/plugin-multitenancy": "0.43.0",
22
+ "@voltro/protocol": "0.43.0",
23
+ "@voltro/runtime": "0.43.0",
24
+ "@voltro/sql-postgres": "0.43.0",
25
25
  "effect": "^3.22.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@voltro/testing": "0.41.0",
28
+ "@voltro/testing": "0.43.0",
29
29
  "typescript": "^6.0.3",
30
30
  "@vitest/coverage-v8": "^4.1.10",
31
31
  "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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/local-first": "0.41.0",
20
- "@voltro/plugin-multitenancy": "0.41.0",
21
- "@voltro/protocol": "0.41.0",
22
- "@voltro/runtime": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/local-first": "0.43.0",
20
+ "@voltro/plugin-multitenancy": "0.43.0",
21
+ "@voltro/protocol": "0.43.0",
22
+ "@voltro/runtime": "0.43.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.41.0",
26
+ "@voltro/testing": "0.43.0",
27
27
  "typescript": "^6.0.3",
28
28
  "@vitest/coverage-v8": "^4.1.10",
29
29
  "vitest": "^4.1.10"
@@ -12,17 +12,17 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.97.0",
14
14
  "@effect/rpc": "^0.76.0",
15
- "@voltro/cli": "0.41.0",
16
- "@voltro/database": "0.41.0",
17
- "@voltro/env": "0.41.0",
18
- "@voltro/plugin-governance": "0.41.0",
19
- "@voltro/plugin-multitenancy": "0.41.0",
20
- "@voltro/protocol": "0.41.0",
21
- "@voltro/runtime": "0.41.0",
15
+ "@voltro/cli": "0.43.0",
16
+ "@voltro/database": "0.43.0",
17
+ "@voltro/env": "0.43.0",
18
+ "@voltro/plugin-governance": "0.43.0",
19
+ "@voltro/plugin-multitenancy": "0.43.0",
20
+ "@voltro/protocol": "0.43.0",
21
+ "@voltro/runtime": "0.43.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.41.0",
25
+ "@voltro/testing": "0.43.0",
26
26
  "typescript": "^6.0.3",
27
27
  "@vitest/coverage-v8": "^4.1.10",
28
28
  "vitest": "^4.1.10"
@@ -12,17 +12,17 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.97.0",
14
14
  "@effect/rpc": "^0.76.0",
15
- "@voltro/cli": "0.41.0",
16
- "@voltro/database": "0.41.0",
17
- "@voltro/env": "0.41.0",
18
- "@voltro/plugin-multitenancy": "0.41.0",
19
- "@voltro/protocol": "0.41.0",
20
- "@voltro/runtime": "0.41.0",
21
- "@voltro/workflow": "0.41.0",
15
+ "@voltro/cli": "0.43.0",
16
+ "@voltro/database": "0.43.0",
17
+ "@voltro/env": "0.43.0",
18
+ "@voltro/plugin-multitenancy": "0.43.0",
19
+ "@voltro/protocol": "0.43.0",
20
+ "@voltro/runtime": "0.43.0",
21
+ "@voltro/workflow": "0.43.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.41.0",
25
+ "@voltro/testing": "0.43.0",
26
26
  "typescript": "^6.0.3",
27
27
  "@vitest/coverage-v8": "^4.1.10",
28
28
  "vitest": "^4.1.10"
@@ -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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/plugin-flags": "0.41.0",
20
- "@voltro/plugin-multitenancy": "0.41.0",
21
- "@voltro/protocol": "0.41.0",
22
- "@voltro/runtime": "0.41.0",
23
- "@voltro/sql-postgres": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/plugin-flags": "0.43.0",
20
+ "@voltro/plugin-multitenancy": "0.43.0",
21
+ "@voltro/protocol": "0.43.0",
22
+ "@voltro/runtime": "0.43.0",
23
+ "@voltro/sql-postgres": "0.43.0",
24
24
  "effect": "^3.22.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.41.0",
27
+ "@voltro/testing": "0.43.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "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.41.0",
17
- "@voltro/database": "0.41.0",
18
- "@voltro/env": "0.41.0",
19
- "@voltro/plugin-audit": "0.41.0",
20
- "@voltro/plugin-governance": "0.41.0",
21
- "@voltro/protocol": "0.41.0",
22
- "@voltro/runtime": "0.41.0",
16
+ "@voltro/cli": "0.43.0",
17
+ "@voltro/database": "0.43.0",
18
+ "@voltro/env": "0.43.0",
19
+ "@voltro/plugin-audit": "0.43.0",
20
+ "@voltro/plugin-governance": "0.43.0",
21
+ "@voltro/protocol": "0.43.0",
22
+ "@voltro/runtime": "0.43.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.41.0",
26
+ "@voltro/testing": "0.43.0",
27
27
  "typescript": "^6.0.3",
28
28
  "@vitest/coverage-v8": "^4.1.10",
29
29
  "vitest": "^4.1.10"