@voltro/cli 0.59.0 → 0.60.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 (67) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/bin.js +1 -1
  3. package/dist/{build-uRMZrT8z.js → build-BxAsLzyU.js} +14 -10
  4. package/dist/{codemodRunner-kNnh1I7_.js → codemodRunner-ClUqkhvR.js} +86 -37
  5. package/dist/{commands-cu34ENUT.js → commands-Ro4hYBHD.js} +7 -7
  6. package/dist/{dashboardCommand-gG1y4PFG.js → dashboardCommand-t8Qsebjr.js} +1 -1
  7. package/dist/{evolveCommand-8CuFBlbQ.js → evolveCommand-CThb0EZZ.js} +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/{infoCommand-D02A4WI2.js → infoCommand-a0WkKcUY.js} +1 -1
  10. package/dist/{start-C75w6ftA.js → start-BhoDQ25C.js} +1 -1
  11. package/dist/{start-DNtIlBcn.js → start-CcdPRG8L.js} +418 -367
  12. package/dist/startEntry.js +1 -1
  13. package/dist/{updateCommand-Bw3mCUDT.js → updateCommand-D9avpQFT.js} +1 -1
  14. package/dist/updateCommand-QMOirvXC.js +2 -0
  15. package/package.json +25 -19
  16. package/templates/AGENTS.md +1 -1
  17. package/templates/agent-docs/_index.md +1 -1
  18. package/templates/agent-docs/plugins/sentry.md +93 -6
  19. package/templates/agent-docs/whats-new.md +29 -26
  20. package/templates/apps/api-ai/package.json +6 -6
  21. package/templates/apps/api-auth/package.json +8 -8
  22. package/templates/apps/api-backend/package.json +7 -7
  23. package/templates/apps/api-backend-deactivation/package.json +7 -7
  24. package/templates/apps/api-backend-mail/package.json +8 -8
  25. package/templates/apps/api-backend-mariadb/package.json +9 -9
  26. package/templates/apps/api-backend-sqlite/package.json +8 -8
  27. package/templates/apps/api-backend-storage/package.json +8 -8
  28. package/templates/apps/api-cms/package.json +9 -9
  29. package/templates/apps/api-collab/package.json +8 -8
  30. package/templates/apps/api-data-advanced/package.json +8 -8
  31. package/templates/apps/api-durable/package.json +8 -8
  32. package/templates/apps/api-feature-flags/package.json +9 -9
  33. package/templates/apps/api-governance/package.json +8 -8
  34. package/templates/apps/api-kv/package.json +8 -8
  35. package/templates/apps/api-moderation/package.json +8 -8
  36. package/templates/apps/api-observability/package.json +8 -8
  37. package/templates/apps/api-ratelimit/package.json +8 -8
  38. package/templates/apps/api-rbac/package.json +8 -8
  39. package/templates/apps/api-rest/package.json +7 -7
  40. package/templates/apps/api-row-history/package.json +8 -8
  41. package/templates/apps/api-saas/package.json +11 -11
  42. package/templates/apps/api-saas-starter/package.json +10 -10
  43. package/templates/apps/api-search/package.json +8 -8
  44. package/templates/apps/api-status/package.json +8 -8
  45. package/templates/apps/api-webhooks/package.json +9 -9
  46. package/templates/apps/changelog/package.json +7 -7
  47. package/templates/apps/edge-functions/package.json +2 -2
  48. package/templates/apps/frontend-admin/package.json +7 -7
  49. package/templates/apps/frontend-app/package.json +8 -8
  50. package/templates/apps/frontend-auth/package.json +7 -7
  51. package/templates/apps/frontend-blank/package.json +6 -6
  52. package/templates/apps/frontend-cms/package.json +8 -8
  53. package/templates/apps/frontend-collab/package.json +9 -9
  54. package/templates/apps/frontend-contact/package.json +7 -7
  55. package/templates/apps/frontend-dashboard/package.json +6 -6
  56. package/templates/apps/frontend-docs/package.json +8 -8
  57. package/templates/apps/frontend-i18n/package.json +6 -6
  58. package/templates/apps/frontend-landing/package.json +7 -7
  59. package/templates/apps/frontend-portal/package.json +7 -7
  60. package/templates/apps/frontend-saas/package.json +7 -7
  61. package/templates/apps/frontend-spa/package.json +6 -6
  62. package/templates/apps/frontend-ssr/package.json +6 -6
  63. package/templates/apps/frontend-ssr-api/package.json +7 -7
  64. package/templates/apps/frontend-static-blog/package.json +8 -8
  65. package/templates/apps/frontend-status/package.json +7 -7
  66. package/templates/apps/mobile-app/package.json +4 -4
  67. package/dist/updateCommand-inInBIAh.js +0 -2
