@voltro/cli 0.60.0 → 0.62.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 (98) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/apiBuild-Dl-djDc_.js +2 -0
  3. package/dist/{apiBuild-BU5cd-Fn.js → apiBuild-DwhySM-6.js} +2 -2
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-BxAsLzyU.js → build-B43mm2g2.js} +7 -7
  6. package/dist/{checkCommand-C1CQc8lD.js → checkCommand-nB68vIo_.js} +1 -1
  7. package/dist/{checkCommand-DC7k1_hP.js → checkCommand-zGYZtIkJ.js} +3 -3
  8. package/dist/{codegenCommand-kS3Twuc9.js → codegenCommand-YvqhgQBj.js} +1 -1
  9. package/dist/{codemodRunner-ClUqkhvR.js → codemodRunner-D90bexh4.js} +233 -159
  10. package/dist/{commands-Ro4hYBHD.js → commands-Dd6Cp7qs.js} +21 -21
  11. package/dist/{dashboardCommand-t8Qsebjr.js → dashboardCommand-BsDfQa-g.js} +3 -3
  12. package/dist/{dataCommand-hNOGKz4K.js → dataCommand-Cgdcl5M0.js} +1 -1
  13. package/dist/{dev-Ce2gEziv.js → dev-BfUG0jrD.js} +32 -12
  14. package/dist/{dev-Dff_pdik.js → dev-BpMSAjMk.js} +1 -1
  15. package/dist/doctorCommand-PHcqxpnx.js +2 -0
  16. package/dist/{doctorCommand-CGjefPmk.js → doctorCommand-pxYq2bH5.js} +13 -13
  17. package/dist/{dormancyCommand-DYgI0Kf-.js → dormancyCommand-DZpSCWy9.js} +1 -1
  18. package/dist/{embeddingsCommand-yJivQSrI.js → embeddingsCommand-YR5Eif0k.js} +1 -1
  19. package/dist/{envCommand-ORJUf8r_.js → envCommand-Yl4TGM4h.js} +2 -2
  20. package/dist/{evolveCommand-CThb0EZZ.js → evolveCommand-B2iYAC9t.js} +2 -2
  21. package/dist/index.js +1 -1
  22. package/dist/{infoCommand-a0WkKcUY.js → infoCommand-DjtMWLZl.js} +1 -1
  23. package/dist/{probeCommand-BUYzc0CT.js → probeCommand-Dsu5if_4.js} +1 -1
  24. package/dist/{runtimeTrace-DDuKJgVi.js → runtimeTrace-CQcn26bI.js} +1 -1
  25. package/dist/{sdkgen-vq1fF9D_.js → sdkgen-DJvyVw7B.js} +1 -1
  26. package/dist/serveCommand-CZ0kgfIX.js +2 -0
  27. package/dist/{serveCommand-Dr4KXmTS.js → serveCommand-CaLTTh68.js} +3 -3
  28. package/dist/serveEntry.js +1 -1
  29. package/dist/{start-BhoDQ25C.js → start-DE4ixCTi.js} +1 -1
  30. package/dist/{start-CcdPRG8L.js → start-bqT4GRH0.js} +427 -418
  31. package/dist/startEntry.js +1 -1
  32. package/dist/updateCommand-DA4OMF7u.js +2 -0
  33. package/dist/{updateCommand-D9avpQFT.js → updateCommand-F6HFgz0i.js} +1 -1
  34. package/dist/{webDev-CBN9KkyQ.js → webDev-CYjhmOTZ.js} +1 -1
  35. package/dist/{webDev-Bzb8ZFus.js → webDev-CuG3Ged-.js} +1 -1
  36. package/dist/{webhooksCommand-BPJQOGTe.js → webhooksCommand-Bgdli8dI.js} +1 -1
  37. package/package.json +37 -19
  38. package/templates/AGENTS.md +2 -2
  39. package/templates/agent-docs/_index.md +2 -2
  40. package/templates/agent-docs/_manifest.json +1 -1
  41. package/templates/agent-docs/ai.md +32 -0
  42. package/templates/agent-docs/data.md +10 -2
  43. package/templates/agent-docs/observability.md +17 -2
  44. package/templates/agent-docs/plugins/sentry.md +29 -12
  45. package/templates/agent-docs/schema-driven-ui.md +38 -10
  46. package/templates/agent-docs/whats-new.md +5 -45
  47. package/templates/agent-docs/workflows.md +7 -2
  48. package/templates/apps/api-ai/package.json +6 -6
  49. package/templates/apps/api-auth/package.json +8 -8
  50. package/templates/apps/api-backend/package.json +7 -7
  51. package/templates/apps/api-backend-deactivation/package.json +7 -7
  52. package/templates/apps/api-backend-mail/package.json +8 -8
  53. package/templates/apps/api-backend-mariadb/package.json +9 -9
  54. package/templates/apps/api-backend-sqlite/package.json +8 -8
  55. package/templates/apps/api-backend-storage/package.json +8 -8
  56. package/templates/apps/api-cms/package.json +9 -9
  57. package/templates/apps/api-collab/package.json +8 -8
  58. package/templates/apps/api-data-advanced/package.json +8 -8
  59. package/templates/apps/api-durable/package.json +8 -8
  60. package/templates/apps/api-feature-flags/package.json +9 -9
  61. package/templates/apps/api-governance/package.json +8 -8
  62. package/templates/apps/api-kv/package.json +8 -8
  63. package/templates/apps/api-moderation/package.json +8 -8
  64. package/templates/apps/api-observability/package.json +8 -8
  65. package/templates/apps/api-ratelimit/package.json +8 -8
  66. package/templates/apps/api-rbac/package.json +8 -8
  67. package/templates/apps/api-rest/package.json +7 -7
  68. package/templates/apps/api-row-history/package.json +8 -8
  69. package/templates/apps/api-saas/package.json +11 -11
  70. package/templates/apps/api-saas-starter/package.json +10 -10
  71. package/templates/apps/api-search/package.json +8 -8
  72. package/templates/apps/api-status/package.json +8 -8
  73. package/templates/apps/api-webhooks/package.json +9 -9
  74. package/templates/apps/changelog/package.json +7 -7
  75. package/templates/apps/edge-functions/package.json +2 -2
  76. package/templates/apps/frontend-admin/package.json +7 -7
  77. package/templates/apps/frontend-app/package.json +8 -8
  78. package/templates/apps/frontend-auth/package.json +7 -7
  79. package/templates/apps/frontend-blank/package.json +6 -6
  80. package/templates/apps/frontend-cms/package.json +8 -8
  81. package/templates/apps/frontend-collab/package.json +9 -9
  82. package/templates/apps/frontend-contact/package.json +7 -7
  83. package/templates/apps/frontend-dashboard/package.json +6 -6
  84. package/templates/apps/frontend-docs/package.json +8 -8
  85. package/templates/apps/frontend-i18n/package.json +6 -6
  86. package/templates/apps/frontend-landing/package.json +7 -7
  87. package/templates/apps/frontend-portal/package.json +7 -7
  88. package/templates/apps/frontend-saas/package.json +7 -7
  89. package/templates/apps/frontend-spa/package.json +6 -6
  90. package/templates/apps/frontend-ssr/package.json +6 -6
  91. package/templates/apps/frontend-ssr-api/package.json +7 -7
  92. package/templates/apps/frontend-static-blog/package.json +8 -8
  93. package/templates/apps/frontend-status/package.json +7 -7
  94. package/templates/apps/mobile-app/package.json +4 -4
  95. package/dist/apiBuild-B9OvhITV.js +0 -2
  96. package/dist/doctorCommand-DXnNpKQT.js +0 -2
  97. package/dist/serveCommand-1R52mo6F.js +0 -2
  98. package/dist/updateCommand-QMOirvXC.js +0 -2
