@voltro/cli 0.30.2 → 0.32.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 (120) hide show
  1. package/CHANGELOG.md +278 -0
  2. package/dist/{apiBuild-BUS1RfPi.js → apiBuild-C4uEbs3s.js} +2 -2
  3. package/dist/apiBuild-OJEjtwcn.js +2 -0
  4. package/dist/appGraph-CvQCte0z.js +2 -0
  5. package/dist/appGraph-gQ_6GkQQ.js +110 -0
  6. package/dist/bin.js +3 -3
  7. package/dist/checkCommand-CwMrzAgV.js +2 -0
  8. package/dist/{checkCommand-D_wdOFhw.js → checkCommand-dm7OHtPt.js} +401 -505
  9. package/dist/{commands-BgGAUcku.js → commands-C0nEePif.js} +2601 -2498
  10. package/dist/dbCommand-By__Ev0R.js +2 -0
  11. package/dist/{dbCommand-CbPRCD1x.js → dbCommand-ifOMafuG.js} +112 -84
  12. package/dist/{dev-GAOIot9G.js → dev-5ficNnvF.js} +1843 -1832
  13. package/dist/{dev-tPcOmjPJ.js → dev-rc3fwPSZ.js} +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/{inspectMetrics-BM1vrsga.js → inspectMetrics-BqO4E9G0.js} +1008 -719
  16. package/dist/{sdkgen-BV4fFucu.js → sdkgen-B_5mHQS2.js} +1 -1
  17. package/dist/{serveCommand-ClqXCX1y.js → serveCommand-PxMmn96o.js} +454 -454
  18. package/dist/serveEntry.js +2 -2
  19. package/dist/{start-CTnNvKlk.js → start-D1-8eKrO.js} +204 -199
  20. package/dist/startEntry.js +2 -2
  21. package/package.json +417 -17
  22. package/templates/AGENTS.md +43 -43
  23. package/templates/agent-docs/_index.md +43 -43
  24. package/templates/agent-docs/_manifest.json +84 -42
  25. package/templates/agent-docs/database/migrations.md +14 -1
  26. package/templates/agent-docs/internationalization.md +29 -0
  27. package/templates/agent-docs/plugins/ai-flows.md +148 -0
  28. package/templates/agent-docs/plugins/analytics-postgres.md +90 -0
  29. package/templates/agent-docs/plugins/atlassian.md +378 -0
  30. package/templates/agent-docs/plugins/audit.md +348 -0
  31. package/templates/agent-docs/plugins/auth-auth0.md +92 -0
  32. package/templates/agent-docs/plugins/auth-clerk.md +90 -0
  33. package/templates/agent-docs/plugins/auth-kinde.md +88 -0
  34. package/templates/agent-docs/plugins/auth-oidc.md +95 -0
  35. package/templates/agent-docs/plugins/auth-supabase.md +103 -0
  36. package/templates/agent-docs/plugins/auth-workos.md +105 -0
  37. package/templates/agent-docs/plugins/auth.md +227 -0
  38. package/templates/agent-docs/plugins/billing.md +324 -0
  39. package/templates/agent-docs/plugins/broadcast.md +144 -0
  40. package/templates/agent-docs/plugins/cdc-out.md +116 -0
  41. package/templates/agent-docs/plugins/clickhouse.md +91 -0
  42. package/templates/agent-docs/plugins/datadog.md +105 -0
  43. package/templates/agent-docs/plugins/deactivation.md +114 -0
  44. package/templates/agent-docs/plugins/duckdb.md +82 -0
  45. package/templates/agent-docs/plugins/flags.md +146 -0
  46. package/templates/agent-docs/plugins/governance.md +143 -0
  47. package/templates/agent-docs/plugins/licensing.md +74 -0
  48. package/templates/agent-docs/plugins/logship.md +56 -0
  49. package/templates/agent-docs/plugins/mail.md +334 -0
  50. package/templates/agent-docs/plugins/moderation.md +65 -0
  51. package/templates/agent-docs/plugins/multitenancy.md +99 -0
  52. package/templates/agent-docs/plugins/notifications.md +252 -0
  53. package/templates/agent-docs/plugins/openapi.md +94 -0
  54. package/templates/agent-docs/plugins/postgis.md +140 -0
  55. package/templates/agent-docs/plugins/posthog.md +91 -0
  56. package/templates/agent-docs/plugins/presence.md +115 -0
  57. package/templates/agent-docs/plugins/prometheus.md +124 -0
  58. package/templates/agent-docs/plugins/ratelimit.md +274 -0
  59. package/templates/agent-docs/plugins/rbac.md +270 -0
  60. package/templates/agent-docs/plugins/scim.md +63 -0
  61. package/templates/agent-docs/plugins/search.md +116 -0
  62. package/templates/agent-docs/plugins/sentry.md +192 -0
  63. package/templates/agent-docs/plugins/soft-delete.md +72 -0
  64. package/templates/agent-docs/plugins/sso-saml.md +132 -0
  65. package/templates/agent-docs/plugins/storage.md +709 -0
  66. package/templates/agent-docs/plugins/tinybird.md +92 -0
  67. package/templates/agent-docs/plugins/versioning.md +226 -0
  68. package/templates/agent-docs/plugins/webhooks.md +585 -0
  69. package/templates/agent-docs/security.md +21 -1
  70. package/templates/agent-docs/whats-new.md +137 -22
  71. package/templates/apps/api-ai/package.json +7 -7
  72. package/templates/apps/api-auth/package.json +8 -8
  73. package/templates/apps/api-backend/package.json +7 -7
  74. package/templates/apps/api-backend-deactivation/package.json +7 -7
  75. package/templates/apps/api-backend-mail/package.json +8 -8
  76. package/templates/apps/api-backend-mariadb/package.json +9 -9
  77. package/templates/apps/api-backend-sqlite/package.json +8 -8
  78. package/templates/apps/api-backend-storage/package.json +8 -8
  79. package/templates/apps/api-cms/package.json +10 -10
  80. package/templates/apps/api-collab/package.json +8 -8
  81. package/templates/apps/api-data-advanced/package.json +8 -8
  82. package/templates/apps/api-durable/package.json +8 -8
  83. package/templates/apps/api-feature-flags/package.json +9 -9
  84. package/templates/apps/api-governance/package.json +8 -8
  85. package/templates/apps/api-kv/package.json +8 -8
  86. package/templates/apps/api-moderation/package.json +8 -8
  87. package/templates/apps/api-observability/package.json +8 -8
  88. package/templates/apps/api-ratelimit/package.json +8 -8
  89. package/templates/apps/api-rbac/package.json +8 -8
  90. package/templates/apps/api-rest/package.json +7 -7
  91. package/templates/apps/api-saas/package.json +11 -11
  92. package/templates/apps/api-saas-starter/package.json +10 -10
  93. package/templates/apps/api-search/package.json +8 -8
  94. package/templates/apps/api-status/package.json +8 -8
  95. package/templates/apps/api-versioning/package.json +8 -8
  96. package/templates/apps/api-webhooks/package.json +9 -9
  97. package/templates/apps/changelog/package.json +6 -6
  98. package/templates/apps/edge-functions/package.json +2 -2
  99. package/templates/apps/frontend-admin/package.json +8 -8
  100. package/templates/apps/frontend-app/package.json +8 -8
  101. package/templates/apps/frontend-auth/package.json +8 -8
  102. package/templates/apps/frontend-blank/package.json +7 -7
  103. package/templates/apps/frontend-cms/package.json +9 -9
  104. package/templates/apps/frontend-collab/package.json +9 -9
  105. package/templates/apps/frontend-contact/package.json +7 -7
  106. package/templates/apps/frontend-dashboard/package.json +7 -7
  107. package/templates/apps/frontend-docs/package.json +7 -7
  108. package/templates/apps/frontend-i18n/package.json +6 -6
  109. package/templates/apps/frontend-landing/package.json +7 -7
  110. package/templates/apps/frontend-portal/package.json +8 -8
  111. package/templates/apps/frontend-saas/package.json +8 -8
  112. package/templates/apps/frontend-spa/package.json +7 -7
  113. package/templates/apps/frontend-ssr/package.json +7 -7
  114. package/templates/apps/frontend-ssr-api/package.json +8 -8
  115. package/templates/apps/frontend-static-blog/package.json +6 -6
  116. package/templates/apps/frontend-status/package.json +8 -8
  117. package/templates/apps/mobile-app/package.json +2 -2
  118. package/dist/apiBuild-DMlxvL8h.js +0 -2
  119. package/dist/checkCommand-CARZ36Tr.js +0 -2
  120. package/dist/dbCommand-Ht47LrV-.js +0 -2