@@ -1,3 +1,3 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
- import { t } from "./start-DNtIlBcn.js";
2
+ import { t } from "./start-CcdPRG8L.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-kNnh1I7_.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-ClUqkhvR.js";
2
2
  import { basename as r, dirname as i, join as a, relative as o, resolve as s } from "node:path";
3
3
  import { existsSync as c, readFileSync as l, readdirSync as u, statSync as d, writeFileSync as f } from "node:fs";
4
4
  import { totalmem as p } from "node:os";
@@ -0,0 +1,2 @@
1
+ import { m as e, p as t } from "./updateCommand-D9avpQFT.js";
2
+ export { t as runApplyCodemods, e as runUpdateCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -892,6 +892,12 @@
892
892
  "title": "UiStrings gained a required `connectAccount` section",
893
893
  "kind": "manual"
894
894
  },
895
+ {
896
+ "version": "0.60.0",
897
+ "id": "0.60.0/01_declared_failures_are_not_incidents",
898
+ "title": "Sentry no longer reports declared failures by default",
899
+ "kind": "manual"
900
+ },
895
901
  {
896
902
  "version": "0.7.0",
897
903
  "id": "0.7.0/01_row-filter-load-failure",
@@ -938,24 +944,24 @@
938
944
  "@effect/platform-node": "^0.108.0",
939
945
  "@effect/sql": "^0.52.0",
940
946
  "@effect/workflow": "^0.19.0",
941
- "@voltro/ai": "0.59.0",
942
- "@voltro/cache": "0.59.0",
943
- "@voltro/client": "0.59.0",
944
- "@voltro/content": "0.59.0",
945
- "@voltro/data-transfer": "0.59.0",
946
- "@voltro/database": "0.59.0",
947
- "@voltro/env": "0.59.0",
948
- "@voltro/kv": "0.59.0",
949
- "@voltro/logger": "0.59.0",
950
- "@voltro/plugin-auth": "0.59.0",
951
- "@voltro/plugin-broadcast": "0.59.0",
952
- "@voltro/plugin-mail": "0.59.0",
953
- "@voltro/plugin-storage": "0.59.0",
954
- "@voltro/plugin-webhooks": "0.59.0",
955
- "@voltro/protocol": "0.59.0",
956
- "@voltro/runtime": "0.59.0",
957
- "@voltro/serverless": "0.59.0",
958
- "@voltro/workflow": "0.59.0",
947
+ "@voltro/ai": "0.60.0",
948
+ "@voltro/cache": "0.60.0",
949
+ "@voltro/client": "0.60.0",
950
+ "@voltro/content": "0.60.0",
951
+ "@voltro/data-transfer": "0.60.0",
952
+ "@voltro/database": "0.60.0",
953
+ "@voltro/env": "0.60.0",
954
+ "@voltro/kv": "0.60.0",
955
+ "@voltro/logger": "0.60.0",
956
+ "@voltro/plugin-auth": "0.60.0",
957
+ "@voltro/plugin-broadcast": "0.60.0",
958
+ "@voltro/plugin-mail": "0.60.0",
959
+ "@voltro/plugin-storage": "0.60.0",
960
+ "@voltro/plugin-webhooks": "0.60.0",
961
+ "@voltro/protocol": "0.60.0",
962
+ "@voltro/runtime": "0.60.0",
963
+ "@voltro/serverless": "0.60.0",
964
+ "@voltro/workflow": "0.60.0",
959
965
  "chokidar": "^5.0.0",
960
966
  "ioredis": "^5.11.1",
961
967
  "tinyglobby": "^0.2.17",
@@ -749,7 +749,7 @@ each plugin's own README.
749
749
 
750
750
  | Topic | Open | Summary |
751
751
  |---|---|---|
752
- | **What's new in 0.59.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
752
+ | **What's new in 0.60.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
753
753
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
754
754
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
755
755
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.59.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
12
+ | **What's new in 0.60.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
13
13
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
14
14
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
15
15
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -243,11 +243,97 @@ Two details worth knowing before the first upload:
243
243
  store, and it fails with `SSL peer certificate … unable to get local issuer
244
244
  certificate`. Add `ca-certificates` to the build stage.
245
245
 
246
- **What we have not measured:** how a given Sentry project resolves the uploaded
247
- artifacts end to end. `sentry-cli sourcemaps upload` matches on debug IDs it
248
- injects into the emitted JS, and the URL prefix is the older release-artifact
249
- fallback which is why the prefix is set correctly per directory rather than
250
- left to one default, and why this paragraph is here instead of a claim.
246
+ **Debug ids are what make a server frame resolve, and they need their own
247
+ step.** `sentry-cli sourcemaps upload` does **not** write them — `inject` is a
248
+ separate subcommand, and `upload` only uses ids that are already there, falling
249
+ back to matching on the artifact NAME when they are not.
250
+
251
+ That fallback cannot work for a server bundle: the artifact is named from
252
+ `--url-prefix` (`~/chunk-ABC.js`) while the frame carries the absolute path the
253
+ node process loaded (`/workspace/…/serveBundle/chunk-ABC.js`), and nothing
254
+ rewrites either side. Measured downstream before this was fixed: 4300 artifacts
255
+ uploaded, the release finalised, every frame still minified — nothing red, which
256
+ is the failure this page's own warning describes.
257
+
258
+ `voltro build` runs `inject` first now, so the ids are in both the shipped JS and
259
+ the map before anything is uploaded. The URL prefix stays as the fallback for the
260
+ browser bundle, whose frames really are URLs under `~/assets`.
261
+
262
+ ## The web server is a SECOND process — and it needs the DSN too
263
+
264
+ A framework app runs two server processes. `voltro serve` is the api, where
265
+ `sentryPlugin()` initialises the SDK through the plugin lifecycle. `voltro start`
266
+ is the **web server** — SSR, loaders, ISR, the revalidation legs — and it has no
267
+ plugin lifecycle at all.
268
+
269
+ It now initialises Sentry itself when `SENTRY_DSN` is set:
270
+
271
+ ```sh
272
+ SENTRY_DSN=… SENTRY_ENVIRONMENT=production SENTRY_RELEASE=1.4.2 voltro start
273
+ ```
274
+
275
+ ```
276
+ {"level":"info","msg":"sentry active","environment":"production","release":"1.4.2","traces":false,"profiling":false,"process":"web"}
277
+ ```
278
+
279
+ Same line and same field names as the api half, from the same function — one
280
+ vocabulary across both pods, plus `process` so a collector can tell them apart.
281
+
282
+ **Why this was a hole rather than a missing log line.** An SSR shell throw is
283
+ caught, logged, and answered with a 500. No browser ever renders it, so the
284
+ client-side ErrorBoundary bridge below cannot see it either: the error existed
285
+ only in the pod's stdout. Both ends of the integration worked and the middle was
286
+ dark.
287
+
288
+ Three things to know:
289
+
290
+ - **`@voltro/plugin-sentry` must be installed in the WEB app**, not only the api
291
+ — the web server resolves the shared init from it. With a DSN set and the
292
+ package missing, the boot says so and names the command; it does not fail.
293
+ - **`traces` is `false` here, deliberately.** The api half contributes a span
294
+ processor to the framework's tracer. The web server has no tracer, so
295
+ initialising with tracing on would put `traces: true` in a boot line while
296
+ nothing produces a span.
297
+ - **No DSN is silent.** On the api side `sentryPlugin()` in your config is a
298
+ declaration, so an inert one is worth a warning. Here there is no declaration
299
+ to contradict, and a line about an integration you did not ask for is noise.
300
+
301
+ **Re-run `voltro build` after upgrading.** Production `voltro start` loads the
302
+ app's precompiled start bundle, so the web half picks this up when that bundle
303
+ is rebuilt — not from the framework version alone.
304
+
305
+ ## What reaches Sentry — a declared failure does not
306
+
307
+ A procedure's `error:` union is a **contract**. Somebody wrote the outcome down,
308
+ the client gets it typed and branches on it, and it describes a state of the
309
+ world: `AccessDeniedError` on a team you are not a member of is the system
310
+ working. A **defect** is the opposite — nobody foresaw it. Sentry is built for
311
+ the second.
312
+
313
+ So a cause that carries only declared failures is **not** reported:
314
+
315
+ ```ts
316
+ sentryPlugin() // captureFailures: false — the default
317
+ sentryPlugin({ captureFailures: true }) // report them again
318
+ sentryPlugin({ captureFailures: (e) => (e as { _tag?: string })._tag === 'PaymentDeclined' })
319
+ ```
320
+
321
+ - **Defects are always reported**, including a defect that arrived beside a
322
+ failure — the rule is failures-*only*, so one cannot hide the other.
323
+ - **Both halves take the option.** A rejected call is an rpc error on the client
324
+ too, so `initSentryBrowser({ captureFailures })` applies the same rule to
325
+ `rpc.*` events that carry a `_tag`. Set them together, or the api goes quiet
326
+ while the browser keeps filing the same rejection.
327
+ - **Route render failures and `reportClientError` are never filtered.** Nobody
328
+ declared those, which is the whole distinction.
329
+ - **The boot line names it** — `sentry active … captureFailures=false` — because
330
+ a default the framework picks for you is one nobody finds again.
331
+
332
+ Why the default is off: an issue list that fills with the expected is one nobody
333
+ reads. Every invalid form value, every click on a resource without access and
334
+ every call on a deleted row was a `level: error` issue. If Sentry is where you
335
+ see errors at all, `captureFailures: true` is the one-line answer and it is a
336
+ reasonable choice.
251
337
 
252
338
  ## Catching errors — what's automatic, what's manual
253
339
 
@@ -257,7 +343,8 @@ Once the two halves are wired, most errors are captured **with no per-call code*
257
343
  |---|---|---|
258
344
  | rpc handler throw (mutation/query/action) | ✅ auto | server interceptor → Sentry, trace-correlated + breadcrumbs |
259
345
  | Unhandled browser error / promise rejection | ✅ auto | `@sentry/react` global handlers (after `initSentryBrowser`) |
260
- | **React render error** (any page, incl. catch-all `[...slug]`, `error.tsx` boundary) | ✅ auto | the framework's route ErrorBoundary publishes to a client-error bus → the Sentry web bridge captures it. React swallows boundary-caught errors before `window.onerror`, so this bridge is what makes them reach Sentry — tagged with the route + component stack. |
346
+ | **React render error, in the BROWSER** (any page, incl. catch-all `[...slug]`, `error.tsx` boundary) | ✅ auto | the framework's route ErrorBoundary publishes to a client-error bus → the Sentry web bridge captures it. React swallows boundary-caught errors before `window.onerror`, so this bridge is what makes them reach Sentry — tagged with the route + component stack. |
347
+ | **React render error, during SSR** (shell throw, loader throw, PPR/SWR refresh) | ✅ auto, from the WEB server | a different process and a different path: the render never reaches a browser, so the row above cannot cover it. Captured by the web server's own Sentry (see the section above), tagged `voltro.process=web` and `voltro.stage`. Needs `SENTRY_DSN` on the **web** deployment. |
261
348
  | **Loader failure** (page/layout `loader` reject) | ✅ auto | same route bus path |
262
349
  | Manual try/catch (event handler, async effect, anywhere) | ✋ one call | `reportClientError(error, context?)` |
263
350
  | **Every other server primitive** — REST route (`defineRestRoute`), aggregate, subscriber, schedule, workflow, webhook, startup | ✅ auto | each primitive's error path publishes to the framework's server-error bus → the plugin captures it, tagged `voltro.errorSource` + `voltro.name` (+ traceId for workflows) |
@@ -1,4 +1,4 @@
1
- # What's new in 0.59.0
1
+ # What's new in 0.60.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,51 +9,54 @@ BREAKING entries name a codemod; run `voltro update` to apply it.
9
9
 
10
10
  ### ⚠ BREAKING
11
11
 
12
- - **@voltro/cli** — **`web.sourcemaps` moved to the top level of `app.config.ts`, and now covers every bundle the build minifies.**
12
+ - **@voltro/plugin-sentry** — **A declared failure no longer reaches Sentry by default, and the source-map upload now injects debug ids.** Two separate defects, both found by the same deployment on its first day of real server-side events.
13
13
 
14
- It reached exactly one of them before: the vite CLIENT build. `voltro build` minifies four the browser bundle, the SSR bundle, the `voltro start` bundle, and for an api app the precompiled entry plus the serve bundle that `voltro serve` actually runs and none of the other three had a `sourcemap` option or a config path that could ask for one. `keepNames` rescues the identifiers that are read as strings and nothing else, so a frame out of the artefact production runs read `at Qo (serveEntry.js:236:76405)`.
14
+ **1. The contract was being reported as an incident.** The rpc interceptor skipped only clean interrupts; everything else went to `captureException`. So a failure declared in a procedure's `error:` union the thing the client gets typed and branches onarrived as `level: error`, `handled: yes`. The first server-side issue a deployment ever received was a person clicking a team they are not a member of.
15
15
 
16
- The docs closed that hole with a sentence instead of a field: *server stack traces are un-minified already*. The built file says otherwise, and the server half is where it costs most a schedule, a workflow step, a subscriber or a REST route has no browser caller whose frame could stand in for it.
16
+ Effect separates a failure from a defect, this framework leans on that split deliberately (a store refusal was made typed so an app could branch on it; an unlookupable conflict key was deliberately left a defect, because it is a broken call rather than a condition in the data), and a descriptor carries it in `error:`. Reporting both as an incident discarded that one layer up.
17
17
 
18
- A second field (`api.sourcemaps`) would have made the same mistake twice. "Do I want resolvable frames from this app" has one answer; how many bundles the build happens to emit is not the caller's business.
18
+ `shouldCapture` now skips a cause that is failures-ONLY. A defect is reported as before, including a defect that travelled beside a failure — the rule is failures-only rather than "any failure present" precisely so one cannot hide the other. `captureFailures: true` restores the old behaviour; a predicate keeps the ones that are signal.
19
19
 
20
- Three details that come with it:
20
+ **The browser half moved with it**, or the option would have been half-wired: a rejected call is an rpc error on the client too, published to the client error bus and captured by the browser bridge. `initSentryBrowser` takes the same option and applies it to `rpc.*` events carrying a `_tag`. Route render failures and `reportClientError` calls are never filtered — nobody declared those.
21
21
 
22
- - **The old spelling is REFUSED, not ignored.** A moved option that is silently dropped goes green, stops emitting maps, and is found by someone reading an unresolvable frame months later. - **Each output directory uploads under the prefix that matches how its files are named** `~/assets` for the browser bundle, `~/` for the bundles a node process loaded. One prefix across both is wrong for one of them. - **The upload and sweep run at every exit of the build**, not after the client bundle. Sweeping there would have deleted the client maps before the server ones existed, leaving those neither uploaded nor deleted.
22
+ **2. `sentry-cli sourcemaps upload` does NOT write debug ids.** `inject` is a separate subcommand; `upload` only uses ids that are already present, and falls back to matching on the artifact NAME when they are not. That fallback cannot work for a server bundle: the artifact is named from `--url-prefix` (`~/chunk- ABC.js`) while the frame carries the absolute path the node process loaded, and nothing rewrites either side.
23
23
 
24
- `minifiedBundlesCarryMaps.test.ts` asserts the property over the SET: every bundler options object in the CLI that turns minification on must also decide `sourcemap`. That is what was missing not one option, three, because nothing was watching the class.
24
+ Measured downstream: 4300 artifacts uploaded, release finalised, every frame still minified. Nothing was red — the exact shape this code's own header warns about, an upload that matched nothing looking like one that worked. The comment above the uploader asserted the injection happened, which made it a description standing where a check belonged.
25
25
 
26
- **`voltro update` carries you across this** codemod `0.59.0/01_sourcemaps_moved_to_the_top_level`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.59.0).
27
- - **@voltro/ai** — **Speech has a cost path, and a built-in provider name can be taken over.**
26
+ `inject` now runs first, over the same directories, and `sourcemapDebugIds.test.ts` drives the real binary to assert an id lands in both the JS and the map. `--url-prefix` stays as the fallback for the browser bundle, whose frames really are URLs.
28
27
 