@@ -1,3 +1,3 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
- import { t } from "./start-CcdPRG8L.js";
2
+ import { t } from "./start-bqT4GRH0.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
@@ -0,0 +1,2 @@
1
+ import { m as e, p as t } from "./updateCommand-F6HFgz0i.js";
2
+ export { t as runApplyCodemods, e as runUpdateCommand };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-ClUqkhvR.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-D90bexh4.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";
@@ -2556,7 +2556,7 @@ The wrapper is what carries \`match\`, and what lets \`voltro doctor\` report wh
2556
2556
  return [
2557
2557
  "",
2558
2558
  "// Browser-side Sentry (frontend half of the deep integration). Errors +",
2559
- "// browser rpc spans flow to Sentry, sharing the server transactions' trace.",
2559
+ "// page-owned rpc spans linked to their server traces flow to Sentry.",
2560
2560
  n === void 0 ? `await initSentryBrowser(${JSON.stringify(r)})` : `await initSentryBrowser({ ...${JSON.stringify(r)}, ...(await __sentryOptions()) })`
2561
2561
  ];
2562
2562
  }, Fo = (e) => e.env ? ["import './env.public'"] : [], Io = (e) => {
@@ -1,2 +1,2 @@
1
- import { C as e, T as t, m as n } from "./webDev-CBN9KkyQ.js";
1
+ import { C as e, T as t, m as n } from "./webDev-CYjhmOTZ.js";
2
2
  export { n as loadConfig, e as tryRunWebServe, t as walkPagesTree };
@@ -222,7 +222,7 @@ createVerifier({ secret: [process.env.WEBHOOK_SECRET, process.env.WEBHOOK_SECRET
222
222
  ...t === void 0 ? {} : { payload: t }
223
223
  };
224
224
  }, S = u({ scope: "voltro:webhooks" }), C = ["--out", "--name"], w = async (e) => {
225
- let { walk: t, loadDiscovered: n } = await import("./dev-Dff_pdik.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-BpMSAjMk.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
226
226
  return r((await n(await t(e), { root: e })).events.map((e) => ({
227
227
  file: e.file,
228
228
  descriptor: e.descriptor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.60.0",
3
+ "version": "0.62.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",
@@ -898,6 +898,24 @@
898
898
  "title": "Sentry no longer reports declared failures by default",
899
899
  "kind": "manual"
900
900
  },
901
+ {
902
+ "version": "0.61.0",
903
+ "id": "0.61.0/01_rpc_errors_have_kinds",
904
+ "title": "`RpcError` now carries an explicit failure kind",
905
+ "kind": "manual"
906
+ },
907
+ {
908
+ "version": "0.61.0",
909
+ "id": "0.61.0/02_sentry_rpc_spans_join_page",
910
+ "title": "Sentry rpc spans now join the active browser transaction",
911
+ "kind": "manual"
912
+ },
913
+ {
914
+ "version": "0.62.0",
915
+ "id": "0.62.0/01_finish_timeout_uses_durable_cancellation",
916
+ "title": "Finish timeouts use durable, once-only cancellation",
917
+ "kind": "manual"
918
+ },
901
919
  {
902
920
  "version": "0.7.0",
903
921
  "id": "0.7.0/01_row-filter-load-failure",
@@ -944,24 +962,24 @@
944
962
  "@effect/platform-node": "^0.108.0",
945
963
  "@effect/sql": "^0.52.0",
946
964
  "@effect/workflow": "^0.19.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",
965
+ "@voltro/ai": "0.62.0",
966
+ "@voltro/cache": "0.62.0",
967
+ "@voltro/client": "0.62.0",
968
+ "@voltro/content": "0.62.0",
969
+ "@voltro/data-transfer": "0.62.0",
970
+ "@voltro/database": "0.62.0",
971
+ "@voltro/env": "0.62.0",
972
+ "@voltro/kv": "0.62.0",
973
+ "@voltro/logger": "0.62.0",
974
+ "@voltro/plugin-auth": "0.62.0",
975
+ "@voltro/plugin-broadcast": "0.62.0",
976
+ "@voltro/plugin-mail": "0.62.0",
977
+ "@voltro/plugin-storage": "0.62.0",
978
+ "@voltro/plugin-webhooks": "0.62.0",
979
+ "@voltro/protocol": "0.62.0",
980
+ "@voltro/runtime": "0.62.0",
981
+ "@voltro/serverless": "0.62.0",
982
+ "@voltro/workflow": "0.62.0",
965
983
  "chokidar": "^5.0.0",
966
984
  "ioredis": "^5.11.1",
967
985
  "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.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. |
752
+ | **What's new in 0.62.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. |
@@ -832,7 +832,7 @@ each plugin's own README.
832
832
  | row-history | `node_modules/@voltro/cli/templates/agent-docs/plugins/row-history.md` (or `node_modules/@voltro/plugin-row-history/README.md`) | Full row history + time-travel. audit() records who/when; row-history records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
833
833
  | scim | `node_modules/@voltro/cli/templates/agent-docs/plugins/scim.md` (or `node_modules/@voltro/plugin-scim/README.md`) | SCIM 2.0 provisioning — Users + Groups REST endpoints so an enterprise IdP (Okta, Entra, OneLogin) can create / update / deactivate users in your app. |
834
834
  | search | `node_modules/@voltro/cli/templates/agent-docs/plugins/search.md` (or `node_modules/@voltro/plugin-search/README.md`) | Keep an external search index (Typesense / Meilisearch / Algolia) in sync with your tables via the ChangeEvent tap, query it tenant-scoped through a typed action + hook. |
835
- | sentry | `node_modules/@voltro/cli/templates/agent-docs/plugins/sentry.md` (or `node_modules/@voltro/plugin-sentry/README.md`) | Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and opt-in performance traces. |
835
+ | sentry | `node_modules/@voltro/cli/templates/agent-docs/plugins/sentry.md` (or `node_modules/@voltro/plugin-sentry/README.md`) | Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and controlled performance traces. |
836
836
  | soft-delete | `node_modules/@voltro/cli/templates/agent-docs/plugins/soft-delete.md` (or `node_modules/@voltro/plugin-soft-delete/README.md`) | The softDelete() schema mixin — deletedAt / deletedBy columns, delete() redirected to an UPDATE, default reads filtered, hardDelete() bypass. |
837
837
  | sso-saml | `node_modules/@voltro/cli/templates/agent-docs/plugins/sso-saml.md` (or `node_modules/@voltro/plugin-sso-saml/README.md`) | Enterprise SAML 2.0 SSO — SP-initiated login, ACS assertion consumer, SP metadata. Signature verification via @node-saml/node-saml; framework session minting built in. |
838
838
  | storage | `node_modules/@voltro/cli/templates/agent-docs/plugins/storage.md` (or `node_modules/@voltro/plugin-storage/README.md`) | File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / MinIO (R2 and GCS via their S3 interop) / Azure / database / filesystem / memory providers, presigned URLs, a dashboard browser. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
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. |
12
+ | **What's new in 0.62.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. |
@@ -92,7 +92,7 @@ each plugin's own README.
92
92
  | row-history | `node_modules/@voltro/cli/templates/agent-docs/plugins/row-history.md` (or `node_modules/@voltro/plugin-row-history/README.md`) | Full row history + time-travel. audit() records who/when; row-history records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
93
93
  | scim | `node_modules/@voltro/cli/templates/agent-docs/plugins/scim.md` (or `node_modules/@voltro/plugin-scim/README.md`) | SCIM 2.0 provisioning — Users + Groups REST endpoints so an enterprise IdP (Okta, Entra, OneLogin) can create / update / deactivate users in your app. |
94
94
  | search | `node_modules/@voltro/cli/templates/agent-docs/plugins/search.md` (or `node_modules/@voltro/plugin-search/README.md`) | Keep an external search index (Typesense / Meilisearch / Algolia) in sync with your tables via the ChangeEvent tap, query it tenant-scoped through a typed action + hook. |
95
- | sentry | `node_modules/@voltro/cli/templates/agent-docs/plugins/sentry.md` (or `node_modules/@voltro/plugin-sentry/README.md`) | Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and opt-in performance traces. |
95
+ | sentry | `node_modules/@voltro/cli/templates/agent-docs/plugins/sentry.md` (or `node_modules/@voltro/plugin-sentry/README.md`) | Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and controlled performance traces. |
96
96
  | soft-delete | `node_modules/@voltro/cli/templates/agent-docs/plugins/soft-delete.md` (or `node_modules/@voltro/plugin-soft-delete/README.md`) | The softDelete() schema mixin — deletedAt / deletedBy columns, delete() redirected to an UPDATE, default reads filtered, hardDelete() bypass. |
97
97
  | sso-saml | `node_modules/@voltro/cli/templates/agent-docs/plugins/sso-saml.md` (or `node_modules/@voltro/plugin-sso-saml/README.md`) | Enterprise SAML 2.0 SSO — SP-initiated login, ACS assertion consumer, SP metadata. Signature verification via @node-saml/node-saml; framework session minting built in. |
98
98
  | storage | `node_modules/@voltro/cli/templates/agent-docs/plugins/storage.md` (or `node_modules/@voltro/plugin-storage/README.md`) | File storage behind one StorageService — public objects served direct from the bucket/CDN, private objects gated by an access policy + per-object grants. S3 / MinIO (R2 and GCS via their S3 interop) / Azure / database / filesystem / memory providers, presigned URLs, a dashboard browser. |
@@ -660,7 +660,7 @@
660
660
  {
661
661
  "slug": "sentry",
662
662
  "title": "Sentry",
663
- "description": "Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and opt-in performance traces.",
663
+ "description": "Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and controlled performance traces.",
664
664
  "pkg": "@voltro/plugin-sentry",
665
665
  "doc": "plugins/sentry.md",
666
666
  "module": "agent-docs/plugins/sentry.md"
@@ -1303,6 +1303,38 @@ export default (input: { message: string }) =>
1303
1303
 
1304
1304
  `streamText` emits `AgentEvent` elements. The `_tag`s are: `token` (text delta), `reasoning` (the model's thinking, separate from the answer), `toolCall`, `toolResult`, `source` (a cited RAG/web source — `url` or `document` variant), `file` (an inline file the model produced, usually an image: `mediaType` + base64 `data`), `message`, `error` (carries `retryable`), and `done`. The stream NEVER fails — an upstream error arrives as a terminal `error` event.
1305
1305
 
1306
+ ## Smooth Streaming
1307
+
1308
+ Provider chunks are transport artifacts: one provider may send a word at a time while another releases a whole paragraph in a burst. Opt into server-side semantic smoothing when the UI should receive predictable word or line chunks:
1309
+
1310
+ ```ts
1311
+ streamText({
1312
+ prompt: input.message,
1313
+ smooth: {
1314
+ chunking: 'word', // default; alternatively 'line'
1315
+ delayMs: 10, // default; null rechunks without adding delay
1316
+ },
1317
+ })
1318
+ ```
1319
+
1320
+ `smooth` is framework-owned and deliberately narrower than the AI SDK's experimental transform surface. It smooths both text and reasoning, flushes pending content before tool/source/file/error/done events, and shares the stream's abort signal. Omit it or set `smooth: false` to preserve provider-native chunks and latency.
1321
+
1322
+ With `resumableStreamText`, put `smooth` inside `options`. Smoothing happens before `AgentEvent` conversion and before the append-only journal, so a reconnect replays the exact same `SeqEvent` boundaries seen live:
1323
+
1324
+ ```ts
1325
+ resumableStreamText({
1326
+ streamId: input.streamId,
1327
+ store,
1328
+ options: {
1329
+ prompt: input.message,
1330
+ smooth: { chunking: 'word', delayMs: 10 },
1331
+ },
1332
+ fromSeq: input.fromSeq,
1333
+ })
1334
+ ```
1335
+
1336
+ Smoothing changes only text/reasoning boundaries and timing. Content, usage, finish reason, tool ids, and cost accounting stay unchanged. The client should still batch rendering (for example, at most once per animation frame); server smoothing normalizes semantics, it does not replace UI backpressure.
1337
+
1306
1338
  ## Cancel, retry
1307
1339
 
1308
1340
  A "Stop" button needs to end an in-flight run out of band. Pass a `streamId` and call `cancelStream(streamId)` from a separate action/mutation — it aborts the provider call (token spend stops) and the stream ends on a terminal, non-retryable `cancelled` error.
@@ -1220,13 +1220,21 @@ try {
1220
1220
  }
1221
1221
  ```