@@ -1,4 +1,4 @@
1
- # What's new in 0.30.2
1
+ # What's new in 0.32.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,46 +7,161 @@ 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/plugin-audit** — **`auditPlugin` stored what a call RETURNED, verbatim, with no option to reach it. `redactOutcome` now exists and defaults to `'all'`.**
13
+
14
+ Found on the first run of `voltro db scan-credentials` after we widened its columns, against a real database:
15
+
16
+ ```
17
+ ✗ _voltro_audit_log.outcome — 9 of 263 row(s) match a credential-shaped key
18
+ ```
19
+
20
+ Four were `webhooks.create` rows carrying a live 64-character `signingSecret` in full. The reporter had BOTH existing options on — `redactInput: 'all'`, `redactSubject: 'metadata'` — and there was no third to reach this field with.
21
+
22
+ **The option that existed covers the field these calls leave empty.** `redactInput`'s own docstring names "an API key at issuance" as its motivating case, and for a credential-ISSUING call the secret is never in the input:
23
+
24
+ ```ts
25
+ apiKeys.createPersonalApiKey({ name, scopes }) // input: nothing sensitive
26
+ → { keyValue: '<the plaintext key>' } // outcome: the whole point
27
+ webhooks.create({ url, subscribedEvents }) // input: nothing sensitive
28
+ → { signingSecret: '<live secret>' } // outcome: returned once, by design
29
+ ```
30
+
31
+ MIGRATION: `outcome.value` and `outcome.error` become `{ __redacted: 'all' }`. The error's `_tag` SURVIVES — a trail recording "something failed" without saying what is not a trail, and a tag is a schema-declared discriminant that structurally cannot be a secret. `kind` and `durationMs` are untouched, the caller still receives the real result, and a `record` predicate still sees the live outcome. Set `redactOutcome: 'none'` to keep the old behaviour.
32
+
33
+ Also documented: a FUNCTION sink gets neither the `_voltro_audit_log` table nor the retention sweep — both are gated on `sink` being the literal `'datastore'`. A function that redacts and delegates to `dataStoreAuditSink` still writes rows on a database where the table exists, while creating it nowhere and arming the TTL nowhere. It works where you tested it and fails on the next fresh database. `redactOutcome` removes the reason to reach for that composition; the docstring now names the cliff for anyone who reaches for it anyway.
34
+
35
+ **`voltro update` carries you across this** — codemod `0.32.0/01_audit-redacts-outcome`.
36
+
37
+ ### Added
38
+
39
+ - **@voltro/cli** — **`voltro doctor` said nothing when the unknown-scope rule did not run, which read exactly like a clean result.**
40
+
41
+ The rule needs a declared scope vocabulary — with `@voltro/plugin-rbac` that is the union of its `roles` map. An app doing RBAC without the plugin (roles as plain literal arrays in `lib/teamRoles.ts`) publishes none, so the rule stays quiet. A consumer confirmed it empirically: doctor reports **0** unknown-scope findings on the tree that contains the exact bug the rule was built for — `webhooks.test` guarded by `webhooks:test` while no role grants it. Silence read as coverage.
42
+
43
+ Two things now:
44
+
45
+ - **The dormant state SAYS it is dormant**, in the human report and in `--json` (`evaluated: false`, which used to be an empty findings array indistinguishable from a clean app). It ends with the sentence that matters: this section is not a clean bill of health. - **An app can point the rule at its own vocabulary**, which the consumer proposed and which doctor now reads:
46
+
47
+ ```ts
48
+ // app.config.ts
49
+ doctor: { scopeVocabulary: './lib/teamRoles.ts#ALL_TEAM_SCOPES' }
50
+ ```
51
+
52
+ Deliberately a plain read of an exported string array. Anything cleverer (evaluating a roles map, following a builder) fails differently per app and lands back at "quiet for reasons you cannot see".
53
+
10
54
  ### Changed