29
- Three changes, one thread: the speech seam existed and nothing could reach it.
28
+ The boot line names the new setting (`sentry active … captureFailures=false`), because a default the framework picks for you is one nobody finds again.
30
29
 
31
- **`registerSpeechProvider(name, provider, { replaceBuiltin: true })`.** Refusing `mock` / `openai` / `gateway` / `anthropic` outright was wrong, and the case that shows it is one the refusal could not see: a provider name is not always an alias the app chooses. `openai` is the segment the gateway puts in its model ids, so it is what a stored catalog's rows carry and what a picker submits — an app told to pick another name cannot, without rewriting persisted rows and live sessions. It keeps its own dispatch instead, for every provider rather than the colliding one. What the refusal protected is kept by putting the intent in the call.
30
+ **`voltro update` carries you across this** codemod `0.60.0/01_declared_failures_are_not_incidents`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.60.0).
32
31
 
33
- **Registered providers resolve BEFORE built-ins.** The dispatch decided from a hard-coded list of built-in names, so without this the flag would let you register `openai` and never route to it — declared, documented, wired at zero call sites.
32
+ ### Added
34
33
 
35
- **`MeteredUsage` — a tally in characters or seconds.** Every speech vendor meters one of those; none meters tokens. A ledger that only knew tokens could not hold a speech call at all, so speech spend was not un-priced, it was *absent*. The token shape is untouched (it is what the AI SDK returns); the new shape is separate rather than a unit tacked onto `AiUsage`, because writing a character count into a field called `inputTokens` puts a lie in a row someone reads later.
34
+ - **@voltro/cli, @voltro/plugin-sentry** **The web server process now initialises Sentry and reports its own errors.**
36
35
 