1222
1222
 
1223
- **Trace id for debugging.** An error caught from `useMutation` / `useAction` carries a **non-enumerable `__voltroTraceId`** — the bridge to the server logs for that exact call:
1223
+ **Trace id for debugging.** An error caught from `useMutation` / `useAction`
1224
+ carries the **non-enumerable client-span id `__voltroTraceId`**:
1224
1225
 
1225
1226
  ```ts
1226
1227
  const traceId = (err as { __voltroTraceId?: string }).__voltroTraceId
1227
- // `voltro logs --trace <traceId>` to see the server-side span
1228
+ // If the server accepted the call: `voltro logs --trace <traceId>`
1228
1229
  ```
1229
1230
 
1231
+ For a declared handler error, the server necessarily accepted the call and the
1232
+ id joins to its span. For a transport error it may not: the connection can fail
1233
+ before the request arrived or after the handler ran but before its response
1234
+ returned. Use `kind` from
1235
+ [`useOnRpcError`](/docs/ui/client-utilities/use-on-rpc-error) for that distinction;
1236
+ the presence of the id alone proves only that the client span existed.
1237
+
1230
1238
  `errorTag` lives in `@voltro/protocol` rather than in the client for a reason
1231
1239
  worth knowing before you decide where your own error handling goes: **`_tag` is a