11
55
 
12
- - **@voltro/plugin-notifications** — **The docs argued for `resolveSubjectId` using a rationale its own source had retracted.** The page justified the option with a consumer's measurement — rows belonging to people with no auth user, which employee-keying would "reach" and `subject.id` would not — and cited their number. That team then reversed the decision and wrote the correction themselves: *the subject is whatever signs in; if your addressing unit is not that, you are addressing something nobody can read.* An inbox belongs to whoever can OPEN it, and only an account can. Keying by employee never delivered those rows — it made them look addressed, and charged a translation on every read path and every push.
56
+ - **@voltro/cli** — **`voltro db scan-credentials` now says its name set is a heuristic, so a clean run stops reading as an all-clear.**
13
57
 
14
- So the framework was teaching, in a permanent document, a lesson that the one team who had lived it had withdrawn. That is worse than an out-of-date example: it is an argument with a measurement attached, which is the most persuasive kind and here the wrong one.
58
+ Reported with the case that proves it: on the same table, in the same column, the scan found `signingSecret` and missed `keyValue` the plaintext API key an `apiKeys.*` mutation returns. `keyValue` matches none of the needles and never will; no name list covers every convention an app can invent.
15
59
 
16
- The page and the option's docstring now lead with the caution, say which question to ask first ("can the thing I am addressing sign in?"), point at translating once at the SENDING seam, and keep `resolveSubjectId` recommended for the case it was actually built for an app whose sign-in identity genuinely IS its own id, which is a different situation from a second identity some accounts happen to map to.
60
+ Not a defect, a heuristic being a heuristic and the reporter's framing is the fix: *"one line in the output saying the name set IS a heuristic would stop a clean run reading as an all-clear."* The report ends with what a clean result actually means: no credential-SHAPED key found, which is not the same as no credential.
61
+ - **@voltro/cli** — **`voltro db scan-credentials` reported a hit count without saying which key matched, under advice that could not be carried out or argued with.**
17
62
 
18
- No behaviour change; the option works exactly as before.
63
+ The output was `69 of 149 row(s) match a credential-shaped key` followed by `Purge them AND rotate the credentials`. Purge what? Rotate which credential? And a column mentioning the word `token` in prose reads identically to one holding a live one — the reporter had both kinds in the same column and no way to separate them from the output.
64
+
65
+ Each hit line now names the needles and their row counts — which OVERLAP and
66
+ do not sum to the hit count, because a row holding both a token and a secret is
67
+ counted by both, and two numbers printed under a total otherwise invite being
68
+ added up:
69
+
70
+ ```
71
+ ✗ _voltro_audit_log.outcome — 69 of 149 row(s) match a credential-shaped key
72
+ matched (rows per needle, may overlap): token (61), secret (12)
73
+ ```
74
+
75
+ One extra COUNT per needle, taken only for a target that already matched — so the cost lands exactly where somebody is about to do work and nowhere else.
19
76
 
20
77
  ### Fixed
21
78
 