37
- `_voltro_ai_usage` gains a `unit` column, and `costSource` gains `'unpriced'`: a non-token unit with no supplied price is written with the real count and a zero cost rather than a fabricated one, since a per-1M-token rate applied to a character count produces a number indistinguishable from a real one. Sum the count columns grouped by `unit`, never across it.
36
+ A framework app runs two server processes. `voltro serve` is the api, where `sentryPlugin()` initialises the SDK through the plugin lifecycle. `voltro start` is the web server SSR, loaders, ISR, the revalidation legs and it has no plugin lifecycle, so it had none.
38
37
 
39
- BREAKING for a caller that had narrowed `CostBreakdown['costSource']` to two values or reads `estimateCostUsd`'s return positionally. No call site changes SHAPE the token path takes the same arguments and returns the same numbers but a narrowing is user-authored code, so this carries a `manual` codemod rather than `none`: nothing here can be transformed (whether the third member belongs in your union is your decision), and the reader who needs it is exactly the one a "touches no user-authored code" line would have sent away. It also names the query that does not fail but answers wrongly: summing `_voltro_ai_usage` counts ACROSS units now adds characters to tokens.
38
+ That was not a cosmetic difference in boot output. A web pod legitimately logs less than an api pod, because it has no store, no rpc, no scheduler, no workflows and no plugins. What it also had was **no error reporting**: an SSR shell throw is caught, logged and answered with a 500, so no browser ever renders it and the client-side ErrorBoundary bridge cannot see it either. Both ends of the integration worked and the middle was dark while the docs' "React render error auto" row, true of the client path, read as covering all of them.
40
39
 