1232
1240
  wire concept, and protocol owns the wire.** So a shared error handler in a package
@@ -213,6 +213,12 @@ Trace continuity is **automatic and on by default** in `voltro dev` — a buffer
213
213
  - **api → api is automatic over HTTP.** An action or handler that calls another api via the framework `HttpClient` sends a W3C `traceparent` header; an inbound `*.webhook.tsx` route (an incoming HTTP handler) **continues** the caller's trace. So frontend → api Y → api Z (via webhook / HTTP) is **one** trace.
214
214
  - **Every log line carries it.** Handlers stamp `fields.traceId` onto each log line they emit, so [`voltro logs --trace <id>`](/docs/observability/cli) returns the whole chain across hops, in order.
215
215
 
216
+ The client span exists before the transport proves delivery. If a connection
217
+ fails, its `traceId` can therefore have no matching server span — or the server
218
+ may have completed the handler and lost only the response. In rpc-error policy,
219
+ use [`RpcError.kind`](/docs/ui/client-utilities/use-on-rpc-error), never the mere
220
+ presence of `traceId`, to distinguish transport failures from handler failures.
221
+
216
222
  ```
217
223
  client.mutation.placeOrder traceId=4bf92f35…
218
224
  └─ mutation.placeOrder traceId=4bf92f35… (api Y, ctx.request.traceId)
@@ -259,7 +265,7 @@ This is the fastest way to answer "where did this error come from?" — far fast
259
265
 
260
266
  The same trace continuity flows to a vendor APM when you install a deep-observability plugin — no `OTEL_*` env. Each contributes an OTel span-processor to the framework's tracer via `contributeObservability`, so the vendor runs as a **consumer** of the existing tracer (the in-app Traces panel keeps working) and receives the identical spans:
261
267
 
262
- - [`@voltro/plugin-sentry`](/docs/plugins/sentry) — every mutation/query/action error becomes a Sentry issue correlated to its `trace_id` + `span_id`, with the request's log lines as breadcrumbs; opt-in performance traces (`traces: true`).
268
+ - [`@voltro/plugin-sentry`](/docs/plugins/sentry) — every mutation/query/action error becomes a Sentry issue correlated to its `trace_id` + `span_id`, with the request's log lines as breadcrumbs; server performance traces are on by default (`traces: false` disables them). The browser integration keeps rpc markers inside the sampled page/navigation transaction and links each marker to its Effect/server trace.
263
269
  - [`@voltro/plugin-datadog`](/docs/plugins/datadog) — framework spans → the Datadog Agent's OTLP receiver (`traces: true`), plus log forwarding with `dd.trace_id` correlation.
264
270
 
265
271
  See [Observability › Routing traces to a vendor](/docs/observability/overview#routing-traces-to-a-vendor) for the contribution surface + the sampler caveat.
@@ -286,7 +292,9 @@ _Inspect traces and logs from the shell — flags, the --trace <id> end-to-end c
286
292
 
287
293
  ## `voltro logs`
288
294
 
289
- Every `voltro dev` / `voltro start` instance buffers the last **2000** server-side `@voltro/logger` records **and** every browser console line that the dev console bridge forwarded.
295
+ Every `voltro dev` / `voltro start` instance buffers the last **2000** server-side `@voltro/logger` records. During `voltro dev`, the browser console bridge also forwards browser console lines into that buffer.
296
+
297
+ The browser-to-inspect bridges are deliberately limited to development builds. Setting `localStorage.framework_debug = '1'` in a production bundle enables the local `[fw …]` connection timeline only; it never starts `clientLog` POSTs or an inspect `EventSource`. In development, both diagnostic transports are single-flight, rate-limited, and bounded: permanent `4xx` responses disable them for the page, while transient failures use exponential backoff and open a circuit after five consecutive failures. The SSE relay probes status and content type before creating an `EventSource`, then explicitly closes a failed source before a bounded retry.
290
298
 
291
299
  ```sh