22
- - **@voltro/cli** — **`voltro doctor --write-authz-allowlist` deleted the list it exists to protect.** Measured by a consumer against their real file, one run, nothing else in between: 2 363 lines / 2 294 debt / 5 reviewed / 2 comment blocks before, and 31 lines / 0 debt / 5 reviewed / 0 comment blocks after. `doctor` went from exit 0 to exit 1 reporting 2 294 × "no access check" — executors that had been recorded as debt and were now recorded nowhere. They restored from backup.
79
+ - **@voltro/cli** — **`voltro update --dry-run` could never preview a jump's codemods the manifest it reads has never been published.** Checked against the registry rather than inferred: `@voltro/cli` at 0.25.0, 0.29.0, 0.30.2 and 0.31.0 all ship no `voltro` field at all, while the repo's own `package.json` carries 65 entries.
80
+
81
+ `scripts/prepare-publish.mjs`'s `cleanManifest` builds a fresh publish manifest field by field rather than deleting from a copy — an allowlist by construction — so a new top-level key is dropped in silence and nothing downstream mentions it.
82
+
83
+ **What this did NOT cost, stated because the alarming reading is the wrong one:** no user has ever missed a codemod that should have run. The manifest feeds the PREVIEW only; the actual run happens after the install, out of the target CLI's own registry, which is present by then. And the missing case was already handled loudly — the preview printed *"could not preview … This is NOT the same as 'no codemods'"* rather than an confident "none". An honest "I could not look" for four releases, where the feature was built to look.
84
+
85
+ The fix carries `voltro` through, and the guard that goes with it is the part worth keeping: `codemodManifest.test.ts` asserts the REPO's package.json carries every codemod, and it was green on every one of those releases. A source-reading guard cannot see what the publish pipeline does downstream of it. So the assertion is made against the STAGED file, read back off disk after it is written, and it runs inside `prepare-publish` itself — which the gate's `Pack + verify` step already invokes, so it needs no separate wiring.
86
+
87
+ Red-verified by removing the one-line fix and re-running: the staged manifest comes back with NO entries against the source's 65, and publishing aborts.
88
+ - **@voltro/cli** — **`voltro dev` read the COMPILED config, so editing `app.config.ts` had no effect in any app that had ever run `voltro build`.**
89
+
90
+ `loadConfig` preferred `.framework/dist/server/appConfig.js` whenever it existed — a build output, i.e. whatever the config said the last time somebody built. The comment on that branch said the `.ts` source is the fallback "for dev", but the condition was `existsSync`, which cannot know which command is running.
91
+
92
+ Measured on the fixture: `app.config.ts` set to `locales: ['de','en'], defaultLocale: 'de'`, `voltro dev` booted, SSR response `<html lang="en">` — the value from a build hours earlier. Neither the declared default nor the first declared locale reached the render.
93
+
94
+ Found while reproducing a consumer's report that a declared `locales:` did not affect `<html lang>` in dev. An app that has never built is unaffected, which is why this survived.
95
+
96
+ **And the second half of the same report: dev never negotiated `Accept-Language` in resolver-only mode.** `makeSsrI18nResolver` returns `() => undefined` for an empty wrap set — right for the PROVIDER, wrong for the locale — so the caller fell through to `cookie ?? defaultLocale`. `voltro build` had already grown the resolver-only runtime; dev had not. The same dev/build drift as the release before, one release later, in the opposite direction.
97
+
98
+ Measured after the fix, all three signals: no headers → the declared default; `Accept-Language: en` → `en`; cookie beats `Accept-Language`. `ssrIntlDiagnostic` is gated on a REAL provider now, so resolver-only mode does not claim one was supplied.
99
+ - **@voltro/cli** — **The duplicate-version check read the pnpm store, so it fired for everyone on the release right after they upgraded.**
100
+
101
+ Last release taught it to see copies a walk from the app root cannot reach — a second version pulled in by a SIBLING workspace package. It did that by reading the pnpm virtual store directly, and the store also holds every version pnpm ever unpacked. On a real tree, one release later:
102
+
103
+ ```
104
+ @voltro/cli — 0.29.0, 0.30.0, 0.30.1, 0.30.2, 0.31.0
105
+ ```
106
+
107
+ …while EVERY `@voltro/*` in that workspace was linked at exactly 0.31.0. The extras were residue: nothing links them, `pnpm store prune` removes them, they cannot be loaded. The reporter's summary is the right one — *"the rule moved from blind-to-the-real-case to noisy-on-every-upgrade, and both endings are the same: the reader stops looking."* This ending is the worse of the two, because it fires for everyone at exactly the moment they are reading the output.
108
+
109
+ **A package manager's cache is not a statement about the program.** It counts what is LINKED now: one `package.json` per (workspace package × framework package), derived from `pnpm-workspace.yaml` or the conventional `packages/*` / `apps/*` layout. That is cheaper than the store walk it replaces, free of residue, and still finds the sibling case it was widened for.
110
+ - **@voltro/cli** — **The declared-event scan searched for the event NAME, so it missed every app following the typed path — 19 findings, 0 real.**
111
+
112
+ `emit` is typed `(event: string | OutgoingEventDescriptor<P> | DeclaredEventLike, …)`, and the DESCRIPTOR overload is the type-safe one — the path the schema-decode behaviour rewards. An app on it writes `emitEvent(ctx, apiKeyCreated, …)` and never repeats the name string anywhere near the emit.
113
+
114
+ So the rule found apps that emit by string literal and missed apps that emit by descriptor, which is backwards: the second group is the one doing it properly. The ADVISORY caveat we shipped was exact and useless — it said "an emit through a variable will read as missing here", and the variable IS the idiomatic call.
115
+
116
+ It resolves the binding now. The exported const and the `name:` are in ONE statement (`export const apiKeyCreated = defineEvent({ name: 'apiKey.created' … })`), so this is a local read of the declaring file rather than a resolution. Both spellings count as an emit. The caveat is narrowed to what actually remains invisible: a name assembled at runtime.
117
+
118
+ Reported with a working reference implementation attached, which is where the three lines came from.
119
+ - **@voltro/sql-postgres** — **A query with no free pooled connection waited forever, with no error and no log line.**
120
+
121
+ `new pg.Pool({...})` was constructed without `connectionTimeoutMillis`, and node-postgres defaults it to `0` — wait indefinitely. Reported as: *"ein Query ohne freie Verbindung wartet unbegrenzt, ohne Fehler und ohne Logzeile."*
122
+
123
+ The distinction that makes this worth a default: `statementTimeoutMs` bounds a query the SERVER is running; nothing bounded a query the CLIENT had not sent yet. Those are the two halves of "a request is stuck", and only one was covered.
124
+
125
+ Defaults to 10 s (`DEFAULT_ACQUIRE_TIMEOUT_MS`), overridable per connection with `acquireTimeoutMs`; `0` restores the driver's unbounded wait. A bounded failure is more useful than an unbounded wait even when the pool would have freed up: it names the pool as the cause at the moment it IS the cause, instead of surfacing as unexplained latency somewhere with no connection information in it.
126
+
127
+ **Also documented, from the same report:** with `changeStrategy: 'cdc'` a process needs `maxConnections + 1`. The LISTEN consumer cannot use a pooled connection, so `@effect/sql-pg` opens a standalone `new Pg.Client(pool.options)` that is outside `max` and outside every number derived from it. A per-pod budget built on `maxConnections` is short by exactly one, which surfaces as the last pod of a rollout failing to connect rather than as a pool warning.
128
+ - **@voltro/cli** — **Shipping `whats-new.md` for the right release depended on a human remembering a step between two other steps.**
129
+
130
+ The module is generated from the top CHANGELOG section, and the command that writes a new one (`changelog-release.mjs --release`) did not regenerate it. So whether the published guide described the release you just installed came down to whether someone ran the generator in the window between the roll and the publish.
131
+
132
+ Measured across the published tarballs rather than asserted:
133
+
134
+ | `@voltro/cli` | ships | |---|---| | 0.28.0 | `# What's new in 0.27.0` | | 0.29.0 – 0.31.0 | each names its own version |
23
135
 
