@voltro/cli 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/apiBuild-B7NgSMhX.js +2 -0
  3. package/dist/{apiBuild-B_iH24qf.js → apiBuild-DF0f69Gf.js} +2 -2
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-DUdi6F_Q.js → build-LC_IJaHL.js} +7 -7
  6. package/dist/{checkCommand-B2RWrpWi.js → checkCommand-DBpTSK8d.js} +1 -1
  7. package/dist/{checkCommand-DWL2_PPR.js → checkCommand-DDxwJCja.js} +2 -2
  8. package/dist/{codegenCommand-DkjoJ30Q.js → codegenCommand-LjxfK4Am.js} +2 -2
  9. package/dist/{codemodRunner-sFuWDETH.js → codemodRunner-Cnnz4-I7.js} +19 -4
  10. package/dist/{commands-DoTClnSk.js → commands-CbQcuWsE.js} +20 -20
  11. package/dist/{dashboardCommand-Bro7Jwts.js → dashboardCommand-CAHjCe_9.js} +3 -3
  12. package/dist/{dataCommand-Rj57y1vg.js → dataCommand-CrqvbVOv.js} +143 -133
  13. package/dist/{dev-D4l_YOzI.js → dev-B2rZfqxw.js} +2 -2
  14. package/dist/{dev-DuqBJC7g.js → dev-D0E5x2Ue.js} +1 -1
  15. package/dist/doctorCommand-CdsctSwg.js +2 -0
  16. package/dist/{doctorCommand-TmZTAepc.js → doctorCommand-Ct_3BHgd.js} +226 -216
  17. package/dist/{dormancyCommand-CUKhRmGG.js → dormancyCommand-D1DKnYce.js} +1 -1
  18. package/dist/{embeddingsCommand-Db-iQM3l.js → embeddingsCommand-1sZRBKlM.js} +1 -1
  19. package/dist/{envCommand-DQ_URZq9.js → envCommand-7qjlQgLD.js} +2 -2
  20. package/dist/{evolveCommand-CghRPrGO.js → evolveCommand-D5opB48I.js} +2 -2
  21. package/dist/index.js +1 -1
  22. package/dist/{infoCommand-BCs_ZFnl.js → infoCommand-Bs_8RU22.js} +1 -1
  23. package/dist/{runtimeTrace-jFKvOPBz.js → runtimeTrace-Wmi7V-wN.js} +1 -1
  24. package/dist/{sdkgen-COQqHB9V.js → sdkgen-DbTFGCjg.js} +1 -1
  25. package/dist/{serveCommand-C512kzZA.js → serveCommand-C8xzYCwa.js} +4 -4
  26. package/dist/serveCommand-CGNTXafv.js +2 -0
  27. package/dist/serveEntry.js +1 -1
  28. package/dist/{start-fifc0xpW.js → start-BAlGcjF5.js} +1 -1
  29. package/dist/{start-BjcuFME_.js → start-Ds3xyn8R.js} +1 -1
  30. package/dist/startEntry.js +1 -1
  31. package/dist/startup.d.ts +1 -1
  32. package/dist/updateCommand-BvkKlV7D.js +2 -0
  33. package/dist/{updateCommand-HZQVBU3T.js → updateCommand-CrI9B4d7.js} +1 -1
  34. package/dist/{webDev-Dk8lFUzf.js → webDev-101csg6Q.js} +2 -2
  35. package/dist/webDev-BLKBO34K.js +2 -0
  36. package/dist/{webhooksCommand-D7ZpH7w0.js → webhooksCommand-BXUP5YjW.js} +1 -1
  37. package/package.json +23 -17
  38. package/templates/AGENTS.md +1 -1
  39. package/templates/agent-docs/_index.md +1 -1
  40. package/templates/agent-docs/authentication.md +1 -1
  41. package/templates/agent-docs/cli.md +2 -2
  42. package/templates/agent-docs/data.md +14 -12
  43. package/templates/agent-docs/database/migrations.md +1 -1
  44. package/templates/agent-docs/internationalization.md +2 -2
  45. package/templates/agent-docs/observability.md +1 -1
  46. package/templates/agent-docs/plugins/openapi.md +1 -1
  47. package/templates/agent-docs/plugins/versioning.md +1 -1
  48. package/templates/agent-docs/plugins/webhooks.md +1 -1
  49. package/templates/agent-docs/plugins.md +1 -1
  50. package/templates/agent-docs/routing.md +1 -1
  51. package/templates/agent-docs/scheduling.md +1 -1
  52. package/templates/agent-docs/security.md +2 -2
  53. package/templates/agent-docs/templates/apibackends.md +1 -1
  54. package/templates/agent-docs/whats-new.md +49 -55
  55. package/templates/apps/api-ai/package.json +7 -7
  56. package/templates/apps/api-auth/package.json +8 -8
  57. package/templates/apps/api-backend/package.json +7 -7
  58. package/templates/apps/api-backend-deactivation/package.json +7 -7
  59. package/templates/apps/api-backend-mail/package.json +8 -8
  60. package/templates/apps/api-backend-mariadb/package.json +9 -9
  61. package/templates/apps/api-backend-sqlite/package.json +8 -8
  62. package/templates/apps/api-backend-storage/package.json +8 -8
  63. package/templates/apps/api-cms/package.json +10 -10
  64. package/templates/apps/api-collab/package.json +8 -8
  65. package/templates/apps/api-data-advanced/package.json +8 -8
  66. package/templates/apps/api-durable/package.json +8 -8
  67. package/templates/apps/api-feature-flags/package.json +9 -9
  68. package/templates/apps/api-governance/package.json +8 -8
  69. package/templates/apps/api-kv/package.json +8 -8
  70. package/templates/apps/api-moderation/package.json +8 -8
  71. package/templates/apps/api-observability/package.json +8 -8
  72. package/templates/apps/api-ratelimit/package.json +8 -8
  73. package/templates/apps/api-rbac/package.json +8 -8
  74. package/templates/apps/api-rest/package.json +7 -7
  75. package/templates/apps/api-saas/package.json +11 -11
  76. package/templates/apps/api-saas-starter/package.json +10 -10
  77. package/templates/apps/api-search/package.json +8 -8
  78. package/templates/apps/api-status/package.json +8 -8
  79. package/templates/apps/api-versioning/package.json +8 -8
  80. package/templates/apps/api-webhooks/package.json +9 -9
  81. package/templates/apps/changelog/package.json +6 -6
  82. package/templates/apps/edge-functions/package.json +2 -2
  83. package/templates/apps/frontend-admin/package.json +8 -8
  84. package/templates/apps/frontend-app/package.json +9 -9
  85. package/templates/apps/frontend-auth/package.json +8 -8
  86. package/templates/apps/frontend-blank/package.json +7 -7
  87. package/templates/apps/frontend-cms/package.json +9 -9
  88. package/templates/apps/frontend-collab/package.json +10 -10
  89. package/templates/apps/frontend-contact/package.json +7 -7
  90. package/templates/apps/frontend-dashboard/package.json +7 -7
  91. package/templates/apps/frontend-docs/package.json +7 -7
  92. package/templates/apps/frontend-i18n/package.json +6 -6
  93. package/templates/apps/frontend-landing/package.json +7 -7
  94. package/templates/apps/frontend-portal/package.json +8 -8
  95. package/templates/apps/frontend-saas/package.json +8 -8
  96. package/templates/apps/frontend-spa/package.json +7 -7
  97. package/templates/apps/frontend-ssr/package.json +7 -7
  98. package/templates/apps/frontend-ssr-api/package.json +8 -8
  99. package/templates/apps/frontend-static-blog/package.json +6 -6
  100. package/templates/apps/frontend-status/package.json +8 -8
  101. package/templates/apps/mobile-app/package.json +4 -4
  102. package/dist/apiBuild-RM5W5YXk.js +0 -2
  103. package/dist/doctorCommand-Bw3V4L1j.js +0 -2
  104. package/dist/serveCommand-gj7MsqXY.js +0 -2
  105. package/dist/updateCommand-B2alql9k.js +0 -2
  106. package/dist/webDev-D6QeXw3P.js +0 -2