292
300
  voltro logs # last 100 from every running process
@@ -624,6 +632,13 @@ proxy path, cleared the moment the stream opens; the proxy moves it into an
624
632
  `Authorization` header. Nothing about the token ever appears in a URL, and the
625
633
  app receives a header like any other caller.
626
634
 
635
+ That dashboard proxy is also why a production app page must not open the
636
+ inspect stream merely because `localStorage.framework_debug` is set. The app
637
+ page has no bearer-capable `EventSource` transport and no authority to mint the
638
+ dashboard cookie. Voltro therefore keeps its in-page console and SSE relays
639
+ development-only; the persisted production flag affects local framework
640
+ console output, not inspect traffic.
641
+
627
642
  ## In the dashboards
628
643
 
629
644
  Both dashboards unwrap `.data` in their fetch layer and keep the envelope. A
@@ -1,6 +1,6 @@
1
1
  # Sentry
2
2
 
3
- > Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and opt-in performance traces.
3
+ > Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and controlled performance traces.
4
4
 
5
5
 
6
6
 
@@ -9,9 +9,9 @@
9
9
  <!-- source: en/plugins/sentry.md -->
10
10
  ## Sentry
11
11
 
12
- _Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and opt-in performance traces._
12
+ _Deep Sentry integration — errors correlated to the distributed trace, breadcrumbs from the log sink, and controlled performance traces._
13
13
 
14
- `@voltro/plugin-sentry` is the deep Sentry integration: every mutation/query/action failure becomes a Sentry issue **correlated to the active distributed trace** (trace_id + span_id) and carrying the request's recent log lines as breadcrumbs. Opt into performance traces and the framework's spans flow to Sentry as transactions.
14
+ `@voltro/plugin-sentry` is the deep Sentry integration: every mutation/query/action failure becomes a Sentry issue **correlated to the active distributed trace** (trace_id + span_id) and carrying the request's recent log lines as breadcrumbs. The framework's spans flow to Sentry as transactions by default.
15
15
 
16
16
  Uses the official `@sentry/node` SDK in **OpenTelemetry-consumer mode** — it consumes the framework's existing tracer (it never becomes the global provider), so the in-app [Traces dashboard](/docs/observability/distributed-tracing) stays intact.
17
17
 
@@ -29,7 +29,7 @@ export default {
29
29
  // dsn defaults to SENTRY_DSN; without a valid DSN the plugin is a no-op.
30
30
  environment: 'production', // or SENTRY_ENVIRONMENT
31
31
  release: '1.4.2', // or SENTRY_RELEASE
32
- traces: true, // route the framework's spans → Sentry (default off)
32
+ traces: true, // route the framework's spans → Sentry (default on)
33
33
  tracesSampleRate: 1.0,
34
34
  profiling: true, // continuous CPU profiler via @sentry/profiling-node
35
35
  }),