24
- The writer built the file from the scan's CURRENT `unchecked` set, and an executor already in the allowlist is classified `allowlisted`, not `unchecked` so it appears in neither input. A first write looks perfect; the SECOND one empties the file. That hole predates the debt/reviewed split (the old writer was `formatAllowlist(res.unchecked.map(f => f.tag))`, identical shape). Rescuing `reviewed` and not debt did not cause the loss but INVERTED it: the five explained lines survived and the 2 294 unexplained ones did not, which is backwards. A reviewed line at least names a human who can be asked again; a lost debt line names nobody. The reporter's sentence is the one to keep**the debt is the part you must not lose.**
136
+ So it has gone wrong once in the last six, not every timeand the four that are right are right because a human did the step, which is exactly the property being removed here. One in six is not a small number for a module whose whole job is *"read this FIRST when a task touches an area you have not worked in recently"*: on that release it was the one piece of the shipped guide guaranteed to be wrong about the version the reader had just installed. And it fails in the direction that reads as fine a real version, described correctly, just not theirs.
25
137
 
26
- The writer is **additive and byte-preserving** now. The existing file is kept verbatim entries, comments, grouping, order and only tags it does not already contain are appended under a marker saying nothing above it was touched. Removal is no longer a capability of this command; it is a hand edit, or it happens on its own when an executor gains a guard and its line stops mattering. The reporter's comment blocks survive as a consequence of the file not being rebuilt, not as a special case.
138
+ The roll regenerates now, in the same command, rather than as a checklist line: a step a human must remember between two others is the step that gets skipped on the release nobody is watching. A regeneration failure is loud and non-fatal, and `check-whats-new-version.mjs` (which already gates this in static-checks) stays as the backstop for the paths that bypass the roll.
27
139
 
28
- The message changed too, because the old one was true and told nobody: `wrote 0 debt tag(s)` becomes `+0 new, 2299 kept, 0 removed`, and a no-op says so. `removed` is reported although it is structurally always zero a success line has to name the quantity that changed.
140
+ **If you are looking at a stale `whats-new.md` in your own project, this is probably not the cause.** The per-project copy is seeded once and never overwritten on boot — it refreshes only on `voltro agents-md --force`. That file being behind is the seeder's documented behaviour, not this defect, and no framework release fixes it for you.
141
+ - **@voltro/cli** — **The retention sweep was armed, announced at every boot, and never ran in a process that restarted more often than every five minutes.**
29
142
 
30
- Also from the same report: a credential-shaped column that is ALSO `.unique()` is now its own finding (`plaintext-secret-lookup-key`) rather than being told to use `.encrypted()`. Encryption and equality lookup are mutually exclusive unless the cipher is deterministic, so the ordinary advice would make the column unfindable; the new advice names the hash-as-lookup-key shape the framework's own `_voltro_api_keys.hashedKey` uses, and the deterministic-cipher trade. Requested by the reporter, who hit it on two of five flagged columns.
31
- - **@voltro/cli** — **The three items the previous round left open, closed.**
143
+ It was `setInterval(sweepAll, 5 * 60_000)` with no initial run, so the first sweep was always five minutes away. `voltro dev` restarts on every file save, and at least one consumer runs `voltro dev` as their deployment: their `_voltro_schedule_claims` reached **86 214 rows / 33 MB** with the policy registered, and the boot printing `retention: N policy(ies) armed rows older than the TTL are DELETED` every time.
32
144
 