41
- **`voltro update` carries you across this** codemod `0.59.0/02_costsource_gained_unpriced`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.59.0).
40
+ Set `SENTRY_DSN` on the web deployment and the boot says `sentry active` with the same message and the same field names as the api half, from the same function `initSentryServer` in `@voltro/plugin-sentry/server`, which the api plugin now calls too. One init, because a copy of `skipOpenTelemetrySetup`, the traces default, the overrides-first spread and the degrade-on-missing-SDK path would have drifted the moment either half gained a case.
42
41
 
43
- ### Added
42
+ Three deliberate asymmetries, each because the two processes are not the same thing:
43
+
44
+ - **`traces: false` on the web side.** The api contributes a span processor to the framework's tracer; the web server has no tracer at all, so tracing on would put `traces: true` in a boot line while nothing produces a span. - **No DSN is SILENT here.** On the api side `sentryPlugin()` is a declaration and an inert one contradicts it. There is no declaration here. - **`@voltro/plugin-sentry` must be installed in the WEB app.** With a DSN set and the package missing, the boot names the command rather than failing — monitoring must never be what stops a deploy.
44
45
 
45
- - **@voltro/ai** **`GatewayModelInfo.raw`** the `/v1/models` entry exactly as it came over the wire, beside the normalised fields.
46
+ Measured against a real production `voltro start`, both branches. Note that production `voltro start` loads the app's precompiled start bundle, so a web deployment picks this up when that bundle is rebuilt — the framework version alone is not enough.
46
47
 