@@ -46,7 +46,7 @@ export default {
46
46
  | `dsn` | `string` | `SENTRY_DSN` env | No valid DSN → the plugin is inert (no interceptors registered). |
47
47
  | `environment` | `string` | `SENTRY_ENVIRONMENT` env | Sentry `environment` tag + `deployment.environment` resource attribute. |
48
48
  | `release` | `string` | `SENTRY_RELEASE` env | Release-health + source-map matching; also `service.version`. |
49
- | `traces` | `boolean` | `false` | Route the framework's OTel spans to Sentry as transactions. Off → errors + breadcrumbs only. |
49
+ | `traces` | `boolean` | `true` | Route the framework's OTel spans to Sentry as transactions. Off → errors + breadcrumbs only. |
50
50
  | `tracesSampleRate` | `number` | `1.0` | Applied by the Sentry client (not a tracer sampler). Only relevant with `traces`. |
51
51
  | `profiling` | `boolean` | `false` | Continuous CPU profiler via `@sentry/profiling-node` (lazy; warns if not installed). |
52
52
  | `profilesSampleRate` | `number` | `1.0` | Only relevant with `profiling`. |
@@ -73,15 +73,15 @@ This guarantees the error links to its trace in Sentry **regardless** of the Eff
73
73
 
74
74
  The framework log sink feeds a bounded **per-trace** ring (capped per-trace + LRU + TTL), keyed on each log line's `traceId`. On an error the trace's breadcrumbs are attached to the event; traces that never error are reaped by the TTL. So a Sentry issue shows the exact log lines leading up to it — for that request only.
75
75
 
76
- ## Traces (opt-in `traces:true`)
76
+ ## Server traces
77
77
 
78
78
  The plugin contributes a `SentrySpanProcessor` to the framework's tracer (via the plugin observability surface — see [Observability › Routing traces to a vendor](/docs/observability/overview#routing-traces-to-a-vendor)). Every framework span becomes a Sentry transaction, sharing the same trace id the errors carry — so in Sentry you land on an error inside its full distributed trace.
79
79
 
80
80
  We deliberately do **not** install a `SentrySampler` as the tracer's sampler: that would gate recording for the WHOLE tracer and blind the framework's own in-app Traces buffer. The framework keeps its always-on sampler; `tracesSampleRate` is applied by the Sentry client.
81
81
 
82
- ## Full-stack browser backend in ONE Sentry trace
82
+ ## Correlating the browser page with server traces
83
83
 
84
- The plugin ships a browser half at `@voltro/plugin-sentry/web` so a click in the UI and the server work it triggers appear as **one trace** in Sentry. Turn it on in the web app's `app.config.ts`:
84
+ The plugin ships a browser half at `@voltro/plugin-sentry/web`. It records one page-load or navigation transaction with rpc child spans, while each rpc child links to the Effect trace continued by the server. This avoids a transaction and network envelope per rpc call without losing the connection between browser and server work. Turn it on in the web app's `app.config.ts`:
85
85
 
86
86
  ```ts
87
87
  // apps/web/app.config.ts
@@ -94,6 +94,8 @@ export default {
94
94
  environment: 'production',
95
95
  release: '1.4.2', // MUST match the api's `release` for unified release health
96
96
  tracesSampleRate: 1.0,
97
+ browserTracing: true,
98
+ rpcSpans: true,
97
99
  },
98
100
  }
99
101
  ```
@@ -102,7 +104,22 @@ The framework's generated entry calls `initSentryBrowser()` before mount (no cod
102
104
 
103
105
  What it does:
104
106
  - **Inits `@sentry/react`** with `browserTracingIntegration` — page loads + client navigations become Sentry transactions, and unhandled errors / promise rejections are captured by the SDK's default global handlers.
105
- - **Unifies the trace.** Every framework rpc call (`useMutation`/`useSubscription`/`useAction`) already wraps itself in an Effect client span and propagates that span's W3C `traceId`+`spanId` to the api (the api's `sentryPlugin({ traces:true })` turns the server spans into Sentry transactions under that same trace id). The browser half subscribes to the framework's client-trace bus and emits a browser-side Sentry span into the **same** trace, parented at the client span so Sentry's trace view shows the browser hop and the server transactions in one tree.
107
+ - **Keeps rpc spans page-owned.** Every framework rpc call (`useMutation`/`useSubscription`/`useAction`) already wraps itself in an Effect client span and propagates that span's W3C `traceId`+`spanId` to the api. The browser half adds a child to the currently active page/navigation transaction and attaches the Effect/server trace as a span link. If no browser transaction is active, it emits no rpc root transaction.
108
+ - **Honours sampling.** `tracesSampleRate` decides whether the page/navigation root is recorded; its rpc children inherit that decision. The bridge does not inject a sampled parent decision.
109
+
110
+ Browser trace controls:
111
+
112
+ | Option | Default | Effect |
113
+ |---|---|---|
114
+ | `tracesSampleRate` | `1.0` | Samples page-load/navigation transactions and therefore their rpc children. Error reporting is independent. |
115
+ | `browserTracing` | `true` | Installs page-load/navigation instrumentation. `false` also defaults `rpcSpans` to off. |
116
+ | `rpcSpans` | same as `browserTracing` | Adds rpc children to an active browser transaction. `false` keeps browser errors and page traces but removes the rpc bridge. Set it to `true` with `browserTracing:false` only if your app creates active Sentry transactions itself. |
117
+
118
+ At browser boot the plugin prints the effective values, including the mode:
119
+
120
+ ```text
121
+ [@voltro/plugin-sentry] browser active { tracesSampleRate: 1, browserTracing: true, rpcSpans: true, rpcSpanMode: 'page-child-with-server-link' }
122
+ ```
106
123
 
107
124
  > **Manual wiring** (no app.config field, e.g. an app that builds its own entry): call it yourself once at boot —