@@ -1,4 +1,4 @@
1
- import { c as e, i as t } from "./dev-D4l_YOzI.js";
1
+ import { c as e, i as t } from "./dev-B2rZfqxw.js";
2
2
  import { n, t as r } from "./cliArgs-D4p8n7EE.js";
3
3
  import { r as i } from "./appPort-B_HpJ_ck.js";
4
4
  import { resolve as a } from "node:path";
@@ -1,4 +1,4 @@
1
- import { c as e, i as t } from "./dev-D4l_YOzI.js";
1
+ import { c as e, i as t } from "./dev-B2rZfqxw.js";
2
2
  import { Effect as n } from "effect";
3
3
  import { embedMany as r } from "@voltro/ai";
4
4
  //#region src/embeddingsBackfill.ts
@@ -1,5 +1,5 @@
1
- import { c as e } from "./dev-D4l_YOzI.js";
2
- import { _t as t, gt as n, mt as r, pt as i } from "./webDev-Dk8lFUzf.js";
1
+ import { c as e } from "./dev-B2rZfqxw.js";
2
+ import { _t as t, gt as n, mt as r, pt as i } from "./webDev-101csg6Q.js";
3
3
  import { join as a } from "node:path";
4
4
  import { promises as o } from "node:fs";
5
5
  import { createLogger as s } from "@voltro/logger";
@@ -1,6 +1,6 @@
1
1
  import { n as e } from "./cliOutput-D1tSBoRM.js";
2
- import { t } from "./checkCommand-DWL2_PPR.js";
3
- import { i as n, n as r } from "./codemodRunner-sFuWDETH.js";
2
+ import { t } from "./checkCommand-DDxwJCja.js";
3
+ import { i as n, n as r } from "./codemodRunner-Cnnz4-I7.js";
4
4
  import { basename as i, join as a, resolve as o } from "node:path";
5
5
  import { branchNamespaceName as s, planBranchProvision as c } from "@voltro/database";
6
6
  import { readFile as l } from "node:fs/promises";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./commands-DoTClnSk.js";
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "./commands-CbQcuWsE.js";
2
2
  import { r as o } from "./templates-BR-fb4SP.js";
3
3
  //#region src/index.ts
4
4
  var s = "framework";
@@ -1,5 +1,5 @@
1
1
  import { n as e, r as t, t as n } from "./cliOutput-D1tSBoRM.js";
2
- import { o as r } from "./updateCommand-HZQVBU3T.js";
2
+ import { o as r } from "./updateCommand-CrI9B4d7.js";
3
3
  import { join as i, resolve as a } from "node:path";
4
4
  import { readFileSync as o, readdirSync as s, statSync as c } from "node:fs";
5
5
  //#region src/infoCommand.ts
@@ -1,4 +1,4 @@
1
- import { n as e } from "./apiBuild-B_iH24qf.js";
1
+ import { n as e } from "./apiBuild-DF0f69Gf.js";
2
2
  import { join as t, resolve as n, sep as r } from "node:path";
3
3
  import { existsSync as i, promises as a } from "node:fs";
4
4
  import { createLogger as o } from "@voltro/logger";
@@ -422,7 +422,7 @@ let package = Package(
422
422
  await l.mkdir(o(e), { recursive: !0 }), await l.writeFile(e, n.content, "utf8");
423
423
  }