33
- **An `app.config.ts` that cannot be imported silently produced an app with NO plugins — and a schema that proposes dropping every plugin table.** A consumer measured 517 tables becoming 506 when they added ONE import, with six `matches no declared table` warnings and a `pluginRef` FATAL naming a plugin that had nothing to do with the cause. The reader was `catch { return [] }`. That is not a smaller schema, it is a wrong one: plugins contribute their tables through `extendSchema.tables`, so zero plugins means every one of those tables reads as undeclared, and the differ plans a DROP. `dev.ts` carries a long comment about this exact class after it cost a release — written beside `loadApiConfigDiagnosed`, while this sibling reader kept the swallow, which is the argument for one reader rather than two. It throws `AppConfigLoadError` now, naming the consequence and the fact that the failure ran at config-EVALUATION time; `voltro check` reports it and continues with an explicit "this report is INCOMPLETE" rather than a silent empty list. The reporter's own caution is carried into the code: they measured the IMPORT and the CALL, not the library, so this is not about any one package — any module doing something non-trivial when first evaluated reaches it.
145
+ That is this file's own lesson one turn further in. We fixed "a standing delete that never introduces itself" and shipped a standing delete that introduces itself and then does not run.
34
146
 
35
- **`locales:` no longer forces you to adopt the framework's i18n.** Declaring `locales: ['de','en']` demanded `src/locales/<code>.ts` and killed the boot with `Failed to resolve import "../src/locales/de"`, so an app with its own i18n stack had to leave the option out and then got `<html lang="en">` and `locale: 'en'` handed to every page's `meta`, for every visitor, in a German-language app. The parameter was not merely useless there, it was misleading: trusting it would have turned every German tab English after hydration. The two decisions are separate now. `locales:` declares the languages (`<html lang>`, the resolved locale, `meta.locale`); the PRESENCE of `src/locales/*` decides whether `<I18nProvider>` is wired. An app that ships catalogs is unchanged. An app that does not gets a boot line saying which mode it is in — silently not wiring a provider would have replaced one surprise with another.
147
+ The first sweep now fires 30 s after boot, then on the interval. The delay is a compromise with the failures on either side: zero would put a multi-table DELETE in front of the first request of every boot, five minutes is what produced the report. The timer is `unref`'d, so it never holds a process open on its own.
148
+ - **@voltro/cli** — **`voltro dev` reported `engine: "in-memory"` while running a cluster engine, and a consumer built a diagnosis on it.**
36
149
 
37
- **The `raw-fetch` advisory no longer reads sharper than its evidence.** It led with "no SSRF guard", flatly. A consumer opened all 13 of their call sites and measured that every host was a compile-time constant, the two dynamic URLs were built from a constant base, and every user-supplied URL already went through their own wrapper "the remaining value is traceparent + retry + http.allowHosts, not security". Counted against this repo: 132 raw `fetch(` sites, 64 with a literal URL. So the smell now states what is true of every site (the framework client is not being used) and scopes the SSRF half to where the URL is not a constant. Being sharper than the evidence has a specific cost: it teaches the reader to skim the one line that would have named the genuinely user-supplied URL.
38
- - **@voltro/cli, @voltro/web, @voltro/database, @voltro/plugin-notifications** — **Seven findings a consumer had carried for two to four releases, all measured by them, all closed.**
150
+ The line derived its label from `store === 'postgres' ? 'cluster-postgres' : 'in-memory'`so mariadb, mysql, sqlite and mssql all printed `in-memory` while the block directly above had just built `cluster-sql` for exactly those stores. The boot therefore printed two lines that contradicted each other:
39
151
 
40
- **No page could set its SSR `<title>`.** The generated shell bakes `<title>{app name}</title>`, and the render's head was APPENDED before `</head>` — so a response carried two title elements, and `document.title` is the FIRST per the HTML spec. The app name won on every server-rendered page; the real title appeared only after hydration. Everything that reads HTML without executing it — link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document — saw the app name. There was no app-side workaround: `.framework/index.html` is regenerated every boot and its title comes from `name`, which is one value per app. **FIVE call sites** spliced a head by hand (`ssrShell`, the static prerender and the SPA shell in `build.ts`, and two in `start.ts`); the fifth was found by the guard written for the first four. One `mergeHeadInjection` now, which replaces the shell's title when the render brought one. Verified in a real chromium against a real `voltro dev`: exactly one `<title>`, and it is the page's.
152
+ ```
153
+ workflow engine: cluster-sql, dialect=mariadb
154
+ workflow engine selected · engine: "in-memory"
155
+ ```
41
156
 
42
- **`voltro db scan-credentials` died on postgres the default dialect, against a framework table.** It asked for a column named `subject`; `_voltro_row_history` has `subjectId`, `actor` and `scope`. Two defects, and the second is the one to keep: only the `COUNT(*)` probe was inside the try, so a missing COLUMN escaped as an unhandled error — jumping clean over this module's own "a missing target reports as missing" promise and over `scanExitCode`'s exit-2-for-a-vacuous-run. Both defaults and both guards are fixed; all three subject-shaped columns are scanned, an unavailable column reports as unavailable, and a missing table is said ONCE rather than once per column. Verified against live postgres: a planted credential in `actor` exits 1, an empty schema exits 2 with "NOTHING WAS SCANNED".
157
+ A consumer read the secondlater, more definitive-sounding and concluded that `voltro dev` runs workflows on a different engine than a deployment does. That is a fair reading of a line that is simply false, and it sent them down the wrong half of an investigation into why their outgoing webhooks delivered in dev and produced zero rows under `voltro serve`.
43
158
 