108
125
  > ```ts
@@ -157,9 +174,9 @@ in deployment env. Setting one as a deployment variable deploys cleanly and does
157
174
  nothing; `voltro start` warns when it finds one, naming whether the bundle
158
175
  carries that key at all.
159
176
 
160
- The two halves are independent: the api plugin alone gives you server traces + errors; add the web half for the browser hop. For the full frontend→backend waterfall, run **both** and both are on by default, so an app with the plugin on the api and `@voltro/plugin-sentry` as a dependency of the web app gets the whole trace from one `SENTRY_DSN` plus one `VOLTRO_PUBLIC_SENTRY_DSN`. Give both halves the same `release`.
177
+ The two halves are independent: the api plugin alone gives you server traces + errors; add the web half for page performance, browser errors, rpc markers, and links back to the server traces. Both tracing halves are on by default, so an app with the plugin on the api and `@voltro/plugin-sentry` as a dependency of the web app gets server transactions plus one sampled browser transaction per page/navigation from one `SENTRY_DSN` plus one `VOLTRO_PUBLIC_SENTRY_DSN`. Give both halves the same `release`.
161
178
 
162
- **Traces default to on, at `tracesSampleRate: 1.0`.** That is the coherent default rather than a generous one: the browser half already sampled every page load, navigation and rpc call at 1.0, so with the api half off each of those spans was emitted, paid for, and belonged to a trace with no server side a browser hop hanging off nothing. The rate is named on the boot line (`sentry active traces=true tracesSampleRate=1`) so a first boot shows what it is about to send; lower it with `tracesSampleRate`, or set `traces: false` to keep errors and breadcrumbs only.
179
+ **Traces default to on, at `tracesSampleRate: 1.0`.** That is explicit, not a production recommendation. Lower the rate independently on api and web to fit the trace budget. Set api `traces:false` to keep server errors and breadcrumbs only; set web `browserTracing:false` to keep browser error capture without automatic performance transactions; or use web `rpcSpans:false` to keep page performance while omitting rpc child spans.
163
180
 
164
181
  ## Source maps (every bundle, browser and server)
165
182
 
@@ -344,7 +361,7 @@ Once the two halves are wired, most errors are captured **with no per-call code*
344
361
  | rpc handler throw (mutation/query/action) | ✅ auto | server interceptor → Sentry, trace-correlated + breadcrumbs |
345
362
  | Unhandled browser error / promise rejection | ✅ auto | `@sentry/react` global handlers (after `initSentryBrowser`) |
346
363
  | **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. |
364
+ | **React render error, during SSR** — six sites: `ssr-shell`, `spa-layout-shell`, `render` (incl. a loader throw), `not-found-render`, `ppr-holes`, `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`. The last three are `warn`-level — the request still serves something — and are reported anyway: quieter is not less real, and nothing else can see them. Needs `SENTRY_DSN` on the **web** deployment. |
348
365
  | **Loader failure** (page/layout `loader` reject) | ✅ auto | same route bus path |
349
366
  | Manual try/catch (event handler, async effect, anywhere) | ✋ one call | `reportClientError(error, context?)` |
350
367
  | **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) |
@@ -1194,10 +1194,20 @@ const { status, reportSuccess } = useConnectionStatus('app')
1194
1194
  `status` is `'connected' | 'degraded' | 'offline'`. `offline` is browser-reported
1195
1195
  (`navigator.onLine` plus its `online` / `offline` events) — reliable for "the
1196
1196
  network is gone", and it wins over `degraded`. `degraded` means an rpc on that
1197
- api failed and nothing has succeeded since; it is real evidence, not a guess.
1198
- Coming back online clears the failure count, since failures counted during an
1199
- offline window *are* that window. `reportSuccess()` clears it eagerly from a
1200
- place that knows a call went through.
1197
+ api failed with `kind: 'transport'` and no later call has proved the server
1198
+ reachable. Declared handler errors do **not** colour the connection; if already
1199
+ degraded, a handler reply clears it because the server demonstrably answered.
1200
+ Local client and unknown failures leave the connection state unchanged. Any rpc
1201
+ success also clears the failure count automatically. Coming back online clears
1202
+ it too, since failures counted during an offline window *are* that window.
1203
+ `reportSuccess()` remains the explicit escape hatch for another signal that
1204
+ proves a round trip succeeded.
1205
+
1206
+ Use this hook to **render aggregate connection state**. To classify the failure
1207
+ that is firing right now — for example, to avoid a data-error toast in the same
1208
+ tick the socket drops — branch on `event.kind` inside
1209
+ [`useOnRpcError`](/docs/ui/client-utilities/use-on-rpc-error). React state updates
1210
+ after the event and therefore cannot classify that event retroactively.
1201
1211
 
1202
1212
  Also returned: `online`, `failureCount`, `lastFailureAt`. There is deliberately
1203
1213
  no polling ping — the framework does not call the server just to colour an
@@ -1578,16 +1588,34 @@ import { useOnRpcError } from '@voltro/client'
1578
1588
  import { errorTag } from '@voltro/protocol'
1579
1589
 
1580
1590
  useOnRpcError('app', useCallback((e) => {
1591
+ if (e.kind === 'transport') {
1592
+ showConnectionLost()
1593
+ return
1594
+ }
1581
1595
  if (errorTag(e.error) === 'Unauthenticated') redirectToSignIn()
1582
1596
  }, []))
1583
1597
  ```
1584
1598
 
1585
- The event is `{ source, tag, error, traceId? }`. `source` is
1599
+ The event is `{ source, tag, error, kind, traceId? }`. `source` is
1586
1600
  `'mutation' | 'action' | 'subscription'`, so both unary calls and stream
1587
1601
  failures arrive here. `error` is the raw value — a `Schema.TaggedError`, a plain
1588
1602
  `Error`, or anything else that was thrown; `errorTag(err)` reads `_tag` off
1589
- tagged errors and returns `undefined` otherwise. `traceId` is the same id the
1590
- server logged, so a handler can point at `voltro logs --trace <id>`.
1603
+ tagged errors and returns `undefined` otherwise.
1604
+
1605
+ `kind` is `'transport' | 'handler' | 'client' | 'unknown'`:
1606
+
1607
+ - `transport` — @effect/rpc could not complete the exchange. Use this branch for
1608
+ connection banners and to suppress misleading data-error toasts.
1609
+ - `handler` — the rpc returned a declared failure, such as `Unauthenticated`.
1610
+ - `client` — framework work around the call failed locally.
1611
+ - `unknown` — a combined or opaque operation could not be classified honestly.
1612
+
1613
+ `traceId` identifies the **client call span**. When the server accepted the
1614
+ request, it propagates the same id and `voltro logs --trace <id>` can join the
1615
+ call to server logs. A transport error can happen before the server saw the
1616
+ request, or after it handled the request but before the response returned, so a
1617
+ `traceId` on `kind: 'transport'` is not proof that server logs exist. For the
1618
+ same reason the event deliberately has no `reachedServer` boolean.
1591
1619
 