47
- Normalising is the right default: the union of `tags`, `supported_parameters` and `modalities` is framework knowledge, and every app on the gateway was writing those forty lines differently. But normalising is lossy by construction, and the fields it drops are not decoration — a picker built on the vendor's own capability tags, a pricing display that shows a structure we have not normalised, `zdr` and `no_training` kept apart instead of folded into `dataPolicy`.
48
+ ### Fixed
48
49
 
49
- Without `raw`, such an app fetches the endpoint a second time and maintains a second parser of the same response. One field rather than three more normalised ones, because the alternative asks the same question again at every field the gateway adds.
50
+ - **@voltro/workflow, @voltro/runtime, @voltro/database, @voltro/cli, @voltro/plugin-sentry** **Six raw writes removed from production log streams and the guard that was supposed to catch them rewritten, because it was green for two independent reasons.**
50
51
 
51
- Absent on the injected-provider path, for the same reason `capabilities` is: that seam never saw a response, and an empty object would turn "no wire data" into the claim that the wire carried nothing.
52
+ A pod tail showed `[voltro:workflow] shard-lock coordination: row-based (dialect=mariadb, mode=row)` sitting between JSON records. `@voltro/logger` is what makes a line JSON in a pod and pretty on a TTY; a `process.stderr.write` bypasses that decision at exactly the place nobody looks, because a dev terminal renders both the same.
52
53
 
53
- ### Fixed
54
+ Fixed at the source: the workflow cluster layer (2), `rpcServer`'s computed-cache warning, the subscription outbox's and the RYW store's `warn`/`onError` defaults — those two are not fallbacks, the callers pass nothing, so the default IS the production path — and the migration file discovery's skip notice, which lands in the migrate job's stream.
55
+
56
+ **The guard is the part worth reading.** `prodLogDiscipline.test.ts` existed for this exact class and reported clean, for two reasons that had to be fixed separately:
54
57
 