424
424
  }, X = async (e) => {
425
- let { offlineManifest: t } = await import("./checkCommand-B2RWrpWi.js"), { manifest: n } = await t(e);
425
+ let { offlineManifest: t } = await import("./checkCommand-DBpTSK8d.js"), { manifest: n } = await t(e);
426
426
  return n;
427
427
  }, Z = async (e) => {
428
428
  let t = J(await (e.manifestProvider ?? X)(e.root), {
@@ -3,10 +3,10 @@ import { t } from "./frameworkInspectState-CX2250XB.js";
3
3
  import { r as n, t as r } from "./appModuleLoader-C9r9mxZt.js";
4
4
  import { t as i } from "./bootRefusal-pgvDxlrU.js";
5
5
  import { n as a, r as o } from "./startupRunner-CEqQq7ax.js";
6
- import { $ as s, $t as ee, A as c, At as l, B as u, C as te, Ct as d, D as ne, Dt as f, E as re, Et as p, F as ie, Ft as m, G as h, Gt as ae, H as oe, I as se, It as ce, J as le, Jt as g, K as ue, Kt as _, L as de, Lt as fe, M as pe, Mt as v, N as me, Nt as he, O as ge, Ot as _e, P as ve, Pt as ye, Q as be, Qt as xe, R as Se, Rt as y, S as Ce, Sn as we, St as Te, T as Ee, Tt as De, U as Oe, Ut as b, V as ke, Vt as Ae, W as je, Wt as Me, X as Ne, Xt as Pe, Y as Fe, Yt as Ie, Z as Le, Zt as x, _ as Re, _n as ze, _t as S, an as Be, at as C, b as Ve, bn as He, bt as Ue, c as We, cn as w, ct as Ge, dn as Ke, dt as qe, en as Je, et as Ye, f as T, fn as E, ft as Xe, g as Ze, gn as Qe, gt as $e, h as et, ht as tt, i as nt, in as rt, it, j as at, jt as ot, k as st, kt as ct, ln as D, lt, m as ut, mn as dt, mt as O, nn as k, nt as ft, o as A, on as pt, ot as mt, p as ht, pn as gt, pt as _t, q as vt, qt as yt, r as bt, rn as xt, rt as St, sn as Ct, st as wt, t as Tt, tn as Et, tt as Dt, u as Ot, un as kt, ut as At, v as jt, vn as j, vt as M, w as N, wt as Mt, x as Nt, xn as Pt, xt as Ft, y as It, yn as Lt, yt as Rt, zt } from "./dev-D4l_YOzI.js";
6
+ import { $ as s, $t as ee, A as c, At as l, B as u, C as te, Ct as d, D as ne, Dt as f, E as re, Et as p, F as ie, Ft as m, G as h, Gt as ae, H as oe, I as se, It as ce, J as le, Jt as g, K as ue, Kt as _, L as de, Lt as fe, M as pe, Mt as v, N as me, Nt as he, O as ge, Ot as _e, P as ve, Pt as ye, Q as be, Qt as xe, R as Se, Rt as y, S as Ce, Sn as we, St as Te, T as Ee, Tt as De, U as Oe, Ut as b, V as ke, Vt as Ae, W as je, Wt as Me, X as Ne, Xt as Pe, Y as Fe, Yt as Ie, Z as Le, Zt as x, _ as Re, _n as ze, _t as S, an as Be, at as C, b as Ve, bn as He, bt as Ue, c as We, cn as w, ct as Ge, dn as Ke, dt as qe, en as Je, et as Ye, f as T, fn as E, ft as Xe, g as Ze, gn as Qe, gt as $e, h as et, ht as tt, i as nt, in as rt, it, j as at, jt as ot, k as st, kt as ct, ln as D, lt, m as ut, mn as dt, mt as O, nn as k, nt as ft, o as A, on as pt, ot as mt, p as ht, pn as gt, pt as _t, q as vt, qt as yt, r as bt, rn as xt, rt as St, sn as Ct, st as wt, t as Tt, tn as Et, tt as Dt, u as Ot, un as kt, ut as At, v as jt, vn as j, vt as M, w as N, wt as Mt, x as Nt, xn as Pt, xt as Ft, y as It, yn as Lt, yt as Rt, zt } from "./dev-B2rZfqxw.js";
7
7
  import { n as Bt } from "./cliArgs-D4p8n7EE.js";
8
8
  import { r as Vt } from "./appPort-B_HpJ_ck.js";
9
- import { ht as Ht, pt as P } from "./webDev-Dk8lFUzf.js";
9
+ import { ht as Ht, pt as P } from "./webDev-101csg6Q.js";
10
10
  import { r as F } from "./dialectDriver-czCHYpeH.js";
11
11
  import { i as Ut, n as Wt } from "./connectionConfig-Bk9IC7D0.js";
12
12
  import { c as Gt, n as Kt, o as qt } from "./seedRunner-IdHEprqf.js";
@@ -16,7 +16,7 @@ import { h as un, o as dn } from "./inspect-B7U7Cl_Z.js";
16
16
  import { n as fn } from "./mssqlClusterPatch-_4cE_nun.js";
17
17
  import { i as pn, n as mn, r as hn, t as gn } from "./webhookDiscovery-CphsQe59.js";
18
18
  import { t as _n } from "./inspectMetrics-CfdKLh6t.js";
19
- import { r as vn, u as yn } from "./apiBuild-B_iH24qf.js";
19
+ import { r as vn, u as yn } from "./apiBuild-DF0f69Gf.js";
20
20
  import { t as bn } from "./bootTiming-BdyP9nYw.js";
21
21
  import { t as xn } from "./dataProfile-Cm0YVKSy.js";
22
22
  import { basename as Sn, join as Cn } from "node:path";
@@ -1702,7 +1702,7 @@ var ji = () => Math.max(500, Math.floor(Yr() * .6)), Mi = (e) => new Promise((t)
1702
1702
  schemaRegistry: Ue,
1703
1703
  tenantScopedTables: _.tenantScopedTables,
1704
1704
  softDeleteScopedTables: _.softDeleteScopedTables,
1705
- dataTransferSnapshot: Hn(x),
1705
+ dataTransferSnapshot: Hn(x, h),
1706
1706
  dataTransferDialect: h,
1707
1707
  ...Gr === void 0 ? {} : { ensureTenantProvisioned: Gr.ensure },
1708
1708
  ...qr === void 0 ? {} : { resolveResidentStore: qr.resolveStore },
@@ -0,0 +1,2 @@
1
+ import { n as e } from "./serveCommand-C8xzYCwa.js";
2
+ export { e as runServeDispatch };
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
2
  import { r as t } from "./appModuleLoader-C9r9mxZt.js";
3
3
  import { i as n } from "./dialectDriver-czCHYpeH.js";
4
- import { t as r } from "./serveCommand-C512kzZA.js";
4
+ import { t as r } from "./serveCommand-C8xzYCwa.js";
5
5
  export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
@@ -1,3 +1,3 @@
1
1
  import "./renderModeScan-D7J1B7Kw.js";
2
- import { t as e } from "./start-BjcuFME_.js";
2
+ import { t as e } from "./start-Ds3xyn8R.js";
3
3
  export { e as runStartCommand };
@@ -1,6 +1,6 @@
1
1
  import { i as e, t } from "./nodeEnvironment-cGFAj1J8.js";
2
2
  import { r as n } from "./appPort-B_HpJ_ck.js";
3
- import { A as r, C as i, D as a, E as o, F as s, H as c, I as l, J as ee, L as u, M as d, N as f, O as te, P as p, R as m, S as h, T as ne, V as g, W as re, X as _, Y as v, Z as y, a as b, b as x, d as S, f as C, ht as w, i as ie, j as T, k as E, n as D, nt as O, q as k, s as ae, tt as A, vt as j, x as oe, y as M, z as se } from "./webDev-Dk8lFUzf.js";
3
+ import { A as r, C as i, D as a, E as o, F as s, H as c, I as l, J as ee, L as u, M as d, N as f, O as te, P as p, R as m, S as h, T as ne, V as g, W as re, X as _, Y as v, Z as y, a as b, b as x, d as S, f as C, ht as w, i as ie, j as T, k as E, n as D, nt as O, q as k, s as ae, tt as A, vt as j, x as oe, y as M, z as se } from "./webDev-101csg6Q.js";
4
4
  import { o as N, t as P } from "./connectionConfig-Bk9IC7D0.js";
5
5
  import { A as F, D as I, a as ce, h as le, j as ue, k as de } from "./inspect-B7U7Cl_Z.js";
6
6
  import { a as fe, c as L, d as pe, f as R, l as me, p as he, u as ge } from "./renderModeScan-D7J1B7Kw.js";
@@ -1,3 +1,3 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
- import { t } from "./start-BjcuFME_.js";
2
+ import { t } from "./start-Ds3xyn8R.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
package/dist/startup.d.ts CHANGED
@@ -22,7 +22,7 @@ import { SyncLogger } from '@voltro/logger';
22
22
  * So the race protected the shape we discourage and penalised the shape we
23
23
  * teach — and the penalised one includes `searchBackfill`, our own example,
24
24
  * which awaits a full-table query plus an index backfill and is the likeliest
25
- * thing in the box to exceed two seconds. Slow AND failing put a consumer back
25
+ * thing in the box to exceed two seconds. Slow AND failing put a deployment back
26
26
  * in exactly the silent state this file was fixed for.
27
27
  *
28
28
  * 60 seconds because the number now has one job: turn "hangs forever" into
@@ -0,0 +1,2 @@
1
+ import { f as e, p as t } from "./updateCommand-CrI9B4d7.js";
2
+ export { e as runApplyCodemods, t as runUpdateCommand };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-sFuWDETH.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-Cnnz4-I7.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";
@@ -401,7 +401,7 @@ __installPublicEnv(publicEnvValues)
401
401
  " same version at release — so this combination was never built or tested",
402
402
  " as a set. It is not a judgement about whether they are compatible.",
403
403
  "",
404
- " Nothing else will tell you. A consumer carried a package two releases",
404
+ " Nothing else will tell you. A deployment carried a package two releases",
405
405
  " behind through `tsc`, 19 317 tests, `voltro doctor` exit 0 in four apps",
406
406
  " and three production builds, all green. None of those checks is ABOUT",
407
407
  " version agreement.",
@@ -2326,7 +2326,7 @@ The wrapper is what carries \`match\`, and what lets \`voltro doctor\` report wh
2326
2326
  " const published = document.documentElement.lang",
2327
2327
  " if (published && I18N_SUPPORTED.includes(published)) return published",
2328
2328
  " // No published locale (a page served by something else, or an",
2329
- " // attribute a consumer overwrote): fall back to the cookie, which",
2329
+ " // attribute a deployment overwrote): fall back to the cookie, which",
2330
2330
  " // both halves can read, and never to a client-only signal.",
2331
2331
  " return resolveLocale({",
2332
2332
  " cookieHeader: document.cookie,",
@@ -0,0 +1,2 @@
1
+ import { _ as e, y as t } from "./webDev-101csg6Q.js";
2
+ export { 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-DuqBJC7g.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-D0E5x2Ue.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
226
226
  return r((await n(await t(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.42.0",
3
+ "version": "0.43.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",
@@ -628,6 +628,12 @@
628
628
  "title": "`middleware.ts` exports `defineMiddleware(...)` and can say where it runs",
629
629
  "kind": "transform"
630
630
  },
631
+ {
632
+ "version": "0.43.0",
633
+ "id": "0.43.0/01_dangling-reference-to-rows-refused",
634
+ "title": "`DanglingReferenceError` → `RowsRefusedError` (named for the outcome, not a guessed cause)",
635
+ "kind": "transform"
636
+ },
631
637
  {
632
638
  "version": "0.5.0",
633
639
  "id": "0.5.0/01_one-terminal-exactly-one",
@@ -715,22 +721,22 @@
715
721
  "@effect/platform-node": "^0.108.0",
716
722
  "@effect/sql": "^0.52.0",
717
723
  "@effect/workflow": "^0.19.0",
718
- "@voltro/ai": "0.42.0",
719
- "@voltro/cache": "0.42.0",
720
- "@voltro/data-transfer": "0.42.0",
721
- "@voltro/database": "0.42.0",
722
- "@voltro/env": "0.42.0",
723
- "@voltro/kv": "0.42.0",
724
- "@voltro/logger": "0.42.0",
725
- "@voltro/plugin-auth": "0.42.0",
726
- "@voltro/plugin-broadcast": "0.42.0",
727
- "@voltro/plugin-mail": "0.42.0",
728
- "@voltro/plugin-storage": "0.42.0",
729
- "@voltro/plugin-webhooks": "0.42.0",
730
- "@voltro/protocol": "0.42.0",
731
- "@voltro/runtime": "0.42.0",
732
- "@voltro/serverless": "0.42.0",
733
- "@voltro/workflow": "0.42.0",
724
+ "@voltro/ai": "0.43.0",
725
+ "@voltro/cache": "0.43.0",
726
+ "@voltro/data-transfer": "0.43.0",
727
+ "@voltro/database": "0.43.0",
728
+ "@voltro/env": "0.43.0",
729
+ "@voltro/kv": "0.43.0",
730
+ "@voltro/logger": "0.43.0",
731
+ "@voltro/plugin-auth": "0.43.0",
732
+ "@voltro/plugin-broadcast": "0.43.0",
733
+ "@voltro/plugin-mail": "0.43.0",
734
+ "@voltro/plugin-storage": "0.43.0",
735
+ "@voltro/plugin-webhooks": "0.43.0",
736
+ "@voltro/protocol": "0.43.0",
737
+ "@voltro/runtime": "0.43.0",
738
+ "@voltro/serverless": "0.43.0",
739
+ "@voltro/workflow": "0.43.0",
734
740
  "chokidar": "^5.0.0",
735
741
  "ioredis": "^5.11.1",
736
742
  "tinyglobby": "^0.2.17",
@@ -707,7 +707,7 @@ each plugin's own README.
707
707
 
708
708
  | Topic | Open | Summary |
709
709
  |---|---|---|
710
- | **What's new in 0.42.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. |
710
+ | **What's new in 0.43.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. |
711
711
  | 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. |
712
712
  | 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. |
713
713
  | 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.42.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.43.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. |
@@ -2166,7 +2166,7 @@ enforcing nothing while looking like it does.
2166
2166
  **A guard naming an unregistered `resourceType` refuses the BOOT.** Denying is
2167
2167
  correct per call and useless as a deployment outcome: the app comes up green and
2168
2168
  every guarded procedure is down, with a log line per refused call as the only
2169
- sign. A consumer put a number on it — 39 procedures, team settings through role
2169
+ sign. A deployment put a number on it — 39 procedures, team settings through role
2170
2170
  administration. So the two facts are compared once, after the startups have run,
2171
2171
  and a missing registration names the type and the procedures that demanded it.
2172
2172
  The commonest cause is a typo: the `resourceType` in a guard and the one in
@@ -1690,7 +1690,7 @@ It covers both halves of the stack:
1690
1690
  | client | `data === undefined` / `!data` on a subscription result | branch on `loading` (and `idle`, if you pass `skip`) |
1691
1691
 
1692
1692
  The subscription rule **resolves the binding** rather than matching text, and
1693
- that distinction is the reason this scanner parses at all. A consumer migrating
1693
+ that distinction is the reason this scanner parses at all. A deployment migrating
1694
1694
  these call sites wrote a regex codemod for the same job, and it rewrote a
1695
1695
  `summary === undefined` check inside a child component where `summary` was a
1696
1696
  PROP. Their compiler happened to catch it, because that name was out of scope
@@ -3777,7 +3777,7 @@ you ran it keeps the module graph it loaded at boot, so it goes on executing the
3777
3777
  previous version indefinitely — and against a `.framework` directory that has
3778
3778
  since been rewritten.
3779
3779
 
3780
- That mix is worse than either version alone. A consumer lost half an hour to a
3780
+ That mix is worse than either version alone. A deployment lost half an hour to a
3781
3781
  pod whose api had started before the upgrade: it served requests, reported
3782
3782
  healthy, and returned no SSR at all, because the running process held the old
3783
3783
  modules while the build output on disk was new.
@@ -275,7 +275,7 @@ fails the call with a `ParseError` naming the key and listing what was expected:
275
275
  ```
276
276
 
277
277
  It used to be discarded silently, and the reason that is worse than it sounds is
278
- what a discarded FILTER means. A consumer's layout called a `userSettings.list`
278
+ what a discarded FILTER means. A deployment's layout called a `userSettings.list`
279
279
  that declares `userId` with `{ employeeId }`; the payload decoded to `{}`, which
280
280
  for a list query is not a narrower filter but the absence of one, and an admin
281
281
  was served another user's row.
@@ -2294,7 +2294,7 @@ export default (input, ctx) => Effect.gen(function* () {
2294
2294
 
2295
2295
  **Inside a mutation it fires on COMMIT, and not at all on rollback.** That is not a nicety: a display reacting to a game start the database rolled back happens on every constraint violation, every deadlock retry, every guard that fails *after* the publish line. Outside a transaction it fires immediately.
2296
2296
 
2297
- Three typed errors reach the **producer**, so a mismatch is one failing call rather than every consumer's handler breaking on a field that is not there: `EventPayloadInvalid`, `EventKeyInvalid`, `EventPayloadTooLarge`.
2297
+ Three typed errors reach the **producer**, so a mismatch is one failing call rather than every deployment's handler breaking on a field that is not there: `EventPayloadInvalid`, `EventKeyInvalid`, `EventPayloadTooLarge`.
2298
2298
 
2299
2299
  `EventPayloadTooLarge` fires at **7,500 bytes** for the encoded envelope (event
2300
2300
  name + key + payload, as JSON). The ceiling is not arbitrary and it is not a
@@ -2631,7 +2631,7 @@ delivery matters drops the ones in between; choosing `each` for a per-frame
2631
2631
  stream makes a slow client work through a backlog to reach a state it could have
2632
2632
  had immediately, and report a "loss" that was never one.
2633
2633
 
2634
- The test: **would a consumer be wrong to miss one?**
2634
+ The test: **would a deployment be wrong to miss one?**
2635
2635
 
2636
2636
  <Callout type="warn">
2637
2637
  `delivery: 'latest'` cannot be combined with `webhook`, and the declaration is
@@ -2654,7 +2654,7 @@ separate, coarser one for the outside world.
2654
2654
  publishing at 10–20Hz is usually indistinguishable to a human and an order of
2655
2655
  magnitude cheaper.
2656
2656
 
2657
- The honest rule: **use an event when a consumer would be wrong to miss one.** If
2657
+ The honest rule: **use an event when a deployment would be wrong to miss one.** If
2658
2658
  missing one is fine because the next one supersedes it, either declare
2659
2659
  `delivery: 'latest'` or model it as state — a table, or a value the client
2660
2660
  replaces.
@@ -4756,23 +4756,25 @@ declared it anyway, that is harmless — the union is the same either way.
4756
4756
 
4757
4757
  ## Framework-shipped error variants
4758
4758
 
4759
- `@voltro/protocol` exports exactly two tagged errors. The rest of your typed errors are ones you declare yourself (above) or ones a plugin / the runtime contributes.
4759
+ `@voltro/protocol` exports the framework's own tagged errors. The rest of your typed errors are ones you declare yourself (above) or ones a plugin contributes.
4760
+
4761
+ **Import them from `@voltro/protocol`, not from `@voltro/runtime`** — even the store errors the runtime raises. A descriptor that declares one in `error:` is loaded value-level by the web client (the `RpcClient` needs every procedure's Schema), and `@voltro/runtime` is server-only, so a descriptor importing from it is refused at boot by the browser-safety guard. `@voltro/runtime` re-exports them for server code, which never sees the difference.
4760
4762
 
4761
4763
  | Variant | From | When |
4762
4764
  |---|---|---|
4763
4765
  | `ScopeError` | `@voltro/protocol` | `requireScope(subject, scope)` failed — `{ required, message }`. |
4764
4766
  | `Unauthenticated` | `@voltro/protocol` | The resolved Subject is anonymous but a signed-in caller was required — optional `{ reason }`. |
4765
- | `TenantScopeViolation` | `@voltro/runtime` | A tenant-scoped `EffectStore` write had no authenticated subject. |
4766
- | `TenantRowNotFound` | `@voltro/runtime` | A keyed-by-id write (`store.update(t, id, …)`, `delete`, `hardDelete`, `patchJson`) on a `tenant()` table found no such row **in the caller's tenant**. Raised identically whether the row is missing or belongs to another tenant — the distinction would be a cross-tenant existence oracle. |
4767
- | `ServerOnlyColumnWrite` | `@voltro/runtime` | A `crud.create` / `crud.update` input tried to set a [`.serverOnly()`](/docs/database/sensitivity) column — `{ table, columns }`. |
4768
- | `StoreOperationFailed` | `@voltro/runtime` | The underlying store operation failed (transient). |
4769
- | `TableValidationFailed` | `@voltro/runtime` | A `table().validate(Schema)` row check rejected the write. |
4770
- | `ConstraintViolation` | `@voltro/runtime` | The database refused the write on a foreign key / unique / NOT NULL / CHECK — `{ kind, table, operation, constraint?, column? }`. See below. |
4767
+ | `TenantScopeViolation` | `@voltro/protocol` | A tenant-scoped `EffectStore` write had no authenticated subject. |
4768
+ | `TenantRowNotFound` | `@voltro/protocol` | A keyed-by-id write (`store.update(t, id, …)`, `delete`, `hardDelete`, `patchJson`) on a `tenant()` table found no such row **in the caller's tenant**. Raised identically whether the row is missing or belongs to another tenant — the distinction would be a cross-tenant existence oracle. |
4769
+ | `ServerOnlyColumnWrite` | `@voltro/protocol` | A `crud.create` / `crud.update` input tried to set a [`.serverOnly()`](/docs/database/sensitivity) column — `{ table, columns }`. |
4770
+ | `StoreOperationFailed` | `@voltro/protocol` | The underlying store operation failed (transient). |
4771
+ | `TableValidationFailed` | `@voltro/protocol` | A `table().validate(Schema)` row check rejected the write. |
4772
+ | `ConstraintViolation` | `@voltro/protocol` | The database refused the write on a foreign key / unique / NOT NULL / CHECK — `{ kind, table, operation, constraint?, column? }`. See below. |
4771
4773
  | `CacheError` | `@voltro/cache` | A cache backend op failed — `{ operation, key, cause }`. |
4772
4774
  | `RateLimited` | `@voltro/plugin-ratelimit` | The limiter rejected the call — `{ limit, retryAfterMs, resetAtMs }`. |
4773
4775
  | `TenantMismatch` | `@voltro/plugin-multitenancy` | `assertOwnTenant(input.tenantId, subject)` rejected a cross-tenant write. |
4774
4776
 
4775
- Each plugin that ships an error (`RateLimited`, `EntitlementExceeded`, `StorageError`, `MailError`, …) merges it into every procedure's wire-error union, so the client decodes it typed without you adding it to each `error:`. To surface a runtime store error to the client, add it to the descriptor's `error:` union yourself (e.g. `error: Schema.Union(TenantScopeViolation, StoreOperationFailed, MyDomainError)`).
4777
+ Each plugin that ships an error (`RateLimited`, `EntitlementExceeded`, `StorageError`, `MailError`, …) merges it into every procedure's wire-error union, so the client decodes it typed without you adding it to each `error:`. To surface a store error to the client, add it to the descriptor's `error:` union yourself (e.g. `error: Schema.Union(TenantScopeViolation, StoreOperationFailed, MyDomainError)`).
4776
4778
 
4777
4779
  All carry `_tag` + typed payloads, all narrow correctly on the client.
4778
4780
 
@@ -1707,7 +1707,7 @@ The cloud dashboard surfaces per-env state with a multi-env tab in the [cloud UI
1707
1707
 
1708
1708
  ## Rehearsing a migration against real data
1709
1709
 
1710
- The strongest check on this pipeline is not that each command exits 0 — it is that **no row moved that you did not ask to move**. A consumer running a MariaDB cutover built this loop and it caught three defects the framework's own suite did not; the method is theirs, written up here because the next person should not have to invent it.
1710
+ The strongest check on this pipeline is not that each command exits 0 — it is that **no row moved that you did not ask to move**. This loop was built for a MariaDB cutover and caught three defects the framework's own suite did not, which is why it is written up here.
1711
1711
 
1712
1712
  1. **Restore a backup into a throwaway database.**
1713
1713
 
@@ -91,7 +91,7 @@ import { LOCALE_COOKIE, THEME_COOKIE } from '@voltro/i18n'
91
91
 
92
92
  Import them rather than retyping `'voltro:locale'`. A cookie name the framework READS and your app WRITES is a public API, and it is the only kind where both sides can disagree without anything failing: nothing throws, no page breaks, the resolver simply finds nothing and falls back to `Accept-Language`. The symptom is a preference that stops working for the subset of users whose browser language differs from their choice — the least likely thing anyone tests.
93
93
 
94
- > Until 0.31.0 the only package exporting these was `@voltro/ui-shadcn`, and `voltro doctor` told you to import from there. An app on this package and not on the shadcn kit had no constant to reach for, and would have had to adopt a UI kit for two strings. Reported by a consumer, who added that the rule "does not fire for us, and we think that is correct-by-accident".
94
+ > Until 0.31.0 the only package exporting these was `@voltro/ui-shadcn`, and `voltro doctor` told you to import from there. An app on this package and not on the shadcn kit had no constant to reach for, and would have had to adopt a UI kit for two strings. Reported by a deployment, who added that the rule "does not fire for us, and we think that is correct-by-accident".
95
95
 
96
96
  ## `Could not find required 'intl' object` during SSR
97
97
 
@@ -106,7 +106,7 @@ holds 2 live copies of @voltro/i18n bound to
106
106
  2 distinct react-intl instances.
107
107
  ```
108
108
 
109
- **Do not use `pnpm ls` to rule this out.** It enumerates versions ON DISK; the failure is module INSTANCES in a running process, and one file loaded down two paths is two instances. A consumer reported a 500 on every SSR page with exactly one version of each installed — they were right, and the check we had published could not observe the cause. If the count says one of each, it is a framework bug and the diagnosis says so.
109
+ **Do not use `pnpm ls` to rule this out.** It enumerates versions ON DISK; the failure is module INSTANCES in a running process, and one file loaded down two paths is two instances. A deployment reported a 500 on every SSR page with exactly one version of each installed — they were right, and the check we had published could not observe the cause. If the count says one of each, it is a framework bug and the diagnosis says so.
110
110
 
111
111
  The framework keeps both packages on one SSR instance by bundling them together (`ssr.noExternal`) and deduping them, in `voltro dev`, `voltro build` and `voltro start` alike. The remaining way to get a second copy is in your own code: building a provider from `react-intl` **directly** rather than from `@voltro/i18n`.
112
112
 
@@ -227,7 +227,7 @@ The in-memory ring always keeps EVERY span for live debugging. SEPARATELY, on po
227
227
  - **Default OFF in production, `interesting` in dev.** `voltro dev` (not `NODE_ENV=production`) persists only "interesting" spans. `voltro serve` / `voltro start` default `NODE_ENV=production` when it's unset, so they persist NOTHING and **`_voltro_traces` is not even created** — use OTLP in prod. An explicit `NODE_ENV` is never overridden.
228
228
  - **`VOLTRO_TRACING_PERSIST`** = `off` | `errors` | `interesting` | `all` (overrides the env default everywhere). `interesting` = error + slow + per-trace root spans, MINUS the high-volume subscription delivery (snapshot/delta) spans, MINUS the framework's own background-task reads. `errors` = error spans only. `all` = every non-delivery span. `off` = no persistence and no table.
229
229
 
230
- **Why background tasks are excluded from "slow".** The framework polls four of its own tables on a timer (`_voltro_schedule_claims`, `_voltro_workflow_pending`, `_voltro_workflow_pauses`, `_voltro_ai_inferences`). Those reads are slow exactly when the database is under pressure — which is when persisting them costs the most. A consumer measured the loop closing on itself: `_voltro_traces` at 476 571 rows / 335 MB, writing ~11 INSERTs/s onto the same 15-slot pooler the app read through, 99 % of it framework poller spans. Pool pressure makes the spans slow, slow spans are "interesting", persisting them costs pool. They are still kept when they ERROR, and `all` mode still keeps everything. A request-path framework table like `_voltro_api_keys` is NOT excluded — a slow lookup there is a real user waiting.
230
+ **Why background tasks are excluded from "slow".** The framework polls four of its own tables on a timer (`_voltro_schedule_claims`, `_voltro_workflow_pending`, `_voltro_workflow_pauses`, `_voltro_ai_inferences`). Those reads are slow exactly when the database is under pressure — which is when persisting them costs the most. A deployment measured the loop closing on itself: `_voltro_traces` at 476 571 rows / 335 MB, writing ~11 INSERTs/s onto the same 15-slot pooler the app read through, 99 % of it framework poller spans. Pool pressure makes the spans slow, slow spans are "interesting", persisting them costs pool. They are still kept when they ERROR, and `all` mode still keeps everything. A request-path framework table like `_voltro_api_keys` is NOT excluded — a slow lookup there is a real user waiting.
231
231
  - **`VOLTRO_TRACING_SLOW_MS`** (default `500`) — the "slow" threshold used by `interesting`.
232
232
  - **`VOLTRO_TRACING_PERSIST_DELIVERY`** (default off) — also persist the subscription delivery spans (the firehose; rarely wanted).
233
233
  - **`VOLTRO_TRACING_SAMPLE`** (`0`–`1`, default `1`) — per-*trace* sampling (a kept trace keeps all its eligible spans; errors are never sampled out). `=0` with no explicit mode means off.
@@ -73,7 +73,7 @@ Per procedure kind:
73
73
  - **stream** → the `element` Schema is documented as a streaming response (`application/x-ndjson`, one element per line) — **not** faked as a single JSON value.
74
74
  - The procedure's **typed error channel** (its `Schema.TaggedError` union) becomes a `422` response referencing the error schema; a procedure with no declared error documents only `200` + `500`.
75
75
 
76
- Mixing rpc + REST in one spec is fine — the rpc operations are grouped under the `rpc` tag and flagged `x-voltro-rpc`, so a consumer can tell them apart from real REST routes. `procedures` is opt-in: omit it to keep the spec REST-only.
76
+ Mixing rpc + REST in one spec is fine — the rpc operations are grouped under the `rpc` tag and flagged `x-voltro-rpc`, so a deployment can tell them apart from real REST routes. `procedures` is opt-in: omit it to keep the spec REST-only.
77
77
 
78
78
  ## Access control
79
79
 
@@ -58,7 +58,7 @@ If 41 surprises you, `exclude` is the knob. The retention sweep (`VOLTRO_ROW_HIS
58
58
 
59
59
  Versioning records **row changes, not domain events**. One entry per row per write, named by *table*. If your product has a user-facing audit feature whose entries are named after an aggregate root — one `Team` event for a call that writes `teams` + `roles` + `userTeams` + `userTeamRoles` — this is the layer **underneath** that, not a replacement for it.
60
60
 
61
- The distinction is worth reading before you plan a migration onto it. A consumer with 331 hand-written audit calls set out to delete them all in favour of this tap and found three hours in that the grain is different: a table-keyed tap does not produce an aggregate-keyed trail with better coverage, it produces a *different artifact*. Their conclusion — and ours — is that the two compose:
61
+ The distinction is worth reading before you plan a migration onto it. A migration off hundreds of hand-written audit calls onto this tap runs into the same wall a few hours in: the grain is different. A table-keyed tap does not produce an aggregate-keyed trail with better coverage, it produces a *different artifact*. The two compose:
62
62
 
63
63
  - **versioning** answers "what did row R look like before, and after" — for every write, whether or not anyone remembered to record it;
64
64
  - an **aggregate trail** (the [audit sink](/docs/plugins/audit), one row per mutation invocation) answers "what business operation happened, to which entity, and did it succeed";
@@ -211,7 +211,7 @@ means equality; an object names an operator. `eq`, `in`, `gt`, `gte`, `lt`, `lte
211
211
  — those six and nothing else. **An unsupported operator falls through to `===`
212
212
  and therefore matches nothing.**
213
213
 
214
- **This was typed `Readonly<Record<string, unknown>>` and it cost a consumer a
214
+ **This was typed `Readonly<Record<string, unknown>>` and it cost a deployment a
215
215
  feature for a year.** They wrote in a code comment that the filter was key-path
216
216
  equality and could not express "id is one of these", refused that capability in
217
217
  their own API with a typed error, and shipped it — while `in` had been supported
@@ -1120,7 +1120,7 @@ store.onChange((event) => {
1120
1120
  ```
1121
1121
 
1122
1122
  Why this exists: two framework tasks polled permanently-empty queues on a
1123
- consumer's deployment and wrote **2 506 claim rows an hour** between them,
1123
+ deployment's deployment and wrote **2 506 claim rows an hour** between them,
1124
1124
  against 18 from the app's own eight schedules. A fixed interval has no way to
1125
1125
  learn that a queue is empty. Without a `wake()` source the ceiling is your
1126
1126
  worst-case latency, so a task whose arrivals cannot announce themselves should
@@ -1058,7 +1058,7 @@ that look like they answer the same question do not:
1058
1058
  branches on the ABSENCE OF A FUNCTION, which says nothing about why it is
1059
1059
  absent and breaks the moment anything else becomes conditional.
1060
1060
  - **`headers` is `{}` in the browser, not `undefined`** — so `if (ctx.headers)`
1061
- is TRUE on both paths. A consumer wrote exactly that check and it silently did
1061
+ is TRUE on both paths. A deployment wrote exactly that check and it silently did
1062
1062
  nothing.
1063
1063
 
1064
1064
  ```ts
@@ -363,7 +363,7 @@ Losers don't write a run row — at scale that would be N−1 noise rows per fir
363
363
  and a plugin's over a framework default.
364
364
  ```
365
365
 
366
- Through 0.32.0 this was a silent last-write-wins: a consumer's 1-hour bound was replaced one second later by the framework's default, their startup went on logging `bounded to 1h` at every boot, and they found it by counting rows. Note the direction — the loser is chosen by WHO registered, not by which TTL is narrower. "Narrower wins" would let a framework default we tighten in a later release silently start deleting your data faster than you asked for.
366
+ Through 0.32.0 this was a silent last-write-wins: a deployment's 1-hour bound was replaced one second later by the framework's default, the startup went on logging `bounded to 1h` at every boot, and it was found by counting rows. Note the direction — the loser is chosen by WHO registered, not by which TTL is narrower. "Narrower wins" would let a framework default we tighten in a later release silently start deleting your data faster than you asked for.
367
367
 
368
368
  ## `cluster`
369
369
 
@@ -59,7 +59,7 @@ payload therefore answers with a decode error, not a `ScopeError` — and readin
59
59
  that as "the guard is not applied" is the wrong conclusion in the dangerous
60
60
  direction.
61
61
 
62
- A consumer reached exactly that conclusion while auditing a guard, and flipped
62
+ That conclusion is easy to reach while auditing a guard, and flipped
63
63
  back the moment they sent a complete payload. So a guarded procedure says it in
64
64
  the failure:
65
65
 
@@ -107,7 +107,7 @@ yield* enrich(userSuppliedUrl).pipe(
107
107
 
108
108
  Two things worth knowing if you are upgrading from a version before this was a failure:
109
109
 
110
- - **It used to be a defect** (`Effect.die`), so code that handled it did so with `Effect.catchAllDefect`. That arm no longer fires. If you also have an `Effect.mapError` above it, the rejection now reaches `mapError` FIRST — and a delivery that classified an SSRF block as *terminal* in the defect arm will be reclassified as whatever `mapError` returns. A consumer hit exactly this: a permanent condition became retryable and burned the full backoff budget re-attempting a request that can never succeed. Nothing in the type system can see that, because the error channel already carried `HttpClientError`.
110
+ - **It used to be a defect** (`Effect.die`), so code that handled it did so with `Effect.catchAllDefect`. That arm no longer fires. If you also have an `Effect.mapError` above it, the rejection now reaches `mapError` FIRST — and a delivery that classified an SSRF block as *terminal* in the defect arm will be reclassified as whatever `mapError` returns. A deployment hit exactly this: a permanent condition became retryable and burned the full backoff budget re-attempting a request that can never succeed. Nothing in the type system can see that, because the error channel already carried `HttpClientError`.
111
111
  - **`description` names the policy** (`SSRF policy: blocked private/reserved address …`), which is what you want in a log line; the `cause` is what you want in a branch.
112
112
 
113
113
  ### Allowing a target on purpose
@@ -992,7 +992,7 @@ export default async ({ store, log, onShutdown, id }: StartupContext) => {
992
992
  log.info(`startup '${id}': warming order-fulfillment caches`)
993
993
 
994
994
  // Example long-lived resource: a heartbeat interval. Replace with a real
995
- // warm-up (preload a dashboard cache, open a consumer, …).
995
+ // warm-up (preload a dashboard cache, open a deployment, …).
996
996
  const timer = setInterval(() => {
997
997
  void store // `store` is the already-migrated DataStore, ready to use.
998
998
  }, 60_000)