1592
1620
  Two things to know. The hook re-subscribes whenever the listener **reference**
1593
1621
  changes, so define it with `useCallback` or at module scope unless you want that.
@@ -1598,9 +1626,9 @@ per api name.
1598
1626
  is also published on the client error bus, so a reporter — the Sentry browser
1599
1627
  integration, or anything wired with `subscribeClientErrors` — already sees it,
1600
1628
  under `source: 'rpc.mutation' | 'rpc.action' | 'rpc.subscription'` with the rpc
1601
- tag and `traceId` in `context`. This hook is for cross-cutting *policy* (redirect
1602
- on `Unauthenticated`, toast on a network failure), not for telemetry; bridging
1603
- the two by hand now reports twice.
1629
+ tag, `rpcErrorKind`, and optional `traceId` in `context`. This hook is for
1630
+ cross-cutting *policy* (redirect on `Unauthenticated`, toast on a network
1631
+ failure), not for telemetry; bridging the two by hand now reports twice.
1604
1632
 
1605
1633
  Everything is published, including a reconnect storm. If that is too loud for
1606
1634
  your reporter, gate it in the subscriber — which failures are worth an event is
@@ -1,4 +1,4 @@
1
- # What's new in 0.60.0
1
+ # What's new in 0.62.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,54 +9,14 @@ BREAKING entries name a codemod; run `voltro update` to apply it.
9
9
 
10
10
  ### ⚠ BREAKING
11
11
 
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.
12
+ - **@voltro/workflow, @voltro/cli, @voltro/voltro** — `timeouts.finish` now filters running rows by the declared workflow, durably cancels each run before notification, and emits `onFailure` only for the successful terminal transition. Finish-timeout reports carry the run's stable `runId`, `executionId`, and original payload; handler-start logs expose the returned status instead of unconditionally claiming a new start.
13
13
 
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 on — arrived 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
-
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
-
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
-
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
-
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
-
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
-
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.
27
-
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.
29
-
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).
14
+ Low-level adapters that construct `AdmissionDrainDeps` must update `cancelRun` to accept the run object and return whether it made the terminal transition, and return the run identity, workflow name, and payload from `listRunningRuns`. Ordinary workflow declarations need no source change; `voltro update` prints the adapter migration only when the project references this low-level surface.
31
15
 
32
16
  ### Added
33
17
 
34
- - **@voltro/cli, @voltro/plugin-sentry** — **The web server process now initialises Sentry and reports its own errors.**
35
-
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.
37
-
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.
39
-
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.
41
-
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.
45
-
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.
18
+ - **@voltro/ai** — `streamText` and `resumableStreamText` now accept an opt-in `smooth` configuration for word- or line-based text and reasoning chunks. Smoothing runs before resumable journaling, preserves provider-native behavior when omitted, and shares the stream abort signal so cancellation also stops a pending smoothing delay.
47
19
 
48
20
  ### Fixed
49
21
 
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.**
51
-
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.
53
-
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:
57
-
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.
59
-
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.
61
-
62
- Verified against a real `voltro serve` under `NODE_ENV=production`: 35 records, zero framework lines that are not JSON.
22
+ - **@voltro/web** Production `framework_debug` now enables local framework diagnostics without opening browser-to-inspect HTTP or SSE transports. Development log relays are single-flight and rate-limited, classify permanent response failures, back off transient failures, expose dropped-log counts, and open a page-scoped circuit after repeated failure; the SSE relay also probes the endpoint before creating an `EventSource` and closes failed sources before retrying.
@@ -1459,7 +1459,7 @@ timeouts: { start: '1 hour', finish: '10 minutes' }
1459
1459
  - **`start`** bounds how long a start may sit in the admission queue, measured from the **first** arrival in its group. A debounced run that never gets a quiet moment is a job that silently did not happen.
1460
1460
  - **`finish`** bounds the run itself once admitted, and also tightens the crash backstop on its concurrency slot.
1461
1461
 
1462
- Both expire into the same path as an exhausted retry: the run is recorded failed and `onFailure` fires. Declaring `timeouts.start` on a workflow that cannot defer is a boot error — it would never fire.
1462
+ Both reach the same `onFailure` signal as an exhausted retry. `timeouts.start` removes the queued intent before a run exists. `timeouts.finish` durably changes the running row to `cancelled`; only the sweep call that wins that terminal transition emits `onFailure`. Seeing the same old row again does not emit another report. Declaring `timeouts.start` on a workflow that cannot defer is a boot error — it would never fire.
1463
1463
 
1464
1464
  ## onFailure — the signal that replaces the sweep
1465
1465
 
@@ -1494,10 +1494,15 @@ export const narrationFailed = workflow({
1494
1494
  failedAt: Schema.Number,
1495
1495
  }),
1496
1496
  success: Schema.Void,
1497
- idempotencyKey: ({ runId, failedAt }) => `failed:${runId ?? 'none'}:${failedAt}`,
1497
+ idempotencyKey: ({ workflow, runId, failedAt }) =>
1498
+ runId === null ? `failed-before-run:${workflow}:${failedAt}` : `failed-run:${runId}`,
1498
1499
  })
1499
1500
  ```
1500
1501
 
1502
+ For failures of an existing run — including `timeouts.finish` — `runId`, `executionId`, and the original `payload` are populated. Use `runId` as the stable idempotency identity. Pre-run failures (`timeouts.start` and a queued start whose workflow was removed) have both IDs set to `null`; they are emitted once when their durable pending row is consumed.
1503
+
1504
+ The framework logs the result of starting the handler as `workflow onFailure start resolved`. Its metadata includes `startStatus`, `handlerRunId`, and `handlerExecutionId`, so a queued, dropped, or singleton-skipped start is not mislabeled as newly started.
1505
+
1501
1506
  There is no `onFailure` for an `onFailure` — a handler that fails is logged and not re-notified, because the alternative is one run per failure per level with no floor.
1502
1507
 
1503
1508
  ## encryptSteps