55
- - **@voltro/cli** **`@sentry/cli` 3.x loads.** The loader read one export spelling `module.exports = SentryCli`, which is 2.x. 3.x ships `exports.SentryCli = SentryCli`, so `mod.default` is a namespace object rather than the class, and a build with a source-map upload configured stopped with *could not be loaded*.
58
+ - Its file set was a hand-written list of five. A guard that opts files IN says nothing about any file added after it was written. - Its matcher was LINE-LOCAL, so `process.stderr.write(` on one line and the `` `[tag] `` on the next never matched — 4 of the 9 call sites in the repo are written that way, including one in a file that WAS on the list. The guard had been pointed straight at an offender and called it clean.
56
59
 
57
- The framework cannot pin its way out of this: `@sentry/cli` is FSL-1.1-MIT, so it is not ours to depend on and the app installs it which means `pnpm add -D @sentry/cli`, the command we ourselves print, resolves the latest. Reading one spelling turned our own documented instruction into a failing build.
60
+ It is opt-OUT now: every server-side package's source is scanned, exceptions carry a reason, and a second test fails if an exception's call site disappears an allowlist entry for code that is gone reads as a rule with a hole in it.
58
61
 
59
- All four positions are accepted now (both spellings, each of which `import()` presents twice), and the two failures are told apart: a package that resolved but had no readable constructor no longer says *install it*. That reading is what cost a pipeline — locally `require()` resolves and the binary is there, so nothing about the install looks wrong.
62
+ Verified against a real `voltro serve` under `NODE_ENV=production`: 35 records, zero framework lines that are not JSON.
@@ -12,12 +12,12 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.97.0",
14
14
  "@effect/rpc": "^0.76.0",
15
- "@voltro/ai": "0.59.0",
16
- "@voltro/cli": "0.59.0",
17
- "@voltro/database": "0.59.0",
18
- "@voltro/env": "0.59.0",
19
- "@voltro/protocol": "0.59.0",
20
- "@voltro/runtime": "0.59.0",
15
+ "@voltro/ai": "0.60.0",
16
+ "@voltro/cli": "0.60.0",
17
+ "@voltro/database": "0.60.0",
18
+ "@voltro/env": "0.60.0",
19
+ "@voltro/protocol": "0.60.0",
20
+ "@voltro/runtime": "0.60.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
@@ -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.59.0",
17
- "@voltro/database": "0.59.0",
18
- "@voltro/env": "0.59.0",
19
- "@voltro/plugin-auth": "0.59.0",
20
- "@voltro/protocol": "0.59.0",
21
- "@voltro/runtime": "0.59.0",
22
- "@voltro/sql-postgres": "0.59.0",
16
+ "@voltro/cli": "0.60.0",
17
+ "@voltro/database": "0.60.0",
18
+ "@voltro/env": "0.60.0",
19
+ "@voltro/plugin-auth": "0.60.0",
20
+ "@voltro/protocol": "0.60.0",
21
+ "@voltro/runtime": "0.60.0",
22
+ "@voltro/sql-postgres": "0.60.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.59.0",
26
+ "@voltro/testing": "0.60.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.59.0",
20
- "@voltro/database": "0.59.0",
21
- "@voltro/env": "0.59.0",
22
- "@voltro/plugin-multitenancy": "0.59.0",
23
- "@voltro/protocol": "0.59.0",
24
- "@voltro/runtime": "0.59.0",
19
+ "@voltro/cli": "0.60.0",
20
+ "@voltro/database": "0.60.0",
21
+ "@voltro/env": "0.60.0",
22
+ "@voltro/plugin-multitenancy": "0.60.0",
23
+ "@voltro/protocol": "0.60.0",
24
+ "@voltro/runtime": "0.60.0",
25
25
  "effect": "^3.22.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@voltro/testing": "0.59.0",
28
+ "@voltro/testing": "0.60.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.59.0",
17
- "@voltro/database": "0.59.0",
18
- "@voltro/env": "0.59.0",
19
- "@voltro/plugin-deactivation": "0.59.0",
20
- "@voltro/protocol": "0.59.0",
21
- "@voltro/runtime": "0.59.0",
16
+ "@voltro/cli": "0.60.0",
17
+ "@voltro/database": "0.60.0",
18
+ "@voltro/env": "0.60.0",
19
+ "@voltro/plugin-deactivation": "0.60.0",
20
+ "@voltro/protocol": "0.60.0",
21
+ "@voltro/runtime": "0.60.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.59.0",
25
+ "@voltro/testing": "0.60.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.59.0",
17
- "@voltro/database": "0.59.0",
18
- "@voltro/env": "0.59.0",
19
- "@voltro/plugin-mail": "0.59.0",
20
- "@voltro/plugin-multitenancy": "0.59.0",
21
- "@voltro/protocol": "0.59.0",
22
- "@voltro/runtime": "0.59.0",
16
+ "@voltro/cli": "0.60.0",
17
+ "@voltro/database": "0.60.0",
18
+ "@voltro/env": "0.60.0",
19
+ "@voltro/plugin-mail": "0.60.0",
20
+ "@voltro/plugin-multitenancy": "0.60.0",
21
+ "@voltro/protocol": "0.60.0",
22
+ "@voltro/runtime": "0.60.0",
23
23
  "effect": "^3.22.0",
24
24
  "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.59.0",
27
+ "@voltro/testing": "0.60.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.59.0",
17
- "@voltro/database": "0.59.0",
18
- "@voltro/env": "0.59.0",
19
- "@voltro/plugin-multitenancy": "0.59.0",
20
- "@voltro/plugin-storage": "0.59.0",
21
- "@voltro/protocol": "0.59.0",
22
- "@voltro/runtime": "0.59.0",
23
- "@voltro/sql-mysql": "0.59.0",
16
+ "@voltro/cli": "0.60.0",
17
+ "@voltro/database": "0.60.0",
18
+ "@voltro/env": "0.60.0",
19
+ "@voltro/plugin-multitenancy": "0.60.0",
20
+ "@voltro/plugin-storage": "0.60.0",
21
+ "@voltro/protocol": "0.60.0",
22
+ "@voltro/runtime": "0.60.0",
23
+ "@voltro/sql-mysql": "0.60.0",
24
24
  "effect": "^3.22.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.59.0",
27
+ "@voltro/testing": "0.60.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.59.0",
17
- "@voltro/database": "0.59.0",
18
- "@voltro/env": "0.59.0",
19
- "@voltro/plugin-multitenancy": "0.59.0",
20
- "@voltro/protocol": "0.59.0",
21
- "@voltro/runtime": "0.59.0",
22
- "@voltro/sql-sqlite": "0.59.0",
16
+ "@voltro/cli": "0.60.0",
17
+ "@voltro/database": "0.60.0",
18
+ "@voltro/env": "0.60.0",
19
+ "@voltro/plugin-multitenancy": "0.60.0",
20
+ "@voltro/protocol": "0.60.0",
21
+ "@voltro/runtime": "0.60.0",
22
+ "@voltro/sql-sqlite": "0.60.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.59.0",
26
+ "@voltro/testing": "0.60.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.59.0",
15
- "@voltro/database": "0.59.0",
16
- "@voltro/env": "0.59.0",
17
- "@voltro/plugin-multitenancy": "0.59.0",
18
- "@voltro/plugin-storage": "0.59.0",
19
- "@voltro/protocol": "0.59.0",
20
- "@voltro/runtime": "0.59.0",
14
+ "@voltro/cli": "0.60.0",
15
+ "@voltro/database": "0.60.0",
16
+ "@voltro/env": "0.60.0",
17
+ "@voltro/plugin-multitenancy": "0.60.0",
18
+ "@voltro/plugin-storage": "0.60.0",
19
+ "@voltro/protocol": "0.60.0",
20
+ "@voltro/runtime": "0.60.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.59.0",
24
+ "@voltro/testing": "0.60.0",
25
25
  "typescript": "^6.0.3",
26
26
  "@vitest/coverage-v8": "^4.1.10",
27
27
  "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.59.0",
17
- "@voltro/cms": "0.59.0",
18
- "@voltro/database": "0.59.0",
19
- "@voltro/env": "0.59.0",
20
- "@voltro/plugin-auth": "0.59.0",
21
- "@voltro/protocol": "0.59.0",
22
- "@voltro/runtime": "0.59.0",
23
- "@voltro/sql-postgres": "0.59.0",
16
+ "@voltro/cli": "0.60.0",
17
+ "@voltro/cms": "0.60.0",
18
+ "@voltro/database": "0.60.0",
19
+ "@voltro/env": "0.60.0",
20
+ "@voltro/plugin-auth": "0.60.0",
21
+ "@voltro/protocol": "0.60.0",
22
+ "@voltro/runtime": "0.60.0",
23
+ "@voltro/sql-postgres": "0.60.0",
24
24
  "effect": "^3.22.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.59.0",
27
+ "@voltro/testing": "0.60.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "vitest": "^4.1.10"