44
- **`doctor` described constrained executors as unconstrained.** An app that registers `setRowFilter` had all four of its filtered executors reported as "nothing constraining WHICH row" — for handlers that are, measured, already narrowed to the caller's own rows. It cannot be downgraded to a pass (the filter is a function from table to predicate; nothing static can say whether it covers a given table), but the wording pushed the reader toward the allowlist, where the line later reads as "checked and accepted" while meaning "the tool could not see it". The finding now names the row filter and what to verify, and the summary says once that the scan is blind there.
159
+ The label is now decided where the engine is chosen. A log line is an assertion the framework makes about itself, and this one had no reader that could catch it: it is not typed against the layer it describes, and nothing asserted the pair agreed.
45
160
 
46
- **A standing DELETE never introduced itself.** Retention is registered by plugins the app never wrote a line about, with defaults in months, and nothing announced it at boota consumer lost 1 944 freshly-migrated rows to a 180-day default, then got it wrong a second time by setting the env var in a running pod rather than a file. One line per policy now: the table, the age, the column it is measured on, whether it is conditional, and the variable that changes it.
161
+ **Both boot paths also report WHICH workflow entity types they registered.** `API listening workflowWorkers: 0` counts app-exported worker LAYERS an advanced surface almost no app usesand reads as "nothing consumes durable work here", which is the question the consumer could then answer from no other line. A delivery that fails with `Entity type 'Workflow/voltro.deliverWebhook' not registered` and one that never starts look identical from outside; this separates them before anyone reproduces anything.
47
162
 
48
- **The notification inbox had no index for its own two read paths.** It declared `byInboxSubject(subjectId)` while the plugin itself issues `subjectId = ? ORDER BY createdAt DESC LIMIT ?` and `subjectId = ? AND readAt IS NULL`. Both are declared now, the unread one PARTIAL — 3 of 2 475 rows were unread in their fullest inbox.
163
+ ### Internal (no consumer-facing effect)
49
164
 
50
- **Four notification routes were served and unreachable.** `archive`, `unarchive`, `markUnread` and `markAllRead` were registered as routes and missing from `notificationsRpcClientImports`, whose comment said "kept in lockstep with the exports". A comment is not a lockstep; `rpcSurfaceLockstep.test.ts` counts both sides.
165
+ - **@voltro/database** The count of `descriptor.order` read sites is ASSERTED rather than stated: the prose said ELEVEN and there are twelve (three in `sqlCompiler.ts`, nine in `jsonEagerCompiler.ts`). Written from memory of a replacement run instead of from the file the exact mistake two consumer rounds have been about, made inside the document describing it.
51
166
 
52
- **Nothing reported a page with no `meta`.** 75 of 243 pages across three of their apps had none — never, not since a migration — each serving the app name as its title. `voltro doctor` reports them now. Their own first gate is worth repeating: it keyed on pages rendering a particular wrapper and ran green while 60 pages without it had no title at all. The condition is the PAGE, not the wrapper it happens to use.
167
+ `queryDescriptorOrderAbsent.test.ts` now pins `{ 'sqlCompiler.ts': 3, 'jsonEagerCompiler.ts': 9 }`, so a compiler growing or losing a read site fails the suite instead of drifting quietly into a number somebody quotes. No behaviour change.
@@ -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.30.2",
16
- "@voltro/cli": "0.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/protocol": "0.30.2",
20
- "@voltro/runtime": "0.30.2",
15
+ "@voltro/ai": "0.32.0",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/protocol": "0.32.0",
20
+ "@voltro/runtime": "0.32.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.30.2",
24
+ "@voltro/testing": "0.32.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.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/plugin-auth": "0.30.2",
20
- "@voltro/protocol": "0.30.2",
21
- "@voltro/runtime": "0.30.2",
22
- "@voltro/sql-postgres": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/plugin-auth": "0.32.0",
20
+ "@voltro/protocol": "0.32.0",
21
+ "@voltro/runtime": "0.32.0",
22
+ "@voltro/sql-postgres": "0.32.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.30.2",
26
+ "@voltro/testing": "0.32.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.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/plugin-multitenancy": "0.30.2",
20
- "@voltro/protocol": "0.30.2",
21
- "@voltro/runtime": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/plugin-multitenancy": "0.32.0",
20
+ "@voltro/protocol": "0.32.0",
21
+ "@voltro/runtime": "0.32.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.30.2",
25
+ "@voltro/testing": "0.32.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.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/plugin-deactivation": "0.30.2",
20
- "@voltro/protocol": "0.30.2",
21
- "@voltro/runtime": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/plugin-deactivation": "0.32.0",
20
+ "@voltro/protocol": "0.32.0",
21
+ "@voltro/runtime": "0.32.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.30.2",
25
+ "@voltro/testing": "0.32.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.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/plugin-mail": "0.30.2",
20
- "@voltro/plugin-multitenancy": "0.30.2",
21
- "@voltro/protocol": "0.30.2",
22
- "@voltro/runtime": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/plugin-mail": "0.32.0",
20
+ "@voltro/plugin-multitenancy": "0.32.0",
21
+ "@voltro/protocol": "0.32.0",
22
+ "@voltro/runtime": "0.32.0",
23
23
  "effect": "^3.22.0",
24
24
  "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.30.2",
27
+ "@voltro/testing": "0.32.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.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/plugin-multitenancy": "0.30.2",
20
- "@voltro/plugin-storage": "0.30.2",
21
- "@voltro/protocol": "0.30.2",
22
- "@voltro/runtime": "0.30.2",
23
- "@voltro/sql-mysql": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/plugin-multitenancy": "0.32.0",
20
+ "@voltro/plugin-storage": "0.32.0",
21
+ "@voltro/protocol": "0.32.0",
22
+ "@voltro/runtime": "0.32.0",
23
+ "@voltro/sql-mysql": "0.32.0",
24
24
  "effect": "^3.22.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.30.2",
27
+ "@voltro/testing": "0.32.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.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/plugin-multitenancy": "0.30.2",
20
- "@voltro/protocol": "0.30.2",
21
- "@voltro/runtime": "0.30.2",
22
- "@voltro/sql-sqlite": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/plugin-multitenancy": "0.32.0",
20
+ "@voltro/protocol": "0.32.0",
21
+ "@voltro/runtime": "0.32.0",
22
+ "@voltro/sql-sqlite": "0.32.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.30.2",
26
+ "@voltro/testing": "0.32.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.30.2",
15
- "@voltro/database": "0.30.2",
16
- "@voltro/env": "0.30.2",
17
- "@voltro/plugin-multitenancy": "0.30.2",
18
- "@voltro/plugin-storage": "0.30.2",
19
- "@voltro/protocol": "0.30.2",
20
- "@voltro/runtime": "0.30.2",
14
+ "@voltro/cli": "0.32.0",
15
+ "@voltro/database": "0.32.0",
16
+ "@voltro/env": "0.32.0",
17
+ "@voltro/plugin-multitenancy": "0.32.0",
18
+ "@voltro/plugin-storage": "0.32.0",
19
+ "@voltro/protocol": "0.32.0",
20
+ "@voltro/runtime": "0.32.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.30.2",
24
+ "@voltro/testing": "0.32.0",
25
25
  "typescript": "^6.0.3",
26
26
  "vitest": "^4.1.10"
27
27
  }
@@ -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.30.2",
17
- "@voltro/cms": "0.30.2",
18
- "@voltro/database": "0.30.2",
19
- "@voltro/env": "0.30.2",
20
- "@voltro/plugin-auth": "0.30.2",
21
- "@voltro/plugin-multitenancy": "0.30.2",
22
- "@voltro/protocol": "0.30.2",
23
- "@voltro/runtime": "0.30.2",
24
- "@voltro/sql-postgres": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/cms": "0.32.0",
18
+ "@voltro/database": "0.32.0",
19
+ "@voltro/env": "0.32.0",
20
+ "@voltro/plugin-auth": "0.32.0",
21
+ "@voltro/plugin-multitenancy": "0.32.0",
22
+ "@voltro/protocol": "0.32.0",
23
+ "@voltro/runtime": "0.32.0",
24
+ "@voltro/sql-postgres": "0.32.0",
25
25
  "effect": "^3.22.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@voltro/testing": "0.30.2",
28
+ "@voltro/testing": "0.32.0",
29
29
  "typescript": "^6.0.3",
30
30
  "vitest": "^4.1.10"
31
31
  }
@@ -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.30.2",
17
- "@voltro/database": "0.30.2",
18
- "@voltro/env": "0.30.2",
19
- "@voltro/local-first": "0.30.2",
20
- "@voltro/plugin-multitenancy": "0.30.2",
21
- "@voltro/protocol": "0.30.2",
22
- "@voltro/runtime": "0.30.2",
16
+ "@voltro/cli": "0.32.0",
17
+ "@voltro/database": "0.32.0",
18
+ "@voltro/env": "0.32.0",
19
+ "@voltro/local-first": "0.32.0",
20
+ "@voltro/plugin-multitenancy": "0.32.0",
21
+ "@voltro/protocol": "0.32.0",
22
+ "@voltro/runtime": "0.32.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.30.2",
26
+ "@voltro/testing": "0.32.0",
27
27
  "typescript": "^6.0.3",
28
28
  "vitest": "^4.1.10"
29
29
  }
@@ -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.30.2",
16
- "@voltro/database": "0.30.2",
17
- "@voltro/env": "0.30.2",
18
- "@voltro/plugin-governance": "0.30.2",
19
- "@voltro/plugin-multitenancy": "0.30.2",
20
- "@voltro/protocol": "0.30.2",
21
- "@voltro/runtime": "0.30.2",
15
+ "@voltro/cli": "0.32.0",
16
+ "@voltro/database": "0.32.0",
17
+ "@voltro/env": "0.32.0",
18
+ "@voltro/plugin-governance": "0.32.0",
19
+ "@voltro/plugin-multitenancy": "0.32.0",
20
+ "@voltro/protocol": "0.32.0",
21
+ "@voltro/runtime": "0.32.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.30.2",
25
+ "@voltro/testing": "0.32.0",
26
26
  "typescript": "^6.0.3",
27
27
  "vitest": "^4.1.10"
28
28
  }
@@ -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.30.2",
16
- "@voltro/database": "0.30.2",
17
- "@voltro/env": "0.30.2",
18
- "@voltro/plugin-multitenancy": "0.30.2",
19
- "@voltro/protocol": "0.30.2",
20
- "@voltro/runtime": "0.30.2",
21
- "@voltro/workflow": "0.30.2",
15
+ "@voltro/cli": "0.32.0",
16
+ "@voltro/database": "0.32.0",
17
+ "@voltro/env": "0.32.0",
18
+ "@voltro/plugin-multitenancy": "0.32.0",
19
+ "@voltro/protocol": "0.32.0",
20
+ "@voltro/runtime": "0.32.0",
21
+ "@voltro/workflow": "0.32.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.30.2",
25
+ "@voltro/testing": "0.32.0",
26
26
  "typescript": "^6.0.3",
27
27
  "vitest": "^4.1.10"
28
28